cocos2d-xのアクションを仕組み
Easingよく利用しますよね?
ただし、Easingを含んだ連続したアニメーションをしたい場合って
アクションとアクションのつなぎ目がスムーズにいかないですよね?
僕はそんな状況に直面しました。
具体的には以下のようなコードで、ユーザ画像をY軸に10移動させた後、背景画像を-10移動させようとしています。
Sequence* sequence = Sequence::create(MoveBy::create(1, Point(0, 10)), CallFunc::create([&](){ bg->runAction(EaseOut::create(MoveBy::create(1, Point(0, -10)), 3)); }), ,NULL); user->runAction(sequence);
背景が動く事で、ユーザ画像はY軸に10しか動いていないのにY軸に20移動したように見えますよね。
しかしこれ動かしてみるとわかるのですが困る事が一点。
背景画像をEaseOutさせているため、ユーザの終速と背景画像の初速が異なってしまい、アクションの切り替えがスムージに見えません。
背景画像の初速を計算して、それをユーザ画像の動作の終速に設定しようかとも考えたんですが、背景画像の初速の初速がわからない!
というのも、EaseOut::createの第2引数がEasingの変化の割合を示しているのはわかるのですが、
その値がどのように実際のEasingに変化を与えるのかがわからなかったのです。
しょうがないので、ソースを読んで少しだけ理解してみました。
大体確認したソースは以下。
ActionInterval
ActionManager
EaseOut
特定のNodeにアクションを実行させるメソッドにrunActionがあります。
このrunAction実際に呼び出されると、第一引数で渡されたActionインスタンスは、ActionManagerのインスタンスにrunActionを実行したインスタンスとともに追加、保持されます。
ここで登場したActionManagerはすべてのアクションを管理する、シングルトンクラスです。
ActionManagerクラスにはupdateメソッドが実装されており、毎フレームupdateメソッドが呼び出されます。
updateメソッド内では、ActionManagerに追加されたすべてのActionを走査し、Actionが実行停止状態でなければ、Actionのstep関数をフレーム間の秒数を引数にして呼び出します。
ActionIntervalで定義されたstep関数は、これまでのアニメーション時間をアニメーション全体の時間で割ったアニメーションの進行割合を引数にしてupdateメソッドを呼び出します。
void ActionInterval::step(float dt) { if (_firstTick) { _firstTick = false; _elapsed = 0; } else { _elapsed += dt; } this->update(MAX (0, // needed for rewind. elapsed could be negative MIN(1, _elapsed / MAX(_duration, FLT_EPSILON) // division by 0 ) ) ); }
updateメソッドは、MoveByやMoveToなどのアクションクラスでそれぞれオーバーライドされています。
実際の位置の変更などのアクションは、このupdateメソッドで行われています。
updateメソッド引数にはこれまでのアニメーションに要した時間をアニメーション全体時間で割った、アニメーションの進行割合が設定されています。
以下はMoveByのupdateメソッドです。進行割合とアニメーションpositionDeltaを掛け合わせた物を最初のポジションに足し合わせて、そのポジションをtargetとなるインスタンスに設定しています。
void MoveBy::update(float t) { if (_target) { #if CC_ENABLE_STACKABLE_ACTIONS Point currentPos = _target->getPosition(); Point diff = currentPos - _previousPosition; _startPosition = _startPosition + diff; Point newPos = _startPosition + (_positionDelta * t); _target->setPosition(newPos); _previousPosition = newPos; #else _target->setPosition(ccpAdd( _startPosition, ccpMult(_positionDelta, t))); #endif // CC_ENABLE_STACKABLE_ACTIONS } }
ここまでで、大体どういった仕組みでアニメーションされているのか理解できましたが。
では、本題であるEasingをする際にはどういった動きになっているのか。
まず、クラスの継承関係から見ていきます。
EaseOutクラスは、EaseRateActionを継承し、EaseRateActionはActionRaseを継承し、ActionRaseがActionIntervalを継承しています。
次に、上記のクラスでここまででアクションに関係すると考えられる、setpとupdateメソッドについてオーバーライドの状況を追っていきます。
ActionEase
updateのオーバーライドをしている。
ActionEase::update(float time)
インスタンスを作るときに渡された第一引数のアクションのupdateメソッドを呼び出す。
EaseRateAction
step、updateのオーバーライドはしていない。
EaseOut
updateメソッドのオーバーライドをしている。
上記のようにstepはオーバーライドされていないのでEaseOutのupdateだけ見れば良さそうです。
void EaseOut::update(float time) { _inner->update(tweenfunc::easeOut(time, _rate)); }
EaseOutに渡したアクションのupdateメソッドにtweenfunc::easeOut(time, _rate)を渡しています。
tweenfunc::easeOutは何をやっているか見てみるとtimeを1/rate乗したものを返しています。
float easeOut(float time, float rate) { return powf(time, 1 / rate); }
ここで帰ってきた値使ってMoveByなどのupdateメソッドを呼び出しているようですね!