Mapsheet - A Tilemap based spritesheet animation library


#1

Hello everyone, my name is Milton and I am a videogame developer.

Most of the games I develop must run on HTML5 and are made on the barebones of OpenFL, so for implementing animations I used to rely on the Spritesheet library (by skylarkstudio)

However, while working on a bigger project I ran into OpenFL’s Tilemap and its performance got me charmed.

This is why I wrote a Tilemap implementation of Spritesheet.

Meet Mapsheet, where animation and Tilemaps meet.

You can see the source code and examples on how to use it on github or you can just try it our by running:

haxelib install mapsheet


I owe you a bunnytest. Will come back when I have one <3

Thank you for your time, and have a nice day :slight_smile:


Best way of optimization animation
Best way of optimization animation
#2

Hello!

Just some thoughts on this:

// name, frames, looping?, frameRate
mapsheet.addBehavior( new Behavior("idle", [3, 4, 5], false, 15) );

Is the framerate refreshrate independent? And why not just use a float for time instead which will be framerate/refreshrate independent per default? For example

// name, frames, looping?, runTime
mapsheet.addBehavior( new Behavior("idle", [3, 4, 5], false, 0.25  /* (15/60) */ ) );

This way the user of your library will not have to bothersome of:doing this kind of stuff in order to be FPS independent:

var fps = new openfl.display.FPS();
var frames:Int = Std.int(15 * (fps.currentFPS/60));
// name, frames, looping?, frameRate
mapsheet.addBehavior( new Behavior("idle", [3, 4, 5], false, frames) );

Again, these are just some thought!

Cheers!


#3

Not sure I am understanding your point here.

The “frameRate” value is like saying “Playback Speed”. If your animation is designed to run at 15 frames per second and you run it at 60 fps it will look accelerated (fast forwarded).

By telling the behavior that your animation should run at 15 fps the library will determinate how long each frame must be on screen so that particular animation changes frame 15 times in a second. (Spoiler alert: each frame lasts for 1/frameRate seconds)

The library doesn’t care how many frames you are rocking on the global. It takes the time that has passed since the last frame rendered (the deltaTime parameter on the update method) and if more than 1/frameRate seconds have passed, it changes to the next frame. (If more than 2/frameRate seconds have passed it skips a frame and so on.)


Why is it made like this?

This is an adaptation of Spritesheet and the methods were copied but more importantly, the artists in our team would give us (the programmers) the spritesheet and say “This is the character animation. It runs at 24 fps.”


#4

We do this in MovieClip animation in OpenFL (by default) for the same reason :wink:

@miltoncandelero We can talk about taking over the “spritesheet” haxelib, too, if you are interested :slight_smile:


#5

Only if I can find a way to fix the offsetting problem that tilemap brings. (I think that is the only thing that broke on the port).

If I offset a tile into the negatives, it falls outside of the tilemap and it doesn’t render.


#6

Tilemap behaves similar to Bitmap in this regard, locations outside of new Rectangle (0, 0, tilemap.width, tilemap.height) are not visible (cropped on GL, not in the software surface on Cairo, canvas or Flash). Perhaps there’s another way to work around it?

Also, I’m curious if you use a different Tilemap per object, or one Tilemap for all objects?


#7

Perhaps there’s another way to work around it?

I was thinking maybe to save all the offsets and then take the most negative as zero and offset everything else accordingly.

Then with custom setters and getters for x and y positions, make it feel like it is actually offset.

Also, I’m curious if you use a different Tilemap per object, or one Tilemap for all objects?

This implementation uses one Tilemap per object.

I have made another class (not included here) that is an “animated tile”. It inherits tile, takes behaviors (name, array of frames, looping and framerate) and has an update and showBehavior method.

Performance-wise is amazing but if you want to use any other kind of display object and place on top of one but under another animation, you can’t. (as the tilemap is only one and you cant squeeze anything that is not part of said map)


#8

I had a misunderstanding of the word: frameRate. My mind was fixed on the usual N frames per second usually synced to the refreshrate of the hardware… And Ive seen libraries in my life that where fixed to that… So my bad.!

Just for my information you basically doing this,. right?

number_of_frames_in_the_array / frameRate = total_animation_time; // 3 / 15 = 0.2s;

Which will work nice, I was suggestion this:

total_animation_time / number_of_frames_in_the_array = display_time_per_frame; 0.2s / 3 = 0.0666;

This has the benifit of the ability to work with time variables directly. I have 3 frames and want to run them for 0.2 seconds!.

But sorry for the presumptions!

Cheerz


#9

Yep.

Line 187 from Animation.hx

loopTime = Std.int ((behavior.frames.length / behavior.frameRate) * 1000);

(the extra “*1000” is the conversion to milliseconds)


#10

If you use different Tilemap instances per object, you could wrap it in a Sprite, then set the Tilemap.x/y internally to get your positions that way, I think that’s similar to what the original spritesheet library does with Bitmap

I think it makes sense to be have both options available… if you can merge into one Tilemap, great! If you cannot, then one Tilemap per object is simplest to manage, it just is not as optimized on the render side. “Easy” and “fast” are great to both have in your toolbelt :wink:


#11

On the performance side of things…

If I use 16 square textures of 512px each on 16 separate tilemaps

or I use 1 square texture of 2048px on only 1 tilemap.

Is a single tilemap still more efficient?

Is it always that way? or we can draw a line on where one thing gets worse than the other?

(I understand that it does some kind of batch work with graphics memory but it makes me unease the size of the textures if I merge everything into a single file. Is it safe to do this?)


#12

It depends: glBindTexture is said to be expensive… So for say a world tilemap which will instantiate more then one frame, it will be better performance wise to put as much of the map in one texture so that when the world renders on the gpu, glBindTexture is called less and with that optimizing performance.

As for animations… I don’t know. If you are just instantiating one frame of the tilemap. It should not matter that much depending on how everything is optimized.

I guess it wil depend on if OpenFL packs as much as possible on one texture (atlas mapping) or if all frames/bitmaps/sprites are single textured? I would like to know that for myself.!?


#13

Loading one 2048 texture, and then making draw calls using the one image, will be more effective than using different textures, due to costs of switching textures, but if you need to switch textures anyway, switching between smaller textures will be less expensive than switching to and from one huge one instead.

One Tileset, and one Tilemap (in general) represents one draw call (unless you use tile.shader).

One Tileset and 16 Tilemap instances will represent 16 draw calls, but still use the same texture.

You can think of it as the same as the cost of 16 Bitmap objects, verses 1 “super” Bitmap… 16 doesn’t break the bank, but 16,000 might. Less is better, but no need to be too extreme


#14

@singmajesty Does OpenFL support texture sharing? Are the normal renderables like bitmaps packed as mush as posiible on the same texutre (atlas mapping) or is every renderable on its own texture. You reply suggest the latter, but stil would like to know for sure.

As users we can of course wright a own texture packer that puts everything in a tilemap, but it would be great if OpenFl had it own implementation for this.

Resonantly wrote a texture atlas packer, but its in C++. If I every port it or write something like it, I will share it.

Edit: Lot’s of typo’s!


#15

Bitmap uses a different texture, right now, but there was a plan to support multiple lime.graphics.Image instances over one lime.graphics.ImageBuffer, if we add that support, then we could (in principle) support multiple BitmapData instances from one texture. Currently, though, Tileset would be the way to support multiple draw calls from one texture, without getting into custom GL rendering code


#16

Thanks for the info!


Spritesheet Upgraded with Tilemaps