Assuming that you have created a simple stage for the game using tile-maps or any other approach. You now want to create a character controller & move it with code in that game level. This 2D platformer player controller recipe solves this problem.
In next recipe, we will see how to shoot bullets or stones in game or throw daggers to kill NPCs. And after that, we will create NPC characters in game.
This recipe is helpful if you have some basic understanding of the Godot engine.
Assets & Project Link
The code of all the platformer tutorial series is present in this repository. All the assets are given credit by linking to their original sources in a.txt
files in every folder.
Making a character
In Godot, create a scene with CharacterBody2D as its root. Add a Sprite2D (or AnimatedSprite2D) nodes as its child. Also add collision shape. Overall setup should look like this:
Motion mechanics
Add a script to the character and add code to move left, right, jump, or stay idle like this (I used AnimatedSprite2D for these animations) if you use Sprite2D, just remove lines dealing with AnimatedSprite2D:
extends CharacterBody2D
@export var speed = 232
@export var jump_speed = -800
@export var gravity = 300 * 9.8
@export_range(0.0, 1.0) var friction = 0.32
@export_range(0.0 , 1.0) var acceleration = 0.32
var is_jumping :bool= false
func _physics_process(delta):
velocity.y += gravity * delta
var dir = Input.get_axis("ui_left", "ui_right")
if dir != 0:
if is_on_floor():
velocity.x = lerp(velocity.x, dir * speed, acceleration)
else:
velocity.x = lerp(velocity.x, dir * speed * 1.4, acceleration)
if dir > 0:
# flip the sprite
$AnimatedSprite2D.flip_h = false
else:
$AnimatedSprite2D.flip_h = true
$AnimatedSprite2D.play("run")
else:
velocity.x = lerp(velocity.x, 0.0, friction)
$AnimatedSprite2D.play("idle")
if Input.is_action_just_pressed("ui_up") and is_on_floor():
velocity.y = jump_speed
is_jumping = true
if is_jumping:
if is_on_floor():
is_jumping = false
$AnimatedSprite2D.play("fall")
move_and_slide()
Adding attack mechanics
Attack adds a bit of complexity. While attacking, we complete the animation even if another animation such as jump or run is triggered. Otherwise, attack animation will be cut abruptly. For this we add some more checks.
Code:
extends CharacterBody2D
@export var speed = 232
@export var jump_speed = -800
@export var gravity = 300 * 9.8
@export_range(0.0, 1.0) var friction = 0.32
@export_range(0.0 , 1.0) var acceleration = 0.32
var is_jumping: bool= false
var is_attacking: bool = false
func _physics_process(delta):
velocity.y += gravity * delta
var dir = Input.get_axis("ui_left", "ui_right")
if dir != 0:
if is_on_floor():
velocity.x = lerp(velocity.x, dir * speed, acceleration)
else:
velocity.x = lerp(velocity.x, dir * speed * 1.4, acceleration)
if dir > 0:
# flip the sprite
$AnimatedSprite2D.flip_h = false
else:
$AnimatedSprite2D.flip_h = true
if not is_attacking: $AnimatedSprite2D.play("run")
else:
velocity.x = lerp(velocity.x, 0.0, friction)
if not is_attacking: $AnimatedSprite2D.play("idle")
if Input.is_action_just_pressed("ui_up") and is_on_floor():
velocity.y = jump_speed
is_jumping = true
if is_jumping:
if is_on_floor():
is_jumping = false
if not is_attacking: $AnimatedSprite2D.play("fall")
if Input.is_action_just_pressed("ui_home"):
is_attacking = true
if is_attacking:
$AnimatedSprite2D.play("attack")
move_and_slide()
func _on_animated_sprite_2d_animation_finished():
if is_attacking:
is_attacking = false
print("ff")
func _on_animated_sprite_2d_animation_looped():
if is_attacking:
is_attacking = false
print("ff")
Thank you for reading <3