An important aspect at video game development is the splash screen.
What is the splash screen?
It is an screen which is shown to the user just when he opens the application. It is usually used to show some information to user while in background the app does the necessary load and preparations to run. We can see the use of splash screen, for example, on Eclipse or GIMP.
Why is it important?
The more important issue is that the splash screen shows to the user that our app is running. If the app needs many resources (common in a video game), it is probably the app spends some seconds on load all it. If we don’t do anything, our app will seem frozen along those seconds and in the better case it will only show a black screen. By using the splash screen the user will know our app is opened and running.
Furthermore, we can take advantage of the splash screen to show our trademark, any message, etc.
How to make it in LibGDX
For this example, I have going to use the class com.badlogic.gdx.Game as superclass of the main class of my game, and every scene of the game will be an implementation of the interface com.badlogic.gdx.Screen, because this is the way I usually work (see Scenes management in LibGDX). Let’s go to see the classes implementations:
SplashScreen
The code of this class will be like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | package com.pixnbgames.splashscreen; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; public class SplashScreen implements Screen { private SpriteBatch batch; private Texture ttrSplash; public SplashScreen() { super(); batch = new SpriteBatch(); ttrSplash = new Texture("splash-screen.png"); } @Override public void render(float delta) { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(ttrSplash, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); batch.end(); } @Override public void hide() { } @Override public void pause() { } @Override public void resume() { } @Override public void show() { } @Override public void resize(int width, int height) { } @Override public void dispose() { ttrSplash.dispose(); batch.dispose(); } } |
Key Issues:
It’s very important to stand out that this class must not depend on any other component which has not been loaded yet. This is why this class draws directly an image on screen with a SpriteBatch object.
Supose you have a class in your project, ImageManager, responsible of load and management of the graphics resources of our app. Probably, ImageManager should be initialized at the aplication startup, so that the resources it manages will be available to the rest of the game. Well, our SplashScreen can not use this class. For two reasons:
- The splash screen have to be as light as possible. Remember that it will be shown to the user as soon as possible at the aplication startup and it will be visible only for a few seconds.
- If we try to use any resource which has not been loaded yet, the splash screen will not can be shown to the user, so our app will still seem «frozen».
Main class of the Game, GdxSplashScreenGame
For the main class of the game, GdxSplashScreenGame, I will supose that there is a second class (not included in this tutorial) with the main menu of the game, named MainMenuScreen (see Scenes management in LibGDX). The main menu will be automatically shown after splash screen.
We also add a condition to splash screen, and it is that the screen will be shown at least for two seconds. Why do we do this? Because if we hide the splash screen just when all the resources are loaded, the time while the splash screen is shown is totally dependent of the device where the app is running. Even, if the device is enough fast at resource loading, our splash screen could not get displayed. By defining a minimum display time, we managed to mitigate the differences between the various devices where our game is running.
The code for the main class GdxSplashScreenGame would be like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.pixnbgames.splashscreen; import com.badlogic.gdx.Game; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.Timer; public class GdxSplashScreenGame extends Game { private static long SPLASH_MINIMUM_MILLIS = 2000L; public GdxSplashScreenGame() { super(); } @Override public void create () { setScreen(new SplashScreen()); final long splash_start_time = System.currentTimeMillis(); new Thread(new Runnable() { @Override public void run() { Gdx.app.postRunnable(new Runnable() { @Override public void run() { // ... carga de datos // ... carga de fuentes tipograficas // ... carga de sonidos // ... carga de imagenes // ... carga de recursos de internacionalizacion // ... otros // Se muestra el menu principal tras la SpashScreen long splash_elapsed_time = System.currentTimeMillis() - splash_start_time; if (splash_elapsed_time < GdxSplashScreenGame.SPLASH_MINIMUM_MILLIS) { Timer.schedule( new Timer.Task() { @Override public void run() { GdxSplashScreenGame.this.setScreen(new MainMenuScreen()); } }, (float)(GdxSplashScreenGame.SPLASH_MINIMUM_MILLIS - splash_elapsed_time) / 1000f); } else { GdxSplashScreenGame.this.setScreen(new MainMenuScreen()); } } }); } }).start(); } @Override public void dispose() { // DISPOSE ALL RESOURCES getScreen().dispose(); Gdx.app.exit(); } } |
Key Issues:
- The resources loading cannot be done in the main thread, so then the application could not show anything until the loading will be completed. This is why it is done in a new
Thread. - If after resource loading, it has been spent less time that the time defined in
SPLASH_MINIMUM_MILLIS, we have to wait the missing time and then show the main menu screen. We get this by passing aTimer.Taskobject to theTimer.schedule()method.
Source code
You can download the source code of a simple project with splash screen from my GitHub repository.
Thanks for the tuto.
Just a question: do you really need to explicitly create another thread? I think it should be enough to tell gdx to post a runnable:
Gdx.app.postRunnable(new Runnable() {
// load stuff…
});
Hi gsim,
Thanks for comment. Surely, there are other possibilities, but the truth is I don’t know why, but the only way I got the desired behavior was as shown here.
Have you tried your solution and checked it is ok?
Thanks again and kind regards!
Yeah, already tried it and worked fine. Your solution worked as well.
I think that this implementation is wrong.. You should load stuff in the new Thread that you create not in Gdx.app.postRunnable(), this becaus stuff inside Gdx.app.postRunnable() happens on Main UI Thread!
Proof? Just put a Thread.sleep(5000) to simulate hard loading inside Gdx.app.postRunnable() run..
So the correct structure should be:
new Thread(new Runnable() {
@Override
public void run() {
//Load stuff… Thread.sleep
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {
//switch screen
}
});
}
}).start();
Hi FGNM, thanks for the clarification! It’s very interesting
Your welcome, however this could’t be the best solution.. By using AssetManager, that is already async, we could reach better results without creating new Threads 😉
@Override
public void show() {
stage = new Stage(new FitViewport(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, new OrthographicCamera()));
stage.getViewport().apply(true);
stage.setDebugAll(true);
atlas = game.getAssetManager().get(«game_textures.atlas»);
background = atlas.findRegion(«splash1»);
Image bck = new Image(background);
bck.setAlign(Align.center);
bck.addAction(Actions.alpha(0f));
bck.addAction(Actions.sequence(
Actions.fadeIn(2f),
Actions.delay(1f),
Actions.fadeOut(2f),
Actions.run(new Runnable() {
@Override
public void run() {
game.setScreen(new GameScreen(game));
}
}))
);
game.setScreen(new GameScreen(game));
stage.addActor(bck);
}
Just another one implementation 🙂 , some animation(action) instead of Thread.sleep();
Hi @Admeo,
Thanks for sharing your solution!
Regards,