このページでは、Scene2D ライブラリについて見ていきます。 まず最初に注意しておくべきことは、scene2d を使用するかどうかについては完全に任意です。 使いたくない場合は使わなくて構いません。 Scene2Dを使わなくても、他ページで説明した内容は問題なく動作します。 さらに、Scene2Dをゲームの一部分だけで使用したい場合は(ゲームのHUDオーバレイのような)、それも可能です。
では scene2Dとは一体何なのでしょうか?一言で言えば、これは2Dシーングラフです。 するとあなたは「シーングラフとは何ですか?」と疑問に思うかもしれません。 良い質問ですね! 本質的に、シーングラフとはゲーム内世界の物質を保持するためのデータ構造です。 ですから、ゲーム内世界が数百のSpriteで構成されている場合、これらのSpriteはシーングラフ内に格納されています。 ゲーム内世界の中身を保持するのに加え、Scene2D にはデータに対して実行できるいくつかの機能が実装されています。 これには、当たり判定、ゲームオブジェクト間の階層の作成、入力の割り振り、時間経過ごとにノードを操作するためのactionの作成などのようなものがあります。
Scene2D は、LibGDX ライブラリ上に構築された、ゲームを作成するためのより高レベルのフレームワークと考えることができます。 これを使って非常に素晴らしいUIウィジットライブラリを作ることもできます… これについては後ほど説明します。
The object design of Scene2D is built around the metaphor of a play ( or at least I assume it is ). 階層の最上位は Stageになります。これはプレイ(ゲーム)が行われる場所です。 そのStageにはViewportが格納されています… Viewportとは、そうですね…ゲームを録画するカメラのようなものと考えてください (もしくは、観客の誰かの視点 )。 次に重要な抽象化された概念は、Actorです。ActorとはStageを成り立たせるための… スタッフです。 Actor とは必ずしもstage上で目に見える演者を意味するものではないので、この名前は少し誤解を招きそうです。 Actors could also include the guy running the lighting, a piece of scenery on stage, etc. 基本的に Actor とはゲームを構成するものです。 ですから基本的に、ゲームはActorによって構成された論理的なシーンごとに分割します( be it screens, stages, levels, whatever makes sense )。 改めて言いますが、この考え方があなたのゲームに合わない場合は、 Scene2Dを使う必要はありません。
これまでの説明はデザインの概念的なものなので、より実用的な例を見てみましょう。 一つのStageのシーンを作成して、それに一つのActorを追加しているだけです。
最近Batch/SpriteBatchに変更が行われたため、古いバージョンのLibGDXで以下コードは動作しません!最新のバージョンのLibGDXを使うようにしてください
package com.gamefromscratch; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; public class SceneDemo implements ApplicationListener { public class MyActor extends Actor { Texture texture = new Texture(Gdx.files.internal("data/jet.png")); @Override public void draw(Batch batch, float alpha){ batch.draw(texture,0,0); } } private Stage stage; @Override public void create() { stage = new Stage(Gdx.graphics.getWidth(),Gdx.graphics.getHeight(),true); MyActor myActor = new MyActor(); stage.addActor(myActor); } @Override public void dispose() { stage.dispose(); } @Override public void render() { Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); stage.draw(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } }
また、前回の例で使用した飛行機の画像を、jet.pngという名前でassets フォルダーに追加するのも忘れないでください。 やり方がわからない場合は、前回のチュートリアルを参照してください。このアプリケーションを実行すると以下が表示されます:
ご覧の通り、Stage2Dでの作業は非常に簡単です。Actor を継承したMyActorという名前の内部クラスを作成します。 MyActor はファイルから自身のtexture を読み込むだけです。 重要な部分は draw() メソッドです。draw() メソッドは、このActorの上位にあるStageから毎フレームごとに呼び出されます。 ここでは引数として渡されたBatchを使ってactorをstageへ描写しています。 Batch とは以前に見たSpriteBatch が実装しているインタフェースで、OpenGLへの描写呼び出しの一括処理を司っています。 今回の例では、batch の(0,0)の位置にTextureを描写しているだけです。 Actor はSpritesheetなどからプログラム上で簡単に生成することもできます。 ここで一つ指摘しておかなければいけないことは、今回の例は簡略化した内容になっています。 MyActor が破棄された時に使用しているTexture がリークしてしまうので、実際にゲームを作る際には別の方法でこれらを管理してください!
アプリケーションの create() メソッドでは、アプリの解像度を引数として渡してstageを作成します。 引数にtrue の値を設定した場合は、端末のアスペクト比を維持するということを示します。 stage を作成した後にMyActorのインスタンスを作成し、stage.addActor()を呼び出してMyActorをstageに追加します。 次にrender()メソッドの最初のほうで、画面をクリアした後に draw() を呼び出してstage を描写します。 stage のdraw()メソッドでは、stage に含まれる全てのactgorのdraw()メソッドを順々に呼び出します。 最後に、アプリのdispose()呼び出し内でstageを破棄し、リークの発生を防いでいることが分かるでしょう。
以上では、Scene2D ベースアプリケーションの基本的な構造について見てきました。 実際にactorに何かをさせたり、actorをどのようにして制御するかについては説明していません。 このプロセスは非常に簡単ですが、いくつか落とし穴があります。 コードを修正したので、それを見てみましょう。修正部分は強調表示をしています。:
package com.gamefromscratch; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.Touchable; public class SceneDemo2 implements ApplicationListener { public class MyActor extends Actor { Texture texture = new Texture(Gdx.files.internal("data/jet.png")); float actorX = 0, actorY = 0; public boolean started = false; public MyActor(){ setBounds(actorX,actorY,texture.getWidth(),texture.getHeight()); addListener(new InputListener(){ public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { ((MyActor)event.getTarget()).started = true; return true; } }); } @Override public void draw(Batch batch, float alpha){ batch.draw(texture,actorX,actorY); } @Override public void act(float delta){ if(started){ actorX+=5; } } } private Stage stage; @Override public void create() { stage = new Stage(); Gdx.input.setInputProcessor(stage); MyActor myActor = new MyActor(); myActor.setTouchable(Touchable.enabled); stage.addActor(myActor); } @Override public void dispose() { } @Override public void render() { Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); stage.act(Gdx.graphics.getDeltaTime()); stage.draw(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } }
上記コードを実装すると、以下が表示されます:
飛行機のspriteをクリックするとaction が開始されます。 それでは、コードを詳しく見てみましょう。
MyActor クラスの変更内容を見てみましょう。一目で分かる変更内容は、コンストラクタが追加されたことです。 これによりイベントリスナーをactorへ追加できます。これは以前入力制御をした時に扱ったイベントリスナーと非常に似た働きをします。 ですが今回は、getTarget()メソッドが定義されたInputEvent クラスを引数として渡しています。getTarget()では、タッチされたActor を取得できます。 取得したActorをMyActor オブジェクトへ変換し、boolean 値のstarted をtrueに設定するだけです。 注意すべき重大な点は、setBounds()を呼び出すことです。 この呼び出しは非常に重要です! Actorを継承した場合は、setBoundを設定する必要があるのです。そうしないとクリックやタッチができません。 私はこの落とし穴にはまって多くの時間を費やしてしまいました。 setBounds()で境界を設定して、Actor が持つTextureとサイズを合わせるだけです。 もう一つ注意すべき重要な点は、Actor イベントの制御に関するサンプルコードや文書の多くは現時点から見て古すぎるものであり、なおかつActor イベントでは過去に重大な変更があったということです!
コンストラクター以外でMyActor へ行った大きな変更は、act()メソッドの追加です。 draw()と同じく、stageのact() メソッドを呼び出すと、stage上にある全てのactorのact()メソッドが呼び出されます。ここでは、時間経過ごとにactor を更新します。 他の多くのフレームワークでは、これはact ではなく update()と呼ばれているでしょう。 今回のコードでは、毎フレームごとにMyActor のX位置に5ピクセル追加しているだけです。 もちろん、この処理はstarted フラグがtrueになった場合のみに行われます。
create() メソッド内では、細かい変更がいくつかあります。 一つ目は。InputProcessorを登録する必要があるということです。 Stage はInputProcessorを実装しているので、stage オブジェクトをsetInputProcessor()に引数として渡すだけです。 以前も見たように、stage は全ての子ActorのInputListeners の呼び出しを処理します。 またactor をタッチ可能なものとして設定していますが、きっとactor は既定でもタッチ可能となっていることでしょう。 actor をタッチやクリック不可として設定したい場合は、代わりにTouchable.disabledを引数として渡してください。 その他の変更はrender()メソッドだけで、前回のフレームからの経過時間を stage.act()に渡して呼び出しています。 stage.act()を実行すると、様々なactorに自身のact()を呼び出させます。
Scene2D は非常に大きな内容なので、何回かのパートに別けて説明していきます。