Welcome to the 4th step in the making of a HTML5 game like Stairs using Godot.
Let’s see a small recap: in first step, we saw how to build an endless staircase, in second step we added spikes and assigned materials to the meshes, then in third step we added a bouncing ball only using trigonometry. Now it’s time to control the ball and add more spikes.
There is quite a lot to do in this step, so let’s jump straight to the point: we need more spikes. We could generate them at runtime just like we did with the steps as seen in step 1, but this time let’s build them directly from the editor.
First, let’s make the step a bit bigger, by selecting it then from Inspector panel changing its size to x = 18.
You may not need to change the size of your step if you followed this tutorial with your own sizes, anyway that’s what I am doing.
So let’s select the step from Scene panel:
Then from Inspector panel set Size x = 18.
Now select the spike and from Inspector panel set Transform -> Translation -> x = -7.5.
Now your step should look like this:
The spike should be on the leftmost side of the step. Now let’s duplicate it.
From Scene panel right click on Spike and select Duplicate.
Your spike has been duplicated, but you won’t see it because it’s in the same place of the original spike, but you should see Spike2 in Scene panel.
Just like we did with the first spike, let’s select Spike2 in Scene panel and from Inspector panel set Transform -> Translation -> x = 5.
Now the step should look this way:
The second spike is next to the first one. Now we should repeat this process five more times, creating five more spikes whose Translation x values are -2.5, 0, 2.5, 5 and 7.5.
At the end of this process, the step should look like this one:
And your Scene panel should have the new spikes in it:
At this time we want to stop randomly moving the first spike as seen in step 2 and just show some random spikes, which will never move.
We have to change Step.gd script this way. A lot of line have been changed, so it’s a good idea to completely rewrite it rather than just editing it.
extends MeshInstance # stair speed var speed = 3 # variable to store y/z ratio var yzRatio # variable to store the amount of steps var steps # initialize the random number generator var rnd = RandomNumberGenerator.new() # maximum amount of spikes on each step var maxSpikes = 4 # Called when the node enters the scene tree for the first time. func _ready() : # set a time-based seed for random number generator rnd.randomize() # determine y/z ratio yzRatio = mesh.size.y / mesh.size.z # get steps value from Spatial node steps = get_node('/root/Spatial').get('steps') # custom function to set the spikes setSpikes() # This function position enables some random spikes func setSpikes() : # loop from 0 to 6 for i in 7 : # set i-th child of the step (the i-th spike) to invisible get_child(i).visible = false # loop from 0 to maxSpikes - 1 for i in maxSpikes : # toss a number from 0 to 6 var randomSpike = rnd.randi_range(0, 6) # set the randomSpike-th child of the step to visible get_child(randomSpike).visible = true # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta) : # move the step along y axis according to speed and elapsed time translation.y -= delta * speed # move the step along z axis according to speed, elapsed time and y/z ratio # y/z ratio is necessary because y and z speed cannot be the same as steps # do not have the same size so the stair does not have a 45 degrees angle translation.z += delta * speed / yzRatio # is z position greater than mesh z size, making the step to be outside camera eye? if (translation.z > mesh.size.z) : # move the step along y axis as if it were the highest step translation.y += steps * mesh.size.y # move the step along z axis as if it were the furthest step translation.z += steps * mesh.size.z * - 1 # set random spikes setSpikes()
Since now we are using the random number generator here, there’s no need to keep using it in Spatial.gd script, which now becomes:
extends Spatial # amount of steps we want in the stair var steps = 15 # Called when the node enters the scene tree for the first time. func _ready() : # get Step node var stepNode = get_node('Step') # this is the height of the step, determined according to mesh size var deltaY = stepNode.mesh.size.y # this is the depth of the step, determined according to mesh size var deltaZ = -stepNode.mesh.size.z # a for loop going from 1 to steps - 1 for i in range (1, steps) : # duplicate newStep node var newStep = stepNode.duplicate() # move the duplicated step along y axis newStep.translation.y = deltaY * i # move the duplicated step along z azis newStep.translation.z = deltaZ * i # add the step to the scene add_child(newStep)
I just removed the two lines var rnd = RandomNumberGenerator.new() and rnd.randomize() which we don’t need anymore.
And this is what we have now:
Now that we have an endless staircase with multiple spikes, we want to move the ball with the mouse.
The idea is to detect mouse position relative to game viewport and translate it to the movement on a step. Basically this means that when the mouse is on the very left side of the canvas, the ball will be on the very left side of the step, when the mouse is on the center of the canvas the ball will be on the center of the step, when the mouse is on the very right side of the canvas, the ball will be on the very right side of the step, and so on.
We need to add just a couple of lines to Ball.gd
extends MeshInstance # ball starting step. 0: first step, 1: second step, and so on var ballStartingStep = 1 # jump height, should be higher than step height var jumpHeight = 5 # here we'll store the jump time, that is the time required for a step to take the place of another var jumpTime # here we'll store the amount of time the ball is in play var ballTime # we need to store ball starting y position to determine its y position when it's jumping var ballY # here we store ball movement range var ballRange # Called when the node enters the scene tree for the first time. func _ready() : # get Step reference var step = get_node('/root/Spatial/Step') # ball movement range is equal to mesh horizontal size minus ball diameter ballRange = step.mesh.size.x - mesh.radius * 2 # jump time, in seconds, is step height divided by step speed jumpTime = step.mesh.size.y / step.get('speed') * 1000 # ballTime starts at zero ballTime = 0 # determine ball y position according to step size and starting step ballY = step.mesh.size.y * ballStartingStep + step.mesh.size.y / 2 + mesh.radius # move the ball to ballY position translation.y = ballY # determine ball z position according to step size and starting step translation.z = -step.mesh.size.z * ballStartingStep # called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta) : # get the viewport var viewport = get_viewport() # get mouse X position relative to viewport width # 0 = left, 1 = right var mouseX = viewport.get_mouse_position().x / viewport.get_visible_rect().size.x # set ball x position according to mouseX value translation.x = -ballRange / 2 + ballRange * mouseX # increase ballTime assing delta to it ballTime += delta * 1000 # if ballTime is greater or equal than jump time... if (ballTime >= jumpTime) : # subtract jumpTime to ballTime ballTime -= jumpTime # ratio ranges from 0 (ball at the beginning of jump time) to 1 (ball at the end of jump time) var ratio = ballTime / jumpTime # move the ball to y position equal to sin of ratio * PI multiplied by jump height translation.y = ballY + sin(ratio * PI) * jumpHeight
This is enough at the moment to have the ball moving following mouse position, try by yourself:
Move the mouse to control the ball. It’s not the best way to control the ball as we would need some kind of virtual trackpad, but it will be added later, once we’ll be able to detect collisions. Meanwhile, download the source code of the entire project.