• 2D
  • Pixel perfect movement and rounding

Yeah, it's finally fixed, I guess. Through talking and trying to explain what is happening, it made me think about how this was implemented back in the days of very low resolution displays and low power machines.

Of course, using integer pixels/frame movements would solve the issue, but movement would be too fast and I wouldn't have much control over the speed, only a few usable speed rates. Originally, I guess in the old days this was implemented using a sub-pixel internal representation of the position that got then rounded to integers before being used elsewhere in the game instead of using sub-pixel placement until the final rendering stage. So why not do that? Since I'm new to godot, I hadn't thought this could be easily done.

I've created a real_position property in the platform, and I animate that property instead of the position. Then in _physics_process, I set the position to the rounded value of real_position. This way, AnimationPlayer uses floats to calculate the position of the platform, but the platform is never at a sub-pixel location, only integers.

I didn't think it could be that easy. And I think I could apply this approach to other future cases.

I guess I could apply it to every object in the game, although the ones using move_and_* would require a bit more fiddling with the position values. Anyway, my issue was only with the objects using AnimationPlayer because those are the only ones that could drag and push the player.

I'm putting here the example project with the fix in case anyone is interested in this solution. I've tried to find resources to develop pixel perfect games in Godot but they always relied on using high resolutions that hid the issue.

I'm sorry if I haven't done a good job explaining it from the start. I hope it makes sense for anyone else getting here searching for a solution.

Thanks for all the ideas, it's really appreciated.

LOL. Just spent the last hour on it and came to the same realization. Don't animate the real position, Animate a variable Vector2 and then on _physics_process simply set the real position to a pixel snapped value. Works perfectly.

extends KinematicBody2D

export var float_position = Vector2.ZERO

func _physics_process(_delta):
	update_to_pixel()

func update_to_pixel():
	global_position = adjust_to_pixel(float_position)
	
func adjust_to_pixel(pos):
	var ret = Vector2.ZERO
	ret.x =  floor(pos.x + 0.5)
	ret.y =  floor(pos.y + 0.5)
	return ret

@cybereality said: LOL. Just spent the last hour on it and came to the same realization. Don't animate the real position, Animate a variable Vector2 and then on _physics_process simply set the real position to a pixel snapped value. Works perfectly.

Almost simultaneously. Thanks for your time. And yes, it works perfectly.

2 years later

You guys saved my life, I had a very noticeable pixel-perfect diagonal movement, I was looking for a solution for days, Thanks for everyone here!!

3 months later

@cybereality Sorry to bug you on not one but two of your posts, but on reddit you said you had posted a project with this issue fixed. Is there a place to find that still?

Thank you!