In our previous lesson, we learned how to place and manipulate static Actors in a level. We also had the chance to experience our creations in virtual reality. In this lesson, we will build new Actors with our own defined behaviour.
To do this, we’re going to work inside the Blueprint visual programming environment. All later lessons in this tutorial series will depend on your familiarity with Blueprint, so we hope you take time to internalize the scripting strategies we encounter here.
If you’re working through this material in a lab setting, please take note that this lesson is significantly longer than the last one. Be prepared to save your work and return to the tutorial at another time.
Let’s recreate the level we used last week to better understand all the components of a good starter map.
We want our new level to be the default level for this project. When a packaged project is launched outside the editor, the default level will automatically open.
There are a number of different ways we can create the illusion of a sky in UE5. For this project, we’re going to use a very large spherical mesh, commonly refered to as a sky sphere. The starter project has an Actor called BP_Simple_Sky_Sphere that will do the trick.
To change the BP_Simple_Sky_Sphere color gradient:
If we place Actors in our level now, they won’t be visible because we haven’t created a light source. We can simulate light from the sun with a Directional Light. You might remember from our first lesson that a directional light is uniform, acting as if it were infinitely far away from the objects it illuminates.
The location of a directional light does not affect how Actors are lit. We usually keep directional lights near the world origin for convenience.
We can improve the quality of light in the level with a Sky Light. A sky light captures ambient lighting information from distant parts of the level for more realistic shading. A sky light can create better shadow quality without a significant performance cost.
Let’s create a simple ground on which we can place other objects.
We can tweak the look and feel of a level with post process effects. In virtual reality, there are a number of effects common to monoscopic experiences that are too expensive and/or inappropriate for stereoscopic rendering. We can control these settings with the Post Process Volume, which allow us to manipulate effects in the bounds of the volume.
Let’s set a few rendering parameters important for any VR experience:
There are many effects available to you by default in Unreal Engine, and you should feel free to play with them however you like.
Just be aware that each one comes with performance implications and concerns specific to virtual reality. Check out the UE5 documentation to learn more about them.
We can better optimize level lighting with the Lightmass Importance Volume. A Lightmass Importance Volume allows the computer to focus high quality lighting efforts on a smaller part of the level.
Areas within the Lightmass Importance Volume will have better, detailed, indirect lighting. Consequently, areas outside the volume only get one bounce of indirect lighting at a lower quality.
Game Mode defines how an experience is played. Put another way, we can enforce rules about the player’s relationship to the level with a custom game mode. There are many components to a game mode, but for now we’ll use a game mode we’ve provided.
You’ll notice that the viewer is not able to move using the keyboard. You might also notice that the viewer can’t rotate with the mouse while in VR. This is an intentional input limitation to avoid discomfort. Why are we imposing this constraint on the viewer?
The vestibular system of the inner ear helps the brain interpret acceleration and orientation. When the player moves virtually, the vestibular system will not recognize the change in position. The dissonance between visual and vestibular perception will often lead to simulation sickness. In later lessons we’ll work out some alternative movement solutions, but for now our player will remain seated.
OK, we’re all set with a basic level! Now we’re ready to create our first Blueprint.
Let’s create a simple Blueprint class made up of basic shapes. We’ll arrange the shapes into a vehicle of your own design.
To recap our previous lesson, an actor is something that we can place in the world. A Blueprint is a type of Actor. Inside a Blueprint we will be able to define how a type of Actor looks and behaves.
Let’s take a look inside.
You’ll be greeted by an empty Viewport and a number of other panels. There’s a lot to learn here, but we’ll take on the editor one piece at a time.
Blueprints are made up of components. Some of these components are visual, like a static mesh; and some are behavioral, like a movement component. You’ll learn more about behavioral components later.
Let’s add a few Static Mesh components to make our vehicle look like a vehicle.
An empty static mesh component has been added to the Blueprint. While the new Static Mesh Component is selected, you’ll see lots of options in the Details panel on the right.
This BP_Vehicle looks talkative, and I bet yours does too. Let’s hear what the vehicles have to say by displaying a message from each when we play the level.
To create this behavior we’ll use UE5’s visual scripting language, Blueprint. Instead of text, UE5’s code looks like a flow chart:
Scripting works by responding to events. For example, we can be notified when a vehicle is created. Once that event fires, let’s script the vehicle to say something.
The event we’re looking for is called Event BeginPlay. Fortunately for us, it’s already there in the Event Graph.
It should look like you’re dragging a wire from the node. When you release your mouse, a Context Sensitive menu will pop up. This menu contains all the possible nodes you can place. Placing a node is like writing a line of code.
You just placed a Print String node. The string printed by default is “Hello”. But can you hear that? It sounds like your vehicle has something else it wants to say.
Programmers call text a String because text is a string of characters.
For every BP_Vehicle in your world, you’ll see a new line of text. That is, if you have 3 BP_Vehicle Actors placed in the world, you’ll see 3 lines of text.
Why?
The important concept to grasp is that when you’re writing code inside of a Blueprint, you’re not writing code for one Actor, you’re writing code for all Actors of that type. Therefore, if you have 3 BP_Vehicle Actors in your world, each one has the instruction to print its message at the start of the game.
Congratulations, you just wrote your first bit of code in UE5!
Print String is extremely useful for debugging. You’ll learn to use it all the time. The problem with Print String is that you won’t be able to see it when in VR. This is because the text is printed right to the screen rather than in 3D space, making it nearly impossible to read (like reading a book a centimeter from your face). Instead, lets print the string in 3D space using a VARlabs utility node called Draw VR Debug String.
The Location input requires a little thought. We want to print the string directly above the vehicle, not at [0, 0, 0]. That means we need to get the vehicle’s location, then add some space in the Z direction. The vector math looks like:
Click here for an intro to vector math if this doesn’t make sense.
Remember, right-click anywhere in the Event Graph to place a new node.
Nice! Go ahead and try it out, this time in VR.
Make sure to use Print String whenever you need to quickly test your code in the Engine Viewport. If you need to test while wearing a VR headset, use the Draw VR Debug String to draw it in 3D space!
In this little Blueprint, we’ll make a chest’s lid open and close over time.
We have a chest bottom, let’s add a top.
OK, our treasure chest is ready to get moving. We want to change the rotation of the lid slightly every frame to create the illusion of movement. To do this, we’ll use Event Tick.
Event Tick fires every frame and is the perfect tool for this task.
Being the technical savvy person that you are, you’ve probably heard of the term Frames Per Second, or FPS. This refers to the number of still images that are displayed on your screen per second. Your brain does the rest to make these still images look like their subjects are actually moving. Classic film is shown at 24 frames per second.
In VR, we need to run at least 75 FPS on the Oculus DK2 and 90 FPS on the Oculus CV1 and HTC Vive. Anything lower than the specified frame rate will cause the viewer to feel simulation sickness.
This adds a ChestLid reference to the graph, allowing us to use it in our script.
The goal is to animate the lid’s Roll Rotation back and forth between 0 to -90 degrees. There are a lot of great ways to do this in UE5, but we’re going to first do this with a math function based on time.
Here’s what the math looks like:
Let’s script it in the Blueprint:
Now we need to plug this into the Roll of the new Set Relative Rotation > New Rotation:
Whew! Hopefully that math wasn’t too intense. You will get used to it as we work on these projects.
Next up! We’re going to make a very delicate balloon that pops when it touches other Actors in the level.
What are balloons really good at doing besides popping? Floating up! Let’s build that functionality now.
We’re going to animate the balloon moving upward in the same way we animated the chest opening and closing, using Event Tick.
Now we want to slightly move the balloon each frame. We can easily do this with the Add Actor World Offset node. Go ahead and give it a shot!
Challenge:
Make your BP_Balloon continuously move in the z direction using Event Tick and Add Actor World Offset.
Make sure to check the Sweep option on the Add Actor World Offset node. Hover over the “Sweep” text to read what it does. We will need this to detect collision later on.
Up, up, and away!!!
Computers will run your experience at different frame rates, which means that the way we currently have the BP_Balloon moving is not ideal. This is because some computers may fire Event Tick twice as often, meaning our balloons would move twice as fast. We can avoid this problem by utilizing the Delta Seconds output on the Event Tick event. Delta Seconds is the time since the previous tick. If we use that in our speed calculation, we can have a uniform experience across all computers.
Assuming we want to move 75 units per second, the math for including Delta Seconds into our tick calculation is: \begin{equation} \text{Offset Distance} = \Delta\text{Seconds} \times 75\end{equation}
In order to multiply Delta Seconds by 75 we use a Multiply node.
To simplify our first lesson, we won’t factor in Delta Seconds in this tutorial. If you’re feeling extra cool, add it in on your own as we go.
Now that we have a moving balloon, it’d be nice if there was some way to control its speed. We’ll do this using a variable. If you’re not already familiar with the concept of a variable, we highly recommend you read our introduction to variables guide before continuing.
This will create a new template variable that we can change however we like.
We always need to compile the Blueprint before we can set the Default Value of a new variable.
To set the rate of the balloon’s movement, we can simply multiply the direction of travel by the speed variable. Our vector math guide can refresh you on what happens when you multiply a vector by a float.
We just added a reference to Speed to the Event Graph. Now we can use it in our script.
When the level is played, your balloon should move twice as fast.
Let’s pop the balloon when it collides with other objects in the level.
The static mesh component in BP_Popping_Balloon includes a seperate collision mesh. A collision mesh allows us to determine when Actors overlap one another.
The OnComponentBeginOverlap event is now available in the Event Graph. We want to pop the balloon when it comes in contact with another Actor, and we can do that using the DestoryActor node.
You may have noticed the ActorBeginOverlap event as well. This event fires when any component on the Actor experiences a collision. We use the OnComponentBeginOverlap because it is more precise and allows us to respond differently when specific components on our Actor are collided with.
By default, our balloon’s collision settings don’t look for overlap events, so this event will never fire.
Great, our balloon is all set up to overlap/collide with other Actors.
It is very likely that the BP_Balloon will not destroy itself on overlap with the other Actor. This is because the other Actor does not generate overlap events by default.
POP!
To add a cool visual effect, lets spawn a particle emitter when the balloon pops using the Spawn Emitter at Location node.
Challenge:
When the balloon collides with another Actor, spawn an explosion emitter using the Spawn Emitter at Location node and a P_NeonExplosion emitter template.
Make sure to spawn the emitter in the right location. The emitter needs to spawn at the balloon’s location. Use the Get Actor Location node.
Oooooh!
Particle Emitters are really cool and a great way to add flare to any experience. Check out the Unreal Documentation to learn more about them.
Balloons are anything but silent when they pop. Let’s play a sound when our balloon explodes. The node we want to use is Play Sound at Location. See if you can set up the node yourself to play a sound when the player is hit.
Challenge:
Play a sound when the balloon collides with another Actor using a Play Sound at Location node. Make sure to set Sound and Location. Don’t destroy the Actor before you get a chance to play the sound!
Oh yes, that’s more like it!
Our level should have more balloons consistently spawning and popping. Pop Pop! We could create a BP_BalloonSpawner Actor, but we’re going to use this opportunity to learn about the Level Blueprint.
The Level Blueprint has an Event Graph like we’ve seen in our other Blueprints. Let’s spawn some balloons!
To repeat a task over time, we want to use a Timer. Timer runs a function every Time seconds.
Event BeginPlay fires when the corresponding Actor is created. In this context, the event will fire when we play the level.
Now we need to create the SpawnBalloon function.
Functions are a fundamental programming tool. The idea is to take a bunch of code and turn it into something that is easily reusable. In this case, we want to make a function that spawns balloons.
Inside of the new function:
We need a location to spawn our balloons. Let’s add a Target Point Actor to the level, then get its location as a spawn point. Target Point Actor gives you a generic point in the world that you can use. Think of it as a dummy Actor with coordinates!
Balloons will spawn at this location when we complete our script.
A transform contains data for Location, Rotation, and Scale.
Test it out! The spawner should be releasing new balloons every few seconds.
Our balloon spawner has introduced a performance issue! As the level plays, our world could contain thousands of uncollided balloons, each moving every single frame. Undoubtedly this will eventually cause the game to lag and slow down.
You can see this become an issue in the World Outliner. As the level goes on, the Actor count in the bottom left gets larger and larger.
We need to destroy balloons after a reasonable amount of time. Using a Delay node, we can write code to run after a specified number of seconds.
Challenge:
Make all BP_Balloons self-destruct after 5 seconds.
Hint: The nodes you’ll need to use are Event BeginPlay, Delay, and Destroy Actor. Write this code in the BP_Balloon Event Graph.
Die balloons! Die!
We taught this way of self-destructing balloons in part to introduce you to the Delay node, but we can also explicitly set an Actor’s life span this way:
- In BP_Balloon, click Class Defaults in the Toolbar.
- In the Details panel, set Initial Life Span to 5.
We’ve done it! Floating, popping, performant balloons.
Let’s create a flashlight that we can turn on and off from the main editor window. This type of script is special in that it uses Public Variables and the Construction Script to achieve this.
A Spot Light emits light from a single point in a cone shape. You can change its many variables in the Details panel.
Great! We have a working flashlight. With our basic components in place, we’re ready to write our script! Let’s create the script that will allow us to turn the light on and off from within the Level Editor.
The Construction Script event is a special type of event that fires as any Actor is constructed; before events like Event BeginPlay fire.
The easiest way to turn our flashlight “on” and “off” is by setting the visibility of the SpotLight component.
By default, the value of New Visibility is false.
Compiling BP_Flashlight will cause all BP_Flashlights in your Level Editor to be reconstructed. For this reason, the flashlight in the level just turned off.
As shown in the final product demo, we want to set be able to set this New Visibility value from the Details panel. To do this, we’ll create a public variable in our Blueprint.
You should now see a new Boolean variable named “On” in the Variables category of the My Blueprint panel.
The default value of a new variable cannot be set until the Blueprint is compiled.
Now we have to use the variable.
We’re nearly done! The only thing left to do is to make On a public variable.
It’s clearly time to make a magnet that attracts other Actors. The perfect way to end our circus of scripts.
We need some way to determine when other Actors are within the “magnetic field” of our magnet. BP_Balloon captured overlap events using the collision mesh of SM_Balloon_A. Unfortunately the collision mesh of SM_Magnet_A won’t work here. We need to define the space using a custom geometry. The engine makes that easy.
This collision box allows us to seperate collision behavior from other aspects of the Blueprint. Let’s relocate and scale the box to something reasonable.
OK, this box will serve as our magnetic field. Other Actors that generate overlap events should respond when inside this space.
Every frame, we want to get all the Actors overlapping with our box.
Get Overlapping Actors does just that. Now we have a list of those Actors available in an array.
Programmers arrange lists of things in arrays. Arrays are commonly used to operate on collections of similar objects.
Loops iterate over arrays to execute code for each element in the array. Powerful stuff!
The For Each Loop will execute nodes following Loop Body once for every element in the array.
Array Element is a reference to the current element of the loop.
Now that we have access to every Actor that is inside the “magnetic field”, we can make each one move towards our magnet. Let’s use AddActorWorldOffset to make this movement happen:
We’re using AddActorWorldOffset again, but this time we need to specify a target location. Let’s create a Scene component, which is basically just a TargetPoint that defines a location we care about.
We want to move Actors in our overlap array toward the TargetPoint component over time. All we have to do is calculate the correct Delta Location for our Add Actor World Offset node. Delta Location needs to be in the direction of TargetPoint’s location.
The node we’re looking for is called Get Unit Direction (Vector). This node outputs a vector of length 1 that points from one location to another location. We need to know the direction from the current Actor to the TargetPoint’s location.
Challenge:
Connect up the nodes so that our Loop makes each Actor in the “magnetic field” move towards the TargetPoint.
Don't forget to compile your Blueprint!
Hmmm, this is strange:
Can you guess why the magnet is moving? Because the “magnetic field” also contains the BP_Magnet itself.
We only want to move other overlapping Actors. We can check if the current Actor is the magnet itself using a Branch.
A Branch works by checking a boolean condition and then directing the code down different paths depending on its value. You’ll notice the node has two execution outputs, one labeled True and another labeled False. This is how we can add logic to our code, executing specific code depending on certain conditions.
If you’re familiar with programming, a Branch node is an if statement.
The Equal node allows us to determine equivalence. In this context we will compare objects to other objects.
Self is a reference to an object within its own context in many programming languages. In the context of BP_Magnet’s Event Graph, Self refers to a BP_Magnet instance.
Great, Branch is ready to start branching. Remember that we only want to call Add Actor World Offset when the current Array Element does not equal self.
That’s it! Your magnet will now work on all Actors that have:
Bonus Challenge:
Make the magnetized Actors move 3 times faster.
Hint: Multiplying a vector by a number makes it longer. The Multiply node will prove useful. You can right click on a pin and select Convert Pin... to change it to a different variable type.
Can you feel the power?!
Commenting
You might come back to this code later and not remember exactly what it does. Fortunately it’s really easy to comment your code:
- Select the nodes you want to comment.
- Press C.
We learned a lot in this lesson! Here are some of the things you got experience doing:
It’s helpful to understand how the node menu works when you right click inside a Blueprint. UE5 calls this the Context Sensitive menu. This menu will list only those nodes available in the current Blueprint context. For example, when we’re inside BP_Balloon and right-click, we are only shown nodes that can be used from within that Blueprint.
The Context Sensitive menu will also open when you drag and release a new wire from an existing node. In this case, the menu will list only those nodes available in the context of that node.
Let’s say you want to change the mesh of a specific static mesh. Here are two different ways to do it:
In this miniProject, you’ll create three levels to inspire three different responses from the viewer. Each experience should leverage the dynamic power of Blueprint. Your scripts should be sufficiently complex, but we’re most concerned that you create compelling VR.
This miniProject is worth 10% of your Assignment Component. For each level, the breakdown of the marks is as such:
Breakdown / Grading | Great | Okay | Bad |
---|---|---|---|
Blueprint Requirements (25%) |
Sufficiently complex Blueprints are used. | Simple Blueprints are used. | No Blueprints are used. |
Fulfillment of Theme (50%) |
Inspires the viewer to do the specified actions to a large extent. | Inspires the viewer to do the specified actions to a moderate extent. | Does not inspire the viewer to do the specified actions. |
Creativity (25%) |
Unique and immersive (if simulated in VR). | Interesting (if simulated in VR). | Boring (if simulated in VR). |
Should you need any further clarification, feel free to contact the TA marking this miniProject.
If you do not have a VR headset, that is fine. Just design the level as per normal and "imagine" what it would look like with the headset. Your TAs will not penalise you on this.
For this miniProject, your completed work must be inside the Lesson2 > miniProject2 folder. Your submission must be a .zip file titled as [YourMatricNumber]_[YourFullName] (e.g. A0123456Z_JohnSmith.zip). You can package your project in Unreal Engine 5 by clicking on File > Zip Project.
If you do not follow the submission guidelines exactly, your marks may get deducted!
You will need to create your own levels to complete this miniProject. Unfortunately, UE5 does not provide an easy way to duplicate levels. We recommend you batch-copy/paste elements from an existing level into a new one. Make sure to select StationaryGameMode in the World Settings panel for each level - we’ll incorporate movement when we learn about good ways to avoid simulation sickness.
Your first submission might have taken place in an unending horizonscape. For brownie points, consider constructing specific environments that elicit stronger reactions. Is the viewer in a long hallway? A small closet? At the top of a mountain? Don’t miss the opportunity to put your Blueprints in the context of “real” worlds.