2

I am currently working on a customizable Player-character for my top down view 2D pixel game. I am using libgdx and aiming for android devices as well as desktop applications.

I am wondering about an efficient, but yet simple concept. So far my character is build up from different parts ( Feet, Body, Shoulder L, Shoulder R, Hand L, Hand R, Head, Haircut). Every part has its on layer, thus they are 8 layers already. My animations (for example walk cycle) has 8 frames. My character fits on a 24 x 48 px image, thus the naked walking character sprite sheet, with all body parts having their own image and keyframe ends up in having 8 layers x 8 images (24x48 px) = 64 frames.

https://i.stack.imgur.com/dSZxE.png

This fills up 40% of a 512 x 512 sprite sheet. This is only for the walking down animation, weapons would be rendered from a separate sprite sheet, underneath the hands. So for the other 3 directions ( okay maybe they fit here), but certainly for other states (dying, hitting, casting spells) I would need separate character sheets.

Now my questions:

Since my character is customizable, it could end up that every layer has to be taken from a different sprite sheet ( naked head, metal armor, poor gloves, icy shoes etc) thus I would need to switch my textures a lot since they can't possibly fit on one sheet. Sometimes I have to use a sheet again later like: render body and so on naked, bind sword sheet, render weapon, use body parts sheet again and render hands on top.

Questions:

  1. In an old book by Mario Zechner I read that sprite sheets with a size of 512 x 512 px are good to go for, since older devices still support this size. Is this still up to date or can I go for bigger Textures (which I then split in TextureRegion Arrays to use for the Animations).

  2. Currently I tested using one sprite sheet (the one above) and took 7 TextureRegion Arrays to store the images of all body parts, then I created 7 Animations, which I render all on top of each other in the corresponding order.

Is it good to do so? Should I rather combine all the textures in a pre rendered TextureRegion (or so) and use this in ONE animation instance?

What are the expensive calls? Are they hidden in the use of the many animation instances? Is it the use of many TextureRegion instances?

As far as I can see I only use the texture ( sprite sheet ) in the beginning anyways to then split it up in my TextureRegion Arrays, which are then used in the render method. So does it even make any difference if I would be using different textures which I'd split up at the beginning? Would this splitting-up and preparing take much time if done during the gameplay? Currently I set it up in the create method. How much / many TextureRegions can be stored in memory simultaniously (since if all sprite sheets fit into memory I could preload them like this during the splash screen). Do I even need to worry about this with my low Pixel Resolution Assets? How to get a feeling for this?

https://i.stack.imgur.com/FaeMH.png

public class MyGdxGame extends ApplicationAdapter {
    SpriteBatch batch;
    Texture img;

    FrameRate frameCounter;

    TextureRegion[] walkDown_Feet;
    TextureRegion[] walkDown_Body;
    TextureRegion[] walkDown_Head;
    TextureRegion[] walkDown_Hand_R;
    TextureRegion[] walkDown_Hand_L;
    TextureRegion[] walkDown_Shoulder_R;
    TextureRegion[] walkDown_Shoulder_L;


    Animation<TextureRegion> walkDown_Feet_Anim;
    Animation<TextureRegion> walkDown_Body_Anim;
    Animation<TextureRegion> walkDown_Head_Anim;
    Animation<TextureRegion> walkDown_Hand_R_Anim;
    Animation<TextureRegion> walkDown_Hand_L_Anim;
    Animation<TextureRegion> walkDown_Shoulder_R_Anim;
    Animation<TextureRegion> walkDown_Shoulder_L_Anim;


    float stateTime = 0f;
    float yPos = 800;
    float xPos = 640;

    @Override
    public void create () {

        frameCounter = new FrameRate();

        Gdx.graphics.setWindowedMode(1280,720);
        Gdx.graphics.setResizable(false);

        batch = new SpriteBatch();
        img = new Texture("Walk_Down_Parts.png");

        TextureRegion[][] walkDown_BodyParts  = new TextureRegion[7][8];    // temp
        for(int y = 0; y < 7; y++) {
            for (int x = 0; x < 8; x++) {
                walkDown_BodyParts[y][x] = new TextureRegion(img, x * 24, y*48, 24, 48);
            }
        }

        System.out.println(Gdx.graphics.getWidth());
        System.out.println(Gdx.graphics.getHeight());

        walkDown_Feet = new TextureRegion[8];
        walkDown_Body = new TextureRegion[8];
        walkDown_Head = new TextureRegion[8];
        walkDown_Hand_R = new TextureRegion[8];
        walkDown_Hand_L = new TextureRegion[8];
        walkDown_Shoulder_R = new TextureRegion[8];
        walkDown_Shoulder_L = new TextureRegion[8];

        loadSheetRegions(walkDown_Feet,walkDown_BodyParts,0);
        loadSheetRegions(walkDown_Body,walkDown_BodyParts,1);
        loadSheetRegions(walkDown_Head,walkDown_BodyParts,2);
        loadSheetRegions(walkDown_Hand_R,walkDown_BodyParts,3);
        loadSheetRegions(walkDown_Hand_L,walkDown_BodyParts,4);
        loadSheetRegions(walkDown_Shoulder_R,walkDown_BodyParts,5);
        loadSheetRegions(walkDown_Shoulder_L,walkDown_BodyParts,6);

        walkDown_Feet_Anim = new Animation<TextureRegion>(0.08f, walkDown_Feet);
        walkDown_Body_Anim = new Animation<TextureRegion>(0.08f, walkDown_Body);
        walkDown_Head_Anim = new Animation<TextureRegion>(0.08f, walkDown_Head);
        walkDown_Hand_R_Anim = new Animation<TextureRegion>(0.08f, walkDown_Hand_R);
        walkDown_Hand_L_Anim = new Animation<TextureRegion>(0.08f, walkDown_Hand_L);
        walkDown_Shoulder_R_Anim = new Animation<TextureRegion>(0.08f, walkDown_Shoulder_R);
        walkDown_Shoulder_L_Anim = new Animation<TextureRegion>(0.08f, walkDown_Shoulder_L);
    }

    private void loadSheetRegions(TextureRegion[] dest, TextureRegion[][]sheet, int y){

        for (int x = 0; x < 8; x++) {
            dest[x] = new TextureRegion(img, x * 24, y*48, 24, 48);
        }
    }

    private void update(){
        stateTime += Gdx.graphics.getDeltaTime(); // Accumulate elapsed animation time
        yPos -= 1.1f;
        frameCounter.update();
    }


    @Override
    public void render () {


        update();
        draw();
    }


    private void draw(){

        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();

        TextureRegion currentFrame;
        currentFrame = walkDown_Feet_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 
        currentFrame = walkDown_Body_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 
        currentFrame = walkDown_Head_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 
        currentFrame = walkDown_Hand_R_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 
        currentFrame = walkDown_Hand_L_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 
        currentFrame = walkDown_Shoulder_R_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 
        currentFrame = walkDown_Shoulder_L_Anim.getKeyFrame(stateTime, true);

        batch.draw(currentFrame, xPos, yPos,48,96); 

        batch.end();

        frameCounter.render();
    }


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


}

Something I read was to combine all the textures using the texture packer.

Texture packing animation images/Sprite sheets efficiently-Libgdx

Is it useful to go for such an approach? Would I need to throw in my sprite sheets, or every single keyframe of every character part as a single image? How to keep order if done so?

The texture packer also changes the color format of the assets, I think this is where the most space is saved. Is this correct?

https://www.badlogicgames.com/wordpress/?p=1444

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
yesyoor
  • 96
  • 8
  • How often do the body parts change? If this not very often (compared to how often you have to render the character), then I would combine the body parts only once and render them into a spritesheet. Then you'd need only one sprite when rendering the character. – BDL Aug 24 '19 at 09:31
  • The body parts only change when the player equips different items (new gear like armor - the breast and shoulder layers would change). yet even if not changed, it would end up loading 8 different sheets for all body parts, if the player uses a combination of completely different gear items, which all have their own sprite sheet with layers for all animations. The question is, how fast would my above approach hit borders? Assume I use my sheets for enemy entities also combined of different gear, like me. Every one of them with 8 sheets, which get also changed when they change directions. – yesyoor Aug 24 '19 at 09:38
  • Using 8 draw calls per character is likely the limiting factor, depending how many characters you end up with on screen. As per the answer from BDL - you really want to merge this into a single sprite sheet per character (you can do this on the fly, and cache it). – solidpixel Aug 24 '19 at 12:21
  • Ok so just for my understanding: the problem is rendering 8 individual texture regions over each other each game loop cycle? You say if they are all cut out from 8 different sheets it will narrow my speed there? So everytime a characters combination changes, I would end up prerendering a combinded spritesheet and use this until the combination changes? i think you mean something like this, but on the fly: https://sanderfrenken.github.io/Universal-LPC-Spritesheet-Character-Generator/ How long does it take to generate such a sheet (ignoring the amount of sheets to search through) – yesyoor Aug 24 '19 at 13:08
  • For changing gear items it will be necessary to open a gui anyways, so I guess a few milliseconds hick up won't be much of a problem there. I guess this (among other factors) is also the reason why in most similar RPGs it is not possible to switch Equipments during a fight. How many spritesheets can be cached? Is it realistic to prerender all possible animation combinations and cache them? How about the Sprite sheet size. Is 512x512 the current standard? – yesyoor Aug 24 '19 at 13:16
  • 1
    2048x2048 is supported by 100% of devices that LibGDX runs on since it dropped support for OpenGL ES 1.0 about five or six years ago. – Tenfour04 Aug 25 '19 at 07:21
  • @Tenfour04 Thanks a lot for the updated information. I thought it would still support both. So I also figured out how to display drawCalls and textureBindings. Any idea which of the two is heavier in terms of speed? What is a good cap to not slow down my game, I mean below which limit of calls/bindings should I try to stay to keep performance good? – yesyoor Aug 25 '19 at 20:31
  • If you want to support low-end phones, maybe 50ish? – Tenfour04 Aug 26 '19 at 02:13
  • Alright now I have one last question. How many sprite sheets of 2048x2048 size can be hold in memory? I guess we talk about GPU memory right? Exceeding this limit will result in an OpenGL flush , correct? Sorry I am still struggling here but you are a great help already! – yesyoor Aug 26 '19 at 09:22

0 Answers0