Get the full commented source code of

HTML5 Suika Watermelon Game

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

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.

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