Gestire texture e sprite atlas in libGDX su Android (OpenGL ES 2.0, Java)
Questo è il quinto articolo di una serie pratica su come sviluppare un semplice gioco 2D con libGDX su Android. Dopo aver visto setup/layout Android, game loop e rendering, input touch e collision detection, qui ci concentriamo su texture, sprite e sull’uso di uno sprite atlas libGDX Android, fondamentali per performance e organizzazione delle risorse grafiche.
1. Introduzione a sprite, texture e sprite atlas in libGDX per Android
In OpenGL ES una texture è semplicemente un’immagine caricata nella memoria della GPU e campionata negli shader per disegnare sprite, sfondi, UI, ecc. Ogni volta che cambi texture (texture bind) la GPU deve fare lavoro extra: troppi cambi rallentano il rendering.
Uno sprite atlas (o texture atlas) è una singola grande texture che contiene molte immagini più piccole (frame di animazioni, icone, tile). Con un atlas puoi:
- ridurre il numero di cambi texture (bind) a 1 per molti sprite;
- avere meno draw call e quindi più FPS;
- organizzare meglio le risorse e gestire in blocco caricamento e rilascio dalla memoria.
2. Dipendenze Gradle per usare libGDX in un progetto Android con grafica 2D
Assumiamo che tu abbia già un progetto Android esistente (non creato col setup ufficiale libGDX multipiattaforma) e voglia usare libGDX “solo” come libreria OpenGL 2D.
Nel file app/build.gradle (module level) aggiungi:
dependencies {
// ... altre dipendenze Android ...
implementation "com.badlogicgames.gdx:gdx:1.12.0"
implementation "com.badlogicgames.gdx:gdx-backend-android:1.12.0"
// Per usare TexturePacker da codice Java (opzionale, spesso lo si usa da linea di comando)
implementation "com.badlogicgames.gdx:gdx-tools:1.12.0"
}
Controlla sul sito ufficiale di libGDX se esiste una versione più recente e sostituisci 1.12.0 con l’ultima disponibile.
3. Caricare una texture con OpenGL ES (glGenTextures, glBindTexture, glTexImage2D) in un’app Android con libGDX
libGDX fornisce già la classe Texture che incapsula l’uso diretto di glGenTextures, glBindTexture e glTexImage2D. Tuttavia è utile capire cosa succede sotto al cofano.
Di seguito un esempio minimale che mostra il caricamento manuale di una texture con OpenGL ES 2.0 in Java sfruttando ancora le utility di libGDX per leggere il file.
public class RawTextureLoader {
// ID della texture in OpenGL
private int textureId;
public int loadTexture(String internalPath) {
// Carica l'immagine come Pixmap (RAM) usando libGDX
Pixmap pixmap = new Pixmap(Gdx.files.internal(internalPath));
// Genera un nuovo ID di texture
IntBuffer buffer = BufferUtils.newIntBuffer(1);
Gdx.gl.glGenTextures(1, buffer);
textureId = buffer.get(0);
// Fai il bind della texture
Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, textureId);
// Imposta i parametri di filtraggio e wrapping
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);
// Carica i pixel nella GPU
Gdx.gl.glTexImage2D(
GL20.GL_TEXTURE_2D,
0, // livello mipmap
pixmap.getGLInternalFormat(),
pixmap.getWidth(),
pixmap.getHeight(),
0, // border
pixmap.getGLFormat(),
pixmap.getGLType(),
pixmap.getPixels()
);
// Liberiamo la RAM, i pixel sono ora in GPU
pixmap.dispose();
return textureId;
}
public void dispose() {
if (textureId != 0) {
IntBuffer buffer = BufferUtils.newIntBuffer(1);
buffer.put(textureId);
buffer.flip();
Gdx.gl.glDeleteTextures(1, buffer);
textureId = 0;
}
}
}
Nella pratica quotidiana con libGDX userai soprattutto new Texture("immagine.png"), che fa tutte queste chiamate per te e si integra con il SpriteBatch.
4. Usare TextureRegion per ritagliare porzioni di una texture in libGDX
TextureRegion rappresenta un rettangolo (x, y, width, height) all’interno di una texture. Serve per animazioni frame-by-frame, tilemap o quando più sprite condividono la stessa immagine.
Esempio: abbiamo un file sprites.png che contiene quattro frame di un personaggio, ognuno 64×64 pixel, disposti in orizzontale.
public class TextureRegionExample implements ApplicationListener {
private SpriteBatch batch;
private Texture texture;
private TextureRegion[] frames;
private float stateTime;
@Override
public void create() {
batch = new SpriteBatch();
texture = new Texture(Gdx.files.internal("sprites.png"));
int frameWidth = 64;
int frameHeight = 64;
int frameCount = 4;
frames = new TextureRegion[frameCount];
for (int i = 0; i < frameCount; i++) {
// x cresce di 64 per ciascun frame, y = 0
frames[i] = new TextureRegion(
texture,
i * frameWidth,
0,
frameWidth,
frameHeight
);
}
stateTime = 0f;
}
@Override
public void render() {
stateTime += Gdx.graphics.getDeltaTime();
// Puliamo lo schermo
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Selezioniamo il frame in base al tempo (animazione semplice)
int frameIndex = (int) (stateTime * 10) % frames.length; // 10 fps
TextureRegion currentFrame = frames[frameIndex];
batch.begin();
batch.draw(currentFrame, 100, 100); // disegna a coordinate (100,100)
batch.end();
}
@Override
public void resize(int width, int height) { }
@Override
public void pause() { }
@Override
public void resume() { }
@Override
public void dispose() {
batch.dispose();
texture.dispose();
}
}
Qui usiamo una singola texture e ritagliamo manualmente le regioni. Lo sprite atlas automatizza e ottimizza questo processo.
5. Creare uno sprite atlas in libGDX per Android con TexturePacker
TexturePacker è lo strumento ufficiale di libGDX per generare sprite atlas. Parte da una cartella di immagini e crea:
- un file PNG (o altro formato) che contiene tutte le immagini impacchettate;
- un file
.atlasdi testo che descrive la posizione (x, y, width, height) e altri dati di ogni regione.
Struttura tipica del progetto risorse:
android-app/
app/
src/
assets/
sprites/
player_idle_1.png
player_idle_2.png
player_idle_3.png
player_idle_4.png
// ... altre immagini ...
Esempio di uso da riga di comando (da una cartella esterna al progetto o tramite task Gradle):

java -cp gdx.jar;gdx-tools.jar com.badlogic.gdx.tools.TexturePacker \
input-dir/sprites \
output-dir \
game-atlas
Questo comando genera due file:
game-atlas.pnggame-atlas.atlas
Il file .atlas ha una struttura testuale simile a:
game-atlas.png
size: 512,512
format: RGBA8888
filter: Linear,Linear
repeat: none
player_idle_1
rotate: false
xy: 2, 2
size: 64, 64
orig: 64, 64
offset: 0, 0
index: -1
player_idle_2
rotate: false
xy: 68, 2
size: 64, 64
orig: 64, 64
offset: 0, 0
index: -1
// ...
Non devi parsare questo file a mano: TextureAtlas lo fa automaticamente.
6. Caricare e usare lo sprite atlas in Java con TextureAtlas e AtlasRegion
TextureAtlas incapsula la texture grande e le singole regioni (frame) descritte dal file .atlas. Una AtlasRegion è una sottoclasse di TextureRegion con qualche informazione extra.
public class AtlasExample implements ApplicationListener {
private SpriteBatch batch;
private TextureAtlas atlas;
private AtlasRegion playerIdle1;
private AtlasRegion playerIdle2;
@Override
public void create() {
batch = new SpriteBatch();
// Carica l'atlas dalla cartella assets
atlas = new TextureAtlas(Gdx.files.internal("game-atlas.atlas"));
// Accedi alle regioni per nome (nome file originale senza estensione)
playerIdle1 = atlas.findRegion("player_idle_1");
playerIdle2 = atlas.findRegion("player_idle_2");
}
@Override
public void render() {
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(playerIdle1, 50, 50);
batch.draw(playerIdle2, 150, 50);
batch.end();
}
@Override
public void resize(int width, int height) { }
@Override
public void pause() { }
@Override
public void resume() { }
@Override
public void dispose() {
batch.dispose();
atlas.dispose(); // rilascia la texture dell'atlas
}
}
Spesso userai anche atlas.findRegions("player_idle") che restituisce una lista di frame per costruire un’animazione, come vedremo a breve.
7. Integrazione di libGDX con layout XML Android (FrameLayout + GLSurfaceView) e gestione delle immagini
Nei primi articoli della serie abbiamo configurato un GLSurfaceView dentro un layout Android XML. Riprendiamo la struttura minima per completezza.
Layout activity_main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rootContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.opengl.GLSurfaceView
android:id="@+id/glSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Qui puoi aggiungere overlay UI nativa (Button, TextView, ecc.) -->
</FrameLayout>
Activity principale che collega libGDX:
useremo un AndroidApplicationConfiguration e un ApplicationListener personalizzato per il nostro gioco.
public class MainActivity extends AndroidApplication {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
glSurfaceView = findViewById(R.id.glSurfaceView);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
config.useImmersiveMode = true;
// Inizializza libGDX e ottieni la View
View gameView = initializeForView(new MyGame(), config);
// Sostituiamo il GLSurfaceView XML con la view di libGDX
FrameLayout root = findViewById(R.id.rootContainer);
root.removeView(glSurfaceView);
root.addView(gameView);
}
}
MyGame è la classe che implementa ApplicationListener (o estende Game) e che useremo nell’esempio completo con sprite atlas.
8. Ottimizzare l’uso della memoria GPU: mipmaps, texture compression e dispose() in libGDX
Le texture possono consumare rapidamente memoria GPU, specialmente su dispositivi mobili con limiti rigidi. Alcune regole pratiche:
- Mipmaps: texture pre-ridimensionate per migliorare qualità e performance a distanza o quando ridimensioni molto lo sprite. In libGDX basta usare i costruttori giusti e i filtri corretti.
- Compressione: formati compressi (ETC2, ASTC, ecc.) riducono RAM e VRAM ma richiedono attenzione alla compatibilità.
- dispose(): qualsiasi
Texture,TextureAtlas,SpriteBatchva rilasciato condispose()quando non serve più.
Esempio di creazione di una texture con mipmaps e filtro trilineare:
Texture textureWithMipmaps = new Texture(
Gdx.files.internal("background.png"),
true // useMipMaps
);
textureWithMipmaps.setFilter(
Texture.TextureFilter.MipMapLinearNearest,
Texture.TextureFilter.Linear
);
Per la compressione, spesso conviene generare le texture già compresse con la toolchain grafica e usare formati supportati dal dispositivo target. libGDX espone enum come Pixmap.Format e può caricare certe varianti, ma la configurazione dettagliata dipende molto dal progetto.
Infine, ricordati sempre di chiamare dispose() sui tuoi oggetti grafici in dispose() del gioco oppure quando cambi schermata (se usi la classe Screen).
9. Esempio completo: animazione di uno sprite da atlas in libGDX (ciclo di frame)
Mettiamo insieme tutto: creeremo un TextureAtlas, caricheremo una sequenza di frame (player_idle_1, player_idle_2, …), costruiremo una Animation<TextureRegion> e la disegneremo in loop.
Assunzioni:
- in
assets/hai i filegame-atlas.pngegame-atlas.atlas; - nel file atlas esistono le regioni
player_idle_1,player_idle_2,player_idle_3,player_idle_4; - stai usando la classe
MainActivitymostrata prima conMyGamecome entry point libGDX.
public class MyGame implements ApplicationListener {
private SpriteBatch batch;
private TextureAtlas atlas;
private Animation<TextureRegion> idleAnimation;
private float stateTime;
@Override
public void create() {
batch = new SpriteBatch();
// Carica l'atlas
atlas = new TextureAtlas(Gdx.files.internal("game-atlas.atlas"));
// Carica tutte le regioni che iniziano con "player_idle"
Array<AtlasRegion> idleFrames = atlas.findRegions("player_idle");
// Crea un'animazione con passo di 0.1 secondi tra i frame (10 fps)
idleAnimation = new Animation<TextureRegion>(0.1f, idleFrames, Animation.PlayMode.LOOP);
stateTime = 0f;
}
@Override
public void render() {
// Aggiorna il tempo
float delta = Gdx.graphics.getDeltaTime();
stateTime += delta;
// Pulizia schermo
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Ottieni il frame corrente in base al tempo trascorso
TextureRegion currentFrame = idleAnimation.getKeyFrame(stateTime);
// Calcola il centro dello schermo per posizionare il personaggio
float x = (Gdx.graphics.getWidth() - currentFrame.getRegionWidth()) / 2f;
float y = (Gdx.graphics.getHeight() - currentFrame.getRegionHeight()) / 2f;
batch.begin();
batch.draw(currentFrame, x, y);
batch.end();
}
@Override
public void resize(int width, int height) {
// Qui potresti aggiornare una camera ortografica, se la usi
}
@Override
public void pause() { }
@Override
public void resume() { }
@Override
public void dispose() {
// Rilascia le risorse GPU
batch.dispose();
atlas.dispose();
}
}
Questo esempio è volutamente minimale: nella pratica vorrai usare una OrthographicCamera e una classe di gestione scene (ad esempio estendendo Game e Screen), ma la logica di caricamento/uso dell’atlas resta invariata.
10. Link agli articoli precedenti della serie su libGDX per Android e gestione delle risorse grafiche
Questa guida fa parte di una serie pratica su sviluppo giochi 2D con libGDX su Android. Puoi recuperare gli articoli precedenti (o crearne le pagine se non esistono ancora) con link del tipo:
- Parte 1 — Setup del progetto Android e layout di base
- Parte 2 — Game loop e rendering con OpenGL ES 2.0
- Parte 3 — Gestione dell’input touch
- Parte 4 — Collision detection per un gioco 2D
Nel prossimo articolo potremo approfondire gestione di tilemap, parallax scrolling o sistemi particellari, sempre riutilizzando texture e atlas per mantenere alte le performance.
Lascia un commento