[cocos2d Advent Calendar 2011] cocos2dでTemplate Methodパターンで拡張して使う簡単ボタンクラスCCSpriteBtnクラスを作りました。

この記事は最終更新日から13年以上経過しています。

早くもcocos2d Advent Calendar 2011の2週目が廻って参りました。 ATND: cocos2d Advent Calendar 2011 前日の記事: cocos2d Advent Calendar 2011 12日目 cocos2d-x nativeビルドまで ( @yoichineji )

今回は過去に何本かアプリをリリースした中で、 Arcede MIDI Controller というMIDIコンアプリを作成した時に、拙いプログラムスキルを絞って編み出した便利クラスをご紹介しつつ、cocos2dにデザインパターンを組み込んで、簡単便利に拡張するテクニックをご紹介させていただこうかと思います。

cocos2dで普通のボタン作るのって面倒じゃないですか?

僕は面倒です・・・というか難しいです・・・ ~けものみちを目指して~ さんの -cocos2dで簡単なボタンを作ろうぜチュートリアル に恐らく最も簡単なボタンの作り方が解説してありますが、 プログラマでは無い僕からしたら Menuでは無い単なるボタンを作成するだけなのに、 ・CCMenuItemを作って配置 ・CCMenuを作って配置 ・CCMenuにCCMenuItemを登録して・・・ という工程をしなきゃいけない事が面倒なので、自作のCCSpriteを継承した簡単ボタンクラスCCSpriteBtnというのを作って使用しています。 (CCLayerは階層を増やす事自体非推奨なので、あまり重いアプリには使用できませんが・・・)

・・・という内容で、クラスの作成までの記事を書きかけていたのですが、 @myb さんも ボタンネタ でちょっと被っていたので、完成したCCSpriteBtnクラスを使用して、GoFデザインパターンの中のTemplate Methodパターンを利用し、拡張しつつ色々なボタンを作成する方法も書こうかと思います。

CCSpriteBtnってどんなクラス?

CCMenuItemやCCMenuを介さず、簡単に宣言してボタンを作成する事が可能な自作のクラスです。 ・onPress,onReleaseのイベントを取得可能 ・Toggleボタンとしても使用可能 という2つの機能も付いています。

ソースコードは今回のサンプルと合わせて一式 githubのリポジトリ にアップしています。 github: https://github.com/is8r/CCSpriteBtn リポジトリ:git@github.com:is8r/CCSpriteBtn.git

GoFデザインパターンって何?

僕もそんなに詳しく無いのですが、 こんなの です。AS3で勉強したのですが、超絶便利です。 Singletonパターン,Template Methodパターン,Mediatorパターンあたりを覚えておいたら作業効率が格段に上がります。

Template Methodパターンって何なの?

この場合はボタンなので凄くわかりやすいかと思うのですが、 ロールオーバーやロールアウト等、ボタンに必要な機能をひと通り内包した元になるクラスを1つ作って、サブクラス側(前述のクラスを継承している子供クラス)で、ボタンを押した時なんかに大きくしたり、色を変えたりなど見栄え上の処理のみを指定してやるという、おおまかに言えば、そんな手法です。 ボタン機能として何か追加の機能を実装したい時は複数の挙動のボタンがあってもひとつのクラスを修正する事で実装が可能にですし、さらに個別のボタンクラスは動きの部分だけが記述されているので可読性も上がり、良いことづくめです。 (厳密にはCCSpriteBtn自体は単体でも使えるクラスになっているのでAbstract Classでは無く、即ちTemplate Methodパターンともちょっと違うのかも知れないのですが、どうぞご容赦を・・・)

まずはCCSpriteBtnの使い方

・onPressで機能するボタンとして

コピーしました
-(id) init
{
	if( (self=[super init])) {
		//
		CGSize stage = [[CCDirector sharedDirector] winSize];

        //btn - onPress
        CCSpriteBtn *b0 = [[CCSpriteBtn alloc] initWithFiles:@"b_border80.png" ro:@"b_border80_on.png"];
        b0.Id = 0;
        b0.position =  ccp( b0.contentSize.width/2 , stage.height - b0.contentSize.height/2 );
        [b0 addListner:self selector:@selector(onPress:)];
        [b0 addLabel:@"onPress"];
        [self addChild: b0];
	}
	return self;
}
-(void) onPress:(CCSpriteBtn*)sender
{
	NSLog(@"onPress:%i", sender.Id);
}

・onReleaseで機能するボタンとして ※onPressと併用可

コピーしました
-(id) init
{
	if( (self=[super init])) {
		//
		CGSize stage = [[CCDirector sharedDirector] winSize];

        //btn - onRelease
        CCSpriteBtn *b1 = [[CCSpriteBtn alloc] initWithFiles:@"b_border80.png" ro:@"b_border80_on.png"];
        b1.Id = 1;
        b1.position =  ccp( b1.contentSize.width/2 , stage.height - b1.contentSize.height/2 - 49 );
        [b1 addListnerRelease:self selector:@selector(onRelease:)];
        [b1 addLabel:@"onRelease"];
        [self addChild: b1];
	}
	return self;
}
-(void) onRelease:(CCSpriteBtn*)sender
{
	NSLog(@"onRelease:%i", sender.Id);
}

・Toggleボタンとして

コピーしました
-(id) init
{
	if( (self=[super init])) {
		//
		CGSize stage = [[CCDirector sharedDirector] winSize];

        //btn - ToggleBtn
        CCSpriteBtn *b2 = [[CCSpriteBtn alloc] initToggleWithFiles:@"b_border80.png" toggle:@"b_border80_on.png"];
        b2.Id = 2;
        b2.position =  ccp( b2.contentSize.width/2 , stage.height - b2.contentSize.height/2 - 98 );
        [b2 addListnerRelease:self selector:@selector(onPress:)];
        [b2 addLabel:@"ToggleBtn"];
        [self addChild: b2];
	}
	return self;
}
-(void) onPress:(CCSpriteBtn*)sender
{
	switch (sender.Id)
	{
		case 2:
            NSLog(@"toggle:%i", sender.isToggle);
			break;
	}
}

というように使用します。

拡張してどんなのを作るの?

・プルプル動くPuruBtnクラス ・押すとプヨっと大きくなるPuyoBtnクラス の2つを試しに作成してみます。

常時プルプル動いているPuruBtnクラスの作り方

まず元になるスーパークラスを継承します。

コピーしました
//  PuruBtn.h
#import "CCSpriteBtn.h"
@interface PuruBtn : CCSpriteBtn{}
@end

CCSpriteBtnは下記のようなオーバーライド用のメソッドを予め用意しています。

コピーしました
-(void) _init;
-(void) _press;
-(void) _release;

PuruBtnは初期化の時にタイマーを設定し、 一定時間ごとにプルプルさせる機能を追加したいので、 _initにその処理を記述します。

コピーしました
// PuruBtn.m
#import "PuruBtn.h"
@implementation PuruBtn
- (void)_init
{
    [self schedule:@selector(tick:) interval: 1.0f];//初回は一秒後に実行
}
- (void)tick:(ccTime) dt
{
    //プルプルさせる処理
    id step0;
    id step1;
    id easeAction;
	step0 = [CCRotateBy actionWithDuration:0.1f angle:3];
	step1 = [CCRotateBy actionWithDuration:0.6f angle:-3];
    easeAction = [CCEaseElasticOut actionWithAction:step1];
	[self runAction:[CCSequence actions:step0, easeAction, nil]];

    //次回の実行をランダムで設定
	srand(rand()%99 + time(nil));
    float time = rand()%3;
    [self unschedule:@selector(tick:)];
    [self schedule:@selector(tick:) interval: time];//
}
@end

と記述すればプルプル動くPuruBtnクラスが完成します。 あとは

コピーしました
PuruBtn *b = [[PuruBtn alloc] initWithFiles:@"b_border80.png" ro:@"b_border80_on.png"];
[b addListner:self selector:@selector(onPress:)];
[self addChild: b];

みたいな感じで配置するだけで動作します。

このボタンは拙アプリ MashMosh のタイトル画面でボタンがプルプル動くのに使用していたりします。 クリッカブルな箇所がわかりやすくなるので。結構効果的かと思います。

押すとプヨっと大きくなるPuyoBtnの作り方

まず元になるスーパークラスを継承します。

コピーしました
//  PuyoBtn.h
#import "CCSpriteBtn.h"
@interface PuyoBtn : CCSpriteBtn{}
@end

PuyoBtnは押した時にちょこっと拡大して、 離した時に元の大きさに戻したいので、 _pressと_releaseにその処理を記述します。

コピーしました
// PuyoBtn.m
#import "PuyoBtn.h"
@implementation PuyoBtn
enum{
    kTagSequence,
};
- (void)_press
{
    //release時のアクションを実行中の場合はキャンセルします
    [self stopActionByTag:kTagSequence];

    //ちょっと拡大します
    id step0;
    id easeAction;
    CCSequence* sequence;
    step0 = [CCScaleTo actionWithDuration:0.1f scale:1.1f];
    easeAction = [CCEaseElasticOut actionWithAction:step0];
    sequence = [CCSequence actions:step0, easeAction, nil];
    sequence.tag = kTagSequence;
    [self runAction:sequence];
}
- (void)_release
{
    //_press時のアクションを実行中の場合はキャンセルします
    [self stopActionByTag:kTagSequence];

    //元の大きさに戻します
    id step0;
    id easeAction;
    CCSequence* sequence;
    step0 = [CCScaleTo actionWithDuration:0.3f scale:1.0f];
    easeAction = [CCEaseElasticOut actionWithAction:step0];
    sequence = [CCSequence actions:step0, easeAction, nil];
    sequence.tag = kTagSequence;
    [self runAction:sequence];
}
@end

と記述すれば押すとプヨっと大きくなるPuyoBtnクラスが完成します。 あとは

コピーしました
PuyoBtn *b = [[PuyoBtn alloc] initWithFiles:@"b_border80.png" ro:@"b_border80_on.png"];
[b addListner:self selector:@selector(onPress:)];
[self addChild: b];

みたいな感じで配置するだけで動作します。

Template Methodパターンは他にも好き勝手にアクションを追加するだけで、同じ機能のボタンがどんどん作れるので、非常に便利です。 ローダーやフェーダー、スクロールバーなんかにもAbstractClassを作成しておけば、このように拡張して再利用する事が可能です。 個人的にはオブジェクティブ指向プログラミングの便利なところが最も簡単に活用できるデザインパターンだと思っているので、ぜひ一度お試しいただければと思います。

以上ご清聴ありがとうございました。

次のカレンダーの方はまだ未定です・・・ 宜しくお願いします!

14日目: cocos2dとTiledMapでスーパーマリオになりたい! ( @aoi68kさん )

Profile

石原 悠 / Yu Ishihara

デザインとプログラミングと編み物とヨーグルトが好きです。