---
title: "How to create sprite sheets & animations for libGDX"
description: "Free tutorial: Learn how to use sprite sheets & animations with libGDX, speed up startup of your app, reduce download size. Full demo source code included."
canonical: "https://www.codeandweb.com/texturepacker/tutorials/libgdx-sprite-sheet-tutorial"
date: "2025-06-26T00:00:00.000Z"
modified: "2025-06-26T00:00:00.000Z"
source: "html"
---

# How to create sprite sheets & animations for libGDX

Joachim Grill

[texturepacker](https://www.codeandweb.com/texturepacker/tutorials.md),  [tutorial](https://www.codeandweb.com/tags/tutorial),  [libgdx](https://www.codeandweb.com/tutorials/libgdx.md),  [sprite-sheets](https://www.codeandweb.com/tags/sprite-sheets),  [animation](https://www.codeandweb.com/tags/animation)

[GitHub](https://github.com/CodeAndWeb/libgdx-sprite-sheet-example)

![How to create sprite sheets & animations for libGDX](https://www.codeandweb.com/static/79ec18d97aa601971545808192c7ae8d/a6312/post-image.png)

In this tutorial you will learn how to use sprite sheets with libGDX.

## Why using sprite sheets?

*   By loading all graphics at once instead of numerous single images, the loading time of your app can be significantly reduced.
*   Using a sprite atlas also enhances the game's performance, because textures only need to be set on the graphics device once, resulting in improved frame rates.

## Creating a sprite sheet

The easiest way to create a sprite sheet is using TexturePacker — available for Windows, macOS and Linux:

[Download TexturePacker 8.3.0](https://www.codeandweb.com/texturepacker/download/8.3.0/win)

for Windows

[Also available for macOS and Linux](https://www.codeandweb.com/texturepacker/download.md)

Creating a sprite sheet with TexturePacker is quite simple:

1.  Drag and drop all the files you want to add to the left pane of TexturePacker
2.  Select the **LibGDX** framework and set the file path for the generated **Data file**. The **Texture file** can be left empty; TexturePacker will automatically save the PNG containing the sprite sheet next to the data file.
3.  Click on **Publish sprite sheet**

  ![Creating a sprite sheet with TexturePacker](https://www.codeandweb.com/static/bc1aabfff1bc83a5651e26321f93900e/283d3/texturepacker-libgdx%402x.png)

We recommend storing the individual sprites in a root folder named _raw-assets_, while only placing the sprite sheet data and texture file in the _assets_ folder. This makes it easier to determine which assets must be deployed later.

## Using a sprite sheet

Let's start with a new, empty project as you can generate it with the libGDX [Project Setup Tool](https://libgdx.com/wiki/start/project-generation). The default implementation of the main class in the _core_ module loads a sprite called _libgdx.png_ and displays it on a dark background.

In the previous section, we've already added the _libgdx.png_ sprite to the sprite sheet. Now, we can load the sprite sheet and extract the sprite from it:

core/src/main/java/com/mygdx/game/MyGdxGame.java

```java
package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.ScreenUtils;

/** {@link com.badlogic.gdx.ApplicationListener} implementation shared by all platforms. */
public class MyGdxGame extends ApplicationAdapter {
    private SpriteBatch batch;
    private TextureAtlas textureAtlas;
    private Sprite sprite;

    @Override
    public void create() {
        batch = new SpriteBatch();
        textureAtlas = new TextureAtlas("spritesheet.atlas");
        sprite = textureAtlas.createSprite("libgdx");
    }

    @Override
    public void render() {
        ScreenUtils.clear(0.57f, 0.77f, 0.85f, 1);
        batch.begin();
        sprite.setPosition(10, 10);
        sprite.draw(batch);
        batch.end();
    }

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

We've also changed the background to a bright blue color:

  ![Screenshot of the demo app](https://www.codeandweb.com/static/f3caf45bb7a146bb33bdad3915ddeaa0/283d3/libgdx-sprite-from-spritesheet%402x.png)

## Creating an animation

Now, we want to replace the static sprite with an animation. TexturePacker can automatically detect animation sprites: If the sprite filenames only differ by a sequence number suffix, TexturePacker will remove this suffix and export all the sprites as animation frames in the data file.

This makes it very easy to create an animation from a sprite sheet:

core/src/main/java/com/mygdx/game/MyGdxGame.java

```java
package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.ScreenUtils;

/** {@link com.badlogic.gdx.ApplicationListener} implementation shared by all platforms. */
public class MyGdxGame extends ApplicationAdapter {
    private SpriteBatch batch;
    private TextureAtlas textureAtlas;
    private Animation<Sprite> animation;
    private float stateTime = 0;

    @Override
    public void create() {
        batch = new SpriteBatch();
        textureAtlas = new TextureAtlas("spritesheet.atlas");
        animation = new Animation<>(0.066f, textureAtlas.createSprites("capguy"), Animation.PlayMode.LOOP);
    }

    @Override
    public void render() {
        ScreenUtils.clear(0.57f, 0.77f, 0.85f, 1);

        stateTime += Gdx.graphics.getDeltaTime();
        Sprite sprite = animation.getKeyFrame(stateTime, true);
        sprite.setX(stateTime * 250 % (Gdx.graphics.getWidth() + 400) - 200);

        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

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

The `Animation` object acts as a container for animation frames. You can retrieve the frame to display at a specific point in time using the `getKeyFrame` method. Note that you need to track the elapsed time externally.

The complete example project is available on [GitHub](https://github.com/CodeAndWeb/libgdx-sprite-sheet-example)

## Using texture compression

Texture compression reduces the amount of GPU memory a texture occupies and can speed up rendering. Unlike PNG or JPEG, which are decompressed into raw pixels before they reach the GPU, compressed textures stay compressed in video memory. This means lower memory usage and faster texture uploads.

libGDX supports **KTX** files with **ASTC**, **ETC1/ETC2** and **DXT (S3TC)** compression, which can be generated with TexturePacker. With an additional library it also loads **KTX2** files with **Basis Universal** compression, see [Using KTX2 and Basis Universal](#ktx2-basis-universal) below. For a full overview of all formats supported by TexturePacker, see [Texture compression](https://www.codeandweb.com/texturepacker/documentation/texture-compression.md).

| Format | bpp | Platform |
| --- | --- | --- |
| ASTC | 0.89 - 8 | iOS, modern Android |
| ETC1 | 4   | All OpenGL ES devices (no alpha) |
| ETC2 | 4 - 8 | OpenGL ES 3.0+ |
| DXT1 | 4   | Desktop (1-bit transparency) |
| DXT5 | 8   | Desktop (full alpha) |
| Basis Universal (ETC1S) | 0.3 - 1.25 | All platforms (KTX2 only, needs gdx-basis-universal) |
| Basis Universal (UASTC) | 4 - 8 | All platforms (KTX2 only, needs gdx-basis-universal) |

Exporting ASTC, ETC2, DXT and Basis Universal textures for libGDX requires TexturePacker 7.10.0 or newer.

### Using KTX2 and Basis Universal

The formats above are tied to specific GPUs: DXT is a desktop format, ETC and ASTC are made for mobile devices. A game that runs on both needs a separate sprite sheet for each. **Basis Universal** solves this with one texture file for all platforms. It is transcoded at load time into a compressed format the device supports.

Basis Universal offers two encoding modes:

| Mode | bpp (file) | Use case |
| --- | --- | --- |
| ETC1S | 0.3 - 1.25 | Smallest download size, medium quality |
| UASTC | 4 - 8 | High quality, larger files |

To create a Basis Universal sprite sheet in TexturePacker, select these texture settings:

*   **Texture format:** _KTX2_
*   **Pixel format:** _BASISU\_UASTC_ or _BASISU\_ETC1S_
*   **Size constraints:** _Compression blocksize_ (`CompressionBlocksize` on the command line)

The size constraint matters. The transcoder can only pick a compressed GPU format if the texture size fits that format, and most formats need a width and height divisible by four. If the size does not fit, the texture is silently decoded to uncompressed RGBA and you lose the memory savings.

If you target iOS devices that only support PVRTC, use _POT_ as size constraint and enable **Force squared** instead: PVRTC needs a square texture with power-of-two sides.

On the command line, the same sprite sheet is created with:

```bash
TexturePacker --format libgdx --texture-format ktx2 --pixel-format BASISU_UASTC \
    --size-constraints CompressionBlocksize \
    --data assets/spritesheet.atlas --sheet assets/spritesheet.ktx2 raw-assets
```

#### Loading KTX2 files in libGDX

libGDX cannot read `.ktx2` files out of the box. `new TextureAtlas("spritesheet.atlas")` fails with _Couldn't load file: spritesheet.ktx2_. The open source library [gdx-basis-universal](https://github.com/crashinvaders/gdx-basis-universal) adds the missing loader.

Add it to the dependencies of your _core_ module:

core/build.gradle

```groovy
dependencies {
    api "com.crashinvaders.basisu:basisu-wrapper:1.1.2"
    api "com.crashinvaders.basisu:basisu-gdx:1.1.2"
}
```

Version 1.1.2 of the library is built for libGDX 1.14.2. If your project uses an older libGDX version, update `gdxVersion` in `gradle.properties`.

Each platform also needs the native transcoder. For the desktop (LWJGL3) module this is:

lwjgl3/build.gradle

```groovy
dependencies {
    runtimeOnly "com.crashinvaders.basisu:basisu-wrapper:1.1.2:natives-desktop"
}
```

The dependencies for Android, iOS and the web backend are listed in the [gdx-basis-universal documentation](https://github.com/crashinvaders/gdx-basis-universal#connecting-dependencies-gradle).

Now register the `Ktx2TextureLoader` for the `.ktx2` file extension and load the sprite sheet through an `AssetManager`. The atlas file references the `.ktx2` texture, the asset manager picks the right loader for it:

core/src/main/java/com/mygdx/game/MyGdxGame.java

```java
package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils;
import com.crashinvaders.basisu.gdx.Ktx2TextureLoader;

public class MyGdxGame extends ApplicationAdapter {
    private SpriteBatch batch;
    private AssetManager assetManager;
    private Sprite sprite;

    @Override
    public void create() {
        batch = new SpriteBatch();
        assetManager = new AssetManager();
        assetManager.setLoader(Texture.class, ".ktx2",
            new Ktx2TextureLoader(assetManager.getFileHandleResolver()));
        assetManager.load("spritesheet.atlas", TextureAtlas.class);
        assetManager.finishLoading();

        TextureAtlas textureAtlas = assetManager.get("spritesheet.atlas", TextureAtlas.class);
        sprite = textureAtlas.createSprite("libgdx");
    }

    @Override
    public void render() {
        ScreenUtils.clear(0.57f, 0.77f, 0.85f, 1);
        batch.begin();
        sprite.setPosition(10, 10);
        sprite.draw(batch);
        batch.end();
    }

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

The asset manager owns the atlas and its texture, so disposing the asset manager is enough. Sprites and animations work exactly as in the sections above.

To check which GPU format was chosen, enable debug logging with `Gdx.app.setLogLevel(Application.LOG_DEBUG)`. The library then prints a line such as _Transcoding to the BC3\_RGBA format_ for each texture. If it says _RGBA32_, the texture is loaded uncompressed: check the size settings above.

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "How to create sprite sheets & animations for libGDX",
  "author": [
    {
      "@type": "Person",
      "name": "Joachim Grill"
    }
  ],
  "dateModified": "2025-06-26T00:00:00.000Z",
  "image": "/static/79ec18d97aa601971545808192c7ae8d/a6312/post-image.png"
}
```
