godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91.07k stars 21.18k forks source link

Odd-sized sprites do not rotate correctly #60079

Open Cerno-b opened 2 years ago

Cerno-b commented 2 years ago

Godot version

v3.4.4.stable.official [419e713a2]

System information

Windows 10 GLES2

Issue description

Apologies for writing another pixel rotation question, I hope this is relevant. I know from the discussion in https://github.com/godotengine/godot/issues/57221 that there used to be some rotation changes recently that had to be reverted, so there might not be a lot of focus on pushing these issues, but I would still like to discuss whether this is worth addressing.

Currently, pixel perfect 2D games have problems with rotating odd-sized sprites.

To my understanding, pixel perfect games in Godot still live in float space, only that the pixels are forced to the pixel grid when they are drawn.

Mathematically, it is a matter of definition whether a pixel's origin in the float grid should be aligned to the pixel's center or the top left corner. Currently in Godot, as far as I understand, pixels are aligned at the top left corner.

sprite_0

This definition plays a role once we try to rotate a sprite. For even-sized sprites, the rotation behaves correctly if the origin is the top-left. For odd sprites the rotation is correct if the origin is centered.

This is what happens when a sprite is rotated in 90° increments if the pixel origin does not fit.

sprite_02

Currently this means that an artist is recommended to only draw even sprite sizes in Godot if any type of precise rotation is needed. For me, this became apparent when I made my Ludum Dare 50 Game Eternal Crisis. In the game, spaceship is made out of parts that connect at specific points, around which the parts rotate. If I had made the connectors odd-sized (which they were in the initial version), then I would have gotten rotation artifacts similar to the ones in the gif above.

A solution could be to add a sprite setting to define the pixel grid origin of each sprite to be either centered or edge-aligned. This has to be done for x and y separately, since we can have pixels that are even in x and odd in y. This setting would not mean that there is a visible half-pixel offset between the centered and edge-aligned sprites, because every pixel would still obey the grid. But it would mean that certain operations that work in float coordinates before rounding to match the grid would be more precise.

An alternative to a manual setting would be to consider this issue automatically depending on the sprite size, but this would be an incomplete solution that would not deal properly with offset rotation and could cause problems if the sprites have transparent backgrounds that e.g. make them appear even although the actual pixels are odd in size.

This is potentially related to https://github.com/godotengine/godot/issues/7015, which asks for a half-pixel offset in case the sprite is set to be centered. I believe that the solution suggested here would address that issue as well.

Steps to reproduce

Minimal reproduction project

game.zip

clayjohn commented 2 years ago

I might be misunderstanding your issue. But is this not where a Sprite's offset property comes in? To me it appears you need a way to control the relationship between the Texture's position and the Sprite's origin. If that is the case, then you should be able to do what you need with the offset property.

Cerno-b commented 2 years ago

@clayjohn That's what I thought but in pixel snap mode the offset always gets rounded into an integer. And tbh I think that is exactly what is supposed to happen, because we don't want a float offset in pixel snap mode since the grid has to be obeyed (it's the law).

I see a number of solutions here that would probably achieve the same thing but could be more or less ugly to integrate or to use. All of them differ in clarity and ugliness, so I am not saying my suggestion is the best one, it just felt like the most natural to me.

(1) Change the definition of the pixel origin (my suggestion)

This would allow for keeping the offsets at integer values, while allowing odd sized sprites and even sized sprites to rotate and scale correctly.

(2) Allow float offsets (your suggestion/assumption)

The disadvantage of this would be that it would feel weird to be able to set offsets like 0.1 in pixel perfect games and it would not make a lot of sense intuitively to actually use the whole range that float offers, since the sprites need to me snapped to the grid for drawing anyway. I think it used to be that way and I'm glad that it was changed to forced int values. For translation, float offsets simply make no sense at all in pixel snap mode. This is only relevant for rotation and scaling

(3) Allow offsets by 0.5 increments in pixel snap mode

This would be a bit clearer than having float offsets because you would only be able to use either the full integer or the half pixel shifts. However, since a half pixel offset would not be visible directly (only implicitly when rotating), this might be confusing. There's another problem though: Should a sprite with an offset of 1.5 be translated by 1 or by 2? Intuitively, it would be 2 because we should generally round naturally. But that would mean that a sprite with an offset of (0.5, 0.5) (upper left pixel with centered pixel coordinates) would move off screen when its position is set to (0,0), because the offset would be rounded to (1,1) when drawing. My suggestion is different in that it does not affect the visible offset at all. The half pixel shift is only added temporarily in transformations like rotation and scaling, but not translation, so the rounding problem does not appear

(4) Automatically fix the rotation based on the sprite's size.

This would be the cleanest solution from a UI perspective because there would be no need for additional settings. The disadvantage is that the user would have to make sure that odd-sized sprites are using odd-sized files (you could embed an odd-sized sprite in an even-sized image by adding transparent borders accidentally). Another problem could occur if you had an even-sized sprite but you want to rotate arond a point that has an odd coordinate in the sprite's coordinate system. So you would have to fiddle around with adding transparent borders so your sprite is recognised as odd even though it is even but you want odd coordinate rotation.

There might be more approaches to this problem, but these are the ones that came to mind.

So the biggest downside with my original idea is that it would require an additional setting for sprites, which would include additional documentation and so on. The upside is that it would be a clean concept that is fairly simple to explain: Need your sprite rotated/scaled around a pixel's center or a pixel's edge? Set this flag to the mode you want and you're good to go.