Colliding while pressing more than one direction rabidly changes velocity

Godot Version

Godot v4.2.2.stable

Question

EDIT: I am silly. I only just thought to add a variable that checks if the player is currently moving or not, by checking if they are currently pressing one of the movement actions. As in:

var moving = false
...
if Input.is_action_pressed("up"):
    facing = UP
    moving = true
... etc
else:
    moving = false

Then checking that to change the state. Which fixes the issue I was having with the states changing suddenly. I’m uncertain if this is the best way to go about it, however. But, for now, it seems to work fine.

END EDIT

Hi! I’m new to Godot and was hoping to get some advice on a problem I’m having. For context:

  • This is a top-down 2d game with top-down movement.
  • The player is a CharacterBody2D with a camera2d, collisionshape and animatedsprite as children.
  • The map the player is colliding with is a tilemap that I have applied collision to select tiles using the physics layers. (I’m still a little unfamiliar with collision layer and collision mask, so both the player and tilemap are on collision and mask layers 1).

I have a basic state machine with LimboAI that controls the player state (whether they’re idling, walking or attacking), which also controls the the player animation. The state is changed between idling and walking depending on the player velocity. However, I’ve found that when colliding with walls, and especially corners, if I press more than one movement at a time, the velocity will jump between values every frame. So if I run into the bottom right corner, it will jump between (0,0) and (113.1371, 0). (or (1,0) normalised).

As a result, the player jumps between the idle and walking state rapidly, causing the sprite to appear to jitter, as it tries to play the idle and walking animations.

Is there something else I can check for to see if the player is colliding with a wall on the tilemap? Or something else I can use to check if the player is currently moving or not? Or should I use move_and_collide instead?

The player code could use some cleaning up since I was using this mainly to learn about state-machines and how to use LimboAI, but this issue is kinda nagging me. Here’s the player code below:

extends CharacterBody2D

@onready var animation = $AnimatedSprite2D

const UP = Vector2(0,-1)
const DOWN = Vector2(0,1)
const LEFT = Vector2(-1,0)
const RIGHT = Vector2(1,0)
const SPEED := 160.0

var dir
var facing = DOWN

var main_sm: LimboHSM

func _ready():
	initiate_state_machine()

func _physics_process(delta):
	#print(main_sm.get_active_state().name)
	#print(facing)
	#print(velocity)
	dir = Input.get_vector("left", "right", "up", "down")
	
	# Determine Facing Direction
	if Input.is_action_pressed("up"):
		facing = UP
	elif Input.is_action_pressed("down"):
		facing = DOWN
	elif Input.is_action_pressed("left"):
		facing = LEFT
	elif Input.is_action_pressed("right"):
		facing = RIGHT
	
	velocity = dir * SPEED
	
	# No movement during attack state
	if main_sm.get_active_state().name != "attack":
		flip_sprite(dir)
		move_and_slide()

func flip_sprite(dir):
	if dir == RIGHT:
		animation.flip_h = false
	elif dir == LEFT:
		animation.flip_h = true

func _unhandled_input(event):
	if event.is_action_released("attack"):
		main_sm.dispatch(&"to_attack")

# State Machine 
func initiate_state_machine():
	main_sm = LimboHSM.new()
	add_child(main_sm)
	
	# Define States
	var idle_state = LimboState.new().named("idle").call_on_enter(idle_start).call_on_update(idle_update)
	var walk_state = LimboState.new().named("walk").call_on_enter(walk_start).call_on_update(walk_update)
	var attack_state = LimboState.new().named("attack").call_on_enter(attack_start).call_on_update(attack_update)
	
	var states = [idle_state, walk_state, attack_state]
	
	for state in states:
		main_sm.add_child(state)
	
	main_sm.initial_state = idle_state
	
	# Define State Transitions
	main_sm.add_transition(main_sm.ANYSTATE, idle_state, &"state_ended")
	main_sm.add_transition(idle_state, walk_state, &"to_walk")
	main_sm.add_transition(main_sm.ANYSTATE, attack_state, &"to_attack")
	
	main_sm.initialize(self)
	main_sm.set_active(true)

func idle_start():
	pass

func idle_update(delta: float):
	if facing == UP:
		animation.play("idle_up")
	elif facing == DOWN:
		animation.play("idle_down")
	elif facing == LEFT:
		animation.play("idle_side")
	elif facing == RIGHT:
		animation.play("idle_side")
	
	if velocity != Vector2(0,0):
		main_sm.dispatch(&"to_walk")

func walk_start():
	pass

func walk_update(delta: float):
	if facing == UP:
		animation.play("run_up")
	elif facing == DOWN:
		animation.play("run_down")
	elif facing == LEFT:
		animation.play("run_side")
	elif facing == RIGHT:
		animation.play("run_side")
	
	if velocity == Vector2(0,0):
		main_sm.dispatch(&"state_ended")

func attack_start():
	if facing == UP:
		animation.play("attack_up")
	elif facing == DOWN:
		animation.play("attack_down")
	elif facing == LEFT:
		animation.play("attack_side")
	elif facing == RIGHT:
		animation.play("attack_side")

func attack_update(delta: float):
	if animation.is_playing() != true:
		main_sm.dispatch(&"state_ended")