Godot 4.4.1
I’m revisiting my player movement and feel like I’m missing a magical piece of the puzzle.
It’s an isometric, pixel art game (tiles are 40x20) and I’m implementing 8-directional movement that aligns with the grid diagonally.
I have texture filters set to nearest (and repeat disabled), snap 2D to transforms/vertices to pixel off, the window stretch mode is viewport, aspect is keep, and scale mode is integer. Movement code is in _physics_process() and the camera process callback is set to physics.
It’s so close! The only challenge left is that, when the player moves diagonally, the tilemap is blurry.
I understand subpixeling, and if I round the player’s position every physics tick, the tilemap looks much better when moving. But the issue then becomes that the cumulative rounding results in diagonal movement no longer being grid-aligned.
I tried moving the rounding functionality to the camera (lerp etc.), with no luck.
So, for now, I’m just rounding when the player comes to a stop so at least it’s not subpixeling at that point (and tweening seems to help ease the abrupt shift).
Many isometric pixel art games have gotten this working before.
How? What am I missing? Or is this fundamentally a compromise I need to learn to live with?
For reference, here’s my code (fyi, while I was testing, I hard-coded my normalized movement vectors to play with):
extends CharacterBody2D
@export var walk_speed := 75 # pixels per second
@export var run_speed := 100
var is_running := false
normalized movement direction constants
const RIGHT: Vector2 = Vector2(1.0, 0.0)
const LEFT: Vector2 = Vector2(-1.0, 0.0)
const DOWN: Vector2 = Vector2(0.0, 1.0)
const UP: Vector2 = Vector2(0.0, -1.0)
const DOWN_RIGHT: Vector2 = Vector2(0.894427, 0.447214)
const DOWN_LEFT: Vector2 = Vector2(-0.894427, 0.447214)
const UP_RIGHT: Vector2 = Vector2(0.894427, -0.447214)
const UP_LEFT: Vector2 = Vector2(-0.894427, -0.447214)
func _physics_process(delta: float) → void:
# input handling
is_running = Input.is_action_pressed(“run”)
var current_speed = run_speed if is_running else walk_speed
var input = Input.get_vector(“ui_left”, “ui_right”, “ui_up”, “ui_down”)
var move_dir = Vector2.ZERO
if input.x == 1.0 and input.y == 0.0: # moving right
move_dir = RIGHT
elif input.x == 0.0 and input.y == 1.0: # moving down
move_dir = DOWN
elif input.x == -1.0 and input.y == 0.0: # moving left
move_dir = LEFT
elif input.x == 0.0 and input.y == -1.0: # moving up
move_dir = UP
elif input.x > 0.0 and input.y > 0.0: # moving down-right
move_dir = DOWN_RIGHT
elif input.x < 0.0 and input.y > 0.0: # moving down-left
move_dir = DOWN_LEFT
elif input.x < 0.0 and input.y < 0.0: # moving up-left
move_dir = UP_LEFT
elif input.x > 0.0 and input.y < 0.0:
move_dir = UP_RIGHT
velocity = move_dir * current_speed
move_and_slide()
# round position when movement complete
if velocity.is_zero_approx():
position = position.round()
var tween = create_tween()
tween.tween_property(self, “position”, position, 0.05)