[Cocos2d-x] Cocos2d-xの座標系の基本とまとめ:要点はanchorPoint(アンカーポイント)
【注意点】
現行の主流であるCocos Creatorは、また違った座標系の特徴がある。
ローカル座標とグローバル座標
CSSやFlashと同じように、cc.Node(CCNode)を継承したクラスでは、addChild()で他のNodeオブジェクト(スプライト)を入れ子にしていける。
その際、内部の子要素の座標は親コンテナの座標系を基準とした、いわゆるローカル座標になる。
一番上のルートに相当するのがSceneオブジェクトで、その座標系がグローバル座標になる(Cocos2d-xではworld座標と呼んでいる)。
たとえば、親がグローバル座標(100, 100)の位置にあるとき、その子のローカル座標が(10, 10)ならば、子のグローバル座標は(110, 110)になる。
座標の取得方法
【ローカル座標からグローバル座標への変換】
親要素を使う。
node.getParent().convertToWorldSpace(node.getPosition());
【グローバル座標からローカル座標への変換】
node.convertToNodeSpace(globalPos);
原点
座標系は基本的に3Dと同じで、y軸が上方向にプラス。
ただし、原点(0, 0)の位置は画面左下隅に設定されている(左下原点)。
CSSや大半の2Dライブラリ、ペイントツールでは普通、左上原点でy軸の下方向がプラスなので、それらとは逆になっている。
アンカーポイント
問題はここから。
Cocos2d-xでいうアンカーポイント(anchorPoint)とは、以下のいくつかの意味がある。
- 拡大縮小・回転などの中心点
- 自身の位置(position)を設定する際の基準点
前者はわかりやすいが、問題は後者。
通常、2Dの座標系では「位置を設定する際の基準点」=「原点」となっている。CSSでもFlashでも同じ。
つまり、ある表示オブジェクト(スプライト)を親コンテナの座標(10, 10)の位置に設定しようとしたら、そこに表示オブジェクトの原点が来るように調整する。
しかし、Cocos2d-xはそうではない。あくまでアンカーポイントがその位置に来るようにする。
上の例でいえば、子の原点の位置が(10, 10)になるのではなく、アンカーポイントの位置が(10, 10)になる。
「原点=アンカーポイント」ではないということを、まずわきまえる必要がある。
子には影響を与えない
既出だが、このアンカーポイントはあくまで「自分自身の位置」を設定する際に基準となるものであって、その中に子要素をaddChild()しても親のアンカーポイントの位置は関係ない。
子から見た場合の親の基準点は、あくまで原点
ここが、まさにわかりにくい。
上記のとおり、親のアンカーポイントは子にとっては直接の関係はない。言い換えれば、子にとって親のアンカーポイントがどこにあろうと直接の影響はない。
親の座標の基準となるのはあくまで原点なので、子をaddChild()した際、デフォルトでは子のアンカーポイントが(0, 0)の位置に配置される。
アンカーポイントのデフォルト位置は、表示オブジェクトの中心
Node系のどのクラスでも、アンカーポイントはその中心に設定されている。百分率の実数で指定するため、(0.5, 0.5)となっている。
要するに、ある表示オブジェクトにおけるローカル座標の原点とアンカーポイントの位置は、基本的に常にずれていることになる。
そのため、Nodeをどんどん入れ子にしていくと、デフォルトでは以下のような奇妙な配置になっていく。
アンカーポイントの意義と矛盾
これだと明らかにわかりづらい+使いづらいので、アンカーポイントを自分で(0, 0)に、すなわち左下のローカル座標における原点と同じ位置にしてしまえばいい。
だが、これだと今度は拡大縮小や回転をするときに、左下を中心に行ってしまうので問題が出る。
これを解決するために、以下のメソッドがある。
Node#ignoreAnchorPointForPosition(bool)
読んで字のごとく、アンカーポイントをそれ自身の位置を決める際には無視する設定にするもの。
言い換えれば、アンカーポイントの位置はあくまで回転などの変形の基準のみに使い、位置決めは他の普通のライブラリと同じく原点と同じにするということ。
本来、アンカーポイントが(0.5, 0.5)などに設定されていながら、該当Nodeの基準位置はそのアンカーポイントを無視するというのは、Cocos2d-xの仕様上もかなり矛盾した感もあるが、苦肉の策なのだろう。
ともかく、これで万事解決かと思いきや、今度はまた厄介な部分が出てくる。
クラスごとに違うignoreAnchorPointForPosition(bool)のデフォルト値
Nodeではデフォルトでfalseなので、大半のスプライト系クラスでも同じなのだが、LayerとSceneはデフォルトで「true」になっている。
また、MenuなどLayerを継承したクラスもtrueなので、基本的にignoreAnchorPointForPosition(bool)の値を常にチェックしながらコーディングする必要がある。
SpriteFrameでは左上原点
Sprite#setSpriteFrame()で設定するSpriteFrameオブジェクトでは、画像の特定の領域を表示するために該当する矩形(Rect)を指定するが、この場合は左上原点になる……
ポイント
- 基本、左下原点
- アンカーポイントは拡大縮小や回転などの変形だけでなく、表示オブジェクト自身の位置を決める基準にもなる
- ignoreAnchorPointForPosition(bool)は、表示位置の基準を原点と同じにする
注意点
このような非常にトリッキーな仕様になっているので、開発者は以下の点を常に注意しながらコーディングする必要がある。
- 表示オブジェクトのクラスは何か
- アンカーポイントの位置はどこか
- ignoreAnchorPointForPositionがtrueか
これらがひとつ異なるだけで、まったく表示が変わってくるので要注意。
対策
- アンカーポイントやignoreAnchorPointForPositionをいじることを禁止する
- いっそ、すべての表示オブジェクトのアンカーポイントを(0, 0)にして左下原点に統一してしまう
- 安易にLayerやSceneを継承することをやめる
これらをコーディング規約として徹底しないと、特にチームでの開発では問題が出やすいだろう。
所感
おそらく、回転や拡大縮小をしやすいようにアンカーポイントを表示オブジェクトの中心にしたのだろうが、かなり裏目に出ている。それを重視するのならUnityの2D機能と同じく、原点も中心位置にすべきだったろう。
ここだけとってみてもかなりトリッキーで、カオス感があるので、プログラマやデザイナーの意図どおりに表示されず、問題の温床になりかねないリスクが常にある。
Cocos2d-xを採用するか否かは、こういった点にも注意して判断しよう。