libGDX. Урок 2. Обнаружение касаний по экрану.

В этом уроке разберёмся, как обнаруживать касания по экрану нашего Android устройства. Эти знания нам послужат для открытия дверей.

 

Обновляем класс GameManager

Нам необходимо сделать некоторые изменения в коде класса GameManager. Изменённые/добавленные строки выделены:

package com.door.managers;

import com.badlogic.gdx.math.Vector3;
import com.door.gameobjects.DoorClass;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.Array;

public class GameManager {

    static Array<DoorClass> doors ; // массив из трёх дверей
    static Texture doorTexture; // текстура для изображения двери

    static Texture carTexture; // текстура для изображения автомобиля
    static Texture goatTexture; // текстура для изображения козы
    static Vector3 temp = new Vector3(); // временный вектор для хранения входных координат

    private static final float DOOR_RESIZE_FACTOR = 2500f;
    private static final float DOOR_VERT_POSITION_FACTOR = 3f;
    private static final float DOOR1_HORIZ_POSITION_FACTOR = 7.77f;
    private static final float DOOR2_HORIZ_POSITION_FACTOR = 2.57f;
    private static final float DOOR3_HORIZ_POSITION_FACTOR = 1.52f;
    static float width,height;

    public static void initialize(float width,float height){
        GameManager.width = width;
        GameManager.height = height;
        doorTexture = new Texture(Gdx.files.internal("door_close.png"));

        carTexture = new Texture(Gdx.files.internal("door_open_car.png"));
        goatTexture = new Texture(Gdx.files.internal("door_open_goat.png"));

        initDoors();
    }

    public static void renderGame(SpriteBatch batch){
        //Отобразить(нарисовать) каждую дверь
        for(DoorClass door : doors)
            door.render(batch);
    }

    public static void dispose() {
        //удалить (уничтожить) текстуру двери, чтобы исключить перегрузку памяти устройства
        doorTexture.dispose();

        carTexture.dispose();
        goatTexture.dispose();
    }

    public static void initDoors(){
        doors = new Array<DoorClass>();

        //создать экземляр каждой двери и добавить их в массив doors
        for(int i=0;i<3;i++){
            doors.add(new DoorClass());
        }

        //установить позиции для отображения каждой двери
        doors.get(0).position.set(width/DOOR1_HORIZ_POSITION_FACTOR,height/DOOR_VERT_POSITION_FACTOR);
        doors.get(1).position.set(width/DOOR2_HORIZ_POSITION_FACTOR,height/DOOR_VERT_POSITION_FACTOR);
        doors.get(2).position.set(width/DOOR3_HORIZ_POSITION_FACTOR,height/DOOR_VERT_POSITION_FACTOR);

        for(DoorClass door : doors){
            // создаём для каждой двери нашего массива спрайт для её отображения
            door.closeSprite = new Sprite(doorTexture);

            door.openSprite = new Sprite();

            door.width = door.closeSprite.getWidth()* (width/DOOR_RESIZE_FACTOR);
            door.height = door.closeSprite.getHeight()* (width/DOOR_RESIZE_FACTOR);
            door.closeSprite.setSize(door.width, door.height);
            door.closeSprite.setPosition(door.position.x,door.position.y);

            //установка размеров для открытой двери
            door.openSprite.setSize(door.width, door.height);
            door.openSprite.setPosition(door.position.x, door.position.y);
        }

     //установка текстур для открытых дверей
     doors.get(0).openSprite.setRegion(goatTexture);
     doors.get(1).openSprite.setRegion(carTexture);
     doors.get(2).openSprite.setRegion(goatTexture);

    }
}

Мы объявили еще две текстуры: одну для изображения открытой двери, позади которой стоит коза и одну для изображения открытой двери, за которой будет виден автомобиль. Также мы объявили вектор для хранения координат точки на экране в месте, где будет касание пользователя. Это поможем нам определить, какую дверь выбрал игрок:

static Vector3 temp = new Vector3(); // временный вектор для хранения входных координат

После чего в методе initialize() мы загружаем в текстуры изображения козы за дверью и автомобиля за дверью. Затем, в методе initDoors() мы устанавливаем размеры и позицию соответствующих спрайтов для каждой из трёх дверей, но какую именно текстуру внести в спрайт мы делаем вне цикла for. А в методе dispose() добавляем к уничтожению новые игровые объекты.

 

Реализация класса InputManager

В папке managers (а точнее – в пакете com.door.managers) создайте новый Java Class и назовите его InputManager:

Введите в файл InputManager следующий код:

package com.door.managers;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.door.gameobjects.DoorClass;

public class InputManager {

    public static void handleInput(OrthographicCamera camera) {

        // Было ли касание экрана?
        if (Gdx.input.justTouched()) {
            // Получаем координаты касания
            // И устанавливаем значения координат в вектор temp
            GameManager.temp.set(Gdx.input.getX(), Gdx.input.getY(), 0);
            // получаем координаты касания
            // относительно области просмотра нашей "камеры"
            camera.unproject(GameManager.temp);
            float touchX = GameManager.temp.x;
            float touchY = GameManager.temp.y;
            // Выполняем итерацию массива doors и проверяем
            // Было ли выполнено касание по какой-нибудь двери?
            for (int i = 0; i < GameManager.doors.size; i++) {
                DoorClass door = GameManager.doors.get(i);
                // проверка выполнится только для закрытой двери
                if (!door.isOpen) {
                    if (handleDoor(door, touchX, touchY)) {
                        break;
                    }
                }
            }
        }
    }

    public static boolean handleDoor (DoorClass door, float touchX, float touchY){

        // Проверяем, находятся ли координаты касания экрана
        // в границах позиции двери
        if((touchX>=door.position.x) && touchX<=(door.position.x+ door.width) && (touchY>=door.position.y) && touchY<=(door.position.y+door.height) ){
            // Открываем дверь, если касание было произведено по ней
            door.isOpen=true;
            return true;
        }
        return false;
    }
}

В этом классе у нас два метода: handleInput() и handleDoor(). В методе handleInput() мы проверяем было ли касание по экрану следующей строкой:

Gdx.input.justTouched()

Этим методом мы проверяем было ли произведено касание по экрану устройства. Если касание было, то входим в оператор if. Мы получаем координаты касания экрана следующими методами:

Gdx.input.getX()
Gdx.input.getY()

После чего устанавливаем значения этих координат в наш вектор temp. Это 3D вектор, но так как мы имеем дело с 2D игрой, то третье значение (координату по z оси) мы установим 0(ноль):

GameManager.temp.set(Gdx.input.getX(), Gdx.input.getY(), 0);

Наверняка у вас возник вопрос: «А для чего нам 3D вектор в 2D игре?». Ответ кроется в том, как libGDX выдаёт данные о координатах, полученных после нажатия пользователя по экрану. А выдаёт он координаты пикселей, соответствующих размеру экрана, на котором будет запущена игра (приложение). В нашем случае размер области просмотра (viewport) совпадает с размером экрана. Но размеры экрана и размеры области просмотра (viewport) могут отличаться. В таком случае мы получим ошибочные результаты.

Чтобы понять это представьте себе экран размером 800х600 пикселей, в котором мы установили область просмотра (viewport) игры размером 400х300 пикселей. Теперь, когда мы нажмём в центр экрана может показаться, что координаты касания будут (200, 150). Вместо этого, libGDX выдаст нам (400, 300) поскольку эти координаты являются фактическим центром экрана нашего устройства. Чтобы избежать этот момент, мы конвертируем координаты экрана в координаты области просмотра (viewport), используя следующий метод:

camera.unproject(GameManager.temp);

А этому методу в качестве аргумента требуется 3D вектор. Метод преобразует координаты и хранит их в векторе (в нашем случае temp), после чего можно использовать его для обработки касаний. Этот метод вызывается на камеру игры (camera), так как она передаётся в качестве аргумента методу handleInput(). После этого мы выполняем итерацию массива doors и проверяем было ли касание по какой-нибудь из наших дверей. Проверка выполняется только для закрытых дверей методом handleDoor():

handleDoor(door, touchX, touchY)

В этом методе мы проверяем, находится ли точка касания на конкретной двери. Мы проверяем находится ли эта точка в пределах горизонтальных границ:

(touchX>=door.position.x) && touchX<=(door.position.x+ door.width)

И вертикальных границ:

(touchY>=door.position.y) && touchY<=(door.position.y+door.height)

На картинке ниже достаточно наглядно объяснено, как это выглядит:

libgdx android studio justTouched

Если точка касания находится в пределах координат двери, то меняем её состояние на «открыто» и выводим соответствующее изображение открытой двери. Метод handleDoor() возвращает значение true если касание было произведено по двери и false в противном случае. Вся обработка происходит в методе handleInput(), который останавливает итерацию массива doors через break.

 
Осталось только добавить небольшое изменение в методе render() класса Door для включения обнаружения касания. Для этого нужно вызвать метод handleInput() класса InputManager:

//устанавливаем вид spriteBatch созданному нами объекту camera
batch.setProjectionMatrix(camera.combined);
InputManager.handleInput(camera);
//отрисовываем игровые объекты путём вызова метода renderGame класса GameManager
batch.begin();
GameManager.renderGame(batch);
batch.end();

Можно запустить приложение и посмотреть, что получилось на данный момент:

justTouched libgdx Android Studio

Внизу доступны для скачивания файлы с кодом для каждого класса нашего проекта на данном этапе. Файлы изображений доступны для скачивания на 1 уроке.

Скачать исходники