cocos2dでスケジューラを使う
今回はcocos2dのスケジューラを見て行きたいと思います。 スケジューラとは指定時間ごとに処理を実行してくれたり、一定時間後に実行したりする際に用いる機能です。毎フレームごとにオブジェクト同士の距離をチェックすれば、当たり判定も実装することができます。 プログラムを組んでいく上で、特に複雑なエフェクトを作ったりするときなど必要になってくる機能ですので、是非使いこなせるようになりましょう。
スケジューラの仕組み
スケジューラを動かすにはまず開始の合図を出さなければなりません。この開始の合図を出す際に、どのメソッドに対して行うのか、何秒ごとに行うのか、何回繰り返すのか、何秒後に処理を開始するのかなどを設定します。その設定を元にプログラムは自動で処理を実行してくれます。
毎フレーム処理を行うスケジューラ – 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」の出力が止まります。
独自で定義したメソッドをスケジュールで実行する
先ほどのscheduleUpdate
はupdate
という固定のメソッドしか用いることができませんでしたが、独自のメソッドを用いることもできます。その場合は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
でインターバルを指定し、この経過時間を出力してみるとよくわかります。
まずsampleScheduler
をccTime
型の引数を持てるように定義し直してやり、スケジューラの呼び出しでインターバルを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におけるスケジューラの使い方を見ていきました。これを使いこなせば複雑な動作やエフェクトを実現することもできるので、アプリのクオリティーがぐっと上がります。是非使いこなしてみてください。