Handling different screen sizes

On to the final topic of this chapter. One important aspect of game development that needs to be addressed, especially in the world of mobile gaming, is the varying screen sizes that a game can be played on.

Currently, our Snake game is just running against whatever native screen size we have configured in our DesktopLauncher class. If you look at this you will see that in fact we haven't configured any; we have just used the default LibGDX desktop configuration of 640 x 480 pixels.

You might now be wondering why this actually matters. Let's take our Snake game as an example. We currently create a grid of 32 x 32 pixels in which our snake moves around. If the screen size is suddenly doubled, our grid will double in size too. This means it would take the snake twice as long to traverse the screen than it took earlier. However, also consider what would would happen if the screen size was half that size; in that case, the grid would also be half the size.

Maybe this is what you might want in your game, or maybe it isn't. From my experience, I can tell you it probably isn't what you want.

If you want to play about with the screen size, feel free. You can change the values on the LwjglApplicationConfiguration class as follows:

public static void main (String[] arg) {
    LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
    config.width = 640;
    config.height = 480;
    new LwjglApplication(new SnakeGame(), config);
}

I suggest that you try different values and see how it changes the game. If you like, enable the code to draw the grid on the screen. You should find that, at 640 x 480, we have 20 cells going across; if you double the screen size, it is now 40! You can try out different aspect ratios as well. You will even find that, if you use values that are not a multiple of our cell size, 32, the game won't work properly!

Handling different screen sizes

In the preceding screenshot, you can see that the game world in which our snake lives in is now twice the size. I set our screen size to be 1280x960 pixels.

Introducing the Viewport

You may want to have a stable playing environment regardless of screen size. Well, let's talk about how we can achieve this. We can do this by creating a Viewport instance and a camera instance.

First, what is a viewport?

In LibGDX terms, it is a way to manage a camera, mapping screen coordinates to the games world's coordinates. LibGDX comes with the following Viewport implementations, but you can also create your own:

  • StretchViewport
  • FitViewport
  • FillViewport
  • ScreenViewport
  • ExtendedViewport
  • CustomViewport

Note

I am not going to discuss each at length here; you can visit the LibGDX wiki at https://github.com/libgdx/libgdx/wiki/Viewports to see them in action.

I suppose the next logical question you are now asking is, "What's a camera?"

Again, in LibGDX, a camera gives us the ability to change the player's view on the world. We can create a camera and use it to zoom in and out, rotate, and project points to/from the screen to the world space.

LibGDX has two camera implementations:

  • Orthographic
  • Perspective

We are going to focus solely on the first one, as the latter is used for 3D.

Note

For more information on the orthographic camera, you can check out the LibGDX wiki at https://github.com/libgdx/libgdx/wiki/Orthographic-camera.

Using the Viewport

Now that we know of the existence of viewports, let's get one into our Snake game.

First, we need to declare our world size. This is what the camera and Viewport will work to when displaying the game world to the player. So, let's add the following to our GameScreen class:

private static final float WORLD_WIDTH = 640;
private static final float WORLD_HEIGHT = 480;

Now, what I am going to say might sound a touch confusing. We need to stop thinking in pixels. I know, I know, what did we just define? A world of 640 x 480. But these dimensions aren't pixels now; the screen size is pixels and these could be anything. These are just units: the player's view on the world is going to be 640 units wide and 480 units high.

Next, let's define our viewport and camera:

private Viewport viewport;
private Camera camera;

public void show() {
   camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
   camera.position.set(WORLD_WIDTH / 2, WORLD_HEIGHT / 2, 0);
   camera.update();
   viewport = new FitViewport(WORLD_WIDTH, WORLD_HEIGHT, camera);
   //Other code ommited for brevity
 }

So, what we have done here is initialize a camera and point it to the center of our game world. We then tell it to call update(), which recalculates the camera's projection and view. This needs to be done any time we interact with the camera ourselves.

Finally, we instantiate a FitViewport viewport class. Why FitViewport? Well, this will maintain the aspect ratio of the game world irrespective of the screen size. The only downside is you may get a black bar border—similar to what you get when you watch old 4:3 TV shows on your 16:9 TV.

Next, we need to do a little-find-and replace. Previously, we used the following lines of code to access the screen size:

Gdx.graphics.getWidth()
Gdx.graphics.getHeight()

However, these return the actual screen size, something we no longer work in. So we need to replace these references with the following:

viewport.getWorldWidth()
viewport.getWorldHeight()

You will notice some errors pop up. These will be related to the phase we have gone from, working in integers from the Gdx.graphics calls to working in floats from the viewport calls.

Just update the members of the GameScreen class to be floats rather than integers. Additionally, in the checkAndPlaceApple() method, we need to cast our maths to an int as the MathUtils.random() method will interpret them as floats and will not place our apple correctly anymore:

appleX = MathUtils.random((int) (viewport.getWorldWidth() / SNAKE_MOVEMENT) - 1) * SNAKE_MOVEMENT;
appleY = MathUtils.random((int) (viewport.getWorldHeight() / SNAKE_MOVEMENT) - 1) * SNAKE_MOVEMENT;

This will do the trick!

Finally, to ensure that our batch instance renders everything in our world, we need to update the project and also update the view of the cameras. So, update the draw() method with the following:

private void draw() {
   batch.setProjectionMatrix(camera.projection);
   batch.setTransformMatrix(camera.view);
   batch.begin();
   // Code ommited for brevity
}

While we are at it, we should do the same for our grid:

private void drawGrid() {
   shapeRenderer.setProjectionMatrix(camera.projection);
   shapeRenderer.setTransformMatrix(camera.view);
   shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
   // Code omitted for brevity
}

Now when you run the game, regardless of the screen size, our game world will always be a 20 x 15 cell grid for our snake to slither around!

Using the Viewport

The preceding screenshot is taken at the same screen size as the previous screenshot but our game world is now consistent with our game.

I would like to end this topic with a suggestion. Go try out the other viewport classes; nothing bad will happen, but have a play with different viewports and different screen sizes so you get an idea of what each one does.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset