サイトのトップへ戻る

libGDX ドキュメント 日本語訳

サイト内検索

入力を制御する - タッチとジェスチャー

前回のチュートリアルでは、 マウスとキーボードのイベントを、イベント駆動型とポーリングで制御する方法を見てきました。 今回はタッチがどのように機能するかを見ていきます。 今回のチュートリアルではタッチ操作が可能な端末が必要ですが( マウスでマルチタッチ操作をするというのは、控えめに言ってやりづらいです! )、全てのコードはデスクトップ環境やHTML環境でも動作します。 コードのテストができないだけです。それでは、例を見てみましょう。この例では複数同時タッチを制御する方法を説明しています:



マルチタッチ

package com.gamefromscratch;

import java.util.HashMap;
import java.util.Map;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class InputDemo2 implements ApplicationListener, InputProcessor {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "Touch something already!";
    private int w,h;
    
    class TouchInfo {
        public float touchX = 0;
        public float touchY = 0;
        public boolean touched = false;
    }
    
    private Map<Integer,TouchInfo> touches = new HashMap<Integer,TouchInfo>();
    
    @Override
    public void create() {        
        batch = new SpriteBatch();    
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
        font.setColor(Color.RED);
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
        Gdx.input.setInputProcessor(this);
        for(int i = 0; i < 5; i++){
            touches.put(i, new TouchInfo());
        }
    }

    @Override
    public void dispose() {
        batch.dispose();
        font.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        
        message = "";
        for(int i = 0; i < 5; i++){
            if(touches.get(i).touched)
                message += "Finger:" + Integer.toString(i) + "touch at:" +
                        Float.toString(touches.get(i).touchX) +
                        "," +
                        Float.toString(touches.get(i).touchY) +
                        "¥n";
                                
        }
        TextBounds tb = font.getBounds(message);
        float x = w/2 - tb.width/2;
        float y = h/2 + tb.height/2;
        font.drawMultiLine(batch, message, x, y);
        
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public boolean keyDown(int keycode) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean keyUp(int keycode) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        if(pointer < 5){
            touches.get(pointer).touchX = screenX;
            touches.get(pointer).touchY = screenY;
            touches.get(pointer).touched = true;
        }
        return true;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        if(pointer < 5){
            touches.get(pointer).touchX = 0;
            touches.get(pointer).touchY = 0;
            touches.get(pointer).touched = false;
        }
        return true;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        // TODO Auto-generated method stub
        return false;
    }
}

このコードを実行すると、あなたがタッチした各指ごとの診断情報が表示されます:

photo

各指ごとに、指がタッチした座標を表示します。座標が表示される指の数は最大で5つまでです;

では、このコードでは具体的にはどういった処理をしているのでしょうか? ここではタッチの詳細時情報(タッチされているかどうか、タッチされたXY座標)を保持する簡単なTouchInfoクラスを作成しています。 それから、キーとしてのInteger型とデータとしてのTouchInfoクラスを持つ、ハッシュマップtouches を作成しています。 キーはタッチした指のインデックスです。 実際のロジック部分はtouchDownイベントハンドラーとtouchUpイベントハンドラーに記述してあります。 touch downイベントでは、pointer値で表されたマップtouchesを更新しています。 前回のチュートリアルを思い出すかもしれませんが、pointer値は現在どの指が押されているかを表します。 指が離された時はtouchUpが呼び出されて。その中でtouchの位置情報をクリアするだけです。 最後に、render()内ではtouchesマップをループ処理して、タッチされた指とタッチされた場所を表示しています。

結局のところ、複数操作できる点とボタンがない点を除けば、基本的にタッチはマウスクリックと同じものです。 おっと、今回のサンプルでタッチの最大数を5に制限していますが、これは私が任意に選んだだけの数字です。 LibGDX は最大で同時20タッチをサポートしていますが、実際にそれだけサポートしている端末はありません。 例えば、iPadは最大11タッチまで追尾でき、iPhoneは最大5タッチまで追尾でき、一方HTCの端末は10タッチまで追尾します。 Google製携帯の場合は、 このアプリを使ってどれだけの数の同時タッチをサポートしているか確認できます。 つまり、5というのはとても安全で合理的な数なのです… まあ、私は端末の操作で指を4つ以上も使おうなんて思いませんが。



タッチジェスチャー

モバイルの世界で広く普及している、いくつかの一般的なジェスチャーがあります。 ピンチズームやフリック(フリング)や長押しのようなジェスチャーが当たり前になってきています 幸いなことに、libGDXではこれら全てのジェスチャーを簡単に使えるようにサポートしています。 それでは、簡単なデモを見てみましょう:

package com.gamefromscratch;


import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.math.Vector2;

public class InputDemo3 implements ApplicationListener, GestureListener {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "No gesture performed yet";
    private int w,h;

    
    @Override
    public void create() {        
        batch = new SpriteBatch();    
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
        font.setColor(Color.RED);
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
        
        GestureDetector gd = new GestureDetector(this);
        Gdx.input.setInputProcessor(gd);
    }

    @Override
    public void dispose() {
        batch.dispose();
        font.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        
        TextBounds tb = font.getBounds(message);
        float x = w/2 - tb.width/2;
        float y = h/2 + tb.height/2;
        
        font.drawMultiLine(batch, message, x, y);
        
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public boolean touchDown(float x, float y, int pointer, int button) {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean tap(float x, float y, int count, int button) {
        message = "Tap performed, finger" + Integer.toString(button);
        return true;
    }

    @Override
    public boolean longPress(float x, float y) {
        message = "Long press performed";
        return true;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        message = "Fling performed, velocity:" + Float.toString(velocityX) +
                "," + Float.toString(velocityY);
        return true;
    }

    @Override
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        message = "Pan performed, delta:" + Float.toString(deltaX) +
                "," + Float.toString(deltaY);
        return true;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        message = "Zoom performed, initial Distance:" + Float.toString(initialDistance) +
                " Distance: " + Float.toString(distance);
        return true;
    }

    @Override
    public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
            Vector2 pointer1, Vector2 pointer2) {
        message = "Pinch performed";
        return true;
    }

}

このコードを実行すると、様々なジェスチャーを行うとそのジェスチャーの情報が画面中央に表示されます。 サポートしているジェスチャーは、タップ、フリック(フリング)、ピンチ(二つの指をお互いへ近づけるように動かす)、ズーム(二つの指をお互いに遠ざけるように動かす )、パン( 指を一つ画面につけた状態でスライドさせる ) 、長押し ( タップした後画面に触れたままの状態にする ) だけでなく、古き良きファッションタッチもサポートしています。

タッチイベント、マウスイベント、キーボードイベントを制御する時にInputProcessorを実装したのと同じ様に、LibGDXからジェスチャーイベントを受け取るにはGestureListenerを実装します。 create()メソッド内で、GestureListenerを引数として渡してGestureDetectorを作成し、前回と同じ様にGdx.input.setInputProcessor()を使ってGestureDetectorを登録します。 各イベントごとに、対応するGestureListenerのイベントを発生させます。 各イベント内では、表示されているメッセージを直近の行われたイベントを反映したものに更新しているだけです。

ここで説明していないませんが、GestureDetectorの設定は重要です …  長押しの長さをどれくらいにするか、ドラッグをどれくらいの長さ続ければフリックになるのか、などはどうやって決めれば良いのでしょうか? 簡単な解決方法は、GestureDetectorのコンストラクタを使用することです。 これについての詳細は ここで読むことができます。



複数のInputProcessorを制御する

では、ジェスチャーイベントとキーボードイベントを同時に制御したい場合は… どうすればいいのでしょうか? 幸いなことに、答えはとても簡単で、setInputProcessorの引数にInputProcessorやGestureDetectorを渡すのではなく、InputMultiplexerを渡します。 以下のように:

package com.gamefromscratch;


import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.math.Vector2;

public class InputDemo4 implements ApplicationListener, GestureListener, InputProcessor {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "No gesture performed yet";
    private int w,h;

    
    @Override
    public void create() {        
        batch = new SpriteBatch();    
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
        font.setColor(Color.RED);
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
        
        InputMultiplexer im = new InputMultiplexer();
        GestureDetector gd = new GestureDetector(this);
        im.addProcessor(gd);
        im.addProcessor(this);
        
        
        Gdx.input.setInputProcessor(im);
    }

    @Override
    public void dispose() {
        batch.dispose();
        font.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        
        TextBounds tb = font.getBounds(message);
        float x = w/2 - tb.width/2;
        float y = h/2 + tb.height/2;
        
        font.drawMultiLine(batch, message, x, y);
        
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public boolean touchDown(float x, float y, int pointer, int button) {
        message = "Touch down!";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean tap(float x, float y, int count, int button) {
        message = "Tap performed, finger" + Integer.toString(button);
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean longPress(float x, float y) {
        message = "Long press performed";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        message = "Fling performed, velocity:" + Float.toString(velocityX) +
                "," + Float.toString(velocityY);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        message = "Pan performed, delta:" + Float.toString(deltaX) +
                "," + Float.toString(deltaY);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        message = "Zoom performed, initial Distance:" + Float.toString(initialDistance) +
                " Distance: " + Float.toString(distance);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
            Vector2 pointer1, Vector2 pointer2) {
        message = "Pinch performed";
        Gdx.app.log("INFO", message);

        return true;
    }

    @Override
    public boolean keyDown(int keycode) {
        message = "Key Down";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean keyUp(int keycode) {
        message = "Key up";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean keyTyped(char character) {
        message = "Key typed";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        message = "Touch Down";
        Gdx.app.log("INFO", message);

        return false;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        message = "Touch up";
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        message = "Touch Dragged";
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        message = "Mouse moved";
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        message = "Scrolled";
        Gdx.app.log("INFO", message);
        return false;
    }

}

一度に複数のイベントを発生させ、それを画面に表示するのに加えて、Gdx.app.log()を使ってログ出力も行っています。 EclipseのLogCatウィンドウで、ログ出力されたイベントを見ることができます:

image

また android-sdk tools フォルダーにはddmsという名前のプログラムがあり( これはWindowsの BAT スクリプトです ) 、これを使うと同じ情報を表示できます。

image

以上が、log() で行っている処理の内容になります…それでは、コードの内容に戻りましょう。重要な部分は以下です:

InputMultiplexer im = new InputMultiplexer();
GestureDetector gd = new GestureDetector(this);
im.addProcessor(gd);
im.addProcessor(this);

Gdx.input.setInputProcessor(im);

基本的にはmultiplexerを作成し、それからaddProcessor()メソッドを使ってInputProcessorとGestureDetectorの両方をmultiplexerに追加し、そのmultiplexerを setInputProcessor()に引数として渡します。 Otherwise the code works pretty much exactly the same.  ここでは致命的に重要なことが二つあります。 一つ目に、processorをmultiplexorへ追加する順番によって、発生したイベントを制御する順番が決まるようなのです。 二つ目に、これはとても重要なことで、イベントハンドラー内でtrueを戻り値として返した場合は、そのイベントは制御済みということを意味します。 これは理解しておくべき重要な概念なので、少しの間これについて考えてみましょう。 touch upやtouch downのようなイベントを制御するのはとても簡単ですが、pinchイベントは簡単ではありません。 実際のところpinchイベントは、マルチタッチイベントを含む、他のいくつかのイベントを組み合わせて作られています。 touchDownイベントでtrueを戻り値として返した場合、そのイベントはgesture detectorまで伝わりません。 したがって、マルチタッチをサポートしている場合はtouchDownやTouchUpのような最小単位のイベントではfalseを戻り値として返し、発生したイベントをマルチタッチイベントでも制御できるようにしてください!