CCLayerとCCSpriteの違い - レイヤーをスプライトのように使うには -.
前回、cocos2dでシーンへレイヤーやスプライトを追加する方法を解説しました。
今回は僕がアプリを作る上で非常に困惑した部分を書いていきます。それはレイヤーとスプライトの挙動の違いです。これはアプリ制作に入る前に知っておくべきだったと思います。
レイヤーとスプライトの違い
簡単にいえば、レイヤーはどーんと画面いっぱいに作り重ねていくのが基本です。一方スプライトは位置や回転、スケールを指定し、画像を画面に配置したり動かしたりするのに長けています。
では、「画像は必要ないが、オブジェクトを自由に配置したり動かしたりしたいとき」はどうすればいいでしょうか。
例えば、僕はよくOpenGLでオブジェクトを描画します。これは画像を使うわけではないので、CCSpriteクラスのオブジェクトとして作ることはできません。なぜならスプライトを生成するときは必ず画像を用いなければならないからです(何か他に画像を用意して、スプライトとし、そこに描画することは可能)。
したがってCCLayerクラスに描画することになります。そして、CCLayerに描画したオブジェクトをスプライトのように扱おうとすると、いろいろな部分で不具合が出てきます。理由を調べた結果、初期化時のデフォルト設定や仕様が違うことがわかったので、それを今後のためにまとめておきたいと思います。
以下のようなプログラムでデフォルト値を出力しました。NSLogはObjective-Cのコンソールへ出力するログ出力関数です。画像は100×100ピクセルの画像を使いました。
-(id) init { if( (self=[super init]) ) { CCLayerColor* layer1 = [CCLayerColor layerWithColor:ccc4(255, 0, 0, 255)]; [self addChild:layer1]; NSLog(@"----Layer Default Attributes----"); NSLog(@"Content Size - width: %f, height: %f", layer1.contentSize.width, layer1.contentSize.height); NSLog(@"Anchor Point - x: %f, y: %f", layer1.anchorPoint.x, layer1.anchorPoint.y); NSLog(@"Scale: %f", layer1.scale); NSLog(@"Position: (%f, %f)", layer1.position.x, layer1.position.y); NSLog(@"Rotation: %f", layer1.rotation); CCSprite* circle = [CCSprite spriteWithFile:@"bb_pic01.png"]; [self addChild:circle]; NSLog(@"----Sprite Default Attributes----"); NSLog(@"Content Size - width: %f, height: %f", circle.contentSize.width, circle.contentSize.height); NSLog(@"Anchor Point - x: %f, y: %f", circle.anchorPoint.x, circle.anchorPoint.y); NSLog(@"Scale: %f", circle.scale); NSLog(@"Position: (%f, %f)", circle.position.x, circle.position.y); NSLog(@"Rotation: %f", circle.rotation); } return self; }
出力した属性は、コンテントサイズ、アンカーポイント、位置、回転角度、スケールです。anchor pointとはポジションやスケーリング、回転の基準となる点です。オブジェクトの左下が(0.0, 0.0)、右上が(1.0, 1.0)で表されます。また、contentSizeはそのオブジェクトの縦横のサイズです。そして、以下の出力結果を得ました。
----Layer Default Attributes---- Content Size - width: 320.000000, height: 480.000000 Anchor Point - x: 0.500000, y: 0.500000 Scale: 1.000000 Position: (0.000000, 0.000000) Rotation: 0.000000 ----Sprite Default Attributes---- Content Size - width: 100.000000, height: 100.000000 Anchor Point - x: 0.500000, y: 0.500000 Scale: 1.000000 Position: (0.000000, 0.000000) Rotation: 0.000000
まず、サイズが違います。スプライトの方は、画像の大きさ(100×100)に設定されています。一方レイヤーはウィンドウのサイズ(iPhone 4のサイズ320×480ピクセル)です。これは、OpenGLで100×100のオブジェクトをレイヤー上で描画しても、1000×1000のオブジェクトを描画しても変わりません。デフォルトはウィンドウのサイズです。
さて、次におかしいのがアンカーポイントです。アンカーポイントはポジションやスケーリングの基準となる点でした。今、両方共(0.5, 0.5)になっています。アンカーポイントはサイズに対する割合で指定されるので、この場合オブジェクトの中心を指しています。
スプライトを見ると、オブジェクトの中心が(0, 0)の場所に来ているのできちんと表示されているようです。一方、レイヤーはポジションが(0, 0)になっているにも関わらず、オブジェクトの中心が画面の中心に来ています。
レイヤーのポジションが(160, 240)(ウィンドウの中心)もしくは、レイヤーのアンカーポイントが(0, 0)なら、この実行結果も納得が行くのですが、出力ログはposition = (0, 0)、anchor point = (0.5, 0.5)です。
これがレイヤーの扱いをややこしくさせる一番の原因です。
試しにレイヤーの位置を(100, 100)にしてみます。
layer1.position = ccp(100, 100);
するとこんな結果になります。
レイヤーの左下が(100, 100)の位置に来ているようです。アンカーポイントが左下(0.0, 0.0)として扱われているようです。では、アンカーポイントを改めて指定してみるとどうでしょうか。以下のコードを追加し、アンカーポイントをレイヤーの右上(1.0, 1.0)に設定してみます。
CCLayerColor* layer1 = [CCLayerColor layerWithColor:ccc4(255, 0, 0, 255)]; layer1.position = ccp(100, 100); layer1.anchorPoint = ccp(1.0, 1.0); [self addChild:layer1];
実行結果は同じです。
----Layer Default Attributes---- Content Size - width: 320.000000, height: 480.000000 Anchor Point - x: 1.000000, y: 1.000000 Scale: 1.000000 Position: (0.000000, 0.000000) Rotation: 0.000000 ----Sprite Default Attributes---- Content Size - width: 100.000000, height: 100.000000 Anchor Point - x: 0.500000, y: 0.500000 Scale: 1.000000 Position: (0.000000, 0.000000) Rotation: 0.000000
出力ログを見るときちんとレイヤーのアンカーポイントは(1.0, 1.0)に設定されています。
つまり、レイヤーの場合、アンカーポイントは無視され、自動的に左下になるということです。
これでは、レイヤーをスプライトのように扱うことができません。(厳密に言うと扱えますが、拡大の基点を変えたい場合など、かなりめんどくさい処理になります。)
CCLayerを CCSpriteのように扱うには
さてここからが重要です。少なくとも僕にとっては。
レイヤーのアンカーポイントを有効にすることはできないのか。
調べてみるときちんと用意されていました。
CCLayerのインスタンス変数に’ignoreAnchorPointForPosition’というのがあります。怪しいと思い、この定義を辿って行くとCCLayer.mのinitメソッドに行き着きました。
self.ignoreAnchorPointForPosition = YES;
とされています。つまり「アンカーポイントを無視します」→「はい」ってことです。
layer1のこれを書き換えてやりましょう。
CCLayerColor* layer1 = [CCLayerColor layerWithColor:ccc4(255, 0, 0, 255)]; layer1.ignoreAnchorPointForPosition = NO; [self addChild:layer1];
として実行してやると
きちんとレイヤーの中心が(0, 0)の位置に表示されました。
あとは必要なオブジェクトのサイズになるようコンテントサイズを指定してやれば、レイヤーでもスプライトのように使用することができます。
補足ですが、レイヤーはタッチに反応するかしないかを「isTouchEnabled」メソッドで切り替えることができます。スプライトは常にタッチ出来る状態です。タッチ関係のアクションを付けるときには、この点も注意が必要ですし、うまく使えば、より効率的なプログラムを書くこともできるでしょう。
ま、その辺は気が向けば書きます。
とにかく、スプライトとレイヤーの仕組みがわかったので、次はアクションあたりを書こうかと思います。