reference count should be greater than 0

リファレンスカウンタ周りのエラー。

基本的にcocos2d-xのクラスはRefクラスを継承していて、リファレンスカウンタの仕組みを使用している。

Node#createインスタンスを生成する((newはスコープで制限されてるので呼べない))とRef#autorelease状態のインスタンスを取得できるが

クラスのメンバ変数は、自分でreleaseさせたいので、自分でリファレンスカウンタを意識する必要がある。

これがちゃんとなっていない所為で上記のエラーメッセージが出てたのでメモ。

とはいえ、カウンタを操作するRef#retainRef#releaseは、自分で呼びたくないので、マクロを使います。

//Part.h

class Part : public cocos2d::Node
{
public:    
    CC_SYNTHESIZE_RETAIN(cocos2d::Sprite*, _sprite, Sprite);
};
}

CC_SYNTHESIZE_RETAIN で宣言すると、内部的にRef#retainを呼んでくれる。setter,getterも自動で作られる。

//Part.cpp

Part::Part(Vec2* pos) : _pos(pos), _sprite(NULL) //まずnullを初期設定
{
 //画像をセット。テクスチャアトラスからとってきてます。
    this->setSprite(Sprite::createWithSpriteFrameName("snake_down.png"));
}

Part::~Part()
{
    CC_SAFE_RELEASE_NULL(_sprite);
}

・デストラクタでCC_SAFE_RELEASE_NULL呼んで、Ref#releaseする。

2Dゲームの数学らへんのことまとめ

サイン、コサイン、タンジェント

#直角三角形の場合のはなし

直角以外の角度から長さの比率を求めることが出来る。

sinθ = 対辺 / 斜辺

→対辺が求まる。= Y座標にあたる。

cosθ = 隣辺 / 斜辺

→隣辺が求まる。= X座標にあたる。

tanθ = 対辺 / 隣辺

アークサイン、アークコサイン、アークタンジェント

サイン、コサイン、タンジェントの値(つまり長さの比率)から、角度を求めることが出来る。

libgdxではアークタンジェントを求めてくれる関数↓

比率ではなく、長さそのものがパラメータになっている。

float angle = MathUtils.atan2( screenY - 200, screenX -200 );

radian(ラジアン)とDegree(角度)

radian(ラジアン)はPIを使って表した角度。

90度 = PI / 2

180度 = PI

パラメータや戻り値になっている角度がradian(ラジアン)なのかDegree(角度)なのかは、わかっておかないといけない。

上に書いたMathUtils#atan2の戻りは、radian(ラジアン)。

それに対して、とく回転で使うActor#setRotationに渡すパラメータはDegree(角度)なので、ラジアンからDegree(角度)に変換する必要がある。

radian * MathUtils.radDeg;

上で変換出来る。逆にデジ→ラジの変換係数もMathUtilsにある。

ベクトル

数学的には向きと力のこと。ゲームプログラミングだと単純に座標(X, Y)とかで使われる。

libGDXではcom.badlogic.gdx.math.Vector2,com.badlogic.gdx.math.Vector3のクラスがこれにあたる。

ピタゴラスの定理

h2 = a2 + b2 h2 のルート が斜辺の長さ

ピタゴラスの定理を使って、斜辺の長さがわかる。libgdxだとVector2#lenがこれに相当する。

2点A,Bの距離を求めたい場合は、以下のようにすればよい。

AのVector.sub(BのVector) = AB差分Vector
2点の距離 = AB差分Vector#len()

上記ピタゴラスの定理では距離が求まるが、正確な距離が知りたいのではない場合(2つの距離を比べてどちらが長いか、だけを知るときなど)は ルートの演算をする前の2乗したそのままの値で比較すればいい。

ルートの処理は地味に思いので、それをしないでいいところではしないほうが処理が軽い。

libgdxではVector2#len2がこれにあたる。

単位ベクトル

上記ピタゴラスの定理では、2点の差の距離と向きがわかるが、

自分で速さを調整する時などのために、向きだけの情報がほしかったりする。

その為、単位ベクトルに変換する。この変換することを正規化する と言ったりもする。

libgdxではVector2#norがこの正規化する関数にあたる。

補完

点AからBへ移動するのに、少しずつ移動する、というようなのを補完という。

移動だけの話じゃなくて、赤から青にちょっとずつ変えたりとか。そういうのにも使える。

libgdxのActions#moveByとかは移動差分と時間を指定しているので、単純に距離を時間でわってちょっとずつ動かしたりしてる。そういうののことをいう。

libgdxのクラスでjsonを使う

今作ってるゲームで、データを外部ファイル化させたいなあ。となった。 xmlでもいいんだけど、今回はjsonで。

jsonといえば、jacksonが有名なのかな、と思いますがlibgdxにもjsonを扱うクラスがあります。

新たなjarを追加しなくていいですしあまり難しいことをしないならこちらでも十分だと思います。

前提

・公式wikiのここ↓の内容です。

https://github.com/libgdx/libgdx/wiki/Reading-%26-writing-JSON

・注意:今作っているゲームは、不思議なダンジョンっぽいゲームなこともあって、 ダンジョンの情報を外で持っておきたいという趣旨なので、コードもそれっぽいです。

javaクラス

public class DungeonDataList {
    public Array<Dungeon> dungeons;  //全ダンジョン情報
}

class Dungeon{
    char id;   //ダンジョンのID
    protected String name; //ダンジョンの名前
    private int floorNo = 0; //ダンジョンのフロア数

    @Override
    public String toString() {
        return "Dungeon [id=" + id + ", name=" + name + ", floorNo=" + floorNo + "]";
    }

    public void setFloorNo(int floorNo) {
        this.floorNo = floorNo;
    }
}

ダンジョンの情報(ダンジョンの名前と、ダンジョンのフロア数) = Dungeonクラス

全てのダンジョンの情報(Dungeonクラス)を配列でもったDungeonDataを用意しておきました。

Javaオブジェクト→JSON文字列

Json#toJson で変換するオブジェクトと、変換する型を指定。

public class ObjectToJsonSampleListener extends ApplicationAdapter {
    @Override
    public void create() {
        DungeonDataList obj = new DungeonDataList();
        obj.dungeons = new Array<>();
        Dungeon dungeon1 = new Dungeon();
        dungeon1.id = 'A';
        dungeon1.name = "Home";  //おうちダンジョン
        dungeon1.setFloorNo(5);
        obj.dungeons.add(dungeon1);

        Dungeon dungeon2 = new Dungeon();
        dungeon2.id = 'B';
        dungeon2.name = "Forest";    //森ダンジョン
        dungeon2.setFloorNo(3);
        obj.dungeons.add(dungeon2);

        Json json = new Json();
        String jsonText = json.toJson(obj, DungeonDataList.class );
        jsonText = json.prettyPrint( jsonText, setting);
        System.out.println( jsonText );
    }
}

・実行結果

{dungeons:[{id:A,name:Home,floorNo:5},{id:B,name:Forest,floorNo:3}]}

JSON文字列→Javaオブジェクト

Json#fromJson に変換する型とJSON文字列を指定。

この例では、jsonファイルを読んで*1、その文字列をオブジェクトに変換します。

public class JsonToObjectSampleListener extends ApplicationAdapter {

    @Override
    public void create() {
        String jsonText = Gdx.files.internal( "dungeon.json" ).readString();
        Json json = new Json();
        DungeonDataList obj = json.fromJson( DungeonDataList.class, jsonText );

        System.out.println("dungeon1: " + obj.dungeons.get(0));
        System.out.println("dungeon2: " + obj.dungeons.get(1));
    }
}

・出力結果

dungeon1: Dungeon [id=A, name=Home, floorNo=5]
dungeon2: Dungeon [id=B, name=Forest, floorNo=3]

型引数を持ったArray,String,int, char を柔軟に変換できています。

また、フィールドのスコープも関係なく(getter/setterを作成しなくても)変換できていることがわかります。

入出力の設定

public class ObjectToJsonSampleListener extends ApplicationAdapter {
    @Override
    public void create() {
        //〜データ詰める部分省略

        Json json = new Json();
        String jsonText = json.toJson(obj, DungeonData.class );
        
        JsonValue.PrettyPrintSettings setting = new JsonValue.PrettyPrintSettings();
        setting.outputType = JsonWriter.OutputType.json;
        jsonText = json.prettyPrint( jsonText, setting);
        System.out.println( jsonText );
    }
}

・実行結果

{
"dungeons": [
    {
        "id": "A",
        "name": "Home",
        "floorNo": 5
    },
    {
        "id": "B",
        "name": "Forest",
        "floorNo": 3
    }
]
}

Json#prettyPrint でjson文字列に変換する際に、インデントなど整形してくれます。

JsonValue.PrettyPrintSettings#outputType で任意の形式(上の例はjson)に指定することで(これをやらないと"がなかったりします)、 整形されたJSON文字列が出力されます。

型が不明確なとき

public Array<Dungeon> dungeons; //全ダンジョン情報

これまでの例では、リスト(Array)クラスのフィールドが、Dungeonクラスだと明確にジェネリクスで宣言していました。

ですが、これでは同じようなことをするクラスが出てきた場合に非常に不便です。idやnameはダンジョンの情報でなくても持っていることも多いと思います。

public Array<T extends BaseData> dungeons; 

なのでBaseDataというスーパークラスを用意しておいて、条件つきのジェネリクスを定義するとします。

この場合、型が明確ではない(BaseDataを継承するなんらかの子クラス)ため、JsonValueオブジェクトとして扱われます。

※型を指定しない場合も同じだった

json文字列をどの型にマッピングするかを指定したい場合は、Json#setElementTypeで指定することができます。

json.setElementType(BaseDataList.class, "dungeons", Dungeon2.class);

基本的なことはここまで。このあとは必要が出てきたら追記

libgdxでandroidをバイブレーションさせる処理

ある日ゲームの中の演出でバイブさせたーい、ってなった。

前提

・ここの内容です↓

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

・libgdxのsetupツールを使ってプロジェクト構成している「てい」でファイルの場所とか書いてます。

AndroidManifest.xml を編集してバイブレーションさせる権限を追加

android/AndroidManifest.xml に以下の行を追加します。

    <uses-permission android:name="android.permission.VIBRATE"/> <!-- ★この行を追加!★ -->

こんなかんじ↓になる。追加する場所は<manifest>直下がよいと思われる。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.snoopopo.bf.android"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="23" />
    <uses-permission android:name="android.permission.VIBRATE"/> <!-- ★この行を追加!★ -->
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/GdxTheme" >
        <activity><!--省略します --></activity>
    </application>
</manifest>

ソース側

バイブレーションさせたいところで、

Gdx.input.vibrate(500);

引数は、ミリ秒なので、これで0.5秒バイブさせれます。

wikiにある通り、 0.5秒→0.2秒→0.8秒→ループ みたいにすることも出来ます。

実機(android)で試してみました〜。これで演出の幅が広がるね!

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;
            }