libGDXでの開発では頻繁にActorというクラス、またはActorのサブクラスを使います。
改めてこのクラスをいろいろ調べてみます。
前提
Scene2D全般。
shinsan68kさんのこの記事を呼んだ方が僕の調べて書いていることも何となく分かるかなと思います。
libGDXの基礎5 Scene2Dを使う by shinsan68k
Actorとは何か
Actorとは2Dゲームを作るためのScene2DというlibGDXの機能の構成要素の1つです。
実際のところまぁ、Javaのクラスです。
ActorはScene2Dのシーングラフ(空間に配置されるすべてのオブジェクトのツリー構造)内のノードであり、階層構造を持ちます。GroupというActorを内包できるActorのサブクラスで階層構造をつくることが出来ます。
イメージ)
例えば、下の画像で考えてみるとフタ、ビン、中の飲料、ラベルなどがActorと捉えることが出来ます。
これらを1つの日本酒の瓶としてまとめるのがGroupです。GroupもActorのサブクラスなのでActorの一種と言えます。
とても単純化したソースコードはこういうことです。(実際には対応する画像の追加や位置調整、サイズ調整などが必要です。)
package sample;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
/**
* Created by yy_yank on 2017/01/22.
*/
public class SparklingNihonsyu extends Group {
public SparklingNihonsyu() {
init();
}
public void init() {
Actor cap = new Actor(); // フタ
Actor bottle = new Actor(); // 瓶
Actor label = new Actor(); // ラベル
Actor sparklingAlcohol = new Actor(); // 日本酒
addActor(cap);
addActor(bottle);
addActor(label);
addActor(sparklingAlcohol);
}
}
このSparklingNihonsyuをnewして別の場所でActorに対してaddActorしてあげれば、シーングラフにこれらのActorが追加され、どこでも日本酒が表示できてハッピーになるのです。
Actor(またはGroup)はそんな感じに使われます。
PhotoshopとかGIMPとか、ああいった複数のレイヤーと扱えるペイントツールを使ったことはありますか?Groupはそのレイヤーに近いです。
Groupの優れているところは、Groupに適用したsize変更などが子要素のActor全てに適用されることですね。従って、日本酒スパークリングにsetScale(0.5f)とかすれば、瓶もラベルもフタも画像倍率を保っていられる感じです。
Actorの持つクラスフィールド
position(x, y), rectangular size, origin(originX,originY), scale(scaleX, scaleY), rotation, color(デフォルトはnew Color(1, 1, 1, 1))、parent
その他privateフィールド
Actorの持つメソッド
全てはjavadocを見てもらうとして。。
Class Actor
ライフサイクル系
- draw
描画メソッドですが、空実装になっています
- act
Actorの更新時に呼ばれます。毎フレーム呼ばれる点に注意が必要です。
- hit
Actorの描画されているx,y座標内をタッチされた場合に自分自身のインスタンスを返します
状態操作
- remove
自分自身をシーングラフから消します
- addListener
- removeListener
イベントリスナーの追加/削除
- addAction
- removeAction
Actionの追加/削除
- getActions
登録した全てを配列にしたものを返します
- hasAction
Actionを持っているかどうか
- clearActions
登録したAcitonを削除
- clearListeners
登録したリスナーを削除
Listenerを全て削除します
- clear
登録したActionもListenerも削除
状態通知系
- positionChanged
- sizeChanged
- rotationChanged
protectedになっているのでoverrideすればそれぞれ状態が変わった場合に通知を受け取れます
座標変換系
- screenToLocalCoordinates
画面座標からActorのローカル座標へと変換します。Stageから当該Actorまでツリー構造を再帰的に呼び出してます(Stageの座標→Actorインスタンス1のローカル座標→Actorインスタンス2のローカル座標...→ActorインスタンスNのローカル座標
- stageToLocalCoordinates
screenToLocalCoordinatesとは違いこのメソッドを呼出したActorの親から座標変換を開始します。
- localToStageCoordinates
Actorのローカル座標をStageの座標に変換します。screenToLocalCoordinatesとは逆ですね。子から親に伝播していきます。
- localToParentCoordinates
当該Actorの座標から1つ上の親の座標へと変換するメソッドです。
- localToAscendantCoordinates
特定のActorの座標を知りたい場合に利用します。今のActor→指定したActorへの座標変換です。
- parentToLocalCoordinates
親要素から子要素のActorへ座標変換する場合に呼び出します。実装を見た感じだとStage(ルート)からActorへの変換時に呼び出すことを想定しているようです。
2フェーズのイベント
Actorの持つイベントは2種類あります。
キャプチャフェーズとノーマルフェーズです。
キャプチャフェーズ
キャプチャフェーズはイベントが発生通知される前のインターセプタと説明すると分かりやすいかもしれません。
以下のような流れの際に、イベントが発生する前段階で親がイベントをハンドリングしてキャンセルしたりする場合に利用されます。
Stage → Actor 1→ Actor 2 → イベントが発生するActor
ノーマルフェーズ
ノーマルフェーズは本当によくあるクリックイベントなどです。
この場合はイベントの発生するActorのみイベントハンドリングが可能です。
また、矢印の向きが逆になりActorからStage(つまりルート)まで登っていくような伝播をします。
Stage ← Actor 1 ← Actor 2 ← イベントが発生するActor
どちらを使うか…というのを考えるとやはりノーマルフェーズのリスナーの方が多いと思います。
ただキャプチャリスナーも必要に応じて利用することでうまく仕組みが作れそうですよね。
リスナーのstopとcancel
リスナーイベントではstopとcancelメソッドが提供されています。
stop = 伝播を中断
cancel = 伝播を中断し、処理を無かったことにする(巻き戻す)
つまり、前述したような流れが止まります。これはキャプチャー/ノーマルフェーズどちらとものようです。
// キャプチャーフェーズ。Actor2には伝わらない
Stage → Actor 1 → stop!! → x Actor 2 → イベントが発生するActor
// ノーマルフェーズ。Actor2には伝わらない
Stage ← Actor 1 ← Actor 2 x ← stop!! ← イベントが発生するActor
Scene2Dのスコープ
Scene2D(というかlibGDX全体も含むかもしれませんが)ざっくりスコープを考えると、
Application = Applicationスコープ
Stage = Sessionスコープ
Actor = Viewスコープ
って感じかなと思います。
Actorは拡大解釈です。Actorの規模感によりますし、結構抽象的なクラスなので断言出来るものではない気がします。画面とかコンポーネントと思えばまぁ外れてはいないかなぁという気がします。
Applicationとはcom.badlogic.gdx.Applicationというインターフェースです。
各プラットフォームの情報とライフサイクル管理をするものです。
Applicationから呼び出されるライフサイクルのみに特化したものがApplicationListenerもしくはApolicationAdapaterとなっていて、gdx-setup.jarとかでXXXGameというクラスが出来上がると思いますが、それが実装しているクラスになっていると思います。
StageはScene2Dのツリー構造のルート要素でActorを束ねるものです。入力イベントなどをハンドリングしてくれます。
Actorの長所・短所
長所短所はどうなんでしょうか。
stack overflowにはこんなやり取りがあります。
When to use actors in libgdx? What are cons and pros? - stack overflow
StageとActorの使い分けってどうするのってところに端を発してどういう時にActorを使うのが良いのかという話になってる感じですね。
長所
- ActorはActionが使える
Actionsクラスを使えば簡単にアニメーションやエフェクトを実装できる
- Groupでグルーピングが出来る
Actorをグループ化してそれぞれの画面やコンポーネントで利用できる
- hitやtouchイベントのテストがしやすい
Stageのhitを使うと常にhitメソッドを実装した(Actorを持つ)一番始めのActorを返す。Actorはiterateしてhitメソッドを探し、見つからなければnullを返す。
hitはStageのメソッドとして使われるがActorのtouchイベントはActorのlocal coordinates(ローカル座標)がStageから渡されてくる。
またStageはオブジェクトを包んでハンドリングしている。例えば他のActorがtouchDownイベントを受け取らないようにイベントがあるActorにのみ通知しActorが内包する別のActorにはtouchDownイベントを通知せず止めるようにしている。それと同時に、イベントが通知されたActorをフォーカスするようになっている。
短所
- Stageを常に必要とする。限定的な機能だとしても。
- ゲームの状態を制御するロジックを作る時にActorではなくbox2dなど別の物を使うものを人達も多い
- Actorを採用するとおそらくUIのStageとゲームの世界のStageの2つが必要。それを使ってないんだとしたらおそらくSpriteBatchとCameraでなんとかしているんでしょう
→これはつまり、UIの状態管理とゲームの状態管理の話だと思われます - Actorのdrawメソッドは空実装になっていて、常に実装者が管理する必要があることを忘れずに。TextureRegionかSpriteをクラスフィールドとして持たないといけないでしょう
- 描画更新のロジックをいじりたい場合はactをoverrideする必要がある
UIの状態とゲームの状態の管理とか、結構大事かなぁと思います。あとStageがルートなので情報を問い合わせするためにStageに依存する形になってしまいます。
ActorとSpriteの違い
もうひとつ、stack overflowネタです。
libgdx difference between sprite and actor
SpriteとActorの違いは何か?ということで。
Sprite
- SpriteはImageである。
- Low Level APIで2D描画が可能なもの
- position、size、rotationを持つ
- SpriteBatchなどで描画が可能
Actor
- scene2dのシーングラフの一部
- High Level API
- シーングラフの中で画像の位置調整などが可能(画面の絶対位置ではなくActor内の相対位置で調整ができます。local coordinates(ローカル座標)とも呼ばれるものだと思います)
- ルートがStageでStage自体は表示されない
- StageはActorのコンテナである
- Actorによって画面が系統立てる事ができる
- InputイベントのStage→Actorのpass down throughが優れている(シーングラフをStageから順々にActorに伝播する際にローカル座標に変換したり…のことだと思います)
- StageはActorがいつ描画されたかを知っている
- ActorはSpriteのようなTextureは持っていない点に注意
- 代わりに使うならImageクラスなどだろう(plainなActorにSpriteを持っているクラス)
- その他TextなどActorのサブクラスを使うのも方法の1つ
初めてlibGDXを触ると1画面とかのものになり、しかもUI構成もとても単純なものになるので Spriteだけで良いんじゃ?Actor要らないんじゃない?ってなりがちなんですが、それに対しての答えって感じで良いですね。
こういった比較は勉強になります。
Textクラスなど汎用的なUIクラスはscene2d.uiで提供されています。
https://github.com/libgdx/libgdx/wiki/Scene2d.ui
まとめ
とりとめなく書きましたが、簡単にまとめてみます。
- Scene2DはStageをルートとしたシーングラフを持つ
- ActorとはScene2Dの構成要素のでシーングラフのノードの1つ
- Actorは入れ子にできる
- Actorは描画機能が無い(空実装)
- Stege-Actor、Actor-Actor間はX、Y座標の変換が行われる
- Stege-Actor、Actor-Actor間はキャプチャ/ノーマルフェーズイベントがある
- 汎用的なActorを継承したUIを使いたければscene2d.uiに準備されている