The first thing we'll do is create a character controller.
This controller will handle:
- Moving left and right
- Playing the right animations
Before this part of the tutrial, read the following article of the manual. It takes about 5 minutes and it will give you the very basic notions you need to know about graphs, machines and macros.
1. Create a flow machine
For the player controller, we will use a flow machine.
Select the Player object in the hierarchy, and click Add Component > Bolt > Flow Machine:
The graph window should now display an default graph with Start and Update events:
2. Convert the source to a macro
This is a bit of a boring step, but it's crucial to Bolt. It only takes a few seconds, but make sure you understand what's happening.
Because the player is a prefab that we will reuse across many scenes, we need to use a macro instead of an embed as our source. This is a simple rule of thumb in Bolt: when using prefabs, you should use a macro instead of an embed. If you want more information about why, you can read the following manual article:
Converting is very easy. In the inspector for the flow machine, click Convert next to the Source property:
Then, save the macro under Macros / PlayerController.
The machine component should then look like this:
Now that our machine is created, apply the changes to the player prefab:
We will repeat this process often in the tutorial for the different graphs we create. From now on, if you see "convert your graph to a macro", use the same process that we described here.
3. Calculate the movement
We want the character to move left and right, depending on the horizontal input axis. The horizontal input axis is a pre-configured Unity input shortcut that represents, for example, A and D on a keyboard, or the left joystick on a controller. When you go left, it returns -1, and when you go right, it returns +1. How fast to move will be controlled by a speed variable that we can tweak to adjust the gameplay.
If you're not familiar with Unity input, remember you can see and edit the available axes and buttons from Edit > Project Settings > Input:
Let's get started.
Because this is the first time we create a variable and add units to our graph, we'll show you every step along with gifs. But to speed things up for the rest of the tutorial, after this section we'll only give you the steps and the final screenshots.
The first thing we need to do is create the speed variable on our player game object:
- Select the Player game object
- Switch to the Object tab in the variables window
- Add a new variable named Speed
- Set its type to Float
- Give it a value of 5
For more information about variables, have a look at this article in the manual:
Then, we need to get the horizontal input axis. We can do that with the Get Axis unit.
- Add the unit to your graph by right-clicking in an empty space and navigating to Codebase > Unity Engine > Input > Get Axis
- Type Horizontal in the axis name field.
You can also search for get axis and let the fuzzy finder to the work for you:
You will notice the new unit shows up as dimmed out. This is because we're not using its value anywhere yet, so Bolt warns us that is is currently useless by fading it out. If you want, you can disable this by toggling off in the graph toolbar.
Next, we need to multiply this value with our speed object variable. We can use Bolt's contextual menu to make that easier.
- Drag and drop the value output port of the Get Axis unit to an empty space in the graph
- Choose Multiply
- Drag and drop the second port of the Multiply unit to an empty space in the graph
- Choose Variables > Get Object Variable
- Type Speed as the variable name
To complete our movement calculation:
- Add a Set Graph Variable unit (under Variables)
- Set its name to Movement
- Connect the result of the multiplication to its value input port
- Connect the Update node to its control input port
Let's recap what's happening here:
- At every frame (Update event)...
- ... we get the horizontal input axis (which is within -1 to +1)...
- ... we multiply it with the speed (so it becomes -5 to +5)...
- ... and store it in a variable called Movement.
Our player isn't moving yet, but we know by how much it should move.
Note two important things about the new movement variable:
- We didn't create it beforehand like we did for the player's speed, because we didn't need to give it a default value. Bolt supports dynamic variables, meaning you can create new variables during play mode on the fly by just assigning them a value like we just did.
- We used a graph variable instead of an object variable, because we will only ever need the movement inside this graph -- no need to share it with the outside world.
Whew. This was pretty boring, but now you know all the basics: creating graphs, variables and units. Now, we'll pick up some speed and actually make something happen.
4. Moving the player
The player prefab in the project already has a Rigidbody 2D component attached. All we have to do to make the player move is tell it at which velocity (speed) to go... and we just calculated that!
Velocity on 2D rigidbodies is a 2D vector: it has a X component for the horizontal speed, and a Y component for the vertical speed. Because we only want to affect the horizontal speed, we'll have to keep the vertical speed intact.
Here is the graph we need:
- Get Graph Variable is located under Variables
- Get Velocity and Set Velocity are located under Codebase > Unity Engine > Rigidbody 2D
- Get Y and Create Vector 2 are located under Codebase > Unity Engine > Vector 2
If you have enabled, you'll notice all of this graph is faded out. That's because we never specified when to set the velocity (the first arrow port on the Set Velocity node). Quite simply, we want to do this after we calculate the movement, so we can connect it with our previous set of units:
Now that our graph is getting a bit more complex, it would be a good time to start organizing bits of logics in group. The left part is used to calculate the movement, and the right part is used to set the velocity. By holding Ctrl and dragging, you can create groups than can be labeled:
We're ready to test! If you enter play mode now, you should be able to move the player with the keyboard or a controller:
If you keep the player selected while in play mode, you'll see our graph now animates its active nodes and connections:
5. Flip the direction
Next we'll add a new part to our graph to flip the player in the direction of the movement.
To do so, we only have to change the scale of the player game object on the X axis.
When going right (movement > 0), the scale should be +1, because the sprite is already facing right.
When going left (movement < 0), the scale should be -1, so the sprite gets flipped to face left.
When not moving (movement = 0), the scale should not change, so the player stays in the direction of the last movement.
The Y and Z axes of the scale should remain at 1.
Here is the graph we need:
- Set Local Scale is located under Codebase > Unity Engine > Transform
- Create Vector 3 is located under Codebase > Unity Engine > Vector 3
- Branch and Select are located under Control
- Comparison is located under Logic
- Float literals will be at the root of the options if dragged contextually from the Select node
Then, connect the control input of the Branch node to the control output of our previous Set Velocity node on the left, and add a Flip group to keep things tidy:
If you play now, you should see the player flip:
6. Playing the animations
The last part of our movement graph will be to play the run animation.
The player prefab is already set up with a proper Unity animator controller. This controller has a Speed parameter that is used to transition between Idle and Walk states:
All we need to do on Bolt's end is to pass our movement speed to the animator. Because we're passing a speed and not a direction, we'll need to make our Movement variable absolute before passing it. This way, if we're going left at -5 movement, we'll tell the animator we're going at 5 speed.
- Set Float is located under Codebase > Unity Engine > Animator
- Absolute is located under Math > Scalar
To connect this part of the graph with our movement code, we'll need to add two connections:
- To the True port of our no-movement check, in case we skipped flipping the player
- To the output port of the Set Local Scale unit, in case we did flip the player
This way, whether or not we flip the player, we'll still update the animation. Bolt supports connecting multiple control outputs to a single control input for these kind of scenarios.
With an Animator group around or last part, your graph should now look a bit like this when zoomed out:
If you test now, the animations will play properly. Movement code: check. Hurray!
Because our player is a physics rigidbody, implementing jump is as easy as adding an upwards vertical force.
First, create a new float object variable called Jump and give it a value of 12:
Then, below our movement graph, add a new group with the following units:
- On Button Input is located under Events > Input
- Add Force is located under Codebase > Rigidbody 2D > Add Force
Just like the Horizontal axis we used before, Jump is a default Unity input for new projects, mapped to Space on the keyboard. If you test your game now, you'll be able to jump!
8. Ground Check
There are two problems with our jump code:
- It doesn't change the animation sprite when you're in the air
- It allows you to jump again while you're already in the air
To fix this, we'll need to create a ground check. This part of the graph will use a raycast. This means we will throw a ray from the player's belly towards the ground, and check if we hit a platform within a small distance. If we did, it means the player is grounded; otherwise, it's in the air.
To make our raycast more reliable, we'll actually use what is called a circle cast. It's basically the same as a raycast, except you can give a thicker width (radius) to your ray. The node we need is called Circle Cast, under Codebase > Unity Engine > Physics 2D:
There are many options for it, depending on what parameters you need. In our case, we need the one with:
- Origin: The source of the circle cast, in our case the player's position
- Radius: The width or the circle cast, which we'll set to 0.3 for more reliability
- Direction: The direction in which we'll cast, in our case down (-1 on the Y axis).
- Distance: How far the circle cast will check until it stops, which we'll set to 1.1
- Layer Mask: On which layer(s) it should check for a collision, which we'll set to Platforms only.
Once properly setup, our circle cast should look like this:
To get the layer mask dropdown, search for Layer Mask and choose Layer Mask Literal.
This graph can be read as: "Throw a ray downwards from my position with a 0.3 thickness, and check if it hits an object on the Platforms layer within 1.1 units".
Next, we need to analyze the result. Drag the little target output port to get the contextual menu open, and choose Raycast Hit 2D > Expose Raycast Hit 2D:
This will expose all the items in the raycast hit result. To check whether we actually hit a platform, we only need to check if the Collider is equal to Null (none). If it is, we didn't hit a platform and the player is in the air. If it isn't, we found a platform below our feet and the player is therefore grounded.
- Not Equal is located under Logic
- Null is located under Nulls
8.1 Preventing double jump
You can then use the result of our ground check to limit the jump to when the player is grounded:
Now, the player can't double jump anymore!
8.2 Setting the jump sprite
We also need change the sprite when the player is in the air. The animator controller is already configured to take a Grounded bool parameter. The only thing we need to do from Bolt is assign it. To do that, we'll copy-paste our ground check code at the end of our previous animator section, then use the Set Bool unit.
Now, if you test your game, the jump sprite should show up when in the air:
8.3 Reusing the ground check with a super unit
In scripting, there is a very important principle called DRY: Don't Repeat Yourself.
What we just did to change the jump sprite broke this principle: we copied and pasted the same part of our graph twice.
Now, if we wanted to make adjustments to our ground check code, we'd have to change two places every time. This may not seem so bad now, but what about when we'll have enemies doing multiple ground checks too? We'd have to update our graphs in three or four or even more places every time we make a change. Yikes.
Fortunately, Bolt provides a way to reuse the same graph in different places called Super Units. We will use super units to turn our ground check graph into a single unit that we will use for both the double-jump prevention and the jump sprite.
First, create a new macro with Assets > Create > Bolt > Flow Macro, and call it GroundCheck:
Then, copy the ground check units from the player graph, and paste them in the ground check graph:
Go back in the player graph, and delete your ground checks. Instead, drag & drop the new GroundCheck macro into your graph. It should appear as a single node, for example like this for the jump:
We have a problem here... Where is the result of the ground check? How can we connect it to the Branch node?
The reason it's not visible is because we haven't created an Output unit in our ground check macro. Let's do that now. From the player graph, just double click the Ground Check super unit to open its full graph. Notice the breadcrumbs in the toolbar let you navigate in nested graphs easily:
Next, add an Output unit, located under Nesting, and select it to display its graph inspector:
We will add a Value Output for the result of the ground check. Set its key to groundedand its type to Boolean. If you want, you can give it a label and summary to add documentation to the graph inspector. Lastly, connect the new grounded port with the result of the ground check.
Navigate back to the parent player graph using the breadcrumbs in the toolbar. You'll see the super unit now has a grounded output port. Use that to connect it for both the double-jump prevention and the jump sprite animation:
If you play your game now, nothing should have changed. But your graph is now a lot cleaner, DRYer, and easier to maintain for the future.
Finally, apply the changes to your prefab so that the object variables like Speed and Jump we created are automatically added on other player prefab instances in the other scenes.
This concludes the character controller. In this part, you learned how to:
- Create machines and macros
- Create and work with variables
- Add units and connections
- Reuse graphs with super units
- Work with input, math, animation, physics and raycasting
Whew! The ice is broken, and you now know most of the basic concepts you need to use Bolt. Take a moment to review what you've learned, make sure you understand, and stretch your legs. Next up, we'll be adding a death mechanic to our games.
|Project Setup||Spikes & Death|
Customer support service by UserEcho