Cookies info

This website uses Google cookies to analyse traffic. Information about your use of our site is shared with Google for that purpose. See details.

libGDX memory game example

In this tutorial, we will create a simple prototype of a memory-like game, using libGDX and the relative AmanithSVG binding API. We will create the project following the official guide.

The complete project can be found here


The setup

First, lets create the project structure:

Now the project folders structure should look like the following:

 
The project folders structure

The next step is to include the AmanithSVG binding for libGDX to the project:

After these operations, the build.gradle file should look like this and the folders structure should look like the following:

 
The project now includes AmanithSVG packages

Now we are ready to check the project setup by running, from the project main directory, the command ./gradlew desktop:run on Linux and MacOS X systems or gradlew desktop:run on Windows systems. If all is ok, you should be able to see the classic libGDX “Hello world”-like window.

 
Project setup has been completed successfully

The basic template

From now until the end we will work on the MyGdxGame.java file. First of all we remove the Badlogic Games image logo, and we plug AmanithSVG in. To do so:

The complete basic template of our game will look as follow:

package com.mygdx.game;

// libGDX
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.HdpiUtils;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.utils.ScreenUtils;

// AmanithSVG for libGDX
import com.mazatech.gdx.SVGAssetsGDX;
import com.mazatech.gdx.SVGAssetsConfigGDX;

// AmanithSVG java binding (high level layer)
import com.mazatech.svgt.SVGAssets;

public class MyGdxGame extends ApplicationAdapter {

    private static final String LOG_TAG = "MyGdxGame";

    private SpriteBatch batch;
    private OrthographicCamera camera;
    // instance of AmanithSVG for libGDX
    private SVGAssetsGDX svg = null;

    // display some basic information about installed AmanithSVG library
    void amanithsvgInfoDisplay() {

        // vendor
        Gdx.app.log(LOG_TAG, "AmanithSVG vendor = " + SVGAssets.getVendor());
        // version
        Gdx.app.log(LOG_TAG, "AmanithSVG version = " + SVGAssets.getVersion());
    }

    @Override
    public void create() {

        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();
        // create configuration for AmanithSVG
        SVGAssetsConfigGDX cfg = new SVGAssetsConfigGDX(screenWidth,
                                                        screenHeight,
                                                        Gdx.graphics.getPpiX());
        // initialize AmanithSVG for libGDX
        svg = new SVGAssetsGDX(cfg);
        // create the batch (used by 'render' function)
        batch = new SpriteBatch();
        // setup orthographic camera
        camera = new OrthographicCamera();

        // display some basic information about installed AmanithSVG library
        amanithsvgInfoDisplay();
    }

    @Override
    public void resize(int width,
                       int height) {

        if ((width > 0) && (height > 0)) {
            // update OpenGL viewport
            HdpiUtils.glViewport(0, 0, width, height);
        }
    }

    @Override
    public void render() {

        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();

        // clear screen
        ScreenUtils.clear(1, 1, 1, 1);

        // setup orthographic camera
        camera.setToOrtho(false, screenWidth, screenHeight);
        camera.update();
        batch.setProjectionMatrix(camera.combined);
    }

    @Override
    public void dispose() {

        // release libGDX resources
        batch.dispose();
        // release AmanithSVG
        svg.dispose();
    }
}

If you run the project, you will get the following result:

 
The basic template of our game

The game background

Now it’s time to add some cute SVG backgrounds to our game, so lets start to copy gameBkg1.svg, gameBkg2.svg, gameBkg3.svg, gameBkg4.svg files to the project’s ‘android/assets’ folder.

We store the SVGDocument instances relative to the four backgrounds and keep track of the generated background texture:

// SVG background documents
private SVGDocument[] backgroundDocs = { null, null, null, null };
// the actual background texture
private SVGTexture backgroundTexture = null;

We load all four SVG backgrounds files at initialization time (create function). Please note that we override the default alignment, because we want the background to cover the whole screen.

@Override
public void create() {

    // get actual backbuffer resolution
    int screenWidth = Gdx.graphics.getBackBufferWidth();
    int screenHeight = Gdx.graphics.getBackBufferHeight();
    // create configuration for AmanithSVG; NB: use the backbuffer to get the real dimensions in pixels
    SVGAssetsConfigGDX cfg = new SVGAssetsConfigGDX(screenWidth,
                                                    screenHeight,
                                                    Gdx.graphics.getPpiX());
    // initialize AmanithSVG for libGDX
    svg = new SVGAssetsGDX(cfg);

    ...

    // load backgrounds SVG documents
    for (int i = 0; i < 4; ++i) {
        backgroundDocs[i] = svg.createDocumentFromFile("gameBkg" + (i + 1) + ".svg");
        // backgrounds viewBox must cover the whole drawing surface, so we use
        // SVGTMeetOrSlice.Slice (default is SVGTMeetOrSlice.Meet)
        backgroundDocs[i].setAspectRatio(SVGTAlign.XMidYMid, SVGTMeetOrSlice.Slice);
    }

    ...
}

We resize the background (i.e. we generate the background texture at a new resolution) at each resize event:

void generateBackground(int screenWidth,
                        int screenHeight) {

    // destroy previous backgound texture
    if (backgroundTexture != null) {
        backgroundTexture.dispose();
    }

    // generate a new background texture
    backgroundTexture = svg.createTexture(backgroundDocs[backgroundIdx],
                                          screenWidth,
                                          screenHeight);
}

@Override
public void resize(int width,
                   int height) {

    if ((width > 0) && (height > 0)) {
        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();
        // update OpenGL viewport
        HdpiUtils.glViewport(0, 0, width, height);
        // generate background
        generateBackground(screenWidth, screenHeight);
    }
}

We draw the background texture within the render function:

// draw the game, just the background for the moment
private void drawGame(int screenWidth,
                      int screenHeight) {

    // clear screen
    ScreenUtils.clear(1, 1, 1, 1);

    // start drawing
    batch.begin();
    // draw the background
    batch.draw(backgroundTexture,
               // destination screen region
               0, 0, screenWidth, screenHeight,
               // source texture region
               0, 0, backgroundTexture.getWidth(), backgroundTexture.getHeight(),
               // flipX, flipY
               false, true);
    // finish drawing
    batch.end();
}

@Override
public void render() {

    // get actual backbuffer resolution
    int screenWidth = Gdx.graphics.getBackBufferWidth();
    int screenHeight = Gdx.graphics.getBackBufferHeight();

    // setup orthographic camera
    camera.setToOrtho(false, screenWidth, screenHeight);
    camera.update();
    batch.setProjectionMatrix(camera.combined);

    // draw the game
    drawGame(screenWidth, screenHeight);
}

And finally we dispose the four background documents and the background texture when the application is being closed (dispose function):

@Override
public void dispose() {

    // release AmanithSVG resources
    for (int i = 0; i < 4; ++i) {
        backgroundDocs[i].dispose();
    }
    // release libGDX resources
    backgroundTexture.dispose();
    batch.dispose();

    // release AmanithSVG
    svg.dispose();
}
 
Backgrounds used within the game

The cards

The characters hidden behind the cards are cute animals, here are their name (see full implementation here):

public enum CardType {

    Undefined(0),
    BackSide(1),
    Panda(2),
    Monkey(3),
    Orangutan(4),
    Panther(5),
    Puma(6),
    Leopard(7),
    Lion(8),
    Cougar(9),
    Tiger(10),
    Elephant(11),
    Penguin(12),
    Zebra(13),
    Hen(14),
    Rooster(15),
    Pig(16),
    Dog(17),
    Rabbit(18),
    Owl(19),
    Sheep(20),
    Cat(21),
    Deer(22),
    Donkey(23),
    Cow(24),
    Fox(25);

    // Number of total animal types
    public static int count();

    // Select a random animal
    public static CardType random();

    // Get the next animal in the list
    public CardType next();

    // Map an animal name to the respective enum value (e.g. "puma" -> Puma)
    static CardType fromName(final String name);
}

We model a single card as a set of basic attributes (see full implementation here):

public class Card {

    public Card() {

        active = true;
        animalType = CardType.BackSide;
        backSide = true;
    }

    // true if card is active (i.e. still part of the current game), else false
    public boolean active;
    // true if card is back side, false if the card is turned
    // (i.e. we can see the animal character)
    public boolean backSide;
    // the animal character associated with this card
    public CardType animalType;
    // card position within the screen, in pixels
    public float x;
    public float y;
    // card dimensions, in pixels
    public float width;
    public float height;
}

Animals vector graphics are defined within a single SVG file, where each animal is a single first-level group (a <g> element):

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
	 id="Animals"
	 xmlns="https://www.w3.org/2000/svg"
	 xmlns:xlink="https://www.w3.org/1999/xlink"
	 width="768px" height="640px"
	 viewBox="0 0 3072 2560"
	 xml:space="preserve">
    <g id="Panda">...</g>
    <g id="Monkey">...</g>
    <g id="Orangutan">...</g>
    <g id="Panther">...</g>
    <g id="Puma">...</g>
    <g id="Leopard">...</g>
    <g id="Lion">...</g>
    <g id="Cougar">...</g>
    <g id="Tiger">...</g>
    <g id="Elephant">...</g>
    <g id="Penguin">...</g>
    <g id="Zebra">...</g>
    <g id="Hen">...</g>
    <g id="Rooster">...</g>
    <g id="Pig">...</g>
    <g id="Dog">...</g>
    <g id="Rabbit">...</g>
    <g id="Owl">...</g>
    <g id="Sheep">...</g>
    <g id="Cat">...</g>
    <g id="Deer">...</g>
    <g id="Donkey">...</g>
    <g id="Cow">...</g>
    <g id="Fox">...</g>
    <g id="back">...</g>
</svg>
 
gameAnimals.svg

Lets start by copying gameAnimals.svg to the project’s android/assets folder. Now we want to implement a function that generates animals sprites from the given SVG file, taking care of the current screen resolution. This is easy with AmanithSVG, we make use of SVGTextureAtlasGenerator class and the SVGScaler utility. Along with the animal sprites creation, we want to create a 1-1 map between each sprite and the relative CardType enum value.

First of all we load gameAnimals.svg file and create an instance of SVGTextureAtlasGenerator within the create function:

// SVG atlas generator
private SVGTextureAtlasGenerator atlasGen = null;
// associate each animal type the respective texture region
private HashMap<CardType, SVGTextureAtlasRegion> animalsSprites = null;

@Override
public void create() {

    // initialize AmanithSVG for libGDX
    ...

    // create backgrounds documents
    ...

    // scale, border, dilateEdgesFix
    atlasGen = svg.createAtlasGenerator(1, 1, false);
    // SVG file, explodeGroups, scale
    atlasGen.add("gameAnimals.svg", true, 1);

Now we want to regenerate animal sprites at each resize event, as we already did for the background:

private SVGTextureAtlas atlas = null;

private void generateAnimalSprites(int screenWidth,
                                   int screenHeight) {

    // the scaler will calculate the correct scaling factor, actual parameters say:
    // "We have created all the SVG files (that we are going to pack in atlas) so
    // that, at 768 x 640 (the 'reference resolution'), they do not need additional
    // scaling (the last passed parameter value 1 is the basic scale relative to
    // the 'reference resolution'); if the device has a different screen resolution,
    // we want to scale SVG contents depending on the actual width and height
    // (MatchWidthOrHeight), equally important (0.5f)"
    SVGScaler scaler = new SVGScaler(768, 640, SVGScalerMatchMode.MatchWidthOrHeight, 0.5f, 1);
    // calculate the scale factor according to the current window/screen resolution
    float scale = scaler.scaleFactorCalc(screenWidth, screenHeight);

    // set generation scale (all other parameters have been set when instantiating
    // the SVGTextureAtlasGenerator class)
    atlasGen.setScale(scale);

    // dispose previous textures atlas
    if (atlas != null) {
        atlas.dispose();
    }

    // do the real generation
    atlas = atlasGen.generateAtlas();

    // empty the previous map
    animalsSprites.clear();

    // now associate to each animal type (i.e. the key) the respective
    // texture region (i.e. the value)
    for (SVGTextureAtlasPage page : atlas.getPages()) {
        for (SVGTextureAtlasRegion region : page.getRegions()) {
            animalsSprites.put(CardType.fromName(region.getElemName()), region);
        }
    }
}

@Override
public void resize(int width,
                   int height) {

    if ((width > 0) && (height > 0)) {
        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();
        // update OpenGL viewport
        HdpiUtils.glViewport(0, 0, width, height);
        // generate background
        generateBackground(screenWidth, screenHeight);
        // generate sprites
        generateAnimalSprites(screenWidth, screenHeight);
    }
}

Now the easiest part! We only have to instantiate six pairs of animals (12 cards in total), randomly chosen among the 25 available:

// number of cards
private static final int CARDS_COUNT = 12;
// the deck of cards
private Card[] cards = new Card[CARDS_COUNT];

private void startNewGame() {

    CardType[] animalCouples = new CardType[CARDS_COUNT];
    // start with a random animal
    CardType currentAnimal = CardType.random();

    // generate animal couples
    for (int i = 0; i < (CARDS_COUNT / 2); ++i) {
        animalCouples[i * 2] = currentAnimal;
        animalCouples[(i * 2) + 1] = currentAnimal;
        currentAnimal = currentAnimal.next();
    }

    // shuffle couples (Knuth shuffle)
    Random rnd = new Random();
    int n = CARDS_COUNT;
    while (n > 1) {
        n--;
        int i = rnd.nextInt(n + 1);
        CardType temp = animalCouples[i];
        animalCouples[i] = animalCouples[n];
        animalCouples[n] = temp;
    }

    // assign cards
    for (int i = 0; i < CARDS_COUNT; ++i) {
        // cards start as active and backside
        cards[i].active = true;
        cards[i].backSide = true;
        cards[i].animalType = animalCouples[i];
    }
}

@Override
public void create() {

    ...

    // start a new game (i.e. select random cards and initilize them as "backside")
    startNewGame();
}

The rendering of cards is really simple, it’s just a matter of looping over them and:

// draw the game, background and cards
private void drawGame(int screenWidth,
                      int screenHeight) {

    // clear screen
    ScreenUtils.clear(1, 1, 1, 1);

    // start drawing
    batch.begin();
    // draw the background
    batch.draw(backgroundTexture,
               // destination screen region
               0, 0, screenWidth, screenHeight,
               // source texture region
               0, 0, backgroundTexture.getWidth(), backgroundTexture.getHeight(),
               // flipX, flipY
               false, true);
    // draw cards
    for (Card card : cards) {
        // draw active cards only
        if (card.active) {
            SVGTextureAtlasRegion region = animalsSprites.get(card.backSide ?
                                                              CardType.BackSide :
                                                              card.animalType);
            batch.draw(region, card.x, card.y);
        }
    }
    // finish drawing
    batch.end();
}

The last detail that is missing is how the 12 cards are disposed on the screen. If the screen has a landscape layout, the cards are arranged on 3 rows and 4 columns; on portrait layouts, instead, they are arranged on 4 rows and 3 columns.

// cards arrangement when native device orientation is portrait
private static final int[] CARDS_INDEXES_NATIVE_PORTRAIT = {
    0,  1,  2,
    3,  4,  5,
    6,  7,  8,
    9, 10, 11
};
// landscape orientation, clockwise from the portrait orientation
private static final int[] CARDS_INDEXES_LANDSCAPE_ROT90 = {
    9, 6, 3, 0,
    10, 7, 4, 1,
    11, 8, 5, 2
};
// landscape orientation, counter-clockwise from the portrait orientation
private static final int[] CARDS_INDEXES_LANDSCAPE_ROT270 = {
    2, 5, 8, 11,
    1, 4, 7, 10,
    0, 3, 6,  9
};

// cards arrangement when native device orientation is landscape
private static final int[] CARDS_INDEXES_NATIVE_LANDSCAPE = {
    0,  1,  2, 3,
    4,  5,  6, 7,
    8,  9, 10, 11
};
private static final int[] CARDS_INDEXES_PORTRAIT_ROT90 = {
    8, 4, 0,
    9, 5, 1,
    10, 6, 2,
    11, 7, 3
};
private static final int[] CARDS_INDEXES_PORTRAIT_ROT270 = {
    3, 7, 11,
    2, 6, 10,
    1, 5, 9,
    0, 4, 8
};

private void placeCards(int screenWidth,
                        int screenHeight) {

    int[] cardsIndexes;
    Application.ApplicationType appType = Gdx.app.getType();
    SVGTextureAtlasRegion region = animalsSprites.get(CardType.BackSide);
    int cardWidth = region.getRegionWidth();
    int cardHeight = region.getRegionWidth();
    // number of card slots in each dimension
    int slotsPerRow = (screenWidth <= screenHeight) ? 3 : 4;
    int slotsPerColumn = (screenWidth <= screenHeight) ? 4 : 3;
    // 5% border
    int borderX = (int)Math.floor(screenWidth * 0.05);
    int borderY = (int)Math.floor(screenHeight * 0.05);
    // space between one card and the adjacent one
    int horizSeparator = ((screenWidth - (slotsPerRow * cardWidth) - (2 * borderX)) / (slotsPerRow - 1));
    int vertSeparator = ((screenHeight - (slotsPerColumn * cardHeight) - (2 * borderY)) / (slotsPerColumn - 1));
    int i = 0;

    // check actual orientation on iOS and Android devices
    if ((appType == Application.ApplicationType.Android) ||
        (appType == Application.ApplicationType.iOS)) {

        // get current orientation
        int deviceRotation = Gdx.input.getRotation();

        if (nativeDeviceOrientation == Input.Orientation.Portrait) {
            // native orientation is portrait, now handle rotations
            switch (deviceRotation) {
                case 90:
                    cardsIndexes = CARDS_INDEXES_LANDSCAPE_ROT90;
                    break;
                case 270:
                    cardsIndexes = CARDS_INDEXES_LANDSCAPE_ROT270;
                    break;
                default:
                    cardsIndexes = CARDS_INDEXES_NATIVE_PORTRAIT;
                    break;
            }
        }
        else {
            // native orientation is landscape, now handle rotations
            switch (deviceRotation) {
                case 90:
                    cardsIndexes = CARDS_INDEXES_PORTRAIT_ROT90;
                    break;
                case 270:
                    cardsIndexes = CARDS_INDEXES_PORTRAIT_ROT270;
                    break;
                default:
                    cardsIndexes = CARDS_INDEXES_NATIVE_LANDSCAPE;
                    break;
            }
        }
    }
    else {
        // Desktop, HeadlessDesktop, Applet; detect orientation according to screen dimensions
        cardsIndexes = (screenWidth <= screenHeight) ? CARDS_INDEXES_NATIVE_PORTRAIT
                                                     : CARDS_INDEXES_NATIVE_LANDSCAPE;
    }

    for (int y = 0; y < slotsPerColumn; ++y) {
        for (int x = 0; x < slotsPerRow; ++x) {
            region = animalsSprites.get(cards[cardsIndexes[i]].animalType);
            cards[cardsIndexes[i]].x = borderX + (x * (cardWidth + horizSeparator));
            cards[cardsIndexes[i]].y = borderY + (y * (cardHeight + vertSeparator));
            cards[cardsIndexes[i]].width = region.getRegionWidth();
            cards[cardsIndexes[i]].height = region.getRegionHeight();
            i++;
        }
    }
}

@Override
public void resize(int width,
                   int height) {

    if ((width > 0) && (height > 0)) {
        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();
        // update OpenGL viewport
        HdpiUtils.glViewport(0, 0, width, height);
        // generate background
        generateBackground(screenWidth, screenHeight);
        // generate sprites
        generateAnimalSprites(screenWidth, screenHeight);
        // place cards on the screen
        placeCards(screenWidth, screenHeight);
    }
}
 
Landscape layout: 3 rows and 4 columns
 
Portrait layout: 4 rows and 3 columns

The game class will also implement InputProcessor, so that it can intercept mouse and touch events. Please refer to the official guide for code details. Once that a mouse/touch event has been caught, it is passed to the following function that simply checks if a card has been “selected” (a trivial “point in a box” test):

private void selectCard(int screenX,
                        int screenY) {

    for (Card card : cards) {
        if (card.active) {
            // check if the card has been touched
            if ((screenX > card.x) && (screenX < (card.x + card.width)) &&
                (screenY > card.y) && (screenY < (card.y + card.height))) {
                ...
                break;
            }
        }
    }
}

@Override
public boolean touchDown(int screenX,
                         int screenY,
                         int pointer,
                         int button) {

    boolean processed = false;

    // ignore if it's not left mouse button or first touch pointer
    if ((button == Input.Buttons.LEFT) && (pointer <= 0)) {
        // deal with HDPI monitors properly
        int realX = HdpiUtils.toBackBufferX(screenX);
        int realY = HdpiUtils.toBackBufferY(screenY);
        // NB: 2D coordinates (screenX, screenY) relative to the upper left
        // corner of the screen, with the positive x-axis pointing to the
        // right and the y-axis pointing downward. In order to be consistent
        // with the SpriteBatch.draw coordinates system, we flip y coordinate
        selectCard(realX, Gdx.graphics.getBackBufferHeight() - realY);
        processed = true;
    }

    return processed;
}

The “You win!” message

When the player has guessed all pairs, a “You win!”-like message will be displayed. This will be implemented using a simple SVG that uses a rotated <text> element:

<svg version="1.1" width="512" height="512" viewBox="0 0 512 512">
    <!-- 45 degree rotation around the center, then a
         translation to vertically center the baseline -->
    <g transform="rotate(-45 256 256) translate(0 42)"
       text-anchor="middle"
       font-family="Acme" font-size="120"
       fill="white" stroke="black" stroke-width="4">
        <!-- make the text centered at (256, 256) -->
        <switch>
            <!-- German language -->
            <text x="256" y="256" systemLanguage="de">Du gewinnst!</text>
            <!-- English language -->
            <text x="256" y="256" systemLanguage="en">You win!</text>
            <!-- Spanish language -->
            <text x="256" y="256" systemLanguage="es">Tú ganas!</text>
            <!-- French language -->
            <text x="256" y="256" systemLanguage="fr">Vous gagnez!</text>
            <!-- Italian language -->
            <text x="256" y="256" systemLanguage="it">Hai vinto!</text>
            <!-- the fallback element with no systemLanguage attribute
                 if none of them match -->
            <text x="256" y="256">You win!</text>
        </switch>
    </g>
</svg>
 
The SVG used to display a congratulation message to the player

In order to display the message, an additional texture is created and updated:

// SVG document containing a congratulation message
private SVGDocument congratsDoc = null;
// texture containing a congratulation message
private SVGTexture congratsTexture = null;

private void generateCongratsMessage(int screenWidth,
                                     int screenHeight) {

    // congratulation SVG is squared by design, we choose to generate a texture with
    // a size equal to 3/5 of the smallest screen dimension; e.g. on a 1920 x 1080
    // device screen, texture will have a size of (1080 * 3) / 5 = 648 pixels
    int size = (Math.min(screenWidth, screenHeight) * 3) / 5;

    // destroy previous texture, if needed
    if (congratsTexture != null) {
        congratsTexture.dispose();
    }

    congratsTexture = svg.createTexture(congratsDoc, size, size);
}

// draw the given texture by centering it on the screen
private void drawCenteredTexture(SVGTexture texture,
                                 int screenWidth,
                                 int screenHeight) {

    int texWidth = texture.getWidth();
    int texHeight = texture.getHeight();
    int texX = (screenWidth - texWidth) / 2;
    int texY = (screenHeight - texHeight) / 2;

    // draw texture at the center of screen
    batch.draw(texture,
               // destination screen region
               texX, texY, texWidth, texHeight,
               // source texture region
               0, 0, texWidth, texHeight,
               // flipX, flipY
               false, true);
}

// draw the background and the congratulation message
private void drawCongratsMessage(int screenWidth,
                                 int screenHeight) {

    // draw congratulation message at the center of screen
    drawCenteredTexture(_congratsTexture, screenWidth, screenHeight);
}

@Override
public void create() {

    ...

    // load congratulations SVG document
    congratsDoc = svg.createDocumentFromFile("gameCongrats.svg");
}

@Override
public void resize(int width,
                   int height) {

    if ((width > 0) && (height > 0)) {

        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();

        ...

        // generate congratulation message texture
        generateCongratsMessage(screenWidth, screenHeight);
    }
}

The remaining code (not reported here because really trivial) simply deals with the gameplay: if the two selected cards match, they are made inactive (Card.active = false) and removed from the game, otherwise they are covered again. The game ends when all the cards are inactive (i.e. all pairs of animals have been discovered).

 
A new game started and we immediately chose a pair that does not match!

The project settings

As seen in the previous chapter, the SVG file used to generate the “You win!” texture message is made of <text> elements; all such elements inherit the use of a Acme font family. In addition a <switch> element is used to provide an ability to specify alternate viewing depending on the capabilities of a given user agent or the user’s language. In the detail not all <text> elements will be rendered, but only the one that matches the user’s language. In order to provide AmanithSVG fonts and language settings, SVGAssetsConfigGDX class must be used.

@Override
public void create() {

    // get actual backbuffer resolution
    int screenWidth = Gdx.graphics.getBackBufferWidth();
    int screenHeight = Gdx.graphics.getBackBufferHeight();
    int deviceRotation = Gdx.input.getRotation();
    // create configuration for AmanithSVG; NB: use the backbuffer to get the real dimensions in pixels
    SVGAssetsConfigGDX cfg = new SVGAssetsConfigGDX(screenWidth, screenHeight, Gdx.graphics.getPpiX());

    // set curves quality (used by AmanithSVG geometric kernel to approximate curves with straight
    // line segments (flattening); valid range is [1; 100], where 100 represents the best quality
    cfg.setCurvesQuality(75);
    // specify the system/user-agent language; this setting will affect the conditional rendering
    // of <switch> elements and elements with 'systemLanguage' attribute specified
    cfg.setLanguage("en");
    // provide fonts, in order to render <text> elements
    cfg.addFont("acme.ttf", "Acme");

    // initialize AmanithSVG for libGDX
    svg = new SVGAssetsGDX(cfg);

    ...
}

In this game example we provide AmanithSVG with the Acme font (as needed by the congratulation message SVG). In this way, when AmanithSVG will be initialized at runtime, it will find and use the font file to render <text> elements.

 
The Acme font

The splash screen

The implementation of an initial splash screen, showing a “Powered by AmanithSVG” logo, is really simple. It is just a matter to load the relative SVG document, and generate a texture. Because the logo must cover the whole screen, while maintaining its original aspect ratio (i.e. the aspect ratio induced by the viewBox attribute), we must calculate the smallest scale factor that “fits” the logo within the screen.

// splash screen SVG document
private SVGDocument splashDoc = null;
// "Powered by AmanithSVG" texture
private SVGTexture splashTexture = null;

// generate "Powered by AmanithSVG" splash screen texture
private void generateSplashScreen(int screenWidth,
                                  int screenHeight) {

    float viewW = splashDoc.getViewport().getWidth();
    float viewH = splashDoc.getViewport().getHeight();
    // keep the SVG aspect ratio
    float sx = screenWidth / viewW;
    float sy = screenHeight / viewH;
    // keep 2% border on each side
    float scaleMin = Math.min(sx, sy) * 0.96f;
    // and at the same time we fit the screen
    int texW = Math.round(viewW * scaleMin);
    int texH = Math.round(viewH * scaleMin);

    // destroy previous texture, if needed
    if (splashTexture != null) {
        splashTexture.dispose();
    }

    splashTexture = svg.createTexture(splashDoc, texW, texH);
}

// display "Powered by AmanithSVG" splash screen
private void drawSplashScreen(int screenWidth,
                              int screenHeight) {

    // draw splash screen texture at the center of screen
    drawCenteredTexture(splashTexture, screenWidth, screenHeight);
}

@Override
public void create() {

    ...

    // load splash screen SVG document
    splashDoc = svg.createDocumentFromFile("powBy_AmanithSVG_Dark.svg");
}

@Override
public void resize(int width,
                   int height) {

    if ((width > 0) && (height > 0)) {

        // get actual backbuffer resolution
        int screenWidth = Gdx.graphics.getBackBufferWidth();
        int screenHeight = Gdx.graphics.getBackBufferHeight();

        ...

        // generate "Powered by AmanithSVG" splash screen texture
        generateSplashScreen(screenWidth, screenHeight);
    }
}
 
Powered by AmanithSVG logo (for dark backgrounds)
 
Powered by AmanithSVG logo (for light backgrounds)

The complete project can be found here. Now you can have fun experimenting with it!

Credits