Prerequisite: You have some understanding of Godot Engine (or any engine in general), and you want to develop Subway Surfers or Temple Run-like endless runner from scratch. If you are new to Godot, you can start with the Godot docs (here or here).
The overall concept is simple as artificial intelligence or complex mechanics are not involved. Having pretty assets will make the game look better. I have linked the project (with its assets) on the bottom of this post.
To keep the tutorial easy to understand, I have omitted some parts not central to the core concept (such as sounds).
Part 2 is here.
Infinite Runners Mechanics
This is how the gameplay of some popular infinite runner games look like:
We have a character that keeps running in same direction, dodging the obstacles along the way. Once we hit the obstacle, the game is over. Usually, we have 3 tracks that we can switch by pressing left or right keys. Above are examples of Temple Run, Subway Surfers, and Minion Rush.
Making the Player
Our player is a scene with CharacterBody3D root node along with a character mesh instance and collision shape & a camera. This is how the overall scene looks like (ignore collision_area
and other nodes that we will come to later):
Moving the World vs Moving the Character
In endless runner games, we have two options:
- Either we move the character forward and spawn the stationary world elements in front of the player.
- Or we keep the player in same position and move the whole world towards him.
In both cases, it gives illusion of player moving forward.
In my implementation, I kept the player stationary, but moved the whole world towards him. Why? it will help in floating point precision errors (though we are not going to fall into those errors any time soon, but its better to stay in safe side).
Player Mechanics
We have created 3 lanes only. the one in center, one on left and one on right. This is how they can be represented in code:
var lanes = [-2, 0, 2]
# Means:
# player.position.x = 2
# or
# player.position.x = 0
# or
# player.position.x = -2
# But never any other value!!
It means, player’s x-axis position can have any of these values and not in-between. So we will lerp
the player position towards these values when player hits left
or right
keys.
The rest is jumping, which is very simple to implement. We give some impulse up[wards when player jumps and when player is in air, we apply gravity to pull the character downward.
Player Mechanics Code
Attach script to the player scene. We want player to move left or right, jump but never move forward. But we will be playing the running
animation to fake movement. While at the same time, our world will be moving backwards.
Player code:
extends CharacterBody3D
@onready var animation_player: AnimationPlayer = $Character/AnimationPlayer
const MOVE_SPEED: float = 8.0
const JUMP_VELOCITY: float = 8.0 # Jump strength
const GRAVITY: float = 24.0 # Gravity strength
const LANES: Array = [-2, 0, 2] # Lane positions on x-axis
var starting_point: Vector3 = Vector3.ZERO
var current_lane: int = 1 # Start at lane index 1 (x = 0)
var target_lane: int = 1
var is_jumping: bool = false
var is_dead: bool = false
func _ready() -> void:
starting_point = global_transform.origin
func _physics_process(delta: float) -> void:
var direction: Vector3 = Vector3.ZERO
# Handle lane switching
if Input.is_action_just_pressed("ui_left") and target_lane > 0:
target_lane -= 1
if Input.is_action_just_pressed("ui_right") and target_lane < LANES.size() - 1:
target_lane += 1
# Move towards the target lane
var target_x: float = LANES[target_lane]
var current_x: float = global_transform.origin.x
global_transform.origin.x = lerp(current_x, target_x, MOVE_SPEED * delta)
# Apply gravity
if not is_on_floor():
velocity.y -= GRAVITY * delta
else:
velocity.y = 0 # Reset vertical velocity when on the floor
# Jumping logic
if is_on_floor() and Input.is_action_pressed("ui_up"):
velocity.y = JUMP_VELOCITY # Apply jump velocity
# Apply the velocity and move the character
move_and_slide()
# Play animations based on movement
if not is_on_floor():
animation_player.play("Jump")
else:
animation_player.play("Run")
This is how the player looks so far:
Project Code & Assets
All the assets & code is found in this project, along with the asset credits in a.txt
files. The original work is credit to this repo; I modified it and adapted it to Godot 4.
Thank you for reading <3