2D Grid Based Movement Help

Godot Version

4.3 Stable

Question

I’m trying to move a Sprite2D on a path that I generate using AStarGrid2D. I handle the movement inside _process function but I’m having trouble giving the Sprite2D a speed. I’ve tried player.position = grid_id_path[0] * speed * delta but that doesn’t work and it gives me an error.

The scene tree is basically just a Node2D with a TileMapLayer as a child. Nothing else.

Here’s my code, please help me solve this. Thanks!!

extends TileMapLayer

var width  = 64
var height = 32

var start_pos  = Vector2i.ZERO
var target_pos = Vector2i.ZERO

var player: Sprite2D
var speed = 16

var astar_grid: AStarGrid2D

var grid_id_path    # Local coords (1 = 16 global coord)
var grid_point_path # Global coords (steps every 16px / cell_size)

func _ready() -> void:
	player = Sprite2D.new()
	player.texture = preload("res://icon.svg")
	player.scale   = Vector2(0.125, 0.125)
	player.position = Vector2(8.0, 8.0)
	
	add_child(player)
	
	astar_grid           = AStarGrid2D.new()
	astar_grid.region    = Rect2i(0, 0, width, height)
	astar_grid.cell_size = Vector2i(16, 16)
	
	astar_grid.default_compute_heuristic  = AStarGrid2D.HEURISTIC_MANHATTAN
	astar_grid.default_estimate_heuristic = AStarGrid2D.HEURISTIC_MANHATTAN
	astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
	
	astar_grid.update()
	
	for x in width:
		for y in height:
			set_cell(Vector2i(x, y), 0, Vector2i(1, 0), 0)


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
			target_pos = local_to_map(get_global_mouse_position())
			print("TARGET: ", target_pos)
			
			grid_id_path = astar_grid.get_id_path(
				local_to_map(player.position),
				target_pos
				)
			grid_point_path = astar_grid.get_point_path(
				local_to_map(player.position),
				target_pos
			)
	
			grid_id_path.remove_at(0)
			
			for x in width:
				for y in height:
					set_cell(Vector2i(x, y), 0, Vector2i(1, 0), 0)
			
			for point in grid_id_path:
				set_cell(point, 0, Vector2i(24, 0), 0)
			
#
func _process(delta: float) -> void:
	if target_pos and local_to_map(player.position) != target_pos:
		player.position = map_to_local(grid_id_path[0])
		
		grid_id_path.remove_at(0)

You want your player node to walk along the path created by the AStarGrid2D, but from your code, I imagine the movement is instant.

I think the simplest solution is to use a tween for every step of the path:

func move_player() -> void:
	if target_pos and local_to_map(player.position) != target_pos:
        var tween = create_tween()
        tween.tween_property(player, "position", player.position, grid_id_path[0])
        await tween.finished		
		grid_id_path.remove_at(0)
		if grid_id_path.size() > 0:
			move_player()

This will only update the player’s position but not it’s rotation. You can call the function using:
move_player()

Do not call it during the _process function or your game will likely freeze. A better location would be in your _unhandled_input function, probably at the end. If you don’t know what await does, consider learning about it since it’s important.