1
votes

What is my complete thinking algorithm to understand all possible width/height transformations on the path PixMap -> (physical) Screen/monitor?

Sorry for the stupid question, but how do these sizes corellate / scale / interact?

What size do I actually see on the screen and what scaling effects happen?

I need a quick-start, but googling does not yield direct and systematic answers.

PixMap is a picture/image (from a file or some programmatically drawn primitive) with its width/height in pixels.

Later on this PixMap will be shown on the physical Screen of some physical device. As I understand, actually it will be shown only in the part of the physical device screen - only within my game's application window (config.width / config.height in LwjglApplicationConfiguration) - I guess it is in pixels...

PixMap is wrapped into Texture (I don't fully understand why - OpenGL says Texture is a container for one or more images, but new Texture(PixMap) takes only a single PixMap). Texture does not have any sizes (OK, it is just a container for PixMap(s).

Now Texture is wrapped into Sprite (new Sprite(Texture)). Sprite has its own width/height. Sprite is some visible part of my game (moving robot, ball, tank, missile, etc). So Sprite size overrides PixMap size and therefore shall cause scaling somehow?

But is Sprite size what I actually see on the screen (if I never called sprite.scale())? sprite.setSize(float, float) - means height/width in pixels??? Can Sprite automatically set size exactly to be the same as the underlying PixMap?

Now Camera size - Camera is what the user actually sees on screen (part of the Game World which might be larger than screen), right? So I can set Camera size equal to Gdx Application Window (config.width / config.height in Launcher class)? Or Camera size defines visible (displayed to the user) part of the physical screen? If my game fits the whole of the screen, I can set Camera size to be equal to screen size and that's all. Why do I need Camera? I think I fail to understand Camera completely.

And ViewPort - it is "same" as Camera (it manages Camera). But I see that ExtendViewPort and ScalingViewPort "tamper" with scaling, so they resize my Sprite/PixMap/Camera sizes?

Besides, all ViewPort classes have minHeight/minWidth in their constructors - another size transformation / scaling?

P.S. Another issue is coordinates in all those cases. What relative to what?

This answer is also partially related and helpful.

1

1 Answers

3
votes

I'll explain for the 2D case only, which means you are using only OrthographicCamera (not PerspectiveCamera).

The OrthographicCamera defines a window into a world with some kind of virtual units of width and height. These units can be whatever you want (in-game meters for example). The camera's viewportWidth and viewportHeight variables define the size of the window you're looking through into your game's world. This window is stretched to fit the screen of the device, so usually, you will want the ratio of viewportWidth to viewportHeight to be the same as screen width to screen height, so the image will not look distorted.

Not all screens have the same aspect ratio, so the Viewport classes act as OrthographicCamera managers that can automatically adjust the viewportWidth and viewportHeight of the camera and also do letterboxing to prevent distortion. By letterboxing, I mean that it can reduce the area of the screen that the camera's image is stretched onto.

Texture does have a height and width, which will match that of the Pixmap it was loaded with. There's a different class, TextureArray, that is used for multiple images. You will likely never use that directly for a 2D game.

Pixmap is image data loaded to heap memory. Creating a Texture from a Pixmap means it is transferring that image data to GPU memory so it can be used with OpenGL.

Sprite is a subclass of TextureRegion. A TextureRegion is a window into a part of a Texture.

IMO, you should almost never use the Sprite class. It was an unfortunate design decision to make it subclass TextureRegion (violation of composition over inheritance) and it conflates an image asset with a specific game object. Instead, you should create your own GameObject class that has a TextureRegion variable and only the position/scale parameters you're actually going to use.

When you draw a TextureRegion and don't provide an explicit width and height, it will draw to the game world with the assumption that each pixel from the original image is the same size as one of your virtual game units. The only case where you should ever do something like this is if your game is supposed to have a retro look with big pixelly graphics. Otherwise you will want to draw it with an explicit width and height corresponding to how big it should look in the units of your game world.

Also, when drawing a TextureRegion, the x and y parameters are where its bottom left corner go in the coordinate system of your game world. It may or may not be fully visible, depending on whether that places it at a position in your game world that is viewable through the window of the OrthographicCamera (whose position can be moved in X and Y).

And so to sum up how to manage all of this:

If you are doing "retro" graphics and prefer to work in units of pixels:

  1. Decide how many pixels of artwork you want to see on screen. Pass these as the minWidth and minHeight parameters of the ExtendViewport constructor. You could alternatively use FitViewport, but IMO users are annoyed by letterboxing. I think if you submit your game to the iOS App Store with letterboxing, they'll straight up reject it.
  2. Make your art assets as 1 pixel to 1 pixel ratio.
  3. When you draw your TextureRegions, you don't need to specify width and height.

Otherwise:

  1. Think of your game world in meters. Decide how many meters of game world should be visible at a time and pass these as the minWidth and minHeight parameters of the ExtendViewport constructor.
  2. Decide what resolution of graphics you want your game to appear as, and save the assets at a high enough resolution to match. For example, if you want your art to look crisp only up to a screen that's 1080 pixels tall, and your camera's minHeight is 3 meters, then your source image of a 1m tall character 1 / 3 * 1080 = 360 should have 360 pixels of height.
  3. When you draw your TextureRegions, supply width and height of the asset in meters of game world.