cocos2dでスケジューラを使う

Posted on 28/04/2013 by admin in cocos2d, Objective-C, プログラミング

今回はcocos2dのスケジューラを見て行きたいと思います。 スケジューラとは指定時間ごとに処理を実行してくれたり、一定時間後に実行したりする際に用いる機能です。毎フレームごとにオブジェクト同士の距離をチェックすれば、当たり判定も実装することができます。 プログラムを組んでいく上で、特に複雑なエフェクトを作ったりするときなど必要になってくる機能ですので、是非使いこなせるようになりましょう。

 

スケジューラの仕組み

スケジューラを動かすにはまず開始の合図を出さなければなりません。この開始の合図を出す際に、どのメソッドに対して行うのか、何秒ごとに行うのか、何回繰り返すのか、何秒後に処理を開始するのかなどを設定します。その設定を元にプログラムは自動で処理を実行してくれます。

schedule_flow

 

毎フレーム処理を行うスケジューラ – scheduleUpdate

まずは一番基本的なスケジューラを見ながら、その使い方に慣れましょう。 scheduleUpdateは、cocos2dの描画が行われる1フレームごとに呼び出されるスケジューラを立てることができます。呼び出されるメソッドもcocos2d側で指定されており、updateというメソッドになります。 プログラム内でscheduleUpdateが呼び出された時点からupdateメソッドが毎フレームごとに呼ばれます。 わかりやすくするために簡単なサンプルプログラムを作ってみました。これはHelloWorldLayerのが生成された時点(initメソッド)でスケジューラを呼び出しています。毎フレーム呼び出されるメソッドがその下に書いてあるupdateメソッドです。毎フレームごとに”update”と出力するようにしました。

-(id) init
{
    if( (self=[super init]) )
    {
        NSLog(@"%s",__PRETTY_FUNCTION__);
        CGSize wSize = [[CCDirector sharedDirector] winSize];
        self.isTouchEnabled = YES;

        MyCircle* circle = [MyCircle spriteWithFile:@"red_circle.png"];

        [self addChild:circle z:0 tag:tagCircleRed];

        circle.position = ccp(wSize.width/8, wSize.height/2);

        MyCircle *circle2 = [MyCircle spriteWithFile:@"green_circle.png"];

        [self addChild:circle2 z:0 tag:tagCircleGreen];

        circle2.position = ccp(100, 200);

        CCMoveBy *moveRight = [CCMoveBy actionWithDuration:3 position:ccp(100,0)];
        CCMoveBy *moveUp = [CCMoveBy actionWithDuration:3 position:ccp(0, 100)];
        CCSequence* seq = [CCSequence actions:moveRight, moveUp, nil];

        [circle runAction:seq];

        [self scheduleUpdate];//スケジューラの呼び出し

    }
    return self;
}

-(void)update:(ccTime)dt
{
    NSLog(@"update");
}

-(void)dealloc
{
    [super dealloc];
}

-(void)onEnter
{
    [super onEnter];
}

-(void)onExit
{
    [super onExit];
}

いつものように描画されているのを確認するため、一応画像を表示していますが気にしないでください。今回重要なのはinitメソッドの最後、unscheduleUpdateです。これを通った時点から、updateメソッドが呼び出され始めます。

実行すると、HelloWorldLayerのイニシャライズが終わったあと、ダ~っと”update”という文字が出力されるはずです。これで成功です。焦らずプログラムを停止してください。

短時間でもかなりの出力があったと思います。これだけの回数このメソッドは呼ばれているので、「少しずつポジションを移動させる」といった処理などを書けば、簡単なアニメーションも作れます。「何かオブジェクトをドラッグしている間は、他のオブジェクトとの当たり判定を行う」といった処理にも有効ですね。

これを止めるにはunscheduleUpdateをプログラム内で呼び出してやります。これが呼ばれた時点で、updateの呼び出しを停止することができます。実際にタッチでスケジューラを止めるプログラムを書いてみましょう。ccTouchesEndedメソッドを用意し、unscheduleUpdateを以下のように呼び出してください。

-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"stop update");
    [self unscheduleUpdate];
}

実行すると、タッチが終了した時点で、「stop update」が出力され、「update」の出力が止まります。

 

独自で定義したメソッドをスケジュールで実行する

先ほどのscheduleUpdateupdateという固定のメソッドしか用いることができませんでしたが、独自のメソッドを用いることもできます。その場合はscheduleメソッドで呼び出したいセレクタを指定します。

[CCNode schedule:@selector(SEL)];

例えば先程のupdateメソッドの代わりに特定の”sampleScheduler”というメソッドをスケジューリングしたい時は以下のように書くことができます。

-(id) init
{
    if( (self=[super init]) )
    {
        NSLog(@"%s",__PRETTY_FUNCTION__);

        //[self scheduleUpdate];

        [self schedule:@selector(sampleScheduler)]; // sampleSchedulerメソッドをスケジューラに指定
    }
    return self;
}

-(void)sampleScheduler
{
    NSLog(@"specific scheduler");
}

動作させたい処理を書いたメソッドをsampleSchedulerとして用意し、9行目で指定します。ここで指定するセレクタが存在しない場合は落ちるので気をつけてください。実行してみると分かりますが、このメソッドも毎フレーム呼び出されます。

セレクタを指定してスケジューラを止める方法も用意されています。先ほどはunscheduleUpdateメソッドを用いましたが、今回は次のようなメソッドを用います。

[CCNode unschedule:@selector(SEL)];

先ほどのようにタッチでスケジューリングを止める際は以下のように書けます。

-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"Stop Specific Selector");
    [self unschedule:@selector(sampleScheduler)];
}

 

一定時間ごとに指定したメソッドを実行する – interval

プログラムの処理速度を落とさないためにも、毎フレーム行う処理は極力少ない方が懸命です。scheduleメソッドはintervalキーワードをつけることができ、処理が呼び出される間隔を指定できます。先ほどのsampleSchedulerを1秒ごとに呼び出したい時は、先ほどの呼び出しを少し変更し以下のように指定します。

[self schedule:@selector(sampleScheduler) interval:1];

実行すると1秒おきに出力が行われます。

 

実行回数と遅延時間の指定 – repeat, delay

スケジューリングの更に細かな指定として、実行回数と遅延時間を与えることができます。ここでの遅延時間とは、スケジューリングが開始されるまでの待ち時間です。以下のように指定します。

[CCNode schedule:(SEL) interval:(ccTime) repeat:(uint) delay:(ccTime)];

repeatキーワードに入る引数は整数でなければなりません。

 

指定時間後に一度だけメソッドを呼び出す – scheduleOnce

あるイベントが発生してから数秒後に次のイベントを発生させるという処理は、ゲームアプリを作っていると様々な場面で必要になります。そのような時に有効なのがこのメソッドです。以下の様な構文で書きます。

[CCNode scheduleOnce:(SEL) delay:(ccTime)];

今回はタッチが終了してから3秒後にsampleSchedulerメソッドを呼び出すようにプログラムを書いてみましょう。ccTouchesEndedを以下のように変更します。

-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self scheduleOnce:@selector(sampleScheduler) delay:3];
}

実行するとタッチが終了してから3秒後に出力が行われます。

 

スケジューラに渡せる引数

スケジューラに指定したメソッドに直接引数を渡すことはできません。しかし唯一定義できる引数があります。それが”ccTime“型の引数です。メソッドの定義側でccTime型の引数を定義しておけば、メソッドが実行される度に前回から今回の実行までに経過した時間が代入されます。言葉で書いてもわかりにくいので実際のプログラムを見てみましょう。

一番初めに用いたupdateメソッドを見なおしてください。以下のようにccTime型の引数が定義されています。

-(void)update:(ccTime)dt

この引数を出力してみましょう。

-(void)update:(ccTime)dt
{
    NSLog(@"update:%f", dt);
}

処理速度によると思いますが、0.01...などというかなり小さな数字が出力されていると思います。これが前回のメソッドからの経過時間です。intervalでインターバルを指定し、この経過時間を出力してみるとよくわかります。

まずsampleSchedulerccTime型の引数を持てるように定義し直してやり、スケジューラの呼び出しでインターバルを1秒にしてみましょう。

-(id) init
{
    if( (self=[super init]) )
    {
        NSLog(@"%s",__PRETTY_FUNCTION__);

        [self schedule:@selector(sampleScheduler:) interval:1];
    }
    return self;
}

-(void)sampleScheduler:(ccTime)delta
{
    NSLog(@"specific scheduler: %f", delta);
}

ここで気をつけなければならないのは7行目のセレクタの指定方法です。コロン”:“がメソッド名の後に付いているのに注意してください。セレクタが引数(今回はccTime)を持っている場合はこのようにセレクタの指定で明示しなければなりません。

実行してみると、ちょうど1秒というのは少ないですが、1にかなり近い数字が出力されていると思います。

このようにスケジューラの引数を用いれば、スケジュールを開始してからの経過時間などを調べることができます。

 

スケジューラ自身でスケジューリングを止める方法

unscheduleメソッドの呼び出しはスケジューリングの対象になっているメソッド内に設置することもできます。以下のサンプルプログラムを見てみましょう。

@implementation HelloWorldLayer
{
    float timeStamp;
}

+(CCScene *) scene
{
	CCScene *scene = [CCScene node];
	HelloWorldLayer *layer = [HelloWorldLayer node];
	[scene addChild: layer];
	return scene;
}

-(id) init
{
    if( (self=[super init]) )
    {
        NSLog(@"%s",__PRETTY_FUNCTION__);
        timeStamp = 0.0f;
        [self schedule:@selector(sampleScheduler:) interval:1];
    }
    return self;
}

-(void)sampleScheduler:(ccTime)delta
{
    NSLog(@"specific scheduler: %f", delta);
    timeStamp += delta;
    if(timeStamp>5)
    {
        NSLog(@"stop schedule: %f", timeStamp);
        [self unschedule:_cmd];
    }
}

このプログラムはHelloWorldLayerのイニシャライズから5秒間sampleSchedulerを1秒ごとに実行し、その後停止するプログラムです。まず隠蔽インスタンス変数としてtimeStampを用意し、イニシャライザで0秒を代入し初期化しています。その後スケジューラを開始し、sampleSchedulerが呼ばれる度に経過時間を蓄積していきます。経過時間が5秒を超えた時点で、if文の中に入り、自身のスケジューリングを停止させています。ここで「_cmd」というのが自身のセレクタを示す省略記号で、ここではsampleSchedulerを示しています。

 

全スケジューラの停止 – unscheduleAllSelectors

現在動いている全てのスケジューラを停止させるunscheduleAllSelectorsメソッドも用意されています(あまり使う機会は無いですが)。以下のように呼び出してやります。

[self unscheduleAllSelectors];

 

今回はざっとcocos2dにおけるスケジューラの使い方を見ていきました。これを使いこなせば複雑な動作やエフェクトを実現することもできるので、アプリのクオリティーがぐっと上がります。是非使いこなしてみてください。

Share on Facebook

コメントを残す

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

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="">