Smoothing 2D Gameplay with LibGDX AssetManager: A Practical Guide
Learn how LibGDX’s AssetManager eliminates frame drops by loading textures, sounds and atlases asynchronously, with a concrete screen‑to‑screen example and verification steps.
16 Aug 2025, 19:48 UTC

The problem: frame drops when loading assets in the render loop
If you load textures, sounds or atlases directly inside render(), the main thread stalls while I/O happens. On low‑end devices this shows up as noticeable hitches, uneven frame times and a poor player experience.
Thesis: centralising asset handling with AssetManager gives predictable frame times
LibGDX’s AssetManager loads resources asynchronously, tracks references so each asset is disposed only when no longer needed, and provides a single point to check completion. The result is smoother gameplay and easier cleanup.
Setup: create and configure the manager
Instantiate the manager once per game (or per screen, depending on your architecture). Load each resource with its type token.
public class GameScreen implements Screen {
private final AssetManager manager = new AssetManager();
private TextureAtlas atlas;
private Sound jumpSound;
@Override
public void show() {
// Load assets – these calls return immediately
manager.load("data/player.atlas", TextureAtlas.class);
manager.load("audio/jump.wav", Sound.class);
}
@Override
public void render(float delta) {
// Update loading progress; returns true when all queued assets are ready
if (manager.update()) {
// Assets are ready – retrieve them once
if (atlas == null) {
atlas = manager.get("data/player.atlas", TextureAtlas.class);
jumpSound = manager.get("audio/jump.wav", Sound.class);
}
// Normal game rendering goes here
// ...
} else {
// Optional: show a loading screen or progress bar
float progress = manager.getProgress();
// draw progress bar based on progress (0.0‑1.0)
}
}
@Override
public void hide() {
// Dispose everything the manager owns
manager.dispose();
}
// … other Screen methods …
}
Place this code in your Screen implementation. The show() method only queues loads; the actual work happens when you call manager.update() from render() (or from a dedicated loading screen).
Worked example: transitioning to a new screen without stutter
Suppose you have a menu screen that loads game assets and then switches to the gameplay screen.
public class MenuScreen implements Screen {
private final AssetManager manager = new AssetManager();
private Game game; // reference to your ApplicationListener
public MenuScreen(Game game) {
this.game = game;
}
@Override
public void show() {
manager.load("data/level1.atlas", TextureAtlas.class);
manager.load("data/music.ogg", Music.class);
}
@Override
public void render(float delta) {
if (manager.update()) {
// All assets ready – switch to gameplay screen
game.setScreen(new GameplayScreen(manager));
return;
}
// Draw a simple loading message while waiting
// ...
}
@Override
public void hide() {
manager.dispose();
}
}
public class GameplayScreen implements Screen {
private final AssetManager manager;
private TextureAtlas levelAtlas;
private Music backgroundMusic;
public GameplayScreen(AssetManager manager) {
this.manager = manager;
}
@Override
public void show() {
levelAtlas = manager.get("data/level1.atlas", TextureAtlas.class);
backgroundMusic = manager.get("data/music.ogg", Music.class);
backgroundMusic.setLooping(true);
backgroundMusic.play();
}
@Override
public void render(float delta) {
// Use levelAtlas to draw sprites, update physics, etc.
// ...
}
@Override
public void hide() {
backgroundMusic.stop();
// Note: we do NOT dispose the manager here; it is still needed by other screens
// If this is the last screen using the manager, call manager.dispose() elsewhere.
}
}
The menu screen shows a loading indicator until manager.update() returns true. Only then does it hand the same AssetManager instance to the gameplay screen, which retrieves already‑loaded assets without any additional I/O.
Trade‑off and limitation
Using AssetManager adds a small upfront memory overhead because the manager keeps internal bookkeeping structures and may hold assets longer than a manual load‑and‑dispose approach if you forget to stop using them. The biggest practical risk is calling manager.update() (or worse, manager.load()) inside render() after the assets are already loaded; this still incurs a trivial cost but can hide bugs if you rely on it for loading new assets mid‑frame. Always keep asset‑loading logic out of the tight render loop.
Actionable closing
- Wrap the manager in a singleton or inject it via your preferred DI framework so screens can share the same instance.
- Before transitioning to a new screen, call
manager.finishLoading()to block until all queued assets are ready – this guarantees the next screen starts with everything in memory. - Always invoke
manager.dispose()when you are certain no screen will need the manager again (e.g., on application exit or when you replace the manager with a new one). - Verify stability: run the desktop launcher, enable the FPS counter via
Gdx.graphics.setTitle("FPS: " + Gdx.graphics.getFramesPerSecond())or use an external monitor, and confirm that frame times stay smooth while assets load in the background. Watch the console for log lines likeLoading completed: X assetsand ensure no “Disposing asset” warnings appear after a screen change.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.