Sprite sizes and positions on different resolutions

Started by
14 comments, last by Scouting Ninja 6 years ago

Hello!

I know that working with absolute positions and sizes on sprites and collision-boxes is not a good idea if one wants to support multiple resolutions.

But what ways are being used to actually scale properly?

What I know is working in percentages (and offsets). One would define a vector of x and y (both between 0 and 1) and an offset vector of x and y of arbitrary pixel size. Now multiply with the screen-size and add the offset and get the scaled absolute size. This would work for size as well, but probably without the offset.

So if an object is outside of the window, its relative coordinates would be higher than 1?

I assumed updating absolute coordinates is done once when the resolution has been altered and every sprite owns two attributes: relative and updated absolute coordinates. But then again sprites can move thus change their relational coordinates; it's probably easier to do the calculation every draw-call or maybe keep a "has sprite changed its size/position"-flag/bool.

And I also assume that physics are calculated the same? Since the transformation is 1:1, every mouse click can be transformed into relational coordinates and then do the usual collision maths. Same goes for sprites, I would calculate movement in relational coordinates and with relational sizes and then alter the relational coordinates.

There is one issue on my mind, since all these values are meant to move in floating numbers (e.g. between 0 and 1 for everything inside the resolution/visible canvas), isn't there a float-point accuracy issue with different hardware? Especially if savegames are usable on other multiple devices, e.g. from desktop to smartphone.

On a final note, are there any other alternative systems for scaling sprites according to a resolution?

Thanks for your time.

 

Advertisement

I'm not a big fan of scaling every sprite on the screen, as it can lead to artifacts if you're not careful enough. Every sprite will need 1- pixel guard around it. Otherwise, when scaling is done, the surrounding texels will be used in the scaling algorithm and results in artifacts.

One thing you can do is to use 2 different resolutions, one for display, and one for drawing/game logic. I think this is something called virtual resolution or target resolution.

Basically, the display resolution will be varies based on different settings. Screen sizes, windows sizes, etc. On the other hand, the target resolution will be fixed. All the drawing and the game logic will be based on the target resolution.

This can be done by using render target. Basically, a render target texture will be created with the same size as the target resolution. All sprite drawing will be done to the render target. And once all drawing is done, this render target will be drawn to the screen.  The scaling will be done once at the last step, and since we fill up the screen, artifacts will be off-screen.

The disadvantage of this method is it requires hardware support for render target. I think almost every hardware in the market now supports render target though.

http://9tawan.net/en/

By artefacts you mean artefacts caused by up- or downscaling?

What I understand: Draw to a render-target or something as a view, this could be 1920x180 big.

Then scale this to the actual window-size, e.g. down to 1280x720.

The question that remains for me: How do I handle different aspect-ratios? If I do not care about what is actually seen on the screen, I can simply show more or less and have 1920x1200 as virtual size?

I always thought relational coordinates and sizes are the way to go, so this is a bit new to me, but interesting : )

 

19 hours ago, Angelic Ice said:

What I know is working in percentages (and offsets).

All forms I have ever encountered is just a alteration of the percentage formula. The most common is the setting the zero to the camera corner and the other common one is using a axis.

So zero point at corner is the one you are describing:

ScalingPer.jpg.b834bd47d9494d0e9fdcdbad1da7b423.jpg

The other is the axis or grid way:

ScalingGrid.jpg.2e3045cf5c09be5ec1934ffc3cdd1fc1.jpg

Here we see 800 * 600 (4:3) and 1600 * 900 (16:9) ratio.

What is important to note is that this is used often in world space and camera space. Often the Percentage method is used for camera space and the axis for world space; this keeps things neat and separate. However this isn't some predefined rule, you are free to use what you want.

21 hours ago, Angelic Ice said:

So if an object is outside of the window, its relative coordinates would be higher than 1?

Yes, if it's (2,2) and on a 800* 600 screen then (800 * 2, 600 * 2) = (1600,1200).

21 hours ago, Angelic Ice said:

and every sprite owns two attributes:

No, because as shown in the above image we can just calculate the position using the resolution and position. No need to give every object an attribute when the screen position is dependant on the underline position.

Note that all ratios are design to make this easy: https://en.wikipedia.org/wiki/Aspect_ratio_(image)

 

The rule of thumb is design your math and physics from 1X upwards and art downwards. Math: 1 * Min Resolution.  || Art: 1 * Max Resolution.

15 hours ago, mr_tawan said:

I'm not a big fan of scaling every sprite on the screen, as it can lead to artifacts if you're not careful enough. Every sprite will need 1- pixel guard around it

Personally I use a 4 pixel margin.

There is no way to prevent scaling errors but there are ways to improve the scaling. One way is to keep things pixel perfect but this requires manipulating the camera based on the device.

My prefered way is using Mips to blend details from higher and lower images, also known as Trilinear filtering.

Thanks! So there seem to be two ways, either one works with absolute pixels and a preset window-size and transforms the view to window-size or one works with a very basic coordinate system of values between 0 and 1 and draws accordingly based on window-size.

The reason I mentioned two attributes was to prevent re-calculating by caching the result until it's outdated (sprite moved, screen resized, ...). Since doing 0.5 * 1280, 0.25 * 720, every time seems unnecessary.

Now, what I do not understand, what do you mean by artefacts?

11 minutes ago, Scouting Ninja said:

Personally I use a 4 pixel margin.

What does this mean? What are artefacts? I know that when I scale images, pixels can jump around into ugly places, is that what this means? Especially if nearest neighbour scales from 100% size to 120% instead of the next full option: 200%.

But what is a margin in this case?

9 hours ago, Angelic Ice said:

The question that remains for me: How do I handle different aspect-ratios? If I do not care about what is actually seen on the screen, I can simply show more or less and have 1920x1200 as virtual size?

 

AFAIK, there are 2 ways to handle this. 

  • Letterbox - render the output to the largest area possible inside the game window while retaining the aspect ratio. The rest might be painted black or have some kind of static images to fill the void at the edge of the screen.
  • Safe Frame - render the output to the smallest area possible that cover the whole window while maintaining the aspect ratio. This implies that the main content is within the safe area at the center of the screen, as contents nears the edge would be eliminated in the case that the aspect ratio doesn't match. This is very similar to what TV people does.

There are pros and cons with those two methods. Letterbox is much easier, but it doesn't look as nice (some players will complains about this all day long).

Safe Frame means different players would have different experiences (depends on their screen size). It is also more difficult to design around it. Imagine a game designed around 16:9 ratio target resolution running on 21:9 ultrawide screen or 3:4 vertically placed screen, a lot of display area would be invisible in these cases. 

One more note, UI could be drawn on top of the game content after the scaling (rather than before scaling). This makes the UI looks crisp even if there are some scaling, as UI is not scaled. Of course this have to be taken into consideration when design the UI layout as well. Floating UI tends to work well in this case.

http://9tawan.net/en/

7 hours ago, Angelic Ice said:

Now, what I do not understand, what do you mean by artefacts?

What does this mean? What are artefacts? I know that when I scale images, pixels can jump around into ugly places, is that what this means? Especially if nearest neighbour scales from 100% size to 120% instead of the next full option: 200%.

But what is a margin in this case?

 

The scaling algorithm like nearest neighborhood uses multiple texel to calculate the output texel value. So imagine that if the target texel is around the edge of the sprite, the texel value from the neighboring sprites might be sampled to use in the scaling process. This could results in artifacts if the texel values are too much distict.

Supposed I have a sprite sheet texture that looks something like this.

sample.png.ba4054441692f35e327384e8f22dc56c.png

And the sprite I'm using is the black one. If I draw the sprite with 120% scaling (for example), I'd expect a larger black square on screen. Instead I'd have this.

 sample2.png.e2424914b040fea65abc3e701d87400f.png

The red lines around the black square is the artifact I'm talking about. 

As @Scouting Ninja mentioned, there's no way to prevent the scaling error. The sprite has to be drawn specifically to minimize the artifacts that can happened in the scaling process. One way is to add the margin around the sprite. 

Anyway, tools like texture packer should helps you minimize the problems. Currently I have sprites in its own separate files and let the texture packer do the margin and stuffs for me. It's seems to be working well so far.

http://9tawan.net/en/

16 hours ago, Angelic Ice said:

The reason I mentioned two attributes was to prevent re-calculating by caching the result until it's outdated (sprite moved, screen resized, ...). Since doing 0.5 * 1280, 0.25 * 720, every time seems unnecessary.

Most games solve this by scaling the game. While playing games you might have encountered this yourself, with the game asking for a restart after the resolution is changed. When the game starts it scales to match the required resolution and there is no longer a need to multiply the values.

The way you are describing here could maybe be used in a multiplayer game where the server runs a 1:1 version while players use a ?:? version. Even so it's often only the visuals that scale not the math, so there isn't that much of a need for tracking it.

I want to point out that most modern engines take care of this. So if you are using Unity, Unreal or any other popular engine then this isn't something to concern yourself with.

 

@mr_tawan describes the artifacts perfectly. It's known as pixel bleeding and is very easy to avoid by just adding a little boarder to the sprite.

The reason I use 4 pixels instead of 1 is because I like using performance focused algorithms. The tradeoff for many of these it that they take much larger samples (so there are fewer samples to take in total) that often result in a larger pixel bleed.

Hm, but wouldn't artefacts occur over the entire sprite-structure anyway? I'm not sure if I understand this issue well enough. Not all sprites are fully filled to the very corner (out of 64x64 pixels, 64x64 are filled) and own a mix of transparency/isometric/dimetric/.. and do not fill the entire sprite-image assigned to them inside the sheet. Thus I do not really understand how adding a "border" would fix this? The bleeding would occur way before the border of the sprite is arrived.

1 hour ago, Scouting Ninja said:

Most games solve this by scaling the game. While playing games you might have encountered this yourself, with the game asking for a restart after the resolution is changed. When the game starts it scales to match the required resolution and there is no longer a need to multiply the values.

Sorry but I still cannot imagine the process.. So, when I have a 64x64 sprite that I load 10 times onto the screen, next to another, I would at least require a width of 640. I decide that this looks good and shall be what every player (with the same aspect ratio) sees and pick 1280x720 as my default resolution.

Now a user with a native resolution of 1920x1080 starts the game - the game starts in 1280x720. The user changes this in the settings. If I draw to a render target, I can simply upsample this to 1920x1080 - there would be no restart required here or would it be? I can reproduce this with a view-element from SFML, rescaling the view does not require a restart, it's a pretty dynamic process.

So the restart would be required if I work in percentages? Deserialise whatever level, and directly save every percentage as absolute coordinates on a sprite's usual way to attribute its position and size?

On another note, I assume physics would work in percentages too and also converted to absolutes?

Thanks for you two's patience with me<3

 

On 4/8/2018 at 4:40 PM, Angelic Ice said:

I assumed updating absolute coordinates is done once when the resolution has been altered and every sprite owns two attributes: relative and updated absolute coordinates. But then again sprites can move thus change their relational coordinates; it's probably easier to do the calculation every draw-call or maybe keep a "has sprite changed its size/position"-flag/bool.

What you typically want to do is work out the scaling factor in both x and y directions and as you load any textures/models scale them at that time.  In the very rare case someone changes resolution mid-game, I would just destroy then reload all your assets.  You can then save this information into a file for the next game load also.  No need to keep any object specific variables around just a global scale factor for loading purposes.  The only other thing that needs to be recalculated might be the hit boxes or AABB's.

On 4/8/2018 at 4:40 PM, Angelic Ice said:

And I also assume that physics are calculated the same? Since the transformation is 1:1, every mouse click can be transformed into relational coordinates and then do the usual collision maths. Same goes for sprites, I would calculate movement in relational coordinates and with relational sizes and then alter the relational coordinates.

 

3 hours ago, Angelic Ice said:

On another note, I assume physics would work in percentages too and also converted to absolutes?

Why would your physics change?  Would physics change if you were to zoomed in?  Does gravity for instance change based on resolution?  Things should move at the same speed regardless of resolution.

On 4/8/2018 at 4:40 PM, Angelic Ice said:

There is one issue on my mind, since all these values are meant to move in floating numbers (e.g. between 0 and 1 for everything inside the resolution/visible canvas), isn't there a float-point accuracy issue with different hardware? Especially if savegames are usable on other multiple devices, e.g. from desktop to smartphone

 

Unless you're modeling the universe and expecting centimeter resolution there should be no accuracy issues across hardware.  Crashlands transitions from PC to mobile very well without issues and it was released in 2016.  And if you're only worried about accuracy for the very small fraction of floating point values that are visible on the screen you would be fine with 1980s hardware.

3 hours ago, Angelic Ice said:

Hm, but wouldn't artefacts occur over the entire sprite-structure anyway? I'm not sure if I understand this issue well enough. Not all sprites are fully filled to the very corner (out of 64x64 pixels, 64x64 are filled) and own a mix of transparency/isometric/dimetric/.. and do not fill the entire sprite-image assigned to them inside the sheet. Thus I do not really understand how adding a "border" would fix this? The bleeding would occur way before the border of the sprite is arrived.

I assume they're talking about when working with sprite sheets, which I think is a little to advanced for you at this point in time imo.  But for Mipmap generation you'll want to use trilinear filtering as mentioned by @Scouting Ninja.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

This topic is closed to new replies.

Advertisement