Pitfalls of pixels as unit in game

Started by
10 comments, last by RoamBlade 6 years, 4 months ago

So I've been trying to develop my own little c++, 2d fighting game from scratch and I'm hung up on what exactly I'm suppose to use as 'units' for my game. Currently, as suggested here on another game dev site, my game screen is thought of as a 160x90 grid. So if my game is being run at 1080p screen resolution, that would equal exactly 12 pixels per 1 grid unit (which I call a world unit). For example, I will have code in my game where I will tell my sprite to move 10.0 units/sec in the positive x direction. This means at 1080p my sprite will move 120 pixels/sec when run (12 pixels/unit * 10 units). 

Recently though, I've started trying to add some collision detection to my game and I've run into a bit of an issue with when to convert my world units into pixels and getting my collisions to work properly. While I'm sure I can fix this after some time, this got me thinking what exactly are the problems with just assuming a certain screen resolution and working strictly with pixels in my game? So instead of saying 'move my sprite 10 game units/sec' just say 'move my sprite 100pixels/sec', avoiding and unit conversion techniques. Then, if I want to, I can come up with some way just to upscale to higher res images or whatever for denser screen resolutions. The reason I ask is on a lot of forums people seem to be against using pixels as units when it comes to collision and physics, even for 2d games.

Advertisement

Do never ever convert your pixels to units or vice versa for any math computations! Thats a rule of thumb.

Just use your world units directly, which should be float or double. Integer is not great when you want to smooth movement.

The conversion for world to screen units is only done in rendering or for screen to world conversions (mouse picking for example). You can precalculate a factor which you can multiply your world coordinates to convert to screen coordinates and vice versa.

Building up your world from tiles/grid is totally fine and i do this too like this:


static constexpr f32 TILE_SIZE = 0.5f;
static constexpr u32 TILE_COUNT_FOR_WIDTH = 40;
static constexpr u32 TILE_COUNT_FOR_HEIGHT = 22;
static constexpr f32 GAME_ASPECT = TILE_COUNT_FOR_WIDTH / (f32)TILE_COUNT_FOR_HEIGHT;
static constexpr f32 GAME_WIDTH = TILE_COUNT_FOR_WIDTH * TILE_SIZE;
static constexpr f32 GAME_HEIGHT = GAME_WIDTH / GAME_ASPECT;
static constexpr f32 HALF_GAME_WIDTH = GAME_WIDTH * 0.5f;
static constexpr f32 HALF_GAME_HEIGHT = GAME_HEIGHT * 0.5f;

Also my worlds are always built in a right handed coordinate system - which is the same as opengl. So negative Y is going down and positive Y is going up and half the screen dimension is added to X and Y so the world coordinate 0, 0 is always the center of the screen - without taking any translation into account of course.

Keep in mind, that you need to take the aspect ratio into account as well - so you should letterbox your game view, so it will fit on any display. I do this for every frame once and store the result:


// Calculate a letterboxed viewport offset and size
// @NOTE: Scale is used later for doing unprojection
viewSize = Vec2f(halfGameWidth, halfGameHeight) * 2.0f;
viewScale = (f32)windowSize.w / (halfGameWidth * 2.0f);
Vec2i viewportSize = Vec2i(windowSize.w, (u32)(windowSize.w / aspectRatio));
if (viewportSize.h > windowSize.h) {
  viewportSize.h = windowSize.h;
  viewportSize.w = (u32)(viewportSize.h * aspectRatio);
  viewScale = (f32)viewportSize.w / (halfGameWidth * 2.0f);
}
Vec2i viewportOffset = Vec2i((windowSize.w - viewportSize.w) / 2, (windowSize.h - viewportSize.h) / 2);
viewport.size = viewportSize;
viewport.offset = viewportOffset;

// Update view projection
Mat4f proj = Mat4f::CreateOrthoRH(-halfGameWidth, halfGameWidth, -halfGameHeight, halfGameHeight, 0.0f, 1.0f);
Mat4f model = Mat4f::Identity;
viewProjection = proj * model;

 

It depends on the engine.

The first pitfall to using pixels directly is that, of course, you can't easily change the resolution. However, this might not necessarily be true if you can just set the old pixels as "world units" with little hassle. So that's going to depend on the engine.

The second possible pitfall is that pixels are by definition integers; you can't have a real half-pixel, so you can't be at pixel position 4.2. This is of course imprecise. But it's trivial to just keep pixel positions as floating-point and round or truncate them at display time. It's just a more simplistic "world units" conversion at that point, really. The engine itself can even transparently handle it.

Personally, I prefer to define the game at a particular resolution and just use pixels for distances and movement and such, for two reasons:

  1. It's more simple to do it that way. It means there's no need to worry about whether I'm talking about the display or the game world; it's all the same.
  2. Changing the resolution would involve changing each individual image anyway, so making it easier to code that change would be of little consequence as I see it.

Of course, this is for 2-D games that use raster graphics. If you use vector graphics or 3-D graphics, I definitely see merit in not using pixels as a measurement; after all, you're not working with pixels in that case.

Also consider fixed point (you can for example divide a pixel into 256 units).

If you are using pixel perfect sprites there is more of a reason to use some kind of pixel unit, whereas with pure 3d there isn't such a concept as a pixel, so the pixel may not make sense, but you still may choose to use integers.

There are benefits to both floats and ints. Although floats are often seen as the 'default choice' these days, integers are good for consistent behaviour, compression, packing, some people use them for units on large scale maps.

@JulieMaru-chan 

3 hours ago, JulieMaru-chan said:

Personally, I prefer to define the game at a particular resolution and just use pixels for distances and movement and such

What about for things like collision detection and physics? Do pixels still work out okay for these kinds of calculations?

@lawnjelly

1 hour ago, lawnjelly said:

If you are using pixel perfect sprites there is more of a reason to use some kind of pixel unit

What about for a 2d game using vector graphics?

Physics and collision response can work okay.  You'll likely end up using your own internal representations, but it won't hurt anything.

Also note that many games people think of as pixel based have higher precision internally.  For example, the original Super Mario Bros had 8 bits of sub-pixel data for position and speed, which enabled variations in running versus walking. Visually players saw the player moving a pixel or not, but internally there was more data.

That is, I think, what was being referred to earlier in the discussion as fixed-point data. 

What you see on the screen does not need to precisely match what the simulator is doing internally.  Games tend to keep far more information than is shown to the player, and tend to keep information at a higher precision than the player thinks about.

8 hours ago, frob said:

That is, I think, what was being referred to earlier in the discussion as fixed-point data. 

Yes, on our 8/16/32/64 bit computers it makes far more sense to divide a pixel into 256 then say, 100, because you can use bitshifts, bitmasks etc. The fixed point can refer to the number of bits given to the fraction and the number to the whole number, instead of referring to the position of a decimal point.

http://x86asm.net/articles/fixed-point-arithmetic-and-tricks/

8 hours ago, frob said:

What you see on the screen does not need to precisely match what the simulator is doing internally.  Games tend to keep far more information than is shown to the player, and tend to keep information at a higher precision than the player thinks about.

While rounding high precision positions to whole pixels for rendering purposes only should be the default strategy to deal with pixels (both for fixed point and for floating point) in some cases deliberately and selectively quantizing positions, times, velocities etc. in the game engine might make sense.

For example, suppose you need to execute a long jump to a ledge in a platformer or to run into a narrow opening in a racing or flying game: the player cannot tell whether his sprite is actually aligned with the pixel grid or slightly off, and therefore basing success or failure (through high precision collision detection) on unavailable information would be unfair.

Omae Wa Mou Shindeiru

5 hours ago, LorenzoGatti said:

For example, suppose you need to execute a long jump to a ledge in a platformer or to run into a narrow opening in a racing or flying game: the player cannot tell whether his sprite is actually aligned with the pixel grid or slightly off, and therefore basing success or failure (through high precision collision detection) on unavailable information would be unfair.

That's not going to factor in to this realistically. No human can possibly be precise enough to be pixel-perfect on a consistent basis anyway. It might even literally be impossible if the speed is too high. So I wouldn't worry about an edge case such as this.

The edge case of caring for pixel-perfect positioning is fairly common in old, low resolution games where 1 pixel is actually a large distance and 1 frame is a long time. When I refer to racing or flying games I mean things like aiming for ramps in Micro Machines or bullet gaps in Space Invaders, not forgiving 3D games.

Omae Wa Mou Shindeiru

This topic is closed to new replies.

Advertisement