LibGDX Tutorial 5: Xử lý đầu vào phần 2 - Xử lý chạm Đa Điểm Và các cử chỉ

Trong bài viết trước, chúng ta đã biết làm thế nào để xử lý thao tác chuột và bàn phím. Bây giờ chúng ta sẽ xem xét làm thế nào để có thể xử lý thao tác chạm. Để xem xét việc này, chúng ta cần một thiết bị có cảm ứng (chạm đa điểm bằng cách sử dụng chuột vô cùng khó khăn). Chúng ta sẽ bắt đầu với ví dụ đầu tiên về chạm đa điểm:

Chạm đa điểm

package com.handlinginputdemo.game;

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.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

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

public class HandlingInputDemo implements ApplicationListener, InputProcessor {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "Bắt đầu chạm!";
    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("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 resize(int width, int height) {

    }

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

	@Override
	public void render () {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.begin();

        message = "";
        for(int i = 0; i < 5; i++){
            if(touches.get(i).touched)
                message += "Ngón tay[" + Integer.toString(i) + "] chạm vào tọa độ:" +
                        Float.toString(touches.get(i).touchX) +
                        "," +
                        Float.toString(touches.get(i).touchY) +
                        "\n";

        }
        BitmapFont.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 pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public boolean keyDown(int keycode) {
        return false;
    }

    @Override
    public boolean keyUp(int keycode) {
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        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) {
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        return false;
    }
}

Đối với mỗi ngón tay chạm vào, nó sẽ hiển thị tọa độ, các bạn có thể nhìn thấy nó có thể lên tới 5 ngón tay cùng lúc.

Ở ví dụ trên, chúng ta đã táo lớp TouchInfo để lưu trữ các tọa độ chạm. Sau đó chúng ta tạo ra một HashMap touches với key là một số nguyên và lớp TouchInfo là value. Vấn đề then chốt là index của ngón tay được chạm vào. Logic xử lý được viết trong sự kiện touchDowntouchUp. Trong touchDown, chúng ta cập nhật touches map index những điểm chạm hiện tại. Khi tay rời khỏi màn hình, sự kiện touchUp đơn giản là xóa bỏ tọa độ được lưu trữ. Cuối cùng render() sẽ lặp touches map và hiển thị thông tin của tất cả những vị trí được chạm.

Ở ví dụ, chúng ta xử lý tối đa 5 ngón chạm cùng 1 lúc, tuy nhiên LibGDX có thể xử lý lên tới 20 chạm mặc dù không có thiết bị nào làm được điều này.

Cử chỉ chạm vào

Có một số cử chỉ đã trở nên phổ biến trong thế giới di động. Như là pinch để phóng to, hoặc flick/flinglong press từ lâu đã trở thành tiêu chuẩn. May mắn thay LibGDX hỗ trợ tất cả những cử chỉ này. Chúng ta hãy bắt đầu luôn với ví dụ đơn giản:

package com.handlinginputdemo.game;

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.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Vector2;

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

public class HandlingInputDemo implements ApplicationListener, GestureDetector.GestureListener {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "Không có cử chỉ nào!";
    private int w,h;

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

        Gdx.input.setInputProcessor(new GestureDetector(this));
	}
    @Override

    public void resize(int width, int height) {

    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.begin();

        BitmapFont.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 pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void dispose() {

    }

    @Override
    public boolean touchDown(float x, float y, int pointer, int button) {
        return false;
    }

    @Override
    public boolean tap(float x, float y, int count, int button) {
        message = "Tap được thực hiện, ngón tay " + Integer.toString(button);
        return true;
    }

    @Override
    public boolean longPress(float x, float y) {
        message = "Long press được thực hiện";
        return true;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        message = "Fling được thực hiện, velocity:" + Float.toString(velocityX) +
                "," + Float.toString(velocityY);
        return true;
    }

    @Override
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        message = "Pan được thực hiện, delta:" + Float.toString(deltaX) +
                "," + Float.toString(deltaY);
        return true;
    }

    @Override
    public boolean panStop(float x, float y, int pointer, int button) {
        return false;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        message = "Zoom được thực hiện, 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 được thực hiện";
        return true;
    }
}

Nếu bạn chạy chương trình, các cử chỉ chạm sẽ được hiển thị. Các cử chỉ hỗ trợ bao gồm tab, fling (flick), pinch (hai ngón tay di chuyển lại gần nhau), zoom (hai ngón tay di chuyển ra ngoài), pan (một ngón tay giữ và trượt) và long press (bấm và giữ).

Cũng giống như việc chúng ta cài đặt interface InputProcessor để xử lý thao tác chạm, chuột và bán phím, ở ví dụ này chúng ta sử dụng GestureListener để chấp nhận các cử chỉ. Trong hàm create() bạn tạo ra một GestureDetector sử dụng GestureListener của bạn và một lần nữa bạn đăng ký nó sử dụng Gdx.input.setInputProcessor (). Mỗi cử chỉ khác nhau sẽ được GestureListener nhận diện. Trong mỗi cử chỉ này, chúng ta lại hiển thị ra màn hình thông qua hàm render.

Sử dụng nhiều InputProcessors

Nếu bạn muốn xử lý cử chỉ và xử lý cả bàn phím thì sẽ phải làm gì nhỉ? May mắn là câu trả lời khá đơn giản, thay vì thông qua setInputProcessor một InputProcessor hay GestureDetector, bạn thay thế bằng một InputMultiplexer. Giống như thế này:

package com.handlinginputdemo.game;

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

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

public class HandlingInputDemo implements ApplicationListener, GestureDetector.GestureListener, InputProcessor {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "Không có cử chỉ nào!";
    private int w,h;

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

        InputMultiplexer inputMultiplexer = new InputMultiplexer();
        inputMultiplexer.addProcessor(new GestureDetector(this));
        inputMultiplexer.addProcessor(this);

        Gdx.input.setInputProcessor(inputMultiplexer);
    }

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

    }

    @Override
    public void render() {

    }

    @Override
    public void pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void dispose() {

    }

    @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 được thực hiện, ngón tay " + Integer.toString(button);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean longPress(float x, float y) {
        message = "Long press được thực hiện";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        message = "Fling được thực hiện, 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 được thực hiện, delta:" + Float.toString(deltaX) +
                "," + Float.toString(deltaY);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean panStop(float x, float y, int pointer, int button) {
        return false;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        message = "Zoom được thực hiện, 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 được thực hiện";
        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;
    }
}

Điều chúng ta cần quan tâm nhất là đoạn code này:

		InputMultiplexer inputMultiplexer = new InputMultiplexer();
        inputMultiplexer.addProcessor(new GestureDetector(this));
        inputMultiplexer.addProcessor(this);

        Gdx.input.setInputProcessor(inputMultiplexer);

Về cơ bản, chúng ta tạo ra Multiplexer, sau đó thêm cả InputProcessorGestureDetector thông qua addProcessor(), sau đó để sử dụng nó chúng ta sử dụng hàm setInputProcessor(). Có hai điều cực kỳ quan trọng ở đây. Thứ nhất, theo thứ tự mà các proccessors được thêm vào Multiplexer dường như để xác định thứ tự cử chỉ được xử lý. Tiếp theo, và điều này rất quan trọng, trong xử lý sự kiến, nếu bạn return true có nghĩa là sự kiện đã được xử lý. Trong khi sự kiện touchUp hoặc touchDown được thực hiện, và sự kiện pinch thì không. Trong thực tế, sự kiện pinch bao gồm các sự kiện khác, gồm có các sự kiện chạm đa điểm. Tuy nhiên nếu bạn return true tại sự kiên touchDown, sự kiện này sẽ không đi qua gesture detector. Vì vậy nếu bạn đang hỗ trợ đa cảm ứng, hãy chắc chắn return false từ các sự kiên nguyên tử hơn như touchDowntouchUp.

Source code

Bạn có có thể tham khảo source code của project này tại đây.


All Rights Reserved