Godot version: Godot_v4.6
How do I fix my code for a follower/trail system in Godot 4 for my RPG adventurers party?
I’m a beginner learning how to code on Godot but has a surface-level, fundamental understanding of Python. Currently I’m working on coding an RPG game where the player can move a main sprite and have the other party members follow behind. The problem I have is that, although my code has no errors to debug anymore, when I run the game only the main sprite can be controlled, while the follower only spawns one and does not move entirely.
Does this have something to do with my child node for my follower tree, or does my code need more specifications for that?
Here is my code for player.gd:
extends CharacterBody2D
const FOLLOWER_SCENE_PRELOAD = preload("res://party.tscn")
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
const SPEED: float = 200.0
const ACCELERATION: float = 25
const FRICTION: float = 22
var look_dir: Vector2 = Vector2(1,1)
var last_dir: Vector2 = Vector2.RIGHT
var followers: Array[CharacterBody2D] = []
var distance_spacing: float = 12.0
var trail_points: Array[Vector2] = []
#-----------------------------------------------------
# MOVEMENT & ANIMATION
#-----------------------------------------------------
func _physics_process(_delta) -> void:
_process_movement()
_process_animation()
move_and_slide()
func _process_movement() -> void:
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction := Input.get_vector("left", "right", "up", "down")
if direction != Vector2.ZERO:
velocity = direction * SPEED
last_dir = direction
else:
velocity = Vector2.ZERO
if direction == Vector2.ZERO: # always update trail
trail_points.insert(0, global_position)
_follower_logic()
func _process_animation() -> void:
if velocity != Vector2.ZERO:
_play_animation("walk", last_dir)
else:
_play_animation("idle", last_dir)
func _play_animation(action: String, dir: Vector2) -> void:
if dir.x != 0: #move left
sprite.flip_h = dir.x < 0
sprite.play(action)
elif dir.y != 0:
sprite.play(action)
#-----------------------------------------------------
# TRAIL SYSTEM
#-----------------------------------------------------
func _follower_logic() -> void:
if trail_points.is_empty() or trail_points[0].distance_to(global_position) >= 1.0:
trail_points.push_front(global_position)
var max_trail_length: float = followers.size() * distance_spacing
while trail_points.size() > max_trail_length:
trail_points.pop_back()
for i in followers.size():
var path_pos: Vector2 = _get_point_along_trail(distance_spacing * (i+1))
followers[i].player_moving = true if round(velocity) else false
followers[i].target_pos = path_pos
followers[i].look_dir = round((followers[i].global_position - path_pos).normalized()) * -1
func _get_point_along_trail(distance: float) -> Vector2:
var total: float = 0.0
for i in range(trail_points.size()):
var points_a: Vector2 = trail_points[i]
var points_b: Vector2 = trail_points[i+1]
if total + distance_spacing >= distance:
var t: float = (distance - total) / distance_spacing
return points_a.lerp(points_b, t)
total += distance_spacing
return trail_points.back()
func _spawn_follower(anim_name: String) -> void:
var new_follower_scene: CharacterBody2D = FOLLOWER_SCENE_PRELOAD.instantiate()
new_follower_scene.ACCELERATION = ACCELERATION
new_follower_scene.FRICTION = FRICTION
if trail_points.is_empty():
trail_points.append(global_position)
new_follower_scene.global_position = _get_point_along_trail(distance_spacing * (followers.size() + 1))
get_parent().add_child.call_deferred(new_follower_scene)
new_follower_scene.set_up.call_deferred(anim_name)
followers.append(new_follower_scene)
Here is my code for party.gd:
extends CharacterBody2D
const SPEED: float = 300.0
var ACCELERATION: float
var FRICTION: float
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
var player_moving: bool = false
var target_pos: Vector2
var look_dir: Vector2
var last_dir: float = 1 # _last_look_dir
#-----------------------------------------------------
# SET-UP
#-----------------------------------------------------
func set_up(anim_name: String) -> void:
while !sprite:
await get_tree().physics_frame
sprite.play("idle")
#-----------------------------------------------------
# MOVEMENT & ANIMATION
#-----------------------------------------------------
func _physics_process(delta: float) -> void:
var old_pos: Vector2 = global_position # identify if player is idle or moving
var pos_lerp_weight: float = 1.0 - exp( -(ACCELERATION if player_moving else FRICTION) * delta)
global_position = lerp(global_position, target_pos, pos_lerp_weight)
if look_dir.y:
last_dir = look_dir.y
if global_position.distance_to(old_pos) > 0.1:
if look_dir.x:
sprite.flip_h = true if look_dir.x < 0 else false
_play_animation(str(last_dir, "walk"))
else:
_play_animation(str(last_dir, "idle"))
func _play_animation(anim_name: String) -> void:
if !sprite:
return
if sprite.sprite_frames.has_animation(anim_name):
sprite.play(anim_name)
Here is what’s showing when I run the game:
(The mage sprite on the left is the main player, the right one is a spawned follower.)
Tutorial video I followed: RPG Party Follow System | Godot 4.4 | Mostly Mad Productions
It would also be great it you could provide some additional tutorial links for switching main sprites in a party, like Omori and/or Pokemon.
