Skip to content

Instantly share code, notes, and snippets.

@Leejjon
Last active May 11, 2023 21:35
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save Leejjon/7fb8aa3ea2e4024a9eba31fa4f3339fb to your computer and use it in GitHub Desktop.
Save Leejjon/7fb8aa3ea2e4024a9eba31fa4f3339fb to your computer and use it in GitHub Desktop.
How to organize your stages and textures in your libGDX game

Starting out

If you have started out to build a game with libGDX, you've were probably creating and disposing textures out like this:

public class MyGdxGame extends ApplicationAdapter {
    SpriteBatch batch;
    Texture badlogic;

    @Override
    public void create () {
        batch = new SpriteBatch();
        badlogic = new Texture("badlogic.jpg");
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();
        batch.draw(badlogic, 0, 0);
        batch.end();
    }

    @Override
    public void dispose () {
        batch.dispose();
        badlogic.dispose();
    }
}

If you use scene2d, you will probably create different stages for your start screen, menus and levels. Once the code becomes bigger, you want to subclass the com.badlogic.gdx.scenes.scene2d.Stage class like this:

public class StartStage extends Stage {

    public StartStage(Texture texture) {
        Table table = new Table();
        table.setFillParent(true);
        table.center();

        Image image = new Image(texture);

        table.add(image);

        addActor(table);
    }
}

In your main class, it will look like:

public class MyGdxGame extends ApplicationAdapter {
    StartStage startStage;
    Texture badlogic;

    @Override
    public void create () {
        badlogic = new Texture("badlogic.jpg");
        startStage = new StartStage(badlogic);
        Gdx.input.setInputProcessor(startStage);
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        startStage.draw();
    }

    @Override
    public void dispose () {
        startStage.dispose();
        badlogic.dispose();
    }
}

Let's add a little bit of logic to be able to make our stage visible/invisible:

public class StartStage extends Stage {
    private boolean visible = true;

    public StartStage(Texture texture) {
        Table table = new Table();
        table.setFillParent(true);
        table.center();

        Image image = new Image(texture);

        table.add(image);

        addActor(table);
    }

    @Override
    public void draw() {
        act(Gdx.graphics.getDeltaTime());
        if (visible) {
            super.draw();
        }
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }
}

Let's add another (almost identical stage):

public class GameStage extends Stage {
    private boolean visible = false;

    public GameStage(Texture anotherTexture) {
        Table table = new Table();
        table.setFillParent(true);
        table.center();

        Image image = new Image(anotherTexture);

        table.add(image);

        addActor(table);
    }

    @Override
    public void draw() {
        act(Gdx.graphics.getDeltaTime());
        if (visible) {
            super.draw();
        }
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }
}

Main class (will only show the start stage because the game stage is invisible by default):

public class MyGdxGame extends ApplicationAdapter {
    StartStage startStage;
    GameStage gameStage;
    Texture badlogic;
    Texture carnaval;

    @Override
    public void create () {
        badlogic = new Texture("badlogic.jpg");
        carnaval = new Texture("carnaval.jpg");
        startStage = new StartStage(badlogic);
        gameStage = new GameStage(carnaval);
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        startStage.draw();
        gameStage.draw();
    }

    @Override
    public void dispose () {
        startStage.dispose();
        gameStage.dispose();
        badlogic.dispose();
        carnaval.dispose();
    }
}

Now the game never switches to another stage. Let's add some logic for that. You can do this by passing the game stage to the start stage in the constructor. Then at the moment we need to go to the game stage, set the visible boolean of the start stage to false, and set the visible boolean of the game stage on true. But wait, what if we want to have the option to return to the start stage from the game stage? We cannot pass the start stage in the constructor of the game stage.

startStage = new StartStage(img, gameStage); // Compile Error, gamestage not initalized yet.
gameStage = new GameStage(img2, startStage);

We can work around it by using a setter but that gets messy aswell. We could make methods in the main class called startGame() and returnToStart() and pass the main class itself in both constructors of the stages to make it available from within the stages.

startStage = new StartStage(img, this); 
gameStage = new GameStage(img2, this);

It is my personal preference to define an interface for these kind of methods rather than exposing the entire main class in the stages. The stages only need to be able to switch to another, they do not need to do anything with all the other methods in the main class. So let's make one:

public interface StageInterface { // At this point you might want to call it StageSwitchInterface or something, but we'll add more later.
    void startGame();
    void returnToStart();
}

Make sure the constructors of the stages have this interface as a parameter, and let's make the start stage switch to the game stage when the image is being clicked on:

public class StartStage extends Stage {
    private boolean visible = true;

    public StartStage(Texture texture, final StageInterface stageInterface) {
        Table table = new Table();
        table.setFillParent(true);
        table.center();

        Image image = new Image(texture);
        image.addListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                setVisible(false);
                stageInterface.startGame();
            }
        });

        table.add(image);

        addActor(table);
    }

    // Other methods
}

And implement the StageInterface in the main class:

public class MyGdxGame extends ApplicationAdapter implements StageInterface {
    StartStage startStage;
    GameStage gameStage;
    Texture badlogic;
    Texture carnaval;

    @Override
    public void create () {
        badlogic = new Texture("badlogic.jpg");
        carnaval = new Texture("carnaval.jpg");
        startStage = new StartStage(badlogic, this);
        Gdx.input.setInputProcessor(startStage);
        gameStage = new GameStage(carnaval, this);
    }

    @Override
    public void startGame() {
        gameStage.setVisible(true);
        Gdx.input.setInputProcessor(gameStage);
    }

    @Override
    public void returnToStart() {
        startGame.setVisible(true);
        Gdx.input.setInputProcessor(startStage);
    }

    // Other methods
}

Textures used in a single stage and textures used in multiple stages

Until now, we have only had two textures. Both textures are only used once in their stage, so you could move the initialization of the textures to the specific stage. In the future however there probably will be textures that will be used in multiple stages. It will make your main class pretty messy to define them all in the create method, and dispose them all in the dispose method and you have to pass them in all the constructors of the stages in which you want to use them.

Textures generated on runtime

Since I am a programmer and not a graphics guy, I prefer to spend my time within my IDE rather than opening up photoshop. Let's say I want to make the background of the table in the StartStage black. I could create a black image of 1x1 pixel and add it to the assets. But I would just generate a 1x1 texture via a pixmap:

public class MyGdxGame extends ApplicationAdapter implements StageInterface {
    StartStage startStage;
    GameStage gameStage;
    Texture badlogic;
    Texture carnaval;
    Texture blackPixel;

    @Override
    public void create () {
        generateTextures();
        badlogic = new Texture("badlogic.jpg");
        carnaval = new Texture("carnaval.jpg");
        startStage = new StartStage(badlogic, this, blackPixel);
        Gdx.input.setInputProcessor(startStage);
        gameStage = new GameStage(carnaval, this);
    }

    private void generateTextures() {
        Pixmap blackPixelPixmap = new Pixmap(1, 1, Pixmap.Format.RGB888);
        blackPixelPixmap.setColor(0f,0f,0f, 1f);
        blackPixelPixmap.fill();
        blackPixel = new Texture(blackPixelPixmap);
        blackPixelPixmap.dispose();
    }

    @Override
    public void dispose () {
        badlogic.dispose();
        carnaval.dispose();
        blackPixel.dispose();
    }

    // Other methods
}

Modify the start stage constructor to take another Texture and use it as a background for the table:

public StartStage(Texture texture, final StageInterface stageInterface, Texture blackPixel) {
    Table table = new Table();
    table.setFillParent(true);
    table.setBackground(new TextureRegionDrawable(new TextureRegion(blackPixel)));
    table.center();

    Image image = new Image(texture);
    image.addListener(new ClickListener() {
        @Override
        public void clicked(InputEvent event, float x, float y) {
            setVisible(false);
            stageInterface.startGame();
        }
    });

    table.add(image);
    addActor(table);
}

Defining your assets in an enumeration

When the amount of Textures I was using started to grow it made my main class (MyGdxGame) messy. I wanted to have the textures in another class. I decided to list the in an enum:

public enum Textures {
    BADLOGIC("badlogic.jpg"),
    BLACKPIXEL(), // Err, it has no file name!
    CARNAVAL("carnaval.jpg");

    private String fileName = null;

    Textures() {}

    Textures(String fileName) {
        this.fileName = fileName;
    }

    public String getFileName() {
        return fileName;
    }
}

Then you can create textures like Texture badlogic = new Texture(Textures.BADLOGIC.getFileName);. It is even better to move this method into the enumeration:

public enum Textures {
    BADLOGIC("badlogic.jpg"),
    BLACKPIXEL() {
        // We override the default "load by file name" get() method and return the generated Texture instead.
        @Override
        public Texture get() {
            Pixmap blackPixelPixmap = new Pixmap(1, 1, Pixmap.Format.RGB888);
            blackPixelPixmap.setColor(0f,0f,0f, 1f);
            blackPixelPixmap.fill();
            Texture blackPixel = new Texture(blackPixelPixmap);
            blackPixelPixmap.dispose();
            return blackPixel;
        }
    }, 
    CARNAVAL("carnaval.jpg");

    private String fileName = null;

    Textures() {}

    Textures(String fileName) {
        this.fileName = fileName;
    }

    public Texture get() {
        if (fileName != null) {
            return new Texture(fileName);
        } else {
            throw new IllegalArgumentException("No filename for this texture.");
        }
    }
}

The nice thing about enumerations is that you can loop over them. Let's provide a method to load all textures at once.

public static ObjectMap<Textures, Texture> loadAllTextures() {
    ObjectMap<Textures, Texture> textureMap = new ObjectMap<Textures, Texture>();
    for (Textures t : Textures.values()) {
        textureMap.put(t, t.get());
    }
    return textureMap;
}

Now we can clean up the main class and use the new Textures enum:

public class MyGdxGame extends ApplicationAdapter implements StageInterface {
    StartStage startStage;
    GameStage gameStage;

    ObjectMap<Textures, Texture> textureMap;

    @Override
    public void create () {
        textureMap = Textures.loadAllTextures();

        startStage = new StartStage(textureMap.get(Textures.BADLOGIC), this, textureMap.get(Textures.BLACKPIXEL));
        Gdx.input.setInputProcessor(startStage);
        gameStage = new GameStage(textureMap.get(Textures.CARNAVAL), this);
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        startStage.draw();
        gameStage.draw();
    }

    @Override
    public void dispose () {
        for (Texture t : textureMap.values()) {
            t.dispose();
        }
    }

    @Override
    public void startGame() {
        gameStage.setVisible(true);
        Gdx.input.setInputProcessor(gameStage);
    }

    @Override
    public void returnToStart() {
        startStage.setVisible(true);
        Gdx.input.setInputProcessor(startStage);
    }
}

One more thing we might want to do is getting rid of all the textures in the constructor. You might think of making the textureMap static, but that can create problems on Android. I wouldn't like to pass the entire textureMap through the constructor, we don't want to give any of the stages the possibility to modify it. So I just expanded the StageInterface with the following method:

public interface StageInterface {
    void startGame();
    void returnToStart();
    Texture getTexture(Textures texture);
}

Now let the stages use this method to get their textures instead:

public StartStage(final StageInterface stageInterface) {
    Table table = new Table();
    table.setFillParent(true);
    table.setBackground(new TextureRegionDrawable(new TextureRegion(stageInterface.getTexture(Textures.BLACKPIXEL))));
    table.center();

    Image image = new Image(stageInterface.getTexture(Textures.BADLOGIC));
    image.addListener(new ClickListener() {
        @Override
        public void clicked(InputEvent event, float x, float y) {
            setVisible(false);
            stageInterface.startGame();
        }
    });

    table.add(image);
    addActor(table);
}

Implement the getTexture(Textures texture) method in your main class (MyGdxGame):

@Override
public void create () {
    textureMap = Textures.loadAllTextures();
    startStage = new StartStage(this);
    Gdx.input.setInputProcessor(startStage);
    gameStage = new GameStage(this);
}

@Override
public Texture getTexture(Textures texture) {
    return textureMap.get(texture);
}

There you have it, a clean way to access any kind of Texture.

The full code sample is available on github: https://github.com/Leejjon/TextureHandlingSample

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment