Implementing the game GUI

In this last part of the chapter, we are going to implement the game's GUI and add it to the scene. The GUI will display the achieved score, extra lives, and an FPS counter.

We will need to load a bitmap font before we are able to write any text output to the screen. Fortunately, LibGDX provides a default font (Arial 15pt) that we can use. Copy the two files arial-15.fnt and arial-15.png to CanyonBunny-android/assets/images/.

This is how LibGDX's default bitmap font looks:

Implementing the game GUI

You can also create your own fonts using Hiero, a font generator tool provided by LibGDX. Check out the official project website https://github.com/libgdx/libgdx/wiki/Hiero. There is also a popular commercial tool called Glyph Designer, which is designed specifically for Mac. The official link is https://71squared.com/en/glyphdesigner.

Add the following import line to Assets:

import com.badlogic.gdx.graphics.g2d.BitmapFont;

Then, add the following lines of code to Assets:

  public AssetFonts fonts;

  public class AssetFonts {
    public final BitmapFont defaultSmall;
    public final BitmapFont defaultNormal;
    public final BitmapFont defaultBig;

    public AssetFonts () {
      // create three fonts using Libgdx's 15px bitmap font
      defaultSmall = new BitmapFont(
          Gdx.files.internal("images/arial-15.fnt"), true);
      defaultNormal = new BitmapFont(
          Gdx.files.internal("images/arial-15.fnt"), true);
      defaultBig = new BitmapFont(
          Gdx.files.internal("images/arial-15.fnt"), true);
      // set font sizes
      defaultSmall.setScale(0.75f);
      defaultNormal.setScale(1.0f);
      defaultBig.setScale(2.0f);
      // enable linear texture filtering for smooth fonts
      defaultSmall.getRegion().getTexture().setFilter(
          TextureFilter.Linear, TextureFilter.Linear);
      defaultNormal.getRegion().getTexture().setFilter(
          TextureFilter.Linear, TextureFilter.Linear);
      defaultBig.getRegion().getTexture().setFilter(
          TextureFilter.Linear, TextureFilter.Linear);
    }
  }
  public void init (AssetManager assetManager) {
    this.assetManager = assetManager;
    // set asset manager error handler
    assetManager.setErrorListener(this);
    // load texture atlas
    assetManager.load(Constants.TEXTURE_ATLAS_OBJECTS,
                      TextureAtlas.class);
    // start loading assets and wait until finished
    assetManager.finishLoading();

    Gdx.app.debug(TAG, "# of assets loaded: "
                       + assetManager.getAssetNames().size);
    for (String a : assetManager.getAssetNames())
      Gdx.app.debug(TAG, "asset: " + a);

    TextureAtlas atlas =
        assetManager.get(Constants.TEXTURE_ATLAS_OBJECTS);

    // enable texture filtering for pixel smoothing
    for (Texture t : atlas.getTextures())
      t.setFilter(TextureFilter.Linear, TextureFilter.Linear);

    // create game resource objects
    fonts = new AssetFonts();
    bunny = new AssetBunny(atlas);
    rock = new AssetRock(atlas);
    goldCoin = new AssetGoldCoin(atlas);
    feather = new AssetFeather(atlas);
    levelDecoration = new AssetLevelDecoration(atlas);
  }

  @Override
  public void dispose () {
    assetManager.dispose();
    fonts.defaultSmall.dispose();
    fonts.defaultNormal.dispose();
    fonts.defaultBig.dispose();
  }

The added code includes a new inner class called AssetFonts that holds the default bitmap font in three differently configured sizes. The size is configured by scaling the font either up or down. Bitmap fonts must be disposed manually so that the corresponding calls are added to the dispose() method.

We are now ready to begin with the implementation of the game's GUI. It is always good to have an idea of what should be implemented so that we have a picture of what we are aiming for, as shown here:

Implementing the game GUI

In the top-left corner, you see an image of the gold coin and the player's current score as text. In the top-right corner, you see three bunny heads that represent the number of extra lives the player has left. Lastly, in the bottom-right corner is a small FPS counter that shows how good or bad the performance of the running game is. The color of the FPS counter will depend on the achieved frames per second.

Add the following import line in WorldRenderer:

import com.badlogic.gdx.graphics.g2d.BitmapFont;

Next, add the following code in WorldRenderer:

  private OrthographicCamera cameraGUI;

  private void init () {
    batch = new SpriteBatch();
    camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH,
                                    Constants.VIEWPORT_HEIGHT);
    camera.position.set(0, 0, 0);
    camera.update();
    cameraGUI = new OrthographicCamera(Constants.VIEWPORT_GUI_WIDTH,
                                      Constants.VIEWPORT_GUI_HEIGHT);
    cameraGUI.position.set(0, 0, 0);
    cameraGUI.setToOrtho(true); // flip y-axis
    cameraGUI.update();
  }

  public void resize (int width, int height) {
    camera.viewportWidth = (Constants.VIEWPORT_HEIGHT
                         / (float)height) * (float)width;
    camera.update();
    cameraGUI.viewportHeight = Constants.VIEWPORT_GUI_HEIGHT;
    cameraGUI.viewportWidth = (Constants.VIEWPORT_GUI_HEIGHT
                            / (float)height) * (float)width;
    cameraGUI.position.set(cameraGUI.viewportWidth / 2,
                           cameraGUI.viewportHeight / 2, 0);
    cameraGUI.update();
  }

The added code creates a second camera that is specifically set up just to render the game's GUI. The viewport of the GUI camera is defined using a different set of constants that uses much higher values. We have to do this to correctly render the bitmap font that is 15 pixels high. If we were to use the 5 meters x 5 meters viewport, only one-third of the font's glyph would be visible at any time. The rest of the added code does the same as the game camera. Also, this allows us to move the world camera (camera) independently from the GUI (cameraGUI).

The following section describes the implementation of the methods for each GUI element.

The GUI score

The following is a screenshot of the GUI element that shows the player's current score:

The GUI score

Add the following code in WorldRenderer:

  private void renderGuiScore (SpriteBatch batch) {
    float x = -15;
    float y = -15;
    batch.draw(Assets.instance.goldCoin.goldCoin,
        x, y, 50, 50, 100, 100, 0.35f, -0.35f, 0);
    Assets.instance.fonts.defaultBig.draw(batch,
        "" + worldController.score,
        x + 75, y + 37);
  }

The gold coin image is drawn in the top-left corner of the screen. Next to it, the player's current score is displayed using the big default font.

The GUI extra lives

The following is a screenshot of the GUI element that shows the player's remaining extra lives:

The GUI extra lives

Add the following code in WorldRenderer:

  private void renderGuiExtraLive (SpriteBatch batch) {
    float x = cameraGUI.viewportWidth - 50 -
Constants.LIVES_START * 50;
    float y = -15;
    for (int i = 0; i < Constants.LIVES_START; i++) {
      if (worldController.lives <= i)
        batch.setColor(0.5f, 0.5f, 0.5f, 0.5f);
      batch.draw(Assets.instance.bunny.head,
          x + i * 50, y, 50, 50, 120, 100, 0.35f, -0.35f, 0);
      batch.setColor(1, 1, 1, 1);
    }
  }

The three bunny head images that will represent the extra lives of the player are drawn in the top-right corner of the screen. The method starts to draw from left to right. Before a new bunny head is drawn, there is an additional check to find out whether this extra life is used up already. If this is the case, the bunny head is darkened and gets a slightly transparent look by setting the tint color of the sprite batch.

The GUI FPS counter

The following is a screenshot of the GUI element that shows the actual frames per second:

The GUI FPS counter

Add the following code in WorldRenderer:

  private void renderGuiFpsCounter (SpriteBatch batch) {
    float x = cameraGUI.viewportWidth - 55;
    float y = cameraGUI.viewportHeight - 15;
    int fps = Gdx.graphics.getFramesPerSecond();
    BitmapFont fpsFont = Assets.instance.fonts.defaultNormal;
    if (fps >= 45) {
      // 45 or more FPS show up in green
      fpsFont.setColor(0, 1, 0, 1);
    } else if (fps >= 30) {
      // 30 or more FPS show up in yellow
      fpsFont.setColor(1, 1, 0, 1);
    } else {
      // less than 30 FPS show up in red
      fpsFont.setColor(1, 0, 0, 1);
    }
    fpsFont.draw(batch, "FPS: " + fps, x, y);
    fpsFont.setColor(1, 1, 1, 1); // white
  }

An FPS counter that shows the text FPS followed by the current number of frames per second is drawn in the bottom-right corner of the screen. The color of the text depends on the achieved frame rate. If the FPS is 45 or higher, the text will show up in green, indicating a good rendering performance. Otherwise, if there are 30 or more FPS, then the text will show up in yellow, which indicates an average rendering performance. Anything below 30 FPS will show up in red, indicating a really poor rendering performance.

Rendering the GUI

Add the following code in WorldRenderer:

  private void renderGui (SpriteBatch batch) {
    batch.setProjectionMatrix(cameraGUI.combined);
    batch.begin();
    // draw collected gold coins icon + text
    // (anchored to top left edge)
    renderGuiScore(batch);
    // draw extra lives icon + text (anchored to top right edge)
    renderGuiExtraLive(batch);
    // draw FPS text (anchored to bottom right edge)
    renderGuiFpsCounter(batch);
    batch.end();
  }

Furthermore, change render() in WorldRenderer:

  public void render () {
    renderWorld(batch);
    renderGui(batch);
  }

The implementation of the game's GUI is now finished.

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

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