Need help with RayCast2D and grid-based movement

I’m fairly new to programming and GDScript. This code for 4-directional grid-based movement has very smooth movement. However, when the raycast collides with a CollisionShape2D, all of this smoothness immediately disappears and the player immediately snaps to the tile. Does anyone have an idea on how to fix this?

class_name Player extends CharacterBody2D

@onready var _raycast: RayCast2D = $RayCast2D
@onready var _animation_player: AnimationPlayer = $AnimationPlayer

@export var speed: float = 7.0 # Player walk speeld (multiples of 4 make the movement look jittery)
const TILE_SIZE: int = 16 # Tilesize of the walk tiles in pixels
 
var initial_position: Vector2 = Vector2.ZERO 
var input_direction = Vector2.ZERO # Stores the direction the player is moving
var moving: bool = false # Checks if the player is moving
var percent_moved_to_next_tile: float = .0
var direction_history: Array # Array that stores what inputs are being pressed

func _ready():
	initial_position = position

func _physics_process(delta):
	direction_storage()
	position = position.snapped(Vector2(TILE_SIZE, TILE_SIZE)) # Snap the player's position at the start
	if !moving: # Only runs when the player is moving
		process_player_input()
	elif input_direction != Vector2.ZERO:
		move(delta)
	else:
		position = initial_position.lerp(initial_position, percent_moved_to_next_tile)
		moving = false

# Stores the different directions into the Array
func direction_storage():
	var directions = ["move_right", "move_left", "move_up", "move_down"]
	for dir in directions:
		if Input.is_action_just_pressed(dir):
			direction_history.push_back(dir)
		elif Input.is_action_just_released(dir):
			direction_history.erase(dir)
		
		if direction_history.size() == 0:
			direction_history.clear()

# Function that runs all the background processes for the movement
func process_player_input():
	var direction_map = { # Dictionary that stores all the inputs
		"move_right": Vector2(1, 0),
		"move_left": Vector2(-1, 0),
		"move_down": Vector2(0, 1),
		"move_up": Vector2(0, -1)
	}
	# If there is an action currently in the array, return the last added direction and give the 
	# input_direction that value from the dictionary
	if direction_history.size() > 0:
		var key = direction_history.back() 
		input_direction = direction_map.get(key, Vector2.ZERO)
	else:
		input_direction = Vector2.ZERO

	if input_direction != Vector2.ZERO:
		initial_position = position
		moving = true

func move(delta):
	_raycast.target_position = input_direction * TILE_SIZE / 2
	_raycast.force_raycast_update()
	if !_raycast.is_colliding():
		percent_moved_to_next_tile += speed * delta	
		if percent_moved_to_next_tile >= .99:
			position = (initial_position + (TILE_SIZE * input_direction)).snapped(Vector2(TILE_SIZE, TILE_SIZE))
			percent_moved_to_next_tile = .0
			moving = false
		else:
			position = initial_position.lerp(initial_position + (TILE_SIZE * input_direction), percent_moved_to_next_tile)
	
	else:
		percent_moved_to_next_tile = .0
		moving = false

	

Why dont you use move_and_collide(), since you are using a characterbody2d?

I thought that grid-based movement like this ignored collision, because the movement is not physics-based, but I could definitely be wrong about that. Would you have more details on how to implement move_and_collide() instead, if collision works with this movement system?

Yes if you do your movement none physicsbased then there no reason to use a characterbody, but you are using a raycast. This means you use physics, but you handle the collisions yourself.
you just have to save the change of movement inside the “velocity”-variable and then call move_and_slide() or move_and_collide() (whichever fits your game better) during the physics_process method.

If you only want to be able to walk one whole tile and not stop between two, i recommend to use Astargrid2d

1 Like

Thank you so much for your help!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.