libGdxで任意のフォント(ttf)を使って日本語を表示する方法

ある日ゲームの中で日本語表示させたいー。ってなった。

前提

・いくつか方法はあるみたいなのですが,FreeTypeFontGeneratorを使う方法です。

・記事投稿現在最新の1.6.1のlibgdxを使用しています。

・内容はまんまここ↓の Other Tools の内容っすね

https://github.com/libgdx/libgdx/wiki/Bitmap-fonts

フォントを用意

www.flopdesign.com

今回はハン丸ゴシックというフォントをお借りしています。

かわいいこと山のごとし。

gdx-freetype-natives.jar, gdx-freetype.jar が必要

今回使ったFreeTypeFontGeneratorはこのjarにいる。

このjarは、extensions/gdx-freetype のディレクトリの配下にいる。

なのでこのjarをパス通しておく必要があります。

使い方

/**
 * Labelで任意のフォントをFreeTypeFontGeneratorで読み込んで日本語を表示するサンプル
 */
public class FreeTypeFontGeneratorSampleListener extends ApplicationAdapter {

    private Stage stage = null;

    @Override
    public void create() {
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);
        
        FreeTypeFontGenerator generator =
                new FreeTypeFontGenerator( Gdx.files.internal("font/" + "HanmaruGothic.ttf"));  //(1)

        FreeTypeFontGenerator.FreeTypeFontParameter parameter =
                new FreeTypeFontGenerator.FreeTypeFontParameter();    //(2)

        parameter.size = 24;
        parameter.borderColor = Color.PURPLE;
        parameter.borderWidth = 2;
        parameter.characters = "あいうえおかきくけこさしすせそたちつてとはひふへほ";    //(3)
        parameter.characters += FreeTypeFontGenerator.DEFAULT_CHARS;
        BitmapFont font = generator.generateFont(parameter);    //(4)

        Label.LabelStyle labelStyle = new Label.LabelStyle(font, Color.WHITE);//(5)
        Label label = new Label( "こんにちは", labelStyle );
        label.setPosition( 200, 300 );

        stage.addActor( label );
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);    //画面クリア

        stage.draw();
    }
}

・(1)でフォントファイル読み込みます。

・(2)FreeTypeFontParameter というクラスで文字色などを設定出来ます

・(3)FreeTypeFontParameter#character に表示したい文字で使う文字を渡してあげます。

これをやらない場合は、クラスみれば一発ですが、デフォルトでアルファベットなどが指定されています。

日本語を表示したい場合は、ここに日本語を指定してあげる必要ありです。

上のようにベタで書かずに文字コードとかでいけないかなーと思っているんですが・・うまくいかない;

・(4)BitmapFont インスタンスを作ります。

これであとは、BitmapFontで描画できる。

・(5)自分はよく直接Sprite使ってなくてscene2dを使っているんで、

com.badlogic.gdx.scenes.scene2d.ui.Labelを使った方が都合がよいでLabelを使う方法で。

LabelもActorなので、stage#addActorしてあげればよい。

表示結果

f:id:snoopopo:20150918183515p:plain

かわいいフォントで文字が出ましたー

「こんにちは」を渡しているのに、「こちは」しか出ていないのは、(3)のFreeTypeFontParameter#character に「ん」と「に」を渡していないからです。

とくにエラーも出ずに、ただ表示されないだけになります。

アクターが動きおわったら別のアクターを動かす【20150818追記】

アクターが動きおわったら別のアクターを動かす

今回やりたいのは、不思議のダンジョン系ゲームで「プレイヤーが攻撃→敵が攻撃」 という動作です。

歩行はプレイヤーと敵が同時に動きますが、それとちがって、「プレイヤーが動いたあとに敵が動く」=「アクターが動き終わったら別のアクターを動かす」を実装します。

関連記事

http://snoopopo.hatenablog.com/entry/2015/05/13/154146

上ではひとつのアクターに対して複数のアクションを順次実行することをやりました。

今回は別のアクターが動き終わったら動かすことするので、複数アクターで、順次シーケンスするような動きについてやります。

アクションが終わったタイミング

https://github.com/libgdx/libgdx/wiki/Scene2d から抜粋。

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

SequenceActionでアクションの動きが終わったあとに、終了時の処理であるオリジナルのアクションを実装すればいいっぽい。

つまり、Runnable#runの実装で別のアクターにアクションを設定してあげればよい!

20150818追記こっから↓↓↓

なんでこんなよくありそうな処理がこんなややこしいプログラムになるのかと思ったけど、落ち着いたら全然もっと簡単にかけたw ので、修正っす。

アクションが実行されるタイミング(超復習)

アクターに設定されたタイミング && (stageにactorが設定されてる前提)

actor.addAction(action);

なので、上の「アクションが終わったタイミング」で次に実行したいアクションをするアクターにアクションを設定してあげればよい。

直接設定しようとするとややこしくなるから今回はフラグでやるのでこんなイメージ

if (!move && 0 != list.size) {        //動いてないけど、動くの待ちのアクターがいる
        move = true;
        Actor actor = list.pop();
        actor.addAction(seqAction); //シーケンスアクションにして最後のアクションでにmoveフラグ解除
}

↓↓↓↓修正版↓↓↓↓

/**
 * 複数のアクターを順次アクションさせるサンプル
 * アクターAがアクションし終わったあとに、アクターBがアクションする
 * リスト可変対応版
 * Scene2dActionAttack2SampleListener からソース簡単にしたバージョン
 */
public class Scene2dActionAttack3SampleListener extends ApplicationAdapter {

    private Stage stage = null;
    private boolean move = false;

    private Array<ActionAndImage> list = new Array<>();

    @Override
    public void create() {
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);

        //ひとつ目のイメージ
        Image image1 = new Image(new Texture("./profile.gif"));
        image1.setPosition(200,300);

        SequenceAction seqAct = Actions.sequence();
        seqAct.addAction(Actions.moveBy( 16 , 16, 0.1F ));
        seqAct.addAction(Actions.moveBy( 16 * -1 , 16 * -1, 0.1F ));
        RepeatAction action1 = Actions.repeat(2, seqAct);
        list.add(new ActionAndImage(image1, action1));
        stage.addActor(image1);

        //2つ目
        Image image2 = new Image(new Texture("./profile.gif"));
        image2.setPosition(200,200);
        Action action2 = Actions.moveBy(-100, 0, 1);
        list.add(new ActionAndImage(image2, action2));
        stage.addActor(image2);

        //3つ目
        Image image3 = new Image(new Texture("./profile.gif"));
        image3.setPosition(200,100);
        Action action3 = Actions.moveBy(-150, 0, 1);
        list.add(new ActionAndImage(image3, action3));
        stage.addActor(image3);

    }

    class ActionAndImage{
        Image image = null;
        Action action = null;
        ActionAndImage(Image image, Action a) {
            this.image = image;
            this.action = a;
        }
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT);  //画面クリア

        stage.act(Gdx.graphics.getDeltaTime()); //よばないと動かなかった

        if (!move && list.size != 0) {
            move = true;

            Action endAction = (Actions.run(new Runnable() {
                public void run () { move = false;}
            }));

            ActionAndImage obj = list.pop();    //これだと詰めた逆順からになるからここは場合で変える

            SequenceAction seqAction = Actions.sequence();
            seqAction.addAction(obj.action);
            seqAction.addAction(endAction);
            obj.image.addAction( seqAction );
        }

        stage.draw();
    }

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

↓↓↓ここから下は追記(修正)前の内容なので見ないほうよい↓↓↓

まずベタに書く

/**
 * 複数のアクターを順次アクションさせるサンプル
 * アクターAがアクションし終わったあとに、アクターBがアクションする
 */
public class Scene2dActionAttackSampleListener extends ApplicationAdapter {

    private Stage stage = null;
    private boolean move = false;

    @Override
    public void create() {
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);

        Array<ActionAndImage> list = new Array<>();

        //ひとつ目のイメージ
        Image image1 = new Image(new Texture("./profile.gif"));
        image1.setPosition(200,300);

        SequenceAction seqAct = Actions.sequence();
        seqAct.addAction(Actions.moveBy( 16 , 16, 0.1F ));
        seqAct.addAction(Actions.moveBy( 16 * -1 , 16 * -1, 0.1F ));
        RepeatAction action1 = Actions.repeat(2, seqAct);
        list.add(new ActionAndImage(image1, action1));
        stage.addActor(image1);

        //2つ目
        Image image2 = new Image(new Texture("./profile.gif"));
        image2.setPosition(200,200);
        Action action2 = Actions.moveBy(-100, 0, 1);
        list.add(new ActionAndImage(image2, action2));
        stage.addActor(image2);

        //3つ目
        Image image3 = new Image(new Texture("./profile.gif"));
        image3.setPosition(200,100);
        Action action3 = Actions.moveBy(-150, 0, 1);
        list.add(new ActionAndImage(image3, action3));
        stage.addActor(image3);

        //----------------↑ここまで↑もらうデータの想定------------------------

        Action endAction = (Actions.run(new Runnable() {
            public void run () { move = false;}
        }));

        Action startAction = Actions.run(new Runnable() {
            public void run () { move = true;}
        });

        //3つめのActorに設定 Action3 -> end
        Action chainE_3 = Actions.run(new Runnable() {
            public void run () {
                SequenceAction seq3 = Actions.sequence();
                seq3.addAction(action3);
                seq3.addAction(endAction);
                image3.addAction(seq3);
            }
        });

        //2つめのActorに設定 Action2 -> ( Action3 -> end )
        Action chainE_3_2 = Actions.run(new Runnable() {
            public void run () {
                SequenceAction seq2 = Actions.sequence();
                seq2.addAction(action2);
                seq2.addAction(chainE_3);
                image2.addAction(seq2);
            }
        });

        //1つめのActorに設定 start -> Action1 -> { Action2 -> ( Action3 -> end ) }
        SequenceAction seq1 = Actions.sequence();
        seq1.addAction(startAction);
        seq1.addAction(action1);
        seq1.addAction(chainE_3_2);
        image1.addAction(seq1);
    }

    class ActionAndImage{
        Image image = null;
        Action action = null;
        ActionAndImage(Image image, Action a) {
            this.image = image;
            this.action = a;
        }
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT);  //画面クリア

        stage.act(Gdx.graphics.getDeltaTime()); //よばないと動かなかった
        stage.draw();

        System.out.println("アクション実行中?→ " + (move ? "yes." : "no!"));
    }

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

3つのアクションとアクションを設定するアクターのリストを用意しておいて、それを順次動かしています。

startActionで実行中フラグをたてて、endActionでフラグを戻します。

これでアクションが動いている間をチェックするつもりです。

アクターの数が可変でも対応

リストの数は可変にしたいので、上の状態を改変して以下のようなコードになります。

/**
 * 複数のアクターを順次アクションさせるサンプル
 * アクターAがアクションし終わったあとに、アクターBがアクションする
 * リスト可変対応版
 */
public class Scene2dActionAttack2SampleListener extends ApplicationAdapter {

    private Stage stage = null;
    private boolean move = false;

    private Array<ActionAndImage> list = new Array<>();

    @Override
    public void create() {

        /* おなじなので省略 */

        //----------------↑ここまで↑もらうデータの想定------------------------

        if (list.size == 0) return;    //0個だったら何もしない

        Action endAction = (Actions.run(new Runnable() {
            public void run () { move = false;}
        }));

        Action startAction = Actions.run(new Runnable() {
            public void run () { move = true;}
        });

        Action chainAction = null;
        if (list.size != 1) {
            ActionAndImage obj = list.pop();
            chainAction = chainAction( obj.action, endAction, obj.image );
        }

        SequenceAction seq = Actions.sequence();
        ActionAndImage first = list.pop();
        seq.addAction(startAction);
        seq.addAction(first.action);

        if (null == chainAction) {
            seq.addAction(endAction);
        } else {
            seq.addAction(chainAction);
        }
        first.image.addAction(seq);

    }

    /**
    * @param origin もともと設定する想定のアクション
    * @param chain ほかのアクターのアクションとつなぐためのアクション
    * @param image 設定対象のアクター
    * @return
    */
    private Action chainAction (Action origin, Action chain, Image image) {
        Action chainAction =  Actions.run(new Runnable() {
            public void run () {
                SequenceAction seq = Actions.sequence();
                seq.addAction(origin);
                seq.addAction(chain);
                image.addAction(seq);

            }
        });

        if (1 == list.size) {
            return chainAction;
        } else {  //listがひとつになるまで繰り返す
            ActionAndImage obj = list.pop();
            return chainAction(obj.action, chainAction, obj.image );
        }
    }

    class ActionAndImage{/* 同じなので省略 */ }

    @Override
    public void render() {/* 同じなので省略 */}

    @Override
    public void dispose() {/* 同じなので省略 */}
}

スマフォな入力処理

スマフォな入力処理

今までキーボードで動かしていたけど、一応スマフォで動くゲームを目指すので入力をそれっぽくしていってます。

https://github.com/libgdx/libgdx/wiki/Mouse%2C-touch-%26-keyboard

↑ここにある通り、PollingとEvent Handlingを使った場合をやります。

以前にやったClickLisnerがなぜここに入らない??っていうのがよくわからん。TODO

関連記事

http://snoopopo.hatenablog.com/entry/2015/08/04/030836

この記事の一番最後の「タッチで上下左右移動」は↑のサンプルを改変したやつ

Pollingを使った場合

https://github.com/libgdx/libgdx/wiki/Polling

/**
 * androidで動かすときに使えそうな入力処理関係
 * pollingをつかった場合
 */
public class InputSampleListener extends ApplicationAdapter {

    @Override
    public void render() {
        Gdx.gl.glClearColor( 1, 0, 0, 1 );
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT ); //画面クリア

        if (Gdx.input.isTouched()) {
            //押されたとき touchdown
            System.out.println("isTouched");
        }

        if (Gdx.input.justTouched()) {
            //押されて指を離したとき touchdown -> touchUp 長押ししている場合は呼ばれてない
            System.out.println("justTouched");
        }

        if (Gdx.input.isTouched(0) && Gdx.input.isTouched(1)) {
            //マルチタップ
            System.out.println("isTouched multi");
        }

        //画面の左上が(0,0)になっている。
        System.out.println("input x pointer :" + Gdx.input.getX() + " : y pointer :" + Gdx.input.getY() );

    }
}

左上が原点になっている!!注意!

       //画面の左上が(0,0)になっている。
        System.out.println("input x pointer :" + Gdx.input.getX() + " : y pointer :" + Gdx.input.getY() );

Pollingを使った場合 スワイプ(ドラッグ)っぽい動き

/**
 * androidで動かすときに使いそうな入力処理関係
 * スワイプしているときに画像が指にくっついてくるサンプル
 */
public class InputSwipeSampleListener extends ApplicationAdapter {

    private Stage stage = null;
    private Image image = null;

    @Override
    public void create() {
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);

        image = new Image(new Texture("./profile.gif"));
        stage.addActor(image);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor( 1, 0, 0, 1 );
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT ); //画面クリア

        if (Gdx.input.isTouched()) {
            //押されたとき touchdown
            System.out.println( "isTouched" );
            image.setPosition( Gdx.input.getX() - image.getWidth() / 2, Gdx.graphics.getHeight() - Gdx.input.getY() - image.getHeight() / 2 );
        }
        stage.draw();
    }

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

InputHandleを使った場合

https://github.com/libgdx/libgdx/wiki/Event-handling

/**
 * androidで動かすときに使いそうな入力処理関係
 * InputProcessorを使った場合
 */
class InputProcessorSampleListener extends ApplicationAdapter {

    @Override
    public void create() {
        //無名クラスで作成
        Gdx.input.setInputProcessor( new InputAdapter (){

            @Override
            public boolean touchDown(int screenX, int screenY, int pointer, int button) {
                //これも左上が0,0になってる
                System.out.println("touchDown " + screenX + ":" + screenY);
                return true;
            }

            @Override
            public boolean touchUp(int screenX, int screenY, int pointer, int button) {
                System.out.println("touchUp " + screenX + ":" + screenY);
                return true;
            }

            @Override
            public boolean touchDragged(int screenX, int screenY, int pointer) {
                System.out.println("touchDragged " + screenX + ":" + screenY);
                return true;
            }
        });
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor( 1, 0, 0, 1 );
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT ); //画面クリア
    }
}

こちらも原点が左上だということがわかります。

Wikiにある通り、

   Gdx.input.setInputProcessor(inputProcessor)

InputProccessorはsetしなくてはいけないです。↑↑

今まで、よく↓みたいにStageのインスタンスを渡していたけど、これはStageがInputProcessorの実装クラスだからでした。

   Gdx.input.setInputProcessor(stage)

InputHandleを使った場合 タッチで上下左右移動

画面のどこタッチしてもOKな上下左右移動です!これはゲームに使う動きの想定(▽)ぱあ

/**
 * タッチで上下左右移動
 */
public class InputProcessor2SampleListener extends ApplicationAdapter {

    private Stage stage = null;
    private OrthographicCamera cam; 

    private int cnt = 1;
    private boolean preEnter = false;

    private Array<Image> imagelist = null;
    private Image player = null;
    
    private float startPositionWidth;
    private float startPositionHeight;

    private int originX = 0;
    private int originY = 0;
    private Direction direction = null;
    enum Direction {
        FORWARD(0, 0, 1),
        LEFT (1, -1, 0),
        RIGHT(2, 1, 0),
        BACKWARD(3, 0, -1),
        ;

        int no;
        int moveX;
        int moveY;

        Direction(int no, int moveX, int moveY){
            this.no = no;
            this.moveX = moveX;
            this.moveY = moveY;
        }
    }

    @Override
    public void create() {
        
        stage = new Stage(){

            @Override
            public boolean touchDown(int screenX, int screenY, int pointer, int button) {
                //ここで最初に触ったところとっておいて
                originX = screenX;
                originY = screenY;
                return true;
            }

            @Override
            public boolean touchUp(int screenX, int screenY, int pointer, int button) {
                //上下左右判定
                int defX = 0;
                if (originX < screenX) {
                    defX = screenX - originX;
                    direction = Direction.RIGHT;
                } else {
                    defX = originX - screenX;
                    direction = Direction.LEFT;
                }

                if (originY < screenY) {
                    if (defX < (screenY - originY)){ direction = Direction.BACKWARD; }

                } else {
                    if (defX < (originY - screenY)){ direction = Direction.FORWARD; }
                }

                for (Image image : imagelist) {
                    image.addAction( Actions.moveBy( direction.moveX * 50, direction.moveY * 50, 1 ) );     //移動方向から移動アクションset
                }
                return true;
            }
        };

        Gdx.input.setInputProcessor(stage);

        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
            
        cam = (OrthographicCamera) stage.getViewport().getCamera();
        cam.setToOrtho(false, w/2, h/2); //カメラのサイズを画面サイズの半分にしてる
    
        imagelist = new Array<>();
        Texture p = new Texture("player_x0_y0.png");
        player = new Image(p);
        imagelist.add(player);  //ひとつ目
        imagelist.add(new Image(new Texture("player_x1_y0.png")));   //2つ目
        imagelist.add(new Image(new Texture("player_x2_y0.png")));   //3つ目
    
        startPositionWidth = cam.viewportWidth/2 - p.getWidth()/2;
        startPositionHeight = cam.viewportHeight - p.getHeight();
    
        for (Image image : imagelist) {
            image.setPosition(startPositionWidth, startPositionHeight);
            stage.addActor(image);
        }
        
        //移動したのがわかりにくいので、適当に表示
        Texture c = new Texture("enemy2_x0_y0.png");
        Image cat1 = new Image(c);
        cat1.setPosition(startPositionWidth + c.getWidth(), startPositionHeight);
        stage.addActor(cat1);
        
        Texture c2 = new Texture("enemy2_x0_y1.png");
        Image cat2 = new Image(c2);
        cat2.setPosition(startPositionWidth - c2.getWidth(), startPositionHeight - 120);
        stage.addActor(cat2);

        Texture c3 = new Texture("enemy2_x0_y2.png");
        Image cat3 = new Image(c3);
        cat3.setPosition(startPositionWidth + c3.getWidth(), startPositionHeight - 180);
        stage.addActor(cat3);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 1, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);   //画面クリア
        
        stage.act(Gdx.graphics.getDeltaTime()); //よばないとアクションが実行されなかった

        if (!preEnter && Gdx.input.isKeyPressed(Keys.ENTER)) {
            for (Image image : imagelist) {
                SequenceAction seqAct = Actions.sequence();
                seqAct.addAction(Actions.moveBy( 0, player.getHeight() /2 * -1, 0.2F ));
                seqAct.addAction(Actions.moveBy( 0, player.getHeight() /2, 0.2F ));
                image.addAction(Actions.repeat(2, seqAct)); //攻撃っぽい動き
            }
        }
        preEnter = Gdx.input.isKeyPressed(Keys.ENTER);
        
        for (Image image : imagelist) {
            image.setVisible(false); //いったん全部を非表示にして
        }

        //今のフレームで表示させる画像だけ表示する
        int rest = cnt % 10;
        if (rest <= 2) {
            imagelist.get(0).setVisible(true);
        } else if (rest <= 6) {
            imagelist.get(1).setVisible(true);
        } else {          
            imagelist.get(2).setVisible(true);
        }
        cnt++;
    
        //カメラの中心をプレイヤーに合わせる
        cam.position.x = player.getX();
        cam.position.y = player.getY();

        System.out.println(null == direction ? "" : direction.toString());
        stage.draw();
    }
    
    @Override
    public void resize(int width, int height) {
        cam.update();
    }

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

タッチされた最初の座標をとっておいて、

           @Override
            public boolean touchDown(int screenX, int screenY, int pointer, int button) {
                //ここで最初に触ったところとっておいて
                originX = screenX;
                originY = screenY;
                return true;
            }

指を離したタイミングで、差分をとって上下左右を判定しています。

例えば、右上方向に指をスライドさせて離した場合は、X座標とY座標で差分が大きい方の方向にしています。斜め移動する場合はいらないけど。

           @Override
            public boolean touchUp(int screenX, int screenY, int pointer, int button) {
                //上下左右判定
                int defX = 0;
                if (originX < screenX) {
                    defX = screenX - originX;
                    direction = Direction.RIGHT;
                } else {
                    defX = originX - screenX;
                    direction = Direction.LEFT;
                }

                if (originY < screenY) {
                    if (defX < (screenY - originY)){ direction = Direction.BACKWARD; }

                } else {
                    if (defX < (originY - screenY)){ direction = Direction.FORWARD; }
                }

                for (Image image : imagelist) {
                    image.addAction( Actions.moveBy( direction.moveX * 50, direction.moveY * 50, 1 ) );     //移動方向から移動アクションset
                }
                return true;
            }

Spriteを使った描画

memo~~

Spriteを使った描画

/**
 * Spriteを使った描画
 */
public class SpriteBatchSampleListener extends ApplicationAdapter {
    private SpriteBatch batch = null;
    Texture t = null;

    @Override
    public void create() {
        batch = new SpriteBatch();

        t = new Texture("./profile.gif");
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT);  //画面クリア

        batch.begin();
        batch.draw(t, 150, 200);

        batch.end();
    }

    @Override
    public void dispose () {
    }
}

libgdxのFileHandleでファイル操作(実機で動かしてつまったとこ。

ここ数日は、操作感を実機でためしてみたかったので作ったアプリを実機(android)で動かすことをしてみています。

今まではdesktop上でしか動かしていませんでしたが、androidで動かすためもあって、

gdx-setup.jar でプロジェクト構成を作りなおし → 今まで作ったものをのせかえ

をやったわけなんだけど、ファイルの読み込みに引っかかったのでメモ。

まずおきた問題

File f = new File(FILENAME);

このFileクラスは java.io.File です。

desktopで動かしていた(eclipse上 and javaコマンドで実行)場合は動いていましたが、これがandroidで動かすと FileNotFound になった。

desktopではカレントディレクトリはプロジェクトのあるフォルダだったのですが、androidだと違ったみたいです。

http://snoopopo.hatenablog.com/entry/2015/04/29/112310 の出力結果が、 / って出たのでroot???)

ただ機種によって違うとかもあったらやだので、あまりカレントディレクトリがどこか?を自分が意識したくないと思いました。

そこで、Libgdxの com.badlogic.gdx.files.FileHandle クラスです。

画像ファイルなどの置き場所

android開発をまったくしたことないのでよくわからないですが(>_<)、

android/assets/ 配下に、画像ファイルなどの資材をおいておくと都合がよかったです。なのでそこにおくことにした。

libgdxのFileHandleでファイル操作

https://github.com/libgdx/libgdx/wiki/File-handling にあるとおり、

Gdx.files.internal(FILENAME);

でとることが出来ます。

ここのFILENAMEは、 android/assets の下からのファイル名、またはフォルダ名を指定することが出来ました。

今までjava.io.Fileでファイルとって、InputStreamReaderで1文字ずつ読み込んで〜 みたいなことをしていたのですが、

FileHandle file = Gdx.files.internal(FILENAME);
Reader reader = file.reader();

while(render.read() != -1) { /* shori... */ }

みたいに、FileHandle#reader()でReaderクラス(中身はFileInputStream)をとることも出来ます。

desktop(たぶんhtmlとかで動かすときも)で動かすときは

android/assets/ の配下に画像ファイルをおいてしまったので、

desktop上で動かすときはパスを通してあげる必要がありましたー(メモ

プレイヤーの動きに合わせてカメラを移動させる

↑の状態、

プレイヤーの歩く動きに合わせてカメラが移動しているように見えるが、

カメラがマス単位(このゲームだと32dot)移動→そのあとにプレイヤーがドット単位で移動 という風になってしまっています。(カメラの動きがかくかくしてるかんじする。)

今回はこれを改善します。

関連記事

http://snoopopo.hatenablog.com/entry/2015/07/26/145013

http://snoopopo.hatenablog.com/entry/2015/07/16/170043

今回は、上記の内容で出来てなかった、アクション実行時もカメラをプレイヤーに合わせることです。

プレイヤーの動きに合わせてカメラを移動させる

移動と攻撃はどちらも、scene2dのActionクラスを使って移動させています。

/**
 * アクションの動きごと追跡するカメラ
 */
public class TrackActionCameraSampleListener extends ApplicationAdapter {

    private Stage stage = null;
    private OrthographicCamera cam; 

    private int cnt = 1;
    private boolean preEnter = false;
    private boolean preDown = false;
    
    private Array<Image> imagelist = null;
    private Image player = null;
    
    private float startPositionWidth;
    private float startPositionHeight;
    
    @Override
    public void create() {
        
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);

        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
            
        cam = (OrthographicCamera) stage.getViewport().getCamera();
        cam.setToOrtho(false, w/2, h/2); //カメラのサイズを画面サイズの半分にしてる
    
        imagelist = new Array<>();
        Texture p = new Texture("player_x0_y0.png");
        player = new Image(p);
        imagelist.add(player);  //ひとつ目
        imagelist.add(new Image(new Texture("player_x1_y0.png")));   //2つ目
        imagelist.add(new Image(new Texture("player_x2_y0.png")));   //3つ目
    
        startPositionWidth = cam.viewportWidth/2 - p.getWidth()/2;
        startPositionHeight = cam.viewportHeight - p.getHeight();
    
        for (Image image : imagelist) {
            image.setPosition(startPositionWidth, startPositionHeight);
            stage.addActor(image);
        }
        
        //移動したのがわかりにくいので、適当に表示
        Texture c = new Texture("enemy2_x0_y0.png");
        Image cat1 = new Image(c);
        cat1.setPosition(startPositionWidth + c.getWidth(), startPositionHeight);
        stage.addActor(cat1);
        
        Texture c2 = new Texture("enemy2_x0_y1.png");
        Image cat2 = new Image(c2);
        cat2.setPosition(startPositionWidth - c2.getWidth(), startPositionHeight - 120);
        stage.addActor(cat2);

        Texture c3 = new Texture("enemy2_x0_y2.png");
        Image cat3 = new Image(c3);
        cat3.setPosition(startPositionWidth + c3.getWidth(), startPositionHeight - 180);
        stage.addActor(cat3);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 1, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);   //画面クリア
        
        stage.act(Gdx.graphics.getDeltaTime()); //よばないとアクションが実行されなかった
        
        if (!preDown && Gdx.input.isKeyPressed(Keys.DOWN)) {
            for (Image image : imagelist) {
                image.addAction(Actions.moveBy(0, -100, 2)); //2秒間で100px前に前進
            }
        }
        
        if (!preEnter && Gdx.input.isKeyPressed(Keys.ENTER)) {
            for (Image image : imagelist) {
                SequenceAction seqAct = Actions.sequence();
                seqAct.addAction(Actions.moveBy( 0, player.getHeight() /2 * -1, 0.2F ));
                seqAct.addAction(Actions.moveBy( 0, player.getHeight() /2, 0.2F ));
                image.addAction(Actions.repeat(2, seqAct)); //攻撃っぽい動き
            }
        }
        
        preDown = Gdx.input.isKeyPressed(Keys.DOWN);
        preEnter = Gdx.input.isKeyPressed(Keys.ENTER);
        
        for (Image image : imagelist) {
            image.setVisible(false); //いったん全部を非表示にして
        }

        //今のフレームで表示させる画像だけ表示する
        int rest = cnt % 10;
        if (rest <= 2) {
            imagelist.get(0).setVisible(true);
        } else if (rest <= 6) {
            imagelist.get(1).setVisible(true);
        } else {          
            imagelist.get(2).setVisible(true);
        }
        cnt++;
    
        //カメラの中心をプレイヤーに合わせる
        cam.position.x = player.getX();
        cam.position.y = player.getY();
        
        stage.draw();
    }
    
    @Override
    public void resize(int width, int height) {
        cam.update();
    }

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

カメラのポジションにプレイヤーの位置をそのまま設定してあげればよい

カメラのポジションにプレイヤーの位置をそのまま設定してあげればよいだけでした。

       //カメラの中心をプレイヤーに合わせる
        cam.position.x = player.getX();
        cam.position.y = player.getY();

アクションが実行された際に、Actor#getX, Actor#getY でとれる値がちゃんと変わってくれてるわかるので、 この値を直接指定してあげればいきます。

というわけで

上記の内容を作っているゲームに反映させるとこのようになります。

歩行はよさそうですが、攻撃のときもカメラが動いているのでせわしない気もします。(SFC風來のシレンでは、攻撃時にはカメラが動いてないように見える)

歩行のときだけカメラを動かすとか、そのあたりの調整は必要なかんじ。

FPSLoggerでフレームレートをはかる

FPSLoggerでフレームレートをはかる

やってく過程で異常に処理が重いことに気づいたので、FPS(ゲームのジャンルのほうじゃなくて、Frame per secondのほう)をはかってみます。

com.badlogic.gdx.graphics.FPSLogger というクラスがLibGDXに存在したので、今回はこれを使ってみました。

FPSLogger (libgdx API)

・FPSLoggerSample.java

/**
 * FPSを計測出来るクラスFPSLoggerの使い方サンプル.
 * コンソールにFPS値のログがはかれます.
 */
public class FPSLoggerSampleListener extends ApplicationAdapter {

    private FPSLogger fpslogger = null;
    
    @Override
    public void create() {
        fpslogger = new FPSLogger();
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);   //画面クリア
        
        fpslogger.log();    //ここ呼んであげればよい
    }
}

インスタンスを生成して、ApplicationLisner#render()でlogを呼んであげればよいようです。

出力結果は以下のようなかんじで、1秒単位でログに出力されます。

FPSLogger: fps: 61
FPSLogger: fps: 60

LibGdxは可変フレームレートかと思っていたのですが、どんなに軽い処理(上のように何もしてない処理でも)60〜62位を保っていて、 それ以上にはあがらないですね。固定??

・パフォーマンス改善前(平均10FPS…)

ゲームに組み込むと、上のようにかなり重くてもちろん環境に依存すると思うのですが古いPCでやったら平均10FPS位でした。。

原因は、newをループの中でしていた為です。FPSが時間経過ごとのどんどん落ちていったのでそれで気づけました。

・パフォーマンス改善後(平均60FPS)

FPS60を保つのが目標でやっていくとよい、とひらしょー本にかいてあった。