サイトのトップへ戻る

libGDX ドキュメント 日本語訳

サイト内検索

Scene2d



概要

scene2d とは、actorの階層を使ったUIのアプリケーションを作成するための、 2D のシーングラフです。 scene2d は以下の機能を持っています:

  • 一つのグループの回転やサイズ変更が、全ての子actorへ適用されます。 子 actorはそれぞれ、自分が属するグループの座標系内で動作し、親に対して行った操作はそのまま子へと適用されます。

  • SpriteBatchを使った2D描写を簡単にします。 子actor は、回転やサイズ変更が反映されていない、左下隅に開始点(0,0)がある独自の座標系内で描写をします。

  • 回転や伸縮をしたActorの衝突判定。各 Actor は回転や伸縮をしていない自身の座標系を使って、自分が衝突しているかどうかを判別します。

  • 入力やその他イベントを適切なactorへ割り振ります。このイベントシステムは柔軟性があり、子 actorよりも前、もしくは後に、親 actorでイベントを制御させることができます。

  • actorを時間経過ごとに操作するためのAction システム。 Actionを連鎖させたり組み合わせたりして、複雑なエフェクトを実現できます。

scene2d には、レイアウト、描写、ゲームメニューの入力制御、HUDオーバーレイ、その他UIが備わっています。 scene2d.ui パッケージでは、多くのactorと、特にUIを作るためのその他ユーティリティが使用できます。

シーングラフには、モデルとビューを分離できないという欠点があります。 各Actorは、自身のサイズや位置といった、ゲーム内ではしばしばモデルデータとして見なされるデータを保持しています。 また、Actorは自分自身をどうやって描写するかを知っているので、ビューでもあります。 モデルとビューの両方の特性を持っているため、ActorをMVCの定義通りに分離するのは困難です。 MVCを気にしないUIやアプリで使用する場合は、特に問題はありません。

scene2d は、中核となる三つクラスを持っています:

  1. Actor クラスはグラフ内の一つのノードで、 position, rectangular size, origin, scale, rotation, colorを持ちます。

  2. Group クラスは、子actorを持つことができる actor です。

  3. Stage クラスは cameraと SpriteBatchとルートになる group を持ち、actorの描写と入力イベントの割り振りを制御します。



Stage

Stage は InputProcessor です。入力イベントを受け取った時、それを適切なactorに割り振ります。 stageを他のコンテンツの上位にあるUI(例えば、HUD)として使用する場合、InputMultiplexerを使って、まず最初にそのstageにイベントを処理するタイミングを渡すことができます。 stage内にあるactorがイベントを処理した場合、stageのInputProcessorメソッドはtrueを戻り値として返します。 trueが戻った場合は、そのイベントは処理されたので次のInputProcessorへは渡さないということを表します。

Stage には、最後のフレームが発生してからの経過時間を取得する act メソッドがあります。 Stageのactメソッドを実行することでscene内にある全てのactorのactメソッドが呼び出され、actorは経過時間に基づいてactionを実行することができます。 既定では、Actorのactメソッドを実行すると、actorの全てのactionが更新されます。 stageのactを呼び出すかどうかはあくまで任意ですが、呼び出さなかった場合はactorのactionとenter/exitイベントは発生しません。



Viewport

stageの viewport は、Viewport インスタンスによって決定されます。 viewport は Camera を管理し、stageが画面上でどのように表示されるか・stageのアスペクト比(伸縮されるかどうか)・黒塗り領域が表示されるか(レターボックス処理)、を制御します。また、 viewport は画面座標とstage座標との相互変換も行います。

viewport は、stageコンストラクタもしくは setViewportを使って設定されます。 アプリケーションウィンドウのサイズ変更が行える実行環境の場合は(例えばデスクトップ環境など)、 アプリケーションウィンドウのサイズが変更された時に stageの viewport を設定する必要があります。

以下は、actorを設定せずScreenViewportを使用した、最も基本的なscene2dアプリケーションの例です。 このviewportを使うと、stage内の各ユニットは1ピクセルに相当します。 つまり、stageは引き伸ばされたりしませんが、画面やウィンドウのサイズによってはstageに見えない範囲ができたり、逆にスペースが余ったりします。 これはUIアプリケーションを作成する場合にしばしば役立ちます。

private Stage stage;

public void create () {
    stage = new Stage(new ScreenViewport());
    Gdx.input.setInputProcessor(stage);
}

public void resize (int width, int height) {
    // See below for what true means.
    stage.getViewport().update(width, height, true);
}

public void render (float delta) {
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    stage.act(delta);
    stage.draw();
}

public void dispose() {
    stage.dispose();
}

viewportを更新する時に引数としてtrueを渡すとcameraの位置が移動し、stageの中央になります。 これにより画面の左下隅が(0,0)の位置になります。 これはcamera の位置がほぼ移動しないUIの場合に便利です。 camera の位置をあなたが自分で管理する場合は、falseを渡すか、 boolean型の引数を省略してください。 stageの位置を設定しない場合、既定では画面の中央が(0,0)の位置になります。

以下は StretchViewportを使った例になります。この stageのサイズ 640x480 は画面サイズに合わせるために変更されるので、stageのアスペクト比が変わる可能性があります。

    stage = new Stage(new StretchViewport(640, 480));

以下は FitViewportを使った例になります。この stageのサイズ 640x480 は画面サイズに合わせるために変更されますが、アスペクト比は変更されません。 余ったスペースを埋めるために、どちらかの側に黒塗り領域が追加されます(レターボックス処理)。

    stage = new Stage(new FitViewport(640, 480));

以下は ExtendViewportを使った例になります。最初にこの stageのサイズ 640x480 は画面サイズに合わせるために変更されますが、アスペクト比は変更されません。 それから、stageの短い側が画面の隙間を埋めるために長くされます。アスペクト比は変更されず、黒塗り領域は発生しませんが、stageの片側が長くなることがあります。

    stage = new Stage(new ExtendViewport(640, 480));

以下は最大サイズを設定して ExtendViewport を使った例になります。前述のように、最初にこの stageのサイズ 640x480 は画面サイズに合わせるために変更されますが、アスペクト比は変更されません。それから、stageの短い側が画面の隙間を埋めるために長くされます。 しかし、stageのサイズは最大サイズ 800x480を越えることはありません。 これを使うことで、黒塗り領域を表示することなく多くの異なるアスペクト比をサポートするゲーム内世界を表示することができます。

    stage = new Stage(new ExtendViewport(640, 480, 800, 480));

黒塗り領域を表示するほとんどの viewports では glViewportを使用しているので、 黒塗り領域内にstage を描写することができません。 glViewport の範囲を画面全体に設定し、stageの外部に黒塗り領域を描写することができます。

// Set the viewport to the whole screen.
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

// Draw anywhere on the screen.

// Restore the stage's viewport.
stage.getViewport().update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);

詳細についてはViewport を参照してください。



描写

stageの draw メソッドが呼び出された時、そのdraw メソッドはstage内の全ての actor のdrawメソッドを呼び出します。 各Actor のdraw メソッドは、以下のように上書きして描写を行えます:

public class MyActor extends Actor {
    TextureRegion region;

    public MyActor () {
        region = new TextureRegion(...);
    }

    @Override
    public void draw (Batch batch, float parentAlpha) {
        Color color = getColor();
        batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
        batch.draw(region, getX(), getY(), getOriginX(), getOriginY(),
            getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
    }
}

上記の draw メソッドでは、actorのposition, origin, size, scale, rotationを使ってregionを描写しています。 drawメソッドに引数として渡されている Batch には親の座標が設定されているので、座標(0,0)は親の左下隅になります。 これによって、親が回転したり拡大したりしても描写が簡単になります。 Batch.begin() については既に呼び出された状態になっているので、ここでは呼び出しません。 上記のように parentAlpha とこのactorのalpha値が掛け合わされた場合は、子actorは親の透明度の影響を受けます。 Batchの色は他のactorによって変更されていることがあるので、描写する前に各Actor内で色の設定をしておく必要があります。注意してください。

actorの setVisible(false) メソッドが実行された場合は、そのactorの draw メソッドは呼び出されなくなります。 そのactorは入力イベントも受け取らなくなります。

ShapeRendererのようなBatchとは異なる描写処理をactorで行う必要がある場合、一旦Batchのendを実行して、その後メソッドの最後に再度beginを実行する必要があります。 もちろん、これによってbatchのフラッシュ処理が行われるので慎重に使うはありますが。 以下のように、BatchのTransformMatrixとProjectionMatrixを使用することができます:

private ShapeRenderer renderer = new ShapeRenderer();

public void draw (Batch batch, float parentAlpha) {
    batch.end();

    renderer.setProjectionMatrix(batch.getProjectionMatrix());
    renderer.setTransformMatrix(batch.getTransformMatrix());
    renderer.translate(getX(), getY(), 0);

    renderer.begin(ShapeType.Filled);
    renderer.setColor(Color.BLUE);
    renderer.rect(0, 0, getWidth(), getHeight());
    renderer.end();

    batch.begin();
}


グループのtransform設定

あるグループが回転や伸縮される場合、子Actorは通常通り描写されて、その後Batchのtransform描写によって子Actorの回転や伸縮が正しく行われます。 groupの描写をする前にBatchのフラッシュ処理を行うことで、transform 処理ができるようになります。 グループが大量にある場合、このフラッシュ処理はパフォーマンス上のボトルネックになる可能性があります。 グループ内のactorに対して回転や伸縮といった処理を行わない場合は、グループに対して setTransform(false)を使用することができます。 When this is done, each child's position will be offset by the group's position for drawing, causing the children to appear in the correct location even though the Batch has not been transformed. このsetTransform(false)は、回転や伸縮といった処理を行うグループでは使用しないでください。



衝突判定

Actor のhitメソッドでは座標を引数として受け取り、その座標上に存在するActorの中で最深のものを戻り値として返します。 その座標にActorがいない場合はnullを返します。以下が既定の hit メソッドです:

public Actor hit (float x, float y, boolean touchable) {
    if (touchable && getTouchable() != Touchable.enabled) return null;
    return x >= 0 && x < width && y >= 0 && y < height ? this : null;
}

座標は、そのactorの座標系の形式で渡されます。渡された座標がこのactorの境界内に存在する場合は、この actor 自身を戻り値として返すだけです。 actorが円形の場合は、より高度なチェックが使用できます。 boolean型のtouchable 引数は、タッチ不可状態のactorを判定対象にするかどうかを表します。 これを使えば、タッチ不可状態ではないActorのみの判定を行うことができます。

stageの hit メソッドが呼び出された時、stageに属するルートグループの hit メソッドが呼び出されます。その後、順番に各子要素の hit メソッドが呼び出されます。最初に見つかったnullではないActorが、指定座標上に存在する階層最深Actorとして返されます。



イベントシステム

scene2d では一般的なイベントシステムを使用しています。各 actor は、actor上で発生するイベントを通知するためのリスナー一覧を持っています。 イベントの伝播には、二つのフェイズがあります。 一つ目、"キャプチャ"フェイズの間には、イベントはルートから各actorへ、 target actorに届くまで伝播されます。 このフェイズ中はキャプチャ用のリスナーのみが通知されます。 これにより、親は子にイベントが伝わる前に、そのイベントに割り込んだりキャンセルしたりすることができます。 二つ目、"通常"フェイズの間には、イベントはtarget actorから各actorへ、ルートに届くまで伝播されます。 このフェイズ中は通常のリスナーのみが通知されます。 これにより、actorはイベントを自分自身で制御したり、親に制御させたりすることができます。

各actorに渡されたイベントには、そのイベントの状態情報が含まれています。 target とは、イベントが発生したactorのことです。 listener actor とは、そのlistenerが登録されているactorのことです。 また、イベントにはいくつか重要なメソッドが実装されています。 stop イベントが呼び出された場合、イベントが発生したActorに設定されている他Lintenerへの通知は継続されますが、それ以外のActorではイベントを受け取らなくなります。 これを使って、そのActorの子(キャプチャフェイズの場合)や親(ノーマルフェイズの場合)にイベントが伝わるのを防ぐことができます。 イベントのcancel メソッドが呼び出された場合、stopメソッドと同じくイベントが伝わるのが停止されます。 さらにコードによって取得された、このイベントを発生させたaction自体も取り消されます。 例えば、チェックボックスがチェックされたイベントの場合、イベントをキャンセルするとチェックボックスのチェック自体が取り消されます。

例えば、子(label)を持つグループ(button)を想像してみてください。 labelがクリックされた時、キャプチャ用リスナーが発生します。 Usually there are none. 次にlabelの通常リスナーが通知されます。 labelの通常リスナーでは、labelはtargetでも listener actorでもあります。 イベントが停止されなかった場合、buttonはイベントを取得し、その通常リスナーが通知されます。 buttonの通常リスナーでは、labelがtargetでbuttonがlistener actorです。 この通知はルート位置にあるウィジットに届くまで伝播します。 このシステムによって、親に設定した一つのLintenerを使ってその子で発生したイベント制御できます。



InputListener

イベントを通知するために、actorへEventListenerを追加します。 EventListenerとは、handle(Event)メソッドとやり取りするためのインタフェースです。 EventListenerインタフェースを実装したクラスでは、instanceofを使ってそのイベントを制御すべきかどうかを判断します。 ほとんどのイベントでは便宜上、固有のlistener クラスが用意されています。 例えば、 InputListener はInputEventを受信して制御するために用意されています。 入力イベントの受信を開始するのに必要なのは、actor にInputListener を追加することだけです。 InputListener には上書き可能なメソッドがいくつかあり、その二つが以下になります:

actor.setBounds(0, 0, texture.getWidth(), texture.getHeight());

actor.addListener(new InputListener() {
    public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
        System.out.println("down");
        return true;
    }

    public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
        System.out.println("up");
    }
});

actorの範囲内で発生した入力イベントを受け取るためには、setBoundsを使ってactor の境界を指定する必要があるので注意してください。

タッチイベントと マウスイベントを制御するには、touchDowntouchDraggedtouchUp を上書きしてください。 touchDragged イベントとtouchUp イベントは、touchDown イベントでtrueを戻り値として返した場合のみ発生します。 またtouchDragged イベントとtouchUp イベントについては、actor上で発生していない場合でも受信します。 これにより、最も一般的なタッチイベントのユースケースを簡略化できます。

タッチやマウスの操作で actor 内に入ったり出たりした場合のenter イベントやexit イベントを制御するには、enterexitを上書きしてください。

マウスボタンが押されていない状態でのマウス移動を制御するには、mouseMoved メソッドを上書きします。

マウスのスクロール (デスクトップ環境でのみ発生)を制御するには、scrolled メソッドを上書きします。 これはスクロールフォーカスが設定されているactorでのみ呼び出されます。スクロールフォーカスはstageのsetScrollFocusを使って設定したりクリアしたりします。

キー入力を制御するには、keyDownメソッド、keyUpメソッド、keyTypedメソッドを上書きします。 これらはキーボードフォーカスが設定されているactorでのみ呼び出されます。キーボードフォーカスはstageのsetKeyboardFocusを使って設定したりクリアしたりします。

actorでsetTouchable(false)setVisible(false)を実行した場合は、入力イベントは受信しません。



その他のリスナー

他にも、一般的な入力イベントを制御するために用意されたリスナーがあります。 ClickListenerは、actor 上でタッチされたりマウスボタンが押されたりした時にtrue となるboolean型の値と、actor がクリックされた時に呼び出されるclickedメソッドを持っています。 ActorGestureListener は、actor上で発生したtap, longPress, fling, pan, zoom, pinchといったジェスチャーを検知します。

actor.addListener(new ActorGestureListener() {
    public boolean longPress (Actor actor, float x, float y) {
        System.out.println("long press " + x + ", " + y);
        return true;
    }

    public void fling (InputEvent event, float velocityX, float velocityY, int button) {
        System.out.println("fling " + velocityX + ", " + velocityY);
    }

    public void zoom (InputEvent event, float initialDistance, float distance) {
        System.out.println("zoom " + initialDistance + ", " + distance);
    }
});


Actions

全てのactorはactionリストを持っています。これらのactionは、Actorの actメソッドによって毎フレームごとに更新されます。 libgdxでは多くの種類のactionが用意されています。 actionはインスタンスを作成し、設定を行い、そしてactorへ追加することができます。 actionが完了すると、そのactionは自動的にactorから削除されます。

MoveToAction action = new MoveToAction();
action.setPosition(x, y);
action.setDuration(duration);
actor.addAction(action);


Action をプールする

必要になった際に毎回actionを新規に割り当てるのを防ぐために、プールを使用することができます。:

Pool<MoveToAction> pool = new Pool<MoveToAction>() {
    protected MoveToAction newObject () {
        return new MoveToAction();
    }
};
MoveToAction action = pool.obtain();
action.setPool(pool);
action.setPosition(x, y);
action.setDuration(duration);
actor.addAction(action);

action が完了した時、そのactionはactorから削除されて再利用のためにプールへ戻されます。 ですが上記のコードはとても冗長です。Actionsクラス(note the plural)には便利なメソッドが用意されています。 そのメソッドを使ってプールされたactionを取得できます:

MoveToAction action = Actions.action(MoveToAction.class);
action.setPosition(x, y);
action.setDuration(duration);
actor.addAction(action);

さらに良いことに、 Actions クラスにはプールされた設定済みactionを戻り値として返すメソッドがあります。

actor.addAction(Actions.moveTo(x, y, duration));

Actions クラスは、一回の呼び出しでactionを全て設定できる便利なメソッドを多く持っています。 static インポートを使用することで、これをさらに簡単にできます。static インポートを使うことで、毎回"Actions."と指定することなくActionsのstaticメソッドを参照することができるようになります。 Eclipseはあなたに代わってstaticインポートを追加しないため、あなたが自分でstaticインポートを追加する必要があるので注意してください。

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
...
actor.addAction(moveTo(x, y, duration));


複雑なアクション

アクションを同時に実行したり連続して実行したりすることで、より複雑なアクションを作成することができます。 パラレルアクションはアクションの一覧を持ち、それらを同時に実行します。 シークエンスアクションはアクションの一覧を持ち、それらを順々に実行します。 Actionsクラスのstaticインポートを使用することで、複雑なアクションの定義が非常に簡単になります:

actor.addAction(sequence(moveTo(200, 100, 2), color(Color.RED, 6), delay(0.5f), rotateTo(180, 5)));


アクションの完了

アクションが完了した時にコードを実行するには、RunnableAction を設定したsequenceを使用します:

actor.addAction(sequence(fadeIn(2), run(new Runnable() {
    public void run () {
        System.out.println("Action complete!");
    }
})));


Interpolation

時間経過ごとにactorを操作するactionでは、トゥイーン曲線を設定することができます。 これはactionにInterpolationのインスタンスを渡すことで行えます。 Interpolationクラスは、多くの静的フィールドを持っています。もしくは自分で独自のフィールドを記述することができます。 各interpolationの動作デモについてはInterpolationTest を参照してください。

MoveToAction action = Actions.action(MoveToAction.class);
action.setPosition(x, y);
action.setDuration(duration);
action.setInterpolation(Interpolation.bounceOut);
actor.addAction(action);

Actions クラスはinterpolationを受け取るメソッドを持っており、staticインポートを使ってInterpolationのstaticフィールドへのアクセスをより簡単にできます。 以下では InterpolationのbounceOutswing を使用しています:

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import static com.badlogic.gdx.math.Interpolation.*;
...
actor.addAction(parallel(moveTo(250, 250, 2, bounceOut), color(Color.RED, 6), delay(0.5f), rotateTo(180, 5, swing)));
actor.addAction(forever(sequence(scaleTo(2, 2, 0.5f), scaleTo(1, 1, 0.5f), delay(0.5f))));





エンジェル戦記