• 2D
  • Pixel perfect movement and rounding

@berarma said: Where would I put that code?

Ideally right after the movement of the KinematicBody2D, so after any movement of the node, the Sprite nodes get updated as well. You could also put it at the end of _physic_process and it'd probably also work.

I think that's what's already happening. The physics work with the floats, and when rendering it gets rounded to the nearest pixel. And that's exactly the problem I'm trying to avoid or solve.

Two objects that are only 0.2 pixels apart can get rendered in the same pixel or 1 pixel apart depending on the exact position. If they're at (50, 50) and (50.2, 50.2) they will show at the same pixel, but if they're at (50.4, 50.4) and (50.6, 50.6) they will be at different pixel positions in the X and Y axes.

It's only an issue when an object is pushing or dragging one another, since the rounding is done after the interaction between objects.

Hmm, what you may want to do then floor the value instead of rounding it, so positions (50, 50), (50.2, 50.2), and (50.6, 50.6) all would be rendered as (50, 50). So instead of global_position.round() you'd want to use global_position.floor().

@TwistedTwigleg said: Hmm, what you may want to do then floor the value instead of rounding it, so positions (50, 50), (50.2, 50.2), and (50.6, 50.6) all would be rendered as (50, 50). So instead of global_position.round() you'd want to use global_position.floor().

And here you have the same problem again: (50, 50),(50.2, 50.2) and (49.9, 49.9), (50.1, 50.1).

It only changes the breaking point from .5 to .0.

Edit: I've tested it now in the example project and it's the same, it doesn't solve the issue using round nor floor.

I'm sorry, but how this supposed to help me? The position values are already rounded in the rendering stage.

If you make sure your nodes/children are in the correct position in the first place then the render stage rounding not only might not be an issue but might not be necessary at all?

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!