Animated multiframe sprite example

Hi,

Is there an example of how to do a animated sprite, ie multi frame, preferably from a spritesheet.
The only examples I can find on the web are for cocos2d-iOS not HTML5.

I’m half tempted to just write a class myself, but I guess there must be a correct way to do this.

I need to replicate a multi-frame sprite in Flash, with play and stop and gotoFrame methods.

Thanks

Roger

It was a bit of a trial and error process, I just wrote this last night basically. But it works :slight_smile:

FYI I assumed you already had a spritesheet loaded.

Enjoy!

function pad(a, b){
return (1e15 + a + “”).slice(-b);
}

this.animationDone = function(sender) {

this.removeChild(sender, true);
}

this.playAnimation = function(name, position, frames, speed) {

sprite = cc.Sprite.createWithSpriteFrameName(cc.SpriteFrameCache.getInstance().getSpriteFrame(name + pad(i,2) + “.png”));
sprite.position = position;

wake = new cc.Animation.create();
for (var i=0; i < frames; i+)
{
// filename frames should be informat name##.png
wake.addSpriteFrame.getSpriteFrame
“.png”));
}
wake.setDelayPerUnit(speed/frames);

var action = cc.Animate.create(wake);
sprite.runAction(cc.Sequence.create(action, cc.CallFunc.create(this.animationDone)));

cc.log(“playing animation”);
}

playAnimation(‘filename’,cc.p(200,200),16,0.5);

Thanks Farmer Joe,

I had to hack your code a bit to get it to work for me, but I now have something that works.

I’m using the alternative syntax e.g. playAnmimation:function(name, position, frames, speed) etc etc so I changed your code to that syntax, and I also needed to reference the “pad” function by this.pad.
The other thing that didn’t initially work for me was the on completion callback, as the “this” var seemed to be out of scope inside the completion handler, so I ended up copying this to var localThis = this; and then used an inline function in the callback so that I could use localThis.

Anyway. I will try to build this into a separate class i.e AnimatedSprite and post my code when its complete - however at the moment I have a deadline to get something working, so I will cheat and not move the code into a separate class until later.

Thanks again

Roger

This was cobbled together from my own code base, altered a bit, so apparently there were a few oversights! Glad you got it working! :slight_smile:

For the completion handler, sorry about that, I actually set a property of the custom sprite to “markForDestroy” since I’m using physics based sprites, so I need to remove it along with the body after it’s been marked. But, it sounds like the solution you came up with is a bit convoluted. You should be able to use the following in the completion handler and side-step the need to for the localThis:

@
sender.removeFromParentAndCleanup();
@

No worries.

You code was a great start.

Currently I’ve ended up splitting your code up a bit, so that there is code which setups the animation (sequence) and the stores it in a member var _animationSequence and a separate function / method to play the sequence - because I need to replace the animation when the user clicks / taps on the screen.

I will eventually get around to putting it into a class, but I’m still working out the best way to do it. i.e I need to inherit from cc.sprite (or perhaps encapsulate cc.sprite), but I don’t want to take copies of the static cc.sprite.create…… functions in my own class as it would be a support issue if the cc.sprite code changed.

Anyway, it least I have something that works for the specific project I’m working on :wink:

Cheers

Roger

This is probably not the perfect solution, but here’s the code I wrote to handle my animated sprites.

The code assumes that you have already cached the spritesheet e.g.

var cache = cc.SpriteFrameCache.getInstance();
cache.addSpriteFrames("res/mc_1.plist", "res/mc_1.png");

The code also presumes that the names in the sprite sheet have a “stub” name and then a 4 digit number with leading zeros. (this is the format exported by Flash)

————— Code for Animated Sprite class —————-

var AnimatedSprite = cc.Node.extend(
{
_sprite:null,
_animationSequence:null,
    ctor:function(spriteNameStub,numFrames, speed)
    {
        var localThis = this;
        this._sprite = cc.Sprite.createWithSpriteFrameName(spriteNameStub + this.pad(0,4));
        this.addChild(this._sprite);

        var animation = new cc.Animation.create();
        for (i=1; i < numFrames;  i++)
        {
            animation.addSpriteFrame(cc.SpriteFrameCache.getInstance().getSpriteFrame(spriteNameStub + this.pad(i,4)));
        }
        animation.setDelayPerUnit(speed/numFrames);

        var action = cc.Animate.create(animation);
        this._animationSequence =cc.Sequence.create(action, cc.CallFunc.create(localThis.animationDone));
    },
    pad:function(a, b)
    {
        return (1e15 + a + "").slice(-b);
    },
    animationDone:function(sender)
    {
        cc.log("animation complete " + sender );
    },
    playAnimation:function()
    {
        this._sprite.runAction(this._animationSequence);
    }
});

Very nice, this could probably be simplified by just extending Sprite instead of Node. I’ll modify it when I get a chance, but good stuff.

Hi Farmer Joe,

I did look at just extending cc.Sprite, but I thought there may be issues with the static “convenience constructors” i.e cc.Sprite.createWithSpriteFrameName as I thought I’d end up having to duplicate the code for that static function into my extended class, because createWithSpriteFrameName calls cc.Sprite.create and I’d need to get it to call the create in my class otherwise its going to return an instance to Sprite instead of AnimatedSprite.

Hence I decided to do it via encapsulation rather than extension; so I used cc.Node as the base class rather than cc.Sprite because I didn’t need the extra baggage that would be in cc.Sprite, as my class contains a cc.Sprite.

I’m sure there are other ways to do it, including extending cc.Sprite, but I’m not a JavaScript guru so I used a method I’d know would work :wink:

Cheers

Roger

@Roger Clark

I have problem using your code , for example I have used the template came up with cocos2d-html5 and in the myApp.js ,init function , I have
written as

var cache = cc.SpriteFrameCache.getInstance();
cache.addSpriteFrames(“res/Bear/AnimBear.plist”, “res/Bear/AnimBear.png”);

var anim = new AnimatedSprite(“bear”,8,0.3);

what is actually spriteNameStub in the AnimatedSprite class ?

And why we are using pad function ?

I have given you a sample animated .plist file and .png file which I am trying to animate , But have problem like when I am reading this .plst file I have found bear1.png,bear2.png…. I think whether to put “bear” as an argument for AnimatedSprite() .

if you just have bear1.png, bear2.png, etc you don’t need to pad with leading zeros
So stubName is bear, but you need to change the code so that it doesnt add leaidng zeros, ie remove the calls to pad and just use 0, and i as appropriate

@Roger Clark

I need an example usage to work with AnimatedSprite ,
like
var animation = new AnimatedSprite(“fist_thump_1*",6,0.2);
animation.playAnimation;
But the above thing doesn’t work.
fist_thump_1_1.png , fist_thump_1_2.png ,… ,fist_thump_1_6.png are my png’s.
I have removed the usage of the pad function and made it as
1.var AnimatedSprite = cc.Node.extend
6. {
7. var localThis = this;
8. this.*sprite = cc.Sprite.createWithSpriteFrameName(spriteNameStub + 1 “.png”);
9. this.addChild;
10. var animation = new cc.Animation.create;
11. for
12. {
13. animation.addSpriteFrame.getSpriteFrame
”.png"));
14. }
15. animation.setDelayPerUnit(speed/numFrames);
16. var action = cc.Animate.create(animation);
17. this.*animationSequence =cc.Sequence.create);
18. },
19. animationDone:function
20. {
21. cc.log;
22. },
23. playAnimation:function
24. {
25. this.*sprite.runAction(this._animationSequence);
26. }
27. });

At line 9 , I think this.addChild() is being added to this layer and nothing gets visible to my window.
I see a blank screen with no errors.

For example I have uploaded a sample .plist file which I have been working with.Please look at this .

I know this topic is a bit old, but this may interest some people.

Here’s my own AnimatedSprite class. It uses a sprite sheet rather than separate images, and extends cc.Sprite.

"use strict";
var AnimatedSprite = cc.Sprite.extend({
    animationSequence: null,
    ctor: function(spriteFilePath, frameRects, speed) {
        this._super();

        var texture = cc.TextureCache.getInstance().addImage(spriteFilePath);
        var frames = [];

        frameRects.forEach(function(frameRect) {
            var frame = cc.SpriteFrame.createWithTexture(texture, frameRect);
            frames.push(frame);
        });

        this.initWithSpriteFrame(frames[0]);

        var anim = cc.Animation.create(frames, speed);
        var action = cc.Animate.create(anim);

        this.animationSequence = cc.Sequence.create(action, cc.CallFunc.create());
    },
    playAnimation: function() {
        this.runAction(cc.RepeatForever.create(this.animationSequence));
    }
});

Usage example:

"use strict";
var Player = cc.Node.extend({
    sprite: null,
    ctor: function() {
        this._super();

        this.setContentSize(new cc.Size(32, 32));
        this.setAnchorPoint(new cc.Point(0, -0.5));

        var frameRects = [];
        frameRects.push(cc.rect(0, 0, 32, 64));
        frameRects.push(cc.rect(32, 0, 32, 64));
        this.sprite = new AnimatedSprite("res/spritesheet.png", frameRects, 1);
        this.addChild(this.sprite);
        this.sprite.playAnimation();
    }
});

Am I getting this all wrong or you’ve just re-implemented AnimationFrame and similar classes?
http://www.cocos2d-x.org/reference/html5-js/symbols/cc.AnimationFrame.html

I’m just getting started, so I wouldn’t be surprised if I’ve overlooked some things.

AnimationFrame sounds interesting, but it seems like it’s only just a single frame, not a whole animated sprite, no? So you couldn’t replace an AnimatedSprite with an AnimationFrame, as they don’t serve the same purpose.
That said, within my AnimatedSprite class, maybe I could use AnimationFrames, and replace the “speed” parameter with a “frameDelays” array. That way, each frame could have a specific delay.

Thomas Goldstein wrote:

I’m just getting started, so I wouldn’t be surprised if I’ve overlooked some things.
>
AnimationFrame sounds interesting, but it seems like it’s only just a single frame, not a whole animated sprite, no? So you couldn’t replace an AnimatedSprite with an AnimationFrame, as they don’t serve the same purpose.
That said, within my AnimatedSprite class, maybe I could use AnimationFrames, and replace the “speed” parameter with a “frameDelays” array. That way, each frame could have a specific delay.

I probably quoted the wrong class, check out the whole documentation at:
http://www.cocos2d-x.org/reference/html5-js/index.html

Or specifically the cc.Animation class:
http://www.cocos2d-x.org/reference/html5-js/symbols/cc.Animation.html

There’s a whole family of classes dedicated to changing sprites for making animations happen it seems.

I’m just starting myself, that’s why I’m not so sure if I’m missing the point of this thread, but it sounds to me that everything you are saying could be done with the classes provided by cocos2d, after all, it’s just animating a sequence of sprites, right?

Well, we certainly know about the cc.Animation class, as it’s used in each of the implementations above. I could be wrong, but I don’t think we’re reinventing the wheel here. But if we are, I’ll be happy to throw my code away.

Thomas Goldstein wrote:

Well, we certainly know about the cc.Animation class, as it’s used in each of the implementations above. I could be wrong, but I don’t think we’re reinventing the wheel here. But if we are, I’ll be happy to throw my code away.

Dah! I’m such a twat, sorry! I hadn’t properly read the code that was posted, I indeed misunderstood the topic, I’m truly ashamed…

That said, there might just be a a better way to do this, but I’ll have to come back to this thread after I’ve experimented enough.

Until then, I go back to the shadows…

I’m sure there are many different ways to do this. I’m still working on my code, and now modifying the AnimatedSprite class so I can have different animations depending on the context (character standing, walking up, right, etc), using an array of animationSequences rather than just one, and passing a parameter to the playAnimation function to specify the wanted animation. It’s not easy to find code examples or tutorials for things like this, so I’m kinda guessing the way forward.

The only “strange” thing that I’ve encountered so far is the reccomendation of using SpriteBatchNode when one’s working with WebGL:
http://www.cocos2d-x.org/reference/html5-js/symbols/cc.SpriteBatchNode.html

Hi,
I’m using Cocos Creator 1.03. I tried to import spritesheet that I made with Adobe Flash and export in Cocos2d format. But I didn’t succeed. There is two files one is a .plist and one .png. When I import .plist file I can see images individually under plist object at assets window. Then I tried to create an animation clip node. I imported these three images seperately and create a keyframe for each in animation clip in Animation Editor. I can give play command to animation clip node on scene. But I can’t go to a specific frame and play it. It can only play three frames from the beginnig everytime I click on my button. Here’s my code for canvas:
cc.Class({
extends: cc.Component,

properties: {
    purpleMonster:{
        default: null,
        type: cc.Node
    },
    ground:{
        default: null,
        type: cc.Node
    },
    star:{
        default: null,
        type: cc.Node
    },
    switch:{
        default: null,
        type: cc.Node
    }
},

setMouseAction: function(){
    var _star = this.star;
    var swch = this.switch;
    var switchAnimation = swch.getComponent(cc.Animation);
    var addFunction = function(){};
    if(cc.sys.capabilities.hasOwnProperty("mouse")){
        cc.eventManager.addListener({
            event: cc.EventListener.MOUSE,
            onMouseDown: function(evt){
            var target = evt.getCurrentTarget();
            var locationInNode = target.convertToNodeSpace(evt.getLocation());
            var size = target.getContentSize();
            var rect = cc.rect(0, 0, size.width, size.height);
            if(evt.getButton() == cc.Event.EventMouse.BUTTON_LEFT && cc.rectContainsPoint(rect, locationInNode)){
                if(!stopStage){
                    switchAnimation.play("switchShieldedTwoStates");
                }
            }
          }
        }, _star);
    }
},
// use this for initialization
onLoad: function () {
    this.setMouseAction();
}

});