今日のlibGDX(20150706):E. 開発記 9 シーケンス遷移を改善した【20150715コメント頂いた分追記】

今日の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()でも出来るみたいです。

リソースの解放のお話も教えていただいたここ↓のサイトが詳しかった。

mikio.github.io

自分の今作ってるゲームの場合だと、シーケンス単位よりも細かい単位でリソースの解放(読み込みも)をするつもりなので、 自分で解放処理を書く、というのは都合良く使えそう。

コメントありがとうございました!