I am using the latest stable Godot 4 on Linux.

Main scene is a Node2D with a TileMap and the player scene
Player scene is a CharacterBody2D with camera and CollisionShape2D as a rectangle.
The TileMap has specific tiles that have been marked in the Physics Layer for collision.

The intention of the game is to have movement exactly like the old Pokemon GBA games. For example, when you press a direction, it will move 1 pixel per frame towards that direction, and stop after 16 pixels, or continue if still holding direction. The player will never be in a x or y position not divisble by 16.

The problem I am having is by using Godot's collision system, the player is being marked as collided when the corners touch. Zooming in the camera with all collision shapes visible, it looks like the CollisionShape2D is a subpixel taller than the tile's collision. I suspect this is the problem, but the tiles are 16x16 and the CollisionShape2D is 16x16. The tiles physics layer shape fills the whole tile, so I have no idea what would account for this subpixel difference in the collision shapes.

Here is the script attached to the CharacterBody2D

extends CharacterBody2D

const TILESIZE = 16

var walking_steps = 0
var speed = 1
var moving = false
var input_direction = Vector2(0, 0)
var move_direction = Vector2(0, 0)

@onready var sprite = $Sprite2D_Player

func move():
	if walking_steps 0:
		move_and_collide(move_direction * -1)
		walking_steps -= 1
	else:
		moving = false

func _physics_process(delta):
	if input_direction.y == 0:
		input_direction.x = int(Input.is_action_pressed("ui_left")) - int(Input.is_action_pressed("ui_right"))
	if input_direction.x == 0:
		input_direction.y = int(Input.is_action_pressed("ui_up")) - int(Input.is_action_pressed("ui_down"))
	if input_direction != Vector2.ZERO and not moving:
		move_direction = input_direction * speed
		walking_steps = TILESIZE
		moving = true
	if moving:
		move()

In my Project Settings, I have enabled (Advanced)Rendering/2D/"Snap 2D Transform to Pixel" and "Snap 2D Vertices to Pixel" but that changed nothing.

I tried rewriting all of this using an Area2D for the player and modifying the position directly, but I was unable to check for a collision before actually moving the sprite because the function has_overlapping_bodies() and the signals body_entered() do not update in time, so the sprite would always move 1 pixel before detecting it had collided, throwing everything off. I was moving the CollisionShape2D first to the next position and checking if collided but it wouldn't update immediately with either the has_overlapping_bodies() or the body_entered() signal.

This is my first day with the engine but I'm already about to give up on it because I'm almost positive this is an engine problem and not something I'm doing, but I hope it's not.

Does anyone know how to get proper pixel accurate tile based movement + collision working with this engine? Thanks

you can also use a CollisionPolygon2D

    el_malo
    That won't fix my problem.
    What I ended up doing was using my rewritten code that modifies the position directly and checking for number of polygon collision data of the next tile, get_collision_polygons_count. This works perfect but seems a little hacky.