We can make sprites that play a sequence of animated or rendered images as an animation. In fact most sprites will be of this type in a game. A running or walking character, a tank, etc… are all examples of animated sprites.
Simple Example
Lets make a simple sprite in the middle of the screen that plays a simple animation.
I’ve made a simple basic sprite sheet for this test. Each image is a number from 0 to 9. Easy!
It looks something like this:
To create an animated sprite:
- First we fill a Vector container with the animation frames as SpriteFrame elements. The Vector is similar to the standard C++ STL vector but more optimized for games by Cocos2d-x team.
Vector<SpriteFrame*> frames; frames.pushBack(spriteCache->getSpriteFrameByName("number0.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number1.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number2.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number3.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number4.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number5.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number6.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number7.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number8.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number9.png"));
- Then we create an Animation instance from those frames. We also set some parameters for our Animation instance and have it loop forever. I chose a delay of 0.1 seconds between switching from one frame to another.
auto animation = Animation::createWithSpriteFrames(frames); animation->setDelayPerUnit(0.01); animation->setLoops(-1);
- We use the Animate class to actually make the Animation play on a Node object which in this case will be a Sprite instance. Animate is an action class that I will have a separate post for it later.
auto action = Animate::create(animation);
- Finally we tell our sprite to play this Animate action by calling its runAction() method.
sprite->runAction(action);
Now lets apply all this in Xcode and make it run:
- Add the sprite sheet to the project (numbers.plist and numbers.png)
- Update the init() method in the HelloWorldScene.cpp with this:
// on "init" you need to initialize your instance bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Scene::init() ) { return false; } auto visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); float x = origin.x + visibleSize.width / 2; float y = origin.y + visibleSize.height / 2; auto spriteCache = SpriteFrameCache::getInstance(); spriteCache->addSpriteFramesWithFile("numbers.plist"); Vector<SpriteFrame*> frames; frames.pushBack(spriteCache->getSpriteFrameByName("number0.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number1.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number2.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number3.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number4.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number5.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number6.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number7.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number8.png")); frames.pushBack(spriteCache->getSpriteFrameByName("number9.png")); auto animation = Animation::createWithSpriteFrames(frames); animation->setDelayPerUnit(0.2); animation->setLoops(-1); auto action = Animate::create(animation); auto sprite = Sprite::createWithSpriteFrame(frames.front()); // or just: // auto sprite = Sprite::create(); sprite->setPosition(x, y); sprite->runAction(action); this->addChild(sprite, 0, 1); //this->scheduleUpdate(); return true; }
You can compile and run this and see your sprite showing the numbers one after another.
Helper Function To Get The Animation Frames
It was OK to write the name of each frame separately for a test, but it is not fun to do it every time for every animation in a game!
We can make a helper function to facilitate this for us. Add the deceleration in the HelloWorldScene.h header file:
cocos2d::Vector<cocos2d::SpriteFrame *> getAnimationFrames(const char *format, int count);
The definition is as below. Note that this method expects the first image file start from 0 instead of 1 similar to arrays in C.
Vector<SpriteFrame *> HelloWorld::getAnimationFrames(const char *format, int count) { auto spriteCache = SpriteFrameCache::getInstance(); Vector<SpriteFrame *> frames; char str[100]; for(int i = 1; i <= count; i++) { sprintf(str, format, i); auto frame = spriteCache->getSpriteFrameByName(str); frames.pushBack(frame); } return frames; }
Now the updated init() method will be shorter and cleaner.
bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Scene::init() ) { return false; } auto visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); float x = origin.x + visibleSize.width / 2; float y = origin.y + visibleSize.height / 2; auto spriteCache = SpriteFrameCache::getInstance(); spriteCache->addSpriteFramesWithFile("numbers.plist"); auto frames = getAnimationFrames("number%d.png", 10); auto animation = Animation::createWithSpriteFrames(frames); animation->setDelayPerUnit(0.2); animation->setLoops(-1); auto action = Animate::create(animation); auto sprite = Sprite::createWithSpriteFrame(frames.front()); sprite->setPosition(x, y); sprite->runAction(action); this->addChild(sprite, 0, 1); return true; }
We can have several ready animations that are made in this method and have our character sprites play different ones depending on which direction they go to or which behavior they need to show.
To do this, you should first stop the previous running action by calling stopAction() and then call runAction() again for a new animation.