Shivashriganesh Mahato
Shivashriganesh Mahato

Reputation: 572

Store sprites from spritesheet in array Libgdx

I have 960x960 spritesheet stored as a png inside of my Libgdx android assets. In a class I have specified towards initializing sprites for use in my game, I am trying to make it so there is a 120x120 sprite cut from the spritesheet (so there should be 64 items in the array). How am I able to do this? This is what I have tried in a similar situation:

public static Texture spritesheet;
public static Sprite[] textures = new Sprite[64];
...
    //inside method that gets called once to initialize variables
    spritesheet = new Texture(
            Gdx.app.getType() == Application.ApplicationType.Android ?
                    "...Spritesheet.png" :
                    "android/assets/...Spritesheet.png"
    );
    spritesheet.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);

    for (int x = 0; x < 64; x ++) {
        textures[x] = new Sprite(spritesheet, x, (x%8), 64, 64);
        textures[x].flip(false, true);
    }

I then render the sprite in other classes with this:

batch.draw(Assets.textures[0 /*this can be any number*/], (float) x, (float) y, 108, 108);

When I do this, it acts really weird. It says there are elements filled in the array, but there's still Array Index Out of Bounds Exceptions or the sprites just render crazily. Overall, it's not working out. What I'm trying to do is make it so I don't have to initialize 64 different sprites separately, and make it so I can easily change the sprite by changing the index inputted when rendering the sprite so I can do some other things later on, like an animation. How can I go about doing this?

Upvotes: 1

Views: 1113

Answers (2)

Madmenyo
Madmenyo

Reputation: 8584

You should use a TextureAtlas for this purpose. A atlas is a file generated automatically from separate images by the LibGDX TexturePacker. It stores everything from image bounds within your sheet to NinePatch information. All you need to do is put your separate images in a folder and run the TexturePacker on that folder. This will create a sheet and a .atlas/.pack file for you that can easily loaded.

There exists a TexturePackerGui if you have difficulty with the commandline but I do recommend the command line or even using it within your app.

What I usually do is create these sheets on the fly when I'm developing. I can easily overwrite separate images and they have a immediate effect after I run my app again. I start with creating a new folder images at the root of my project. Then for each pack I need I create another folder. For this example I create the folder tileset in images. In the DesktopLauncher I will setup this folder to produce a atlas from the images.

    TexturePacker.Settings settings = new TexturePacker.Settings();
    settings.maxWidth = 1024;
    settings.maxHeight = 1024;

The settings file specifies everything about your atlas. The maximum size of a single sheet, if it should strip transparency from images, padding, rotation, etc. They are all very straightforward and you can look these all up in the documentation. Using these settings you can create your atlas.

    TexturePacker.process(settings, 
            "../../images/tileset", //where to find the images to be packed.
            "../../android/assets/tileset/", //where to store the atlas and sheet
            "tileset"); //what filename to use

Now you can open your .atlas file and you see it uses the filenames as a alias. This alias is used to look them up but let's load the atlas first.

TextureAtlas atlas = new TextureAtlas("tileset/tileset.atlas");

By passing just a string to the constructor it looks by default in your local path which in turn is by default in android/assets/. Now we can ask the atlas to hand over our assets in the sheet.

atlas.findRegion("alias"); //hands over the image on the sheet named "alias"

Looking up textures like this is somewhat expensive. You don't want to look up many textures like this each update so you still need to store them somewhere.

If you name your image sequence like image_01.png, image_02.png, image_03.png it stores them all under the same name and within the atlas it sorts them bu it's index. So if you want a separate array of certain textures you can name them with _xx and get them all in one go:

atlas.findRegions("alias");

This is especially handy for Animation. Just copy your image sequence to a folder and specify it to be packed. Name your sequence correctly and give the regions to the animation. Everything will work right off the bat.

Loading a TextureAtlas with the AssetManager is pretty much the same as a normal Texture except you would specify it's from a TextureAtlas.class. You always load the .atlas which in turn handles your image.

I always use AssetDescriptor to load my assets. If I where you I would get rid of the static Assets.textures[] since that will get you into trouble sooner or later.

//None static AssetManager with getter
private AssetManager manager = new AssetManager();
public AssetManager getManager() { return manager; }

//Specify a descriptor for the atlas, this is static so I can acces it anywhere. 
//It's just a descriptor of the asset so this is safe.
public static final AssetDescriptor<TextureAtlas> TileSet = new AssetDescriptor<TextureAtlas>("tileset/tileset.atlas", TextureAtlas.class);

//then just load everything
public void load()
{
    manager.load(tileSet);
    //... load other stuff
}

Now just pass the AssetManager object anywhere you need access to your assets and you can load any asset just like so:

TextureAtlas tileset = assetManager.get(Assets.TileSet);

Upvotes: 4

Russell Elfenbein
Russell Elfenbein

Reputation: 551

I think your for loop should look like this

for(int x = 0; x < 64; x ++){
    textures[x] = new Sprite(
        spritesheet, 
        (x%8)*64, //where x=3, (3%8)*64 = 3*64 = 192px sourceX
        (x/8)*64, //where x=3, (int)(3/8)*64 = 0*64 = 0px sourceY
        64, //source width
        64 //source height
    );


Another test case where x=20;
(20%8)*64 = 4*64 = 256px SourceX
(20/8)*64 = 2*64 = 128px SourceY

Upvotes: 0

Related Questions