cocos2d-xでCCActionIntervalを自作する

Pocket

cocos2dx

最近プライベートで色々なことがあったりして心が折れかけ、ちょっと開発からも遠ざかっちゃってたんですけど、また再開したんでその過程で勉強したことをまとめます。

以前からCCActionIntervalを自作したいと思ってたんで、今回ひとつ作ってみました。
※ただ、それを扱ったサイトなどがほとんど全くなかったから間違えてる箇所があるかもしれませんが…。でも一応ちゃんと動いたから多分できました。

CCActionIntervalとは?

cocos2d-xのCCActionには大きくわけて2種類のアクションがあるみたいです。

CCActionInstant

その名の通り、瞬時に実行されるアクションで、CCCallFuncやCCRemoveSelfといった実行時間の指定がないやつになります。
これに関しては、以前も自作してブログ書いてみました。

cocos2d-xで簡単なCCActionを自作する

CCActionInterval

これも文字通りですね。指定した時間によって動かされるようなアクションです。CCMoveByとかCCScaleByといったものがこれになります。

今回はこのCCActionIntervalを自作してみます。

CCActionIntervalで実装すべきクラスとメソッド

当たり前ですが、自作アクションはCCActionIntervalが実装されてる必要があります。
また、以下のメソッドを最低限オーバーライドする必要があるみたいです。

/**
 * アクションの初期化を行う。 durationは必須で、あとは指定したいパラメータを入れる感じですね
 * ちなみにこれは正確にはオーバーライドじゃなく、
 * 単に初期化を行うメソッドであるとわかりやすく統一させるだけです。
 */
bool initWithDuration(float duration , ...);
/**
 *  コピーする際に必要
 */
virtual CCObject* copyWithZone(CCZone* pZone);
/**
 * CCNode::runAction()時に呼び出されるメソッドみたいです。
 * ここでCCNodeのパラメーターの取得などを行います
 * 例:CCPoint pos = pTarget->getPosition();
 */
virtual void startWithTarget(CCNode* pTarget);
/**
 * アクションの実行中の動作を記載します。
 * float time は0〜1.0の間で変化していきます(0=開始 1=終了)
 */
virtual void update(float time);
/**
 * reverseさせたい場合に必要。
 * 必要ない場合はオーバーライドしなくてもいいみたいです。
 */
virtual CCActionInterval* reverse();

ちなみに、initWithDurationのメソッド内では必ずCCActionInterval::initWithDuration(duration)を呼び出して、親クラスにアクション時間をセットする必要があります。

最後のreverse()は必須ではないです。これはたとえばCCMoveByであるポイントまで移動し、それを元の場所に戻したいというような時に呼び出すメソッドです。

//(例)右に1秒かけて右へ100移動し、1秒かけて元の場所に戻ってくる
CCMoveBy* move = CCMoveBy::create(1.0f , ccp(100,0));
this->runAction(CCSequence::create(move , move->reverse() , NULL));

そういう動作が必要ない場合、作成する必要はありません。無理に呼び出そうとしたらAssert!ってなって怒ってくるようになります。

実際にひとつ作ってみる

とまあ、こんな感じで前知識は得たので、今度は実際に作成してみます。
とはいっても、ほとんどのものはちゃんと実装されてて不便なく使えるし、改めて何か新しくゼロから作るものってないんですよね(´・ω・`)

そんなわけで、CCScaleToを親クラスにした拡張CCScaleToクラスを作成してみます。

CCScaleTo(CCScaleBy)って、CCNode::setAnchorPointがデフォルト(0.5,0.5)の場合、中心を基準に拡大(縮小)が行われるんですよね。

もちろんAnchorPointをいじれば基準点が変わり、たとえば上から下にぐちゃって潰れるというような動きもできるのですが、あれをいじると今度はスプライトの位置も変わる上にCCRotateTo(RotateBy)などと合わせて使うとまた変なことになったりしちゃうんでちょっぴり不便です。

scaleto

そんなわけで、CCScaleToの内部的に別のAnchorPointを持つようなクラスを作成します。

//CCScaleFixed.h
//CCScaleToを実装
class CCScaleToFixed : public CCScaleTo{
public:
	static CCScaleToFixed* create(float $duration , float $s , const CCPoint& $point);
	static CCScaleToFixed* create(float $duration , float $sx , float $sy , const CCPoint& $point);
	bool initWithDuration(float $duration , float $s , const CCPoint& $point);
	bool initWithDuration(float $duration , float $sx , float $sy , const CCPoint& $point);
	virtual CCObject* copyWithZone(CCZone* $zone);
	virtual void startWithTarget(CCNode* $target);
	virtual void update(float $time);
	/* virtual CCActionInterval* reverse(); 必要なかった */
protected:
	CCPoint m_apoint; //内部的なアンカーポイント
};

//CCScaleFixed.cpp
CCScaleToFixed* CCScaleToFixed::create(float $duration, float $s, const cocos2d::CCPoint &$point){
	CCScaleToFixed* scale = new CCScaleToFixed();
	scale->initWithDuration($duration, $s, $point);
	scale->autorelease();
	return scale;
}

CCScaleToFixed* CCScaleToFixed::create(float $duration, float $sx, float $sy, const cocos2d::CCPoint &$point){
	CCScaleToFixed* scale = new CCScaleToFixed();
	scale->initWithDuration($duration, $sx , $sy, $point);
	scale->autorelease();
	return scale;
}

bool CCScaleToFixed::initWithDuration(float $duration, float $s, const cocos2d::CCPoint &$point){
	if(CCScaleTo::initWithDuration($duration, $s)){
		m_apoint = $point;
		return true;
	}
	return false;
}

bool CCScaleToFixed::initWithDuration(float $duration, float $sx, float $sy, const cocos2d::CCPoint &$point){
	if(CCScaleTo::initWithDuration($duration, $sx , $sy)){
		m_apoint = $point;
		return true;
	}
	return false;
}

CCObject* CCScaleToFixed::copyWithZone(cocos2d::CCZone *$zone){
	CCZone* pNewZone = NULL;
    CCScaleToFixed* pCopy = NULL;
    if($zone && $zone->m_pCopyObject)
    {
        pCopy = (CCScaleToFixed*)($zone->m_pCopyObject);
    }
    else
    {
        pCopy = new CCScaleToFixed();
        $zone = pNewZone = new CCZone(pCopy);
    }

    CCScaleTo::copyWithZone($zone);

    pCopy->initWithDuration(m_fDuration, m_fEndScaleX, m_fEndScaleY , m_apoint);

    CC_SAFE_DELETE(pNewZone);
    return pCopy;
}

void CCScaleToFixed::startWithTarget(cocos2d::CCNode *$target){
	CCScaleTo::startWithTarget($target);
}

void CCScaleToFixed::update(float $time){
	if(m_pTarget){
		CCSize size = m_pTarget->getContentSize();
		float sx = m_pTarget->getScaleX();
		float sy = m_pTarget->getScaleY();
		CCScaleTo::update($time);
		float ex = m_pTarget->getScaleX();
		float ey = m_pTarget->getScaleY();
		CCPoint epos = ccp(size.width * (sx - ex) * (m_apoint.x - 0.5f) , size.width * (sy - ey) * (m_apoint.y - 0.5f));
		m_pTarget->setPosition(m_pTarget->getPosition() + epos);
	}
}

かなり長くなっちゃいましたが、こんな感じです。

initWithDurationではccScaleTo::initWithDurationを呼び出します。
ccScaleToの中ではCCActionInterval::initWithDurationを呼び出しているんで、これでちゃんと時間のセットもされます。

copyWithZoneはほとんどこんな感じのをひな形にして、initWithDurationでそれぞれ作ったのを実装してあげたらいいみたいです。

updateの部分がかなり強引で汚いコードになっちゃってる感がありますが…。
要は変更前のスケールと変更後のスケールから大きさの差分をとり、位置を調節してあげてる感じです。

使ってみる

あとは実際に使ってみます。使い方は、ほとんどCCScaleToと同じです。

唯一違うのは、最後の引数に偽アンカーポイントを指定するところです。

//CCSprite内
CCDelayTime* delay = CCDelayTime::create(1.0f);
CCScaleToFixed* scale1 = CCScaleToFixed::create(0.05f, 1.0f,0.3f, ccp(0.5,0));
CCScaleToFixed* scale2 = CCScaleToFixed::create(0.05f, 1.0f,1.0f, ccp(0.5,0));
CCSequence* action = CCSequence::create(delay , scale1 , scale2 , NULL);
this->runAction(action);

こんな感じに指定すると、

ccscaletofixed1

この子が1秒後に…

ccscaletofixed2

ぐしゃってなって

ccscaletofixed1

またお戻りになります。

今回はCCScaleToのだけですが、CCScaleByでも同じように作っておけば何かと便利かもです。

便利かな? というか、こんなの作らなくてもAnchorPointをいじらずに同じ動きができそうな気もするんだけどなぁ。。

ちなみに、余談ですがcocos2d-x2.2がリリースされましたね。
iOS7での不具合とか、2.2だとおよそ解消されてるみたいです。

cocos2d-xでCCActionIntervalを自作する” への2件のコメント

  1. 頑張ってください!
    ファンがいっぱいいます。
    テレビに出てた説をまだ言うてます(笑)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です