今日のlibGDX目次
http://snoopopo.hatenablog.com/entry/2015/04/27/220545
今日のテーマ:E. 開発記 9 シーケンス遷移を改善した
人に助言を頂けたのもありシーケンス遷移を改善したので、メモしておきます。
ちょいちょいシーンとシーケンスってまざって言ってるけど、僕は同じ意味だと思って書いてるので注意。
やったことは以下です。
・それぞれのシーンをクラス化
・swich文,シーンを定義してた列挙型の削除
それぞれのシーンをクラス化
今までメソッドでそれぞれシーンを作っていましたが、クラスにした。以下理由。
・処理のながれ一緒
初期処理(遷移直後の1フレームだけ) → 更新処理
・シーンそれぞれに書いていた処理でだぶってる部分ある
というわけで上記の同じ部分は基底クラス(抽象クラス)で行い、固有の処理だけ子クラス側でやるように修正。
package jp.snoopopo.e.core; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Image; import static jp.snoopopo.e.core.MainLisner.assetMng; import static jp.snoopopo.e.core.MainLisner.PATH_SE_ENTER; import static jp.snoopopo.e.core.MainLisner.PATH_IMG_GAME; /** * シーン基本クラス. * @author snoopopo * */ public abstract class BaseScene { private static final int ROUTINE_INIT = 0; private static final int ROUTINE_PROCESSING = 1; private static int routineNo = ROUTINE_INIT; protected static boolean spTutch = false; protected Image retry = null; protected TextureAtlas gameResource = assetMng.get(PATH_IMG_GAME, TextureAtlas.class); /** * このシーンの処理を実行する * @param stage このシーンで生成されたアクターを設定するステージ */ public void update(Stage stage) { switch (routineNo) { case ROUTINE_INIT : init(stage); routineNo = ROUTINE_PROCESSING; break; case ROUTINE_PROCESSING : processing(); break; } } abstract void init(Stage stage); abstract void processing(); protected void transition(BaseScene scene) { //シーケンス設定 MainLisner.scene = scene; routineNo = ROUTINE_INIT; spTutch = false; assetMng.get(PATH_SE_ENTER, Sound.class).play(); } }
switch文,シーンを定義してた列挙型の削除
・改善前のMainLisner(ApplicationAdapter)
public class MainLisner extends ApplicationAdapter { //actor. private Stage stage = null; @Override public void render() { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); //画面クリア updateGame(); draw(); } private void updateGame() { stage.act(Gdx.graphics.getDeltaTime()); switch (status){ case TITLE : sceneTitle(); break; case PLAYING : sceneMain(); break; case CLEAR : sceneClear(); break; case GAMEOVER : sceneGameOver(); break; } } private static enum GameState { TITLE, PLAYING, CLEAR, GAMEOVER; } }
今までのソース↑はswitchで列挙型で定義してた分を分岐してたわけなのだが、 これだと、シーンが増える度にケースと、列挙型の定義を増やす必要が出てくる。
かなり小規模だから今はいいけど、大きいゲームだとまずいので、以下のように修正。
・MainLisner(ApplicationAdapter)
public class MainLisner extends ApplicationAdapter { static BaseScene scene = null; private Stage stage = null; @Override public void render() { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); //画面クリア updateGame(); draw(); } /** * ゲームの更新 */ private void updateGame() { stage.act(Gdx.graphics.getDeltaTime()); if (!assetMng.update()) { System.out.println("Now Loading"); return; } if (null == scene) { //初回のみ scene = new SceneTitle(); } scene.update(stage); }
・それぞれのシーンクラス(これはタイトル用のクラス)
package jp.snoopopo.e.core; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import static jp.snoopopo.e.core.MainLisner.assetMng; import static jp.snoopopo.e.core.MainLisner.PATH_BGM; import static jp.snoopopo.e.core.MainLisner.preEnter; /** * タイトルクラス. * @author snoopopo * */ public class SceneTitle extends BaseScene { private Image start = null; private Image title = null; @Override void init(Stage stage) { /* 初期処理してる*/ } @Override void processing() { if (spTutch || (!preEnter && Gdx.input.isKeyPressed(Keys.ENTER))) { //「タイトル」→「プレイ中」 transition(new SceneMain()); title.remove(); start.remove(); } } }
遷移するタイミングで次のシーンクラスをnewして設定します。 ここでnewしたSceneMainはBaseSceneの子クラスです。
設定後は、次のフレームでMainLisnerが子クラスのシーンクラスを意識せずにBaseSceneのupdateをよんでくれるかんじです。
ここまでのソース
https://github.com/snoopopo/e/tree/v0.11
↓↓↓↓ 20150715 ここから追記分 ↓↓↓↓
Gameクラスでシーケンス遷移
ApplicationListenerを実装してるGameクラス(abstract)というクラスがLibGDXに用意されているというコメントを頂きました!ありがとうございます。
というわけでさっそく試してみた。
Gameクラスの中にはScreen(インタフェース)のメンバがいて、これが今のシーケンス(シーン?スクリーン?)になるみたいです。
・今までApplicationAdaptorを継承していたクラスは、Gameにかわる
public class MainLisner extends Game { @Override public void create() { setScreen(new ScreenTitle(this)); } }
はじめに遷移したいタイトル画面相当のクラスScreenTitleをnewして、設定してあげます。(以前はSceneTitleだったクラス)
・それぞれのスクリーンクラスはScreenインタフェースを実装すればいい。
今回は、Screenを実装したScreenAdapter(abstract)がいたので、これを継承しました。 ScreenAdapterの中身は全部空メソッドなので、上書きしたいとこだけ書きます。
以下はタイトル画面から遷移したゲームのメイン画面相当のクラスです。
public class ScreenGame extends ScreenAdapter { private Game game = null; public ScreenGame(Game game){ this.game = game; stage = new Stage(); Gdx.input.setInputProcessor(stage); camera = new OrthographicCamera(); assetMng = new AssetManager(); assetMng.load(ATLAS_FIELD_CONTENT, TextureAtlas.class); // いかりゃく } @Override public void render (float delta){ Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); //画面クリア stage.act(delta); if (assetMng.update()) { draw(stage, assetMng); } update(); stage.draw(); // シーケンス遷移 setScreenする } @Override public void hide() { assetMng.dispose(); stage.dispose(); } @Override public void resize(int width, int height) { camera.update(); } //something.... }
コンストラクタでGame自体を受け取らないと、次のスクリーンを設定出来ないので、引数でもらいます。
コンスラクタでStageやAssetManagerをnewしているのですが、こういう初期処理は、show()でも出来るみたいです。
リソースの解放のお話も教えていただいたここ↓のサイトが詳しかった。
自分の今作ってるゲームの場合だと、シーケンス単位よりも細かい単位でリソースの解放(読み込みも)をするつもりなので、 自分で解放処理を書く、というのは都合良く使えそう。
コメントありがとうございました!