Build a 3D HTML5 game like “Stairs” using Godot – Step 2: adding endless randomly placed spikes and assigning materials to meshes

Read all posts about "" game

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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers