I have a spritesheet for a character. The spritesheet has several rows, and each row represents an animation. The elements within a row might vary in width.
How can I load the different sections of the spritesheet and set animations for each game element?
My first approach to this problem was to manually calculate the rectangle for each frame and hardcoding them into the respective object classes. The Hero
class, for example, would have several arrays of rectangles with the hardcoded bounds of every frame of animation. This method is really primitive, however, so I am looking for a solution that takes this to the next level.
-
I am at the moment using an interesting approach which although in the end is very easy to use, it sounds a little complex, but i would like an opinion about it.
I have an Animation class which has a function setFrames(Rectangle.Float[] frames); Every game element has an animation, even if it only has a sigle sprite its whole lifetime. This class takes care of which frame is time to show at a certain time of the game. The way to use it is simply set the frames everytime a new animation is supposed to play, and receive the rectangle in the draw (and update is, ofc)
Example: While Idle, I press right button, this makes me walk right setFrames method is called with the "walking" rectangle array passed in its argument, thus changing which frames are looping.
Now, the way i abstracted from the rectangles was I started packing my sprites with a program called TexturePacker which places the sprites in a spritesheet by itself and creates an XML file with each sheet element's name and position, as simple as this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with TexturePacker http://www.codeandweb.com/texturepacker-->
<!-- $TexturePacker:SmartUpdate:d4e1eb1edb1e925f5e2b7fea77dae064:e103ddd8b5f54e4f084f4075e778872a:e8412e2a02129a8b5ad20319afc153de$ -->
<!--Format:
n => name of the sprite
x => sprite x pos in texture
y => sprite y pos in texture
w => sprite width (may be trimmed)
h => sprite height (may be trimmed)
oX => sprite's x-corner offset (only available if trimmed)
oY => sprite's y-corner offset (only available if trimmed)
oW => sprite's original width (only available if trimmed)
oH => sprite's original height (only available if trimmed)
r => 'y' only set if sprite is rotated
-->
<TextureAtlas imagePath="blabla.png" width="128" height="128">
<sprite n="four" x="2" y="2" w="100" h="32"/>
<sprite n="one" x="2" y="36" w="32" h="32"/>
<sprite n="three" x="36" y="36" w="53" h="32"/>
<sprite n="two" x="91" y="36" w="12" h="32"/>
</TextureAtlas>
Then, I created a SheetLoader class, which is essentialy a parser of the XML file and receives the XML path in its argument and fills this:
private HashMap<String, Rectangle.Float> textures;
with the elements names as keys and the elements rectangles. This class also has a method called getSprite(String); which returns a rectangle of a sprite based on its name (its a simple call from the hashmap)
I then created this weird Enum class:
package render;
import java.awt.Rectangle;
public enum Anims {
HEROIDLE("Resources/SpriteSheets/blabla.xml", "one", "two", "three", "four", "three");
Rectangle.Float[] frames;
Anims(String path, String... animation)
{
frames = new Rectangle.Float[animation.length];
SheetLoader sL = new SheetLoader(path);
for(int i=0; i<animation.length; i++)
{
frames[i] = sL.getSprite(animation[i]);
}
}
public Rectangle.Float[] getFrames() {return frames;}
}
So basically the way to use this is, to create a new animation I add a new enum with the info from the XML path (where i get the rectangles from), and the sequence of the sprites that form the animation, and if this info exists, the enum is successfuly created. Using this is very easy later, for example if i wanna change the hero animation to idle id just do:
animation.setFrames(Anims.HEROIDLE.getFrames());
The thing i like about this method is, i can forget about worrying about the rectangles. I just make the spritesheet, I send the XML file to the game and i just use the sprite names directly in the enum declaration.
Still this way might not be the best in therms of simplicity or optimization, so if that is the case id like to know different solutions or how it is usually done.