くまのがっこう神経衰弱バトルのオープニングとエンディングのアニメーションの作り方をご紹介します。
はじめはベタにアニメーションの処理をプログラムに書いていたのですがコードが長くなりちょっとした変更も難しくなってしまいました。そこでシナリオをplistにして外に出し、それを読み込んでタイマーでアニメーション処理をする仕組みを作りました。
HelloWorldScene.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" USING_NS_CC; class HelloWorld : public cocos2d::Layer { private: // スタートボタン Menu* _start; // アニメーション ValueVector _Animation; int _size; int _step; double _time; double _startTime; Layer* _layerAnimation; void animationStart(Ref* sender); void animationStop(); // アニメーションに使う画像のタグ std::map<std::string, int> animationTag; // アニメーションを実行する関数 void animation(int step, ValueMap frame); // タイマー処理 void timerAction(float fDelta); // unix時間を取得 double unixtime(); public: static cocos2d::Scene* createScene(); virtual bool init(); CREATE_FUNC(HelloWorld); }; #endif // __HELLOWORLD_SCENE_H__ |
HelloWorldScene.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
#include "HelloWorldScene.h" // アニメーション用の画像のタグの初期値 #define TAG_ANIMATION 100 Scene* HelloWorld::createScene() { auto scene = Scene::create(); auto layer = HelloWorld::create(); scene->addChild(layer); return scene; } bool HelloWorld::init() { if ( !Layer::init() ) { return false; } // アニメーションを初期化 _size = 0; _step = 0; _time = 0; _Animation = FileUtils::getInstance()->getValueVectorFromFile("animation.plist"); // アニメーションの素材を載せるレイヤーを生成 _layerAnimation = Layer::create(); this->addChild(_layerAnimation); // スタートボタン表示 auto start = MenuItemFont::create("START", CC_CALLBACK_1(HelloWorld::animationStart, this)); _start = cocos2d::Menu::create(start, NULL); _start->setPosition(Point(320, 120)); _start->alignItemsHorizontally(); this->addChild(_start); // タイマー処理登録 this->schedule(schedule_selector(HelloWorld::timerAction)); return true; } // アニメーション開始 void HelloWorld::animationStart(Ref* sender) { // ボタンを非表示 _start->setVisible(false); // アニメーションの画像を初期化 animationTag.clear(); _layerAnimation->removeAllChildren(); // アニメーションをスタート _startTime = unixtime(); _size = (int)_Animation.size(); _step = 0; _time = _Animation.at(_step).asValueMap().at("time").asDouble(); } // タイマー処理 void HelloWorld::timerAction(float fDelta) { // 現在の時間を取得 double now = unixtime(); // 最後のステップではない場合はアニメーションを実行 if (_step < _size) { // フレームの実行時間を過ぎていたらフレームの内容を実行 if (_time <= now - _startTime) { // フレームを取得してアニメーションを実行 auto frame = _Animation.at(_step).asValueMap(); animation(_step, frame); // ステップを進める _step++; // 次のフレームを実行する時間を取得 if (_step < _size) { _time = _Animation.at(_step).asValueMap().at("time").asDouble(); } } } else { // スタートボタンを表示 _start->setVisible(true); } } // アニメーションを実行する関数 void HelloWorld::animation(int step, ValueMap frame) { // アニメーションのフレーム情報から画像名を取得 auto imageName = frame.at("image").asString(); // 画像の処理 Sprite* image; if(frame.at("mode").asString().compare("ACTION") == 0) { // アニメーションフレームのモードが[ACTION]の場合は既に表示されている画像のタグから画像を取得 image = (Sprite*)_layerAnimation->getChildByTag(animationTag[imageName]); } else if(frame.at("mode").asString().compare("ADD") == 0) { if(imageName.compare("") != 0) { // 画像名がある場合は画像を表示 image = Sprite::create(StringUtils::format("%s", imageName.c_str())); // 画像を指定の座標へ設置 image->setPosition(Point(frame.at("x").asInt(), frame.at("y").asInt())); // 画像を指定の透明度へ設定 image->setOpacity(frame.at("opacity").asFloat()); // 画像を指定の縮尺へ設定 image->setScale(frame.at("scale").asFloat()); // 画像を指定の角度へ設定 image->setRotation(frame.at("rotation").asFloat()); // 画像のタグを設定 image->setTag(TAG_ANIMATION + step); // 画像タグリストの画像名に対するタグを記録 animationTag[imageName] = TAG_ANIMATION + step; // 画像を表示 _layerAnimation->addChild(image); } } // 画像にアクションをつける auto action = frame.at("action").asValueMap(); // フレーム情報の[action]にデータがある場合はその内容を実行する if(0 < action.size()) { if(action.at("type").asString().compare("MOVE") == 0) { image->runAction(MoveTo::create(action.at("duration").asFloat(), Point(action.at("param_1").asInt(), action.at("param_2").asInt()))); } else if(action.at("type").asString().compare("FADE") == 0) { image->runAction(FadeTo::create(action.at("duration").asFloat(), action.at("param_1").asInt())); } else if(action.at("type").asString().compare("ROTATE") == 0) { image->runAction(RotateTo::create(action.at("duration").asFloat(), action.at("param_1").asFloat())); } else if(action.at("type").asString().compare("SCALE") == 0) { image->runAction(ScaleTo::create(action.at("duration").asFloat(), action.at("param_1").asFloat())); } } } // unix時間を取得 double HelloWorld::unixtime() { struct timeval t; gettimeofday(&t, NULL); return (double)t.tv_sec + (double)t.tv_usec / 1000000; } |
このプログラムにアニメーションのシナリオのanimation.plistを読み込ませるとアニメーションを表示します。
http://gameshonen.com/blog/uploads/animation.plist
このplistはエクセルで作ったシナリオを一旦csvへ保存してphpを使ってplistへ変換しています。
http://gameshonen.com/blog/uploads/animation.xlsx
これはplistを生成する為のエクセルデータです。
- time: アニメーションの処理を実行する時間(秒)intervalから自動計算
- interval: スレームの表示時間(秒)
- image: 表示する画像ファイル名
- mode: 処理の内容
- position: 画像を表示する座標(x y)
- scale: 画像を表示する倍率
- rotation: 画像を表示する角度
- opacity: 画像の透明度(0〜1.0)
- action: 表示した画像にかけるエフェクト
- parameter: actionのパラメーター(SCALE:倍率、MOVE:座標、FADE:透明度)
- duration: actionの実行時間
このエクセルデータをcsvで保存して下記のphpでplistへ変換してアプリへ組み込みました。(ブログで表示するために拡張子を無効にしています)
http://gameshonen.com/blog/uploads/generate_plist_php
$ php generate_plist_php > animation.plist
自分が使えれば良いというレベルで作っているのでオレオレ仕様ですが、くまのがっこう神経衰弱バトルを作るときにはこの方法で正確かつ効率的にアニメーションを作る事ができましたのでご参考になればと思い公開しました。
実際にゲームに組み込んでいる処理は座標の指示や途中でイベントを差し込んだりともう少し複雑ですが、見づらくなるので根幹の部分を抽出してあります。
これからもアニメーションがたくさん入った楽しいゲームを作りたいと思います。