Making 3D Endless Runner Game Part 1

infinite runner like temple run tutorial preview

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.

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:

  1. Either we move the character forward and spawn the stationary world elements in front of the player.
  2. 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:

infinite runner character demo

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

Leave a Reply

Your email address will not be published. Required fields are marked *