Sprite cannot walk up or down (it can only walk side-to-side)

Godot Version

4.3 Dev 6

Question

How would I implement up and down movement for the sprite? (I just need it to be able to walk up when I press move_up which is W and walk down when I press move_down which is S).

The script for the main CharacterBody2D

extends CharacterBody2D
class_name Player

var x_input: float = 0.0
var pause_lock: bool = false

@onready var sprite = $PlayerSprite
@onready var f_arm = $PlayerSprite/Torso/R_Arm
@onready var b_arm = $PlayerSprite/Torso/L_Arm
@onready var hand = $PlayerSprite/Torso/R_Arm/Hand
@onready var aim_pivot = $PlayerSprite/Torso/AimPivot
@onready var fsm = $FSM
@onready var leg_anim = $LegsAnimation

@export var MAX_SPEED		: int = 18
@export var ACCELERATION	: int = 100
@export var FRICTION		: float = 1.0

@export var base_hp			: int = 0
@onready var hp: int = base_hp

var motion : Vector2 = Vector2.ZERO

signal health_updated()
signal health_depleted()
signal got_hurt()

func _idle():
	motion.x = lerp(motion.x, 0.0, FRICTION)

func _move(delta, direction):
	motion.x += direction * ACCELERATION * delta
	motion.x = clamp(motion.x, -MAX_SPEED, MAX_SPEED)
	velocity = motion
	up_direction = Vector2.UP
	move_and_slide()

func hurt(damage: int):
	_set_health(self.hp - damage)
	emit_signal("got_hurt")

func _set_health(new_val: int):
	self.hp = new_val
	emit_signal("health_updated")
	if self.hp <= 0:
		emit_signal("health_depleted")

func apply_physics():
	velocity = motion
	up_direction = Vector2.UP
	move_and_slide()

func _player_input():
	x_input = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")


func aim(pos: Vector2):
	_flip_player_sprite(pos.x < self.global_position.x)
	if (pos.x < self.global_position.x):
		f_arm.rotation = lerp_angle(f_arm.rotation, -(aim_pivot.global_position - pos).angle(), (0.10))
	else:
		f_arm.rotation = lerp_angle(f_arm.rotation, (pos - aim_pivot.global_position).angle(), (0.10))
	b_arm.look_at(hand.global_position)


func _flip_player_sprite(flip: bool):
	match flip:
		true:
			sprite.scale.x = -1
		false:
			sprite.scale.x = 1

func _animate_legs():
	if (x_input == 0):
		leg_anim.play("Idle")
	else:
		var is_forward: bool = (
				(sprite.scale.x == 1 and x_input > 0)
				or (sprite.scale == Vector2(-1,1) and x_input < 0)
		)
		
		match is_forward:
			true:
				leg_anim.play("Walk_Forward")
			false:
				leg_anim.play("Walk_Backward")

The script for a child of Node (the Finite State Machine):

extends Node

enum STATES {IDLE, MOVE}

var state: int = 0

@onready var parent = get_parent()


func _physics_process(delta):
	run_state(delta)


func run_state(delta):
	parent._player_input()
	parent.aim(parent.get_global_mouse_position())
	
	match state:
		STATES.IDLE:
			parent._idle()
			
			if (parent.x_input != 0):
				_set_state(STATES.MOVE)
		STATES.MOVE:
			parent._animate_legs()
			parent._move(delta, parent.x_input)
			
			if (parent.x_input == 0):
				_set_state(STATES.IDLE)


func _set_state(new_state: int):
	if (state == new_state):
		return
	
	state = new_state

Please help I’ve already spent half the day working on this bit of code. I tried implementing velocity and removing Velocity.up, but there is always some sort of error (e.g. walking infinitely). Thanks and any piece of constructive feedback/help would be appreciated.

Github page for project: GitHub - Joshulties/Godot_3.5.1_2DPlayerAnimation (though it is in 3.5.1)

Might not help you at all but when my character walked infinitely it was because the velocity did not reset after every frame. If you could find a way to redo the infinite walking and find a way to reset the velocity up, I think that might do what you’re looking for.

1 Like

Hey! Thank you for your reply :D. I have rested my code so that the character does not move infinitely, but I still can’t figure out how to get it to move up or down? Thank you though.

Is your game a platformer or a top view 2D?
I think you need to track the W and S inputs, just like you did with A and D and apply that to the Vector2 ( motion ) you created.
You’re only applying movement to the X axis of the character.

1 Like

Hi. It is a topdown. I’ll try using y_input along with x_input then.

It doesn’t seem to work. Now I got the character only moving top left to bottom right

Can you update the code on the repo?
I downloaded it but it does not seem up to date with your post. I’m doing some tests on my end

1 Like

I’ll post my version which works in godot4: GitHub - JChips01/boneanimationgodot4

1 Like

Hey @Amosao, thank you for your help. I fixed my code :smiley: . IT seemed the problem lay in creating counterparts of each of the x motion. Printing the variables helped a lot as well.

1 Like

I’m glad it helped, I just made it some changes in the code on my side and made it work

Player.gd

extends CharacterBody2D
class_name Player

var input: Vector2
var pause_lock: bool = false

@onready var sprite = $PlayerSprite
@onready var f_arm = $PlayerSprite/Torso/R_Arm
@onready var b_arm = $PlayerSprite/Torso/L_Arm
@onready var hand = $PlayerSprite/Torso/R_Arm/Hand
@onready var aim_pivot = $PlayerSprite/Torso/AimPivot
@onready var fsm = $FSM
@onready var leg_anim = $LegsAnimation

@export var MAX_SPEED		: int = 18
@export var ACCELERATION	: int = 100
@export var FRICTION		: float = 1.0

@export var base_hp			: int = 0
@export var move_speed: int = 200
@onready var hp: int = base_hp

var motion : Vector2 = Vector2.ZERO

signal health_updated()
signal health_depleted()
signal got_hurt()

func _idle():
	motion.x = lerp(motion.x, 0.0, FRICTION)

func _move(delta, direction):
	motion = direction * move_speed
	velocity = motion
	move_and_slide()

func hurt(damage: int):
	_set_health(self.hp - damage)
	emit_signal("got_hurt")

func _set_health(new_val: int):
	self.hp = new_val
	emit_signal("health_updated")
	if self.hp <= 0:
		emit_signal("health_depleted")

#func apply_physics():
	#velocity = motion
	#up_direction = Vector2.UP
	#move_and_slide()

func _player_input():
	input = Input.get_vector("move_left", "move_right", "move_up", "move_down")

func aim(pos: Vector2):
	_flip_player_sprite(pos.x < self.global_position.x)
	if (pos.x < self.global_position.x):
		f_arm.rotation = lerp_angle(f_arm.rotation, -(aim_pivot.global_position - pos).angle(), (0.10))
	else:
		f_arm.rotation = lerp_angle(f_arm.rotation, (pos - aim_pivot.global_position).angle(), (0.10))
	b_arm.look_at(hand.global_position)


func _flip_player_sprite(flip: bool):
	match flip:
		true:
			sprite.scale.x = -1
		false:
			sprite.scale.x = 1


func _animate_legs():
	if (input == Vector2.ZERO):
		leg_anim.play("Idle")
	else:
		var is_forward: bool = (
				(sprite.scale.x == 1 and input.x > 0)
				or (sprite.scale == Vector2(-1,1) and input.x < 0)
		)
		
		match is_forward:
			true:
				leg_anim.play("Walk_Forward")
			false:
				leg_anim.play("Walk_Backward")

FSM_Player.gd

extends Node

enum STATES {IDLE, MOVE}

var state: int = 0

@onready var parent = get_parent()


func _physics_process(delta):
	run_state(delta)


func run_state(delta):
	parent._player_input()
	parent.aim(parent.get_global_mouse_position())
	
	match state:
		STATES.IDLE:
			parent._idle()
			
			if (parent.input != Vector2.ZERO):
				_set_state(STATES.MOVE)
		STATES.MOVE:
			parent._animate_legs()
			parent._move(delta, parent.input)
			
			if (parent.input == Vector2.ZERO):
				_set_state(STATES.IDLE)


func _set_state(new_state: int):
	if (state == new_state):
		return
	
	state = new_state

1 Like

Thank you so much for your input :smiley:. I realised your code is way more efficient than mine, 1000 thanks!

1 Like