Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Stairs game, 3D, Game development, Godot Engine and HTML5.

Here we go with the second part of the tutorial series about the development of a game like Stairs using Godot.

In first step I showed you how to build an endless 3D staircase, now it’s time to add spikes to each step, just like in this prototype built with Phaser and Three.js.

Before we add new content to our Godot project, we must first fix a couple of things.

In previous step we assigned the size of the cube MeshInstance by acting on its Tranform panel. Since we are going to add spikes as children of cube MeshInstance, and we do not want spikes to be transformed, we have to roll back a bit.

Select the step MeshInstance, and in the Inspector panel of the step reset its Transform -> Scale values to Scale x = 1, Scale y = 1 and Scale z = 1.

Now in the same Inspector panel, let’s operate directly on Size form, setting Size x = 16, Size y = 2 and Size z = 8. Needless to say, these are arbitrary values set by me, and you are free to give the step the size you prefer. It’s just I will be using these sizes during the process.

It’s also time to change the default color of the step, and we can do this by assigning the MeshInstance a material. In the Inspector panel, go to Material and select New SpatialMaterial.

Now click on the white sphere you should see in the Inspector panel to see all material properties.

To change the color, we need to select Albedo -> Color and replace the default white choosing a color from the palette.

I wanted a green step, so now my Inspector panel and my main window look like this. You should get a similar result.

At this time, we need to modify a bit the scripts, because we worked with transform size while now we have to work with mesh size.

This is the Spatial script, with edited lines highlighted. I also added some steps to make the staircase fill the camera view again.

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)

And this is the Step script, with edited lines highlighted.

extends MeshInstance

# stair speed
var speed = 2

# variable to store y/z ratio
var yzRatio

# variable to store the amount of steps
var steps

# Called when the node enters the scene tree for the first time.
func _ready() :
	
	# determine y/z ratio
	yzRatio = mesh.size.y / mesh.size.z
	
	# get steps value from Spatial node
	steps = get_node('/root/Spatial').get('steps')

# 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

And now if you run the project you should see something like this:

Now we are ready to add a spike for each step. Right click on Step in Scene panel and select Add Child Node.

Select MeshInstance.

Your Scene panel now should show you the new MeshInstance created as a Step child, let’s rename it as Spike by right clicking on it and select Rename.

This is how your Scene panel should look now:

Just like we did when building the step, now we have to assign a mesh to the spike. Select the spike, then go to Inspector panel and in Mesh selector choose New CylinderMesh.

Click on the white cylinder, then set Top Radius to zero, this will turn the cylinder into a cone.

Just like we did with the step MeshInstance, now go to Material selector and choose New SpatialMaterial.

Now click on the white sphere, and select Albedo -> Color from the properties and replace the default white color. I choosed a red.

Finally, go to Transform form in Inspector panel and change Translation y = 2 to match step’s y size, if you used the same values I did.

Now your main window should show something like this:

Now we have a step and we also have the spike, let’s run the project and see what happens:

We have the endless staircase with endless spikes on it. But now we want spikes to be randomly placed, rather than always appearing in the same position.

We first need to edit Spatial‘s script to initialize random number generation, in the highlighted lines:

extends Spatial

# amount of steps we want in the stair
var steps = 15

# initialize the random number generator
var rnd = RandomNumberGenerator.new()

# 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()
	
	# 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)

Then in Step‘s script we can manage random spike placement:

extends MeshInstance

# stair speed
var speed = 2

# variable to store y/z ratio
var yzRatio

# variable to store the amount of steps
var steps

# Called when the node enters the scene tree for the first time.
func _ready() :
	
	# 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 place the spike
	placeSpike()
	
# This function positions the spike randomly along the step
func placeSpike() :
	
	# now we determine the half width of the step, and we subtract 1
	# this is the half range where we are going to place the spike
	# if the step has width = 10, halfPositionRange will be 10 / 2 - 1 = 4
	# this means we'll place the spike in a random position between -4 and 4
	var halfPositionRange = (mesh.size.x) / 2 - 1
	
	# and this is how we toss a random integer number in the range -n, n
	var randomPosition = get_node('/root/Spatial').get('rnd').randi_range(-halfPositionRange, halfPositionRange)
	
	# now we get the first child of the step (the spike) and place it in the random position
	get_child(0).translation.x = randomPosition

# 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
		
		# place the spike in a random position
		placeSpike()

And finally we have the endless ladder with endless randomly placed spikes.

And that’s all at the moment, next time we’ll face the hardest step: adding the bouncing ball. Meanwhile, download the entire project and play with it.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.