Использование libGDX и Scala

Scala – функциональный, объектно-ориентированный язык программирования для JVM, который работает с Java библиотеками, фреймворками и средствами. Имеет краткий синтаксис, что делает его схожим со скриптовым языком, но он берет свое начало в использовании в серверном программном обеспечении, в таких компаниях как Twitter и LinkedIn.

Хотя Scala и Java можно свободно смешать, но стандартный набор инструментов для работы со Scala отличается от набора Java разработчиков. Есть libgdx-sbt-project проект, который предоставляет простой способ для начала работы с libGDX и Scala, используя стандартные средства сборки и лучшую практику.

Предполагается, что у вас установлены g8 и sbt 0.12, которые используется в Scala сообществе для создания и взаимодействия с проектами.

Настройка нового проекта

В вашей любимой оболочки введите:

$ g8 ajhager/libGDX-sbt-project

После заполнения некоторой информации о вашем проекте, вы сможете начать размещать исходные файлы игры и asset файлы в common/src/main/scala и common/src/main/resources директории соответственно.

Управление проектом

Обновляет библиотеки до последней версии:

$ sbt
> update 

Запускает проект персонального компьютера:

> desktop/run

Упаковывает проект персонального компьютера в отдельный jar файл:

> assembly

Запускает Android проект на устройстве:

> android/start

Посетите android-plugin для более подробного руководства по Android настройке и использованию.

Запускает iOS проект на устройстве:

> ios/device

Посетите sbt-robovm для более подробного руководства по iOS настройке и использованию.

Использование Unit тестов

Выполняет все Unit тесты для Core, Desktop и Android проектов (поддиректории src/test/scala):

> test

Выполняет определенный набор Unit тестов:

> common/test

Использование популярных сред разработки

В большинстве случаев вы сможете открывать и редактировать каждый под-проект (общий, Android или проект персонального компьютера), но вам все еще нужно использовать SBT для сборки проекта.

Смотрите подробности о sbt плагинах для каждого редактора.

Использование libGDX и Python

Python – динамический и строго типизированный язык, который поддерживает множество парадигм программирования, таких как процедурное, объектно-ориентированное и функциональное программирование.

Python был реализован несколькими различными способами: стандартный интерпретатор на C (CPython), на самом Python (PyPy), на .Net DLR (C#) (IronPython) и на Java с JVM (Jython). Jython поставляется с Java совместимостью, что позволяет использовать мощные Java библиотеки, такие как libGDX, при этом сохраняя лаконичность и читаемость языка Python.

В этой статье используется последняя бета версия Jython (Jython 2.7b1), этот релиз нацелен на совместимость с CPython 2.7, поэтому в этой статье мы будет программировать с синтаксисом Python 2.7.

Jython и libGDX

На момент написания статьи, использование Jython и libGDX возможно только на персональном компьютере.

Установка

С Jython можно работать в любом тестовом редакторе, в том числе в Vim или Emacs. PyDev вариант для пользователей Eclipse. Как только настроена среда разработки, создайте новый Jython проект и задайте все libGDX зависимости в PYTHONPATH, для использования LWJGL backend персонального компьютера. Это включает gdx.jar, gdx-backend-lwjgl.jar, gdx-backend-lwjgl-natives.jar, gdx-backend-lwjgl.jar, gdx-natives.jar и gdx-sources.jar.

Программирование на Python

Полный урок простой игры может содержаться в одно python файле.

from com.badlogic.gdx.backends.lwjgl import LwjglApplication, LwjglApplicationConfiguration
from com.badlogic.gdx.utils import TimeUtils, Array
from com.badlogic.gdx.math import MathUtils, Rectangle, Vector3
from com.badlogic.gdx import ApplicationListener, Gdx, Input
from com.badlogic.gdx.graphics.g2d import SpriteBatch
from com.badlogic.gdx.graphics import Texture, OrthographicCamera, GL10

class PyGdx(ApplicationListener):
    def __init__(self):
        self.camera = None
        self.batch = None
        self.texture = None
        self.bucketimg = None
        self.dropsound = None
        self.rainmusic = None
        self.bucket = None
        self.raindrops = None
        
        self.lastdrop = 0
        self.width = 800
        self.height = 480
    
    def spawndrop(self):
        raindrop = Rectangle()
        raindrop.x = MathUtils.random(0, self.width - 64)
        raindrop.y = self.height
        raindrop.width = 64
        raindrop.height = 64
        self.raindrops.add(raindrop)
        self.lastdrop = TimeUtils.nanoTime()
        
    def create(self):        
        self.camera = OrthographicCamera()
        self.camera.setToOrtho(False, self.width, self.height)
        self.batch = SpriteBatch()
        
        self.dropimg = Texture("assets/droplet.png")
        self.bucketimg = Texture("assets/bucket.png")
        self.dropsound = Gdx.audio.newSound(Gdx.files.internal("assets/drop.wav"))
        self.rainmusic = Gdx.audio.newSound(Gdx.files.internal("assets/rain.mp3"))
        
        self.bucket = Rectangle()
        self.bucket.x = (self.width / 2) - (64 / 2)
        self.bucket.y = 20
        self.bucket.width = 64
        self.bucket.height = 64
        
        self.raindrops = Array()
        self.spawndrop()
        
        self.rainmusic.setLooping(True, True)
        self.rainmusic.play()
    
    def render(self):
        Gdx.gl.glClearColor(0,0,0.2,0)
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT)
        
        self.camera.update()
        
        self.batch.setProjectionMatrix(self.camera.combined)
        self.batch.begin()
        self.batch.draw(self.bucketimg, self.bucket.x, self.bucket.y)
        for drop in self.raindrops:
            self.batch.draw(self.dropimg, drop.x, drop.y)
        self.batch.end()
        
        if Gdx.input.isTouched():
            touchpos = Vector3()
            touchpos.set(Gdx.input.getX(), Gdx.input.getY(), 0)
            self.camera.unproject(touchpos)
            self.bucket.x = touchpos.x - (64 / 2)
        if Gdx.input.isKeyPressed(Input.Keys.LEFT): self.bucket.x -= 200 * Gdx.graphics.getDeltaTime()
        if Gdx.input.isKeyPressed(Input.Keys.RIGHT): self.bucket.x += 200 * Gdx.graphics.getDeltaTime()
        
        if self.bucket.x < 0: self.bucket.x = 0
        if self.bucket.x > (self.width - 64): self.bucket.x = self.width - 64
        
        if (TimeUtils.nanoTime() - self.lastdrop) > 1000000000: self.spawndrop()
                        
        iterator = self.raindrops.iterator()
        while iterator.hasNext():
            raindrop = iterator.next()
            raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
            if (raindrop.y + 64) < 0: iterator.remove()
            if raindrop.overlaps(self.bucket):
                self.dropsound.play()
                iterator.remove()
        
    def resize(self, width, height):
        pass

    def pause(self):
        pass

    def resume(self):
        pass
    
    def dispose(self):
        self.batch.dispose()
        self.dropimg.dispose()
        self.bucketimg.dispose()
        self.dropsound.dispose()
        self.rainmusic.dispose()


def main():

    cfg = LwjglApplicationConfiguration()
    cfg.title = "PyGdx";
    cfg.width = 800
    cfg.height = 480
    
    LwjglApplication(PyGdx(), cfg)
        
if __name__ == '__main__':
    main()

Отметим, что во время создания asset, нужно указать assets/ директорию. Когда не используется Android, мы должны указывать структуру директорий, которую мы используем. В то время как на Android считается, что все внутренние asset ресурсы находятся в assets/ директории.

Использование libgdx и Clojure

Clojure – диалект языка программирования Lisp с функциональным программированием, написанный для JVM. Clojure поставляется с Java совместимостью, делая его способным использовать существующие мощные библиотеки в Java экосистеме. Существует несколько источников для разработки игр на Clojure языке, используя libGDX.

Этот статья предполагает, что у вас установлен Leiningen наряду со среднем уровнем знаний Clojure, или Lisp. Есть много хороших видео у ClojureTV на YouTube, в частности Clojure for Java Programmers (Part 2).

project.clj

Так как по умолчанию Leiningen Maven репозитории не содержат libGDX, нужно добавить его самостоятельно из Sonatype репозитория.

(defproject cljdx "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :license {:name "Apache License, Version 2.0"
          :url "http://www.apache.org/licenses/LICENSE-2.0.html"
          :distribution :repo
          :comments "Same as LibGDX"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [com.badlogicgames.gdx/gdx "0.9.9-SNAPSHOT"]
                 [com.badlogicgames.gdx/gdx-backend-lwjgl "0.9.9-SNAPSHOT"]
                 [com.badlogicgames.gdx/gdx-platform "0.9.9-SNAPSHOT"
                  :classifier "natives-desktop"]]
  :repositories [["sonatype"
                  "https://oss.sonatype.org/content/repositories/snapshots/"]]
  :source-paths ["src/clojure"]
  :java-source-paths ["src/java"]
  :aot [cljdx.desktop-launcher]
  :main cljdx.desktop-launcher)

Код может быть переведен довольно простым способом:

здесь есть эквивалент Main.java, который мы назвали cljdx.desktop-launcher

(ns cljdx.desktop-launcher
  (:require [cljdx.core])
  (:import [com.badlogic.gdx.backends.lwjgl LwjglApplication])
  (:gen-class))

(defn -main
  []
  (LwjglApplication. (cljdx.core.Game.) "cljdx" 800 600 true))

здесь реализация Screen интерфейса (представлена как cljdx.core)

(ns cljdx.core
  (:import [com.badlogic.gdx Game Gdx Graphics Screen]
           [com.badlogic.gdx.graphics Color GL20]
           [com.badlogic.gdx.graphics.g2d BitmapFont]
           [com.badlogic.gdx.scenes.scene2d Stage]
           [com.badlogic.gdx.scenes.scene2d.ui Label Label$LabelStyle]))

(gen-class
  :name cljdx.core.Game
  :extends com.badlogic.gdx.Game)

(def main-screen
  (let [stage (atom nil)]
    (proxy [Screen] []
      (show []
        (reset! stage (Stage.))
        (let [style (Label$LabelStyle. (BitmapFont.) (Color. 1 1 1 1))
              label (Label. "Hello world!" style)]
          (.addActor @stage label)))
      (render [delta]
        (.glClearColor (Gdx/gl) 0 0 0 0)
        (.glClear (Gdx/gl) GL20/GL_COLOR_BUFFER_BIT)
        (doto @stage
          (.act delta)
          (.draw)))
      (dispose[])
      (hide [])
      (pause [])
      (resize [w h])
      (resume []))))

(defn -create [^Game this]
  (.setScreen this main-screen))

и вот весь необходимый код! Команда lein uberjar сгенерирует необходимый работоспособный jar файл.

Игры, написанные на Clojure с использованием libGDX

Запись PCM аудио

Вы можете получить доступ к PCM данным микрофона персонального компьютера или Android телефона через AudioRecorder (исходный код) интерфейс. Чтобы создать экземпляр этого интерфейса, используйте метод newAudioRecorder() из audio модуля libGDX фреймворка:

AudioRecorder recorder = Gdx.audio.newAudioRecorder(22050, true);

Метод newAudioRecorder() создаст AudioRecorder с частотой семплов 22.05 кГц и моно режимом. Если рекордер не может быть создан, то libGDX фреймворком будет брошено GdxRuntimeException исключение.

Семплы могут быть прочитаны как 16-разрядное PCM:

short[] shortPCM = new short[1024]; // 1024 семплов
recorder.readSamples(shortPCM, 0, shortPCM.length);

Стерео сэмплы чередуются как обычно (первый сэмпл -> левый канал, второй сэмпл -> правый канал).

Удаление AudioRecorder ресурса

AudioRecorder является нативным ресурсом и должен быть удален, если больше не используются в libGDX игре или приложении:

recorder.dispose();

Запись звука не поддерживается в JavaScript/WebGL бекэнде.

Воспроизведение PCM аудио

Аудио модуль libGDX фреймворка может предоставить прямой доступ к аудио аппаратуре для записи PCM семплов.

Звуковое оборудование абстрагировано через AudioDevice (исходный код) интерфейс.

Чтобы создать новый экземпляр AudioDevice можно сделать следующее:

AudioDevice device = Gdx.audio.newAudioDevice(44100, true);

Метод newAudioDevice() создает новый экземпляр AudioDevice, который имеет частоту сэмплов 44.1 кГц и моно выход. Если устройство не может быть создано, то libGDX фреймворком будет брошено GdxRuntimeException исключение.

Мы можем писать на устройство либо 16-битные PCM или 32-битные PCM данных:

float[] floatPCM = ... полученные от синуса к примеру ...
device.writeSamples(floatPCM, 0, floatPCM.length);

short[] shortPCM = ... полученные от декодера ...
device.writeSamples(shortPCM, 0, shortPCM.length);

Если используется стерео, то сэмплы левого и правого канала чередуются как обычно (первый float/short -> левый канал, второй float/short -> правый канал).

Для получения латентности, libGDX предоставляет специальный метод. Латентность в миллисекундах может быть получена с помощью вызова getLatency() метода:

int latencyInSamples = device.getLatency();

Метод getLatency() вернет размер аудио буфера в сэмплах и таким образом вы получите индикатор латентности. Чем больше возвращаемое значение, тем больше времени требуется аудио, чтобы прийти к получателю после записи.

Латентность почти на всех Android телефонах высокая.

В режиме реального времени аудио приложениям очень трудно попасть в 10-30 мс рабочий диапазон. Обычно вы можете достичь латентности в 100 мс, многие телефоны будут иметь латентность до 400 мс. К сожалению, эта проблема связана с драйверами и операционной системой и не может быть как-то обойдена.

Удаление AudioDevice ресурса

AudioDevice является нативным ресурсом и должен быть удален, если больше не используется в libGDX игре или приложении:

device.dispose();

Прямой PCM выход не поддерживается в JavaScript/WebGL бекэнде.

Потоковое воспроизведение музыки

Для любого звука, который дольше нескольких секунд, предпочтительнее его потоковое воспроизведение с диска, место полной загрузки в оперативную память. libGDX предоставляет Music интерфейс, который позволяет это сделать.

Для загрузки экземпляра музыки мы можем вызвать newMusic() метод из audio модуля libGDX фреймворка:

Music music = Gdx.audio.newMusic(Gdx.files.internal("data/mymusic.mp3"));

Метод newMusic() загружает MP3 файл с именем "mymusic.mp3" из внутренней директории data.

Воспроизведение экземпляра музыки работает следующим образом:

music.play();

Конечно, вы можете установить различные атрибуты Music экземпляра, например:

music.setVolume(0.5f);                 // устанавливает громкость на половину максимального объема
music.setLooping(true);                // повторное воспроизведение, пока не будет вызван music.stop()
music.stop();                          // останавливает воспроизведение
music.pause();                         // приостанавливает воспроизведение
music.play();                          // возобновляет воспроизведение
boolean isPlaying = music.isPlaying(); // проверка воспроизводится ли музыка 
boolean isLooping = music.isLooping(); // проверка установлено ли повторение
float position = music.getPosition();  // возвращает позицию воспроизведения в секундах

Music экземпляры тяжелые и как правило, вы должны иметь не более одного-двух загруженных экземпляров.

Удаление Music ресурса

Music является нативным ресурсом и должен быть удален, если больше не используется в libGDX игре или приложении:

music.dispose();

Доступ к Music экземпляру после удаления приведет к неопределенным ошибкам в libGDX игре или приложении.

Звуковые эффекты

Звуковые эффекты – небольшие аудио семплы, как правило, не дольше нескольких секунд. Звуковые эффекты воспроизводятся на специальные события в игре, такие как прыжок героя или стрельба из оружия.

Звуковые эффекты могут храниться в различных форматах. libGDX поддерживает форматы файлов MP3, OGG и WAV файлов.

Звуковые эффекты представлены Sound интерфейсом. Загрузка звукового эффекта происходит при помощи вызова newSound() метода из audio модуля libGDX фреймворка:

Sound sound = Gdx.audio.newSound(Gdx.files.internal("data/mysound.mp3"));

Этот код загружает аудио файл с именем "mysound.mp3" из внутренней директории data.

Как только звук загружен, мы можем воспроизвести его.

sound.play(1.0f);

Метод play() воспроизведет звуковой эффект один раз на полной громкости. Метод play() одного Sound экземпляра, может быть вызван несколько раз подряд. Например, для очереди выстрелов в игре, которые будут соответственно накладываться друг на друга.

libGDX предоставляет более тонкое управление. Каждый вызов Sound.play() возвращает long идентификатор данного экземпляра звука. С помощью этого идентификатора мы можем изменить конкретный экземпляр воспроизведения звука:

long id = sound.play(1.0f); // воспроизводит новый звук и сохраняет идентификатор для дальнейших изменений
sound.stop(id);             // немедленно останавливает воспроизведения экземпляра звука
sound.setPitch(id, 2);      // увеличивает высоту звука в 2 раза от оригинальной высоты

id = sound.play(1.0f);      // воспроизводит звук второй раз, рассматривается как другой экземпляр
sound.setPan(id, -1, 1);    // устанавливает панораму звука в левой стороне на полную громкость
sound.setLooping(id);       // устанавливаем циклическое воспроизведение
sound.stop(id);             // останавливает циклического воспроизведения звука

Методы модификации в настоящее время не будут работать на JavaScript/WebGL бекэнде.

Удаление Sound ресурса

Sound является нативным ресурсом и должен быть удален, если больше не используется в libGDX игре или приложении:

sound.dispose();

Доступ к звуковому эффекту после удаления приведет к неопределенным ошибкам в libGDX игре или приложении.

Аудио

libGDX предоставляет методы для воспроизведения небольших звуковых эффектов, а так же потокового воспроизведения больших музыкальных произведений с диска. libGDX также предоставляет удобный доступ к аудио оборудованию на чтение и запись.

Весь доступ к аудио аппаратуре осуществляется через audio модуль:

Audio audio = Gdx.audio;

libGDX будет автоматически приостанавливать, и возобновлять воспроизведения звука при остановке и возобновлении приложения.

Управление памятью

Игры являются тяжеловесными, своими ресурсами, приложениями. Изображения и звуковые эффекты могут занять значительное количество оперативной памяти. Кроме того, сборщик мусора Java не управляет большинством этих ресурсов. Вместо этого они управляются посредством собственных драйверов.

Мы хотим иметь точный контроль над временем жизни наших ресурсов. В libGDX есть несколько классов, которые представляют такие ресурсы. Все они реализуют общий Disposable интерфейс, который указывает, что экземпляры класса должны быть удалены вручную в конце времени жизни.

Не освобождение ресурсов приведет к серьезным утечкам памяти.

Следующие классы должны быть удалены вручную.

  • AssetManager
  • Bitmap
  • BitmapFont
  • BitmapFontCache
  • CameraGroupStrategy
  • DecalBatch
  • ETC1Data
  • FrameBuffer
  • Mesh
  • ParticleEffect
  • Pixmap
  • PixmapPacker
  • ShaderProgram
  • Shape
  • Skin
  • SpriteBatch
  • SpriteCache
  • Stage
  • Texture
  • TextureAtlas
  • TileAtlas
  • TileMapRenderer
  • World

Ресурсы должны быть удалены, как только они больше не нужны, освобождая связанную с ними память.

Доступ к удаленному ресурсу

Доступ к ресурсу, который уже удален, приведет к неопределенным ошибкам, поэтому убедитесь, что все ссылки, указывающие на удаленные ресурсы, очищены.

При сомнениях в том, нужно ли удалять ресурсы определенного класса или нет, проверьте его на наличие disposed() метода. Если он есть, значит, вы работаете с нативным ресурсом и потом должны его удалить в libGDX игре или приложении.

Объединение объектов

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

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

libGDX предоставляет интерфейсы и классы для легкой работы с пулом.

Реализация Poolable интерфейса означает, что объект имеет reset() метод, который в libGDX автоматически вызывается при освобождении объекта.

Ниже приведен краткий пример пула для пуль.

public class Bullet implements Poolable {

    public Vector2 position;
    public boolean alive;

    /**
     * Конструктор пули. Простая инициализация переменных.
     */
    public Bullet() {
        this.position = new Vector2();
        this.alive = false;
    }

    /**
     * Инициализация пули. Вызовите этот метод после получения пули из пула.
     */
    public void init(float posX, float posY) {
        position.set(posX,  posY);
        alive = true;
    }

    /**
     * Метод обратного вызова, когда объект освобожден.
     * Автоматически вызывается Pool.free() методом.
     * Необходимо сбросить каждое смысловое поле данной пули
     */
    public void reset() {
        position.set(0,0);
        alive = false;
    }

    /**
     * Метод вызывается при каждом кадре и обновляет пулю.
     */
    public void update (float delta) {

        // обновление позиции пули
        position.add(1*delta*60, 1*delta*60);

        // если пуля вне экрана, установка состояние в "мертвая"
        if (isOutOfScreen()) alive = false;
    }
}

В классе игрового мира:

public class World() {

    // Массив, содержащий активные пули.
    private final Array<Bullet> activeBullets = new Array<Bullet>();

    // Пул для пуль.
    private final Pool<Bullet> bulletPool = new Pool<Bullet>() {
        @Override
        protected Bullet newObject() {
            return new Bullet();
        }
    };

    public void update(float delta) {

        // если вам нужно создать новую пулю:
        Bullet item = bulletPool.obtain();
        item.init(2, 2);
        activeBullets.add(item);

        // если вы хотите освободить "мертвые" пули, верните их обратно в пул:
        Bullet item;
        int len = activeBullets.size;
        for (int i = len; --i >= 0;) {
            item = activeBullets.get(i);
            if (item.alive == false) {
                activeBullets.removeIndex(i);
                bulletPool.free(item);
            }
        }
    }
}

Класс Pools предоставляет статические методы для динамического создания пулов любых объектов (используя ReflectionPool). В приведенном выше примере, это можно использовать в libGDX игре так:

private final Pool<Bullet> bulletPool = Pools.get(Bullet.class);

Перехват кнопки возврата и меню

Когда пользователь на Android устройстве нажимает кнопку возврата, обычно происходит завершение текущей Activity. Игры могут показывать диалог для подтверждения выхода. Для того чтобы это работало в libGDX, нужно перехватить нажатие кнопки возврата, так чтобы событие о нажатии не было передано операционной системе. Используйте setCatchBackKey() метод libGDX фреймворка.

Gdx.input.setCatchBackKey(true);

Вы по-прежнему будете получать сообщения о событиях клавиш, если зарегистрирован InputProcessor, но операционная система не закроет приложение.

Обратите внимание, что общей Android парадигмой является то, что кнопка возврата закрывает текущюю Activity. Уклонение от этого, обычно рассматривается как плохая практика.

Другая кнопка, которую возможно нужно будет перехватывать в libGDX, является кнопка меню. Если не перехватывать эту кнопку, то при долгом нажатии будет показа экранная клавиатура. libGDX предоставляет для перехвата меню метод setCatchMenuKey().

Gdx.input.setCatchMenuKey(true);

Экранная клавиатура

Большинство Android устройств не обладают аппаратной клавиатурой. Вместо нее пользователю предлагается программная или экранная клавиатура. Для вызова экранной клавиатуры нужно вызвать setOnscreenKeyboardVisible() метод libGDX фреймворка:

Gdx.input.setOnscreenKeyboardVisible(true);

Как только клавиатура видна, любое нажатие клавиши будет сообщаться libGDX приложению в виде события. Кроме того, может использоваться опрос конкретных клавиш для определения их состояния.

Заметьте, что в настоящие время в ландшафтном режиме существует ошибка в реализации экранной клавиатуры.

По умолчанию, экранную клавиатуру Android можно переключить на пользовательскую, многие производители, подобно HTC, воспользовались этим. К сожалению, их реализация клавиатуры, как правило, имеет ошибки, которые приводят к проблемам, когда клавиатура не появляется в ландшафтном режиме.

Функциональность экранной клавиатуры доступна только на Android.

Видимость и перехват курсора

Для некоторых игр, вроде шутеров от первого лица, часто необходимо перехватывать курсор мыши, чтобы он оставался в центре экрана, и использовать только смещение для вращения камеры. В других случаях, возможно понадобиться задать положение курсора вручную. В libGDX, то и другое можно сделать с помощью метода setCursorCatched() и setCursorPosition();

Gdx.input.setCursorCatched(true);
Gdx.input.setCursorPosition(x, y);

Обратите внимание, что перехват курсора работает надежно только для Lwjgl beck-end. Для Jogl back-end используется класс AWT/Swing Robot, который имеет несколько причуд.

Перехват и позиционирование курсора в libGDX, доступно только на персональном компьютере.

Вибратор

Хотя вибратор не устройство ввода, тем не менее, он является своего рода периферией. Было решено, что вибратор принадлежит модели ввода. libGDX предоставляет методы для работы с функциями вибратора на телефоне.

Вибратор позволяет вибрировать телефону. Вибратор может быть использован по аналогии с более сложной по функциональности отдачей устройства, которая нашла применение в контроллерах игровых консолей.

Вибратор доступен только на Android и требует специальных разрешений в файле манифеста – android.permission.VIBRATE. Смотрите раздел настройки приложения, если вы не знаете, как изменить разрешения в Android манифесте.

Вибратор на телефоне работает посредством вызова vibrate() метода libGDX фреймворка:

Gdx.input.vibrate(2000);

Так как параметр указывается в миллисекундах, то в приведенном выше примере телефон будет вибрировать в течение двух секунд.

Более сложные шаблоны могут быть определены через второй vibrate() метод:

Gdx.input.vibrate(new long[] { 0, 200, 200, 200}, -1); 

Этот код включит вибратор на 200 миллисекунд, а затем отключить его на 200 миллисекунд, затем снова включит еще на 200 миллисекунд. Второй параметр указывает на то, что шаблон не должен повторяться.Чтобы получить дополнительные сведения, обратитесь к документации vibrate() метода.

Компас

libGDX предоставляет методы для работы с компасом, чтобы определить в игре или приложении текущею ориентацию устройства. Некоторые Android телефоны имеют встроенный датчик магнитного поля, который предоставляет информацию о том, как устройство ориентированно относительно северного магнитного полюса.

В libGDX запрос на доступность компаса с помощью метода isPeripheralAvailable(), работает следующим образом:

boolean compassAvail = Gdx.input.isPeripheralAvailable(Peripheral.Compass);

После того, как вы определили что компас действительно доступен, вы можете опросить ее состояние:

float azimuth = Gdx.input.getAzimuth();
float pitch = Gdx.input.getPitch();
float roll = Gdx.input.getRoll();

Углы даны в градусах. Вот интерпретация этих значений:

  • azimuth: представляет угол ориентации устройства вокруг Z-оси. Положительное направление Z-оси указывает на центр земли.
  • pitch: представляет угол устройства вокруг X-оси. Положительном направлении X-оси. Положительное направление X-оси примерно указывает на запад и ортогонально Z-и Y-оси.
  • roll: представляет угол ориентации устройства вокруг Y-оси. Положительное направление Y-оси указывает на северный магнитный полюс Земли, оставаясь ортогональным двух других осям.

Вот иллюстрация осей относительно Земли.

Иллюстрация осей относительно Земли

Акселерометр

libGDX фреймворк поддерживает работу с акселерометром, который измеряет ускорение устройства по трем осям (по крайней мере, на Android). Из этого ускорения можно получить наклон или ориентацию устройства.

Ускорение измеряется в метрах на секунду в квадрате (м/сек2). Если ось направлена ​​к центру Земли, это ускорение будет примерно 10 м/сек2. Если ось указывает в противоположном направлении, то ускорение будет -10 м/сек2.

Оси в Android устройстве могут быть настроены следующим образом:

Оси акселерометра в Android устройстве

К сожалению, эта конфигурация отличается для планшетов. Android устройства имеют так называемое понятие ориентация по умолчанию. Для телефонов, портретный режим (как в изображении выше) является ориентацией по умолчанию. Для планшетов, ландшафтный режим является ориентацией по умолчанию. Устройство с ландшафтной ориентацией по умолчанию имеет свои повороты осей так, что Y-ось направлена ​​вверх к меньшей стороне устройства и X-ось направлена вправо к более широкой стороне.

libGDX заботится об этом и представляет настройку акселерометра, как показано на изображении выше, независимо от ориентации устройства по умолчанию (положительная Z-ось направлена из экрана, положительная X-ось направлена вправо к меньшей стороне устройства, положительная Y-ось направлена вверх к широкой стороне устройства).

Проверка наличия акселерометра

Различные устройства Android имеют разные конфигурации оборудования. Проверка, имеет ли устройство акселерометр, может быть выполнена следующим образом:

boolean available = Gdx.input.isPeripheralAvailable(Peripheral.Accelerometer);

Запрос текущей/естественной ориентации

Если ваша libGDX игра должна знать о текущей ориентации устройства, то можно использовать getOrientation() метод:

int orientation = Gdx.input.getOrientation();

Этот код вернет значение 0, 90, 180 или 270, что дает вам разницу между углом текущей ориентации и естественной ориентации.

Естественной ориентацией является портретный режим (как на рисунке выше), или ландшафтный режим (в основном для планшетов). Ее можно запросить следующим образом:

Orientation nativeOrientation = Gdx.input.getNativeOrientation();

Этот код возвращает либо Orientation.Landscape или Orientation.Portrait.

Чтение показаний акселерометра

Показания акселерометра могут быть доступны в libGDX только через опрашивание.

float accelX = Gdx.input.getAccelerometerX();
float accelY = Gdx.input.getAccelerometerY();
float accelZ = Gdx.input.getAccelerometerZ();

Платформы или устройства, которые не имеют поддержки акселерометра, вернут ноль.

Смотрите игру Super Jumper для демонстрации использования акселерометра.

Матрица вращения

Если вы хотите использовать ориентацию устройства для визуализации, то работа с матрицей вращения может быть полезной. Смотрите о матрице вращения. Вы можете включить полученную матрицу непосредственно в OpenGL визуализацию:

Matrix4 matrix = new Matrix4();
Gdx.input.getRotationMatrix(matrix.val);

Простой текстовый ввод

Если libGDX игре или приложению нужно от пользователя запросить ввод строки, например имя пользователя или пароль, то это можно сделать, используя простое диалоговое окно, которое можно в некоторой степени настраивать.

На персональном компьютере откроется Swing диалоговое окно, запрашивая от пользователя ввода строки.

На Android откроется стандартное Android диалоговое окно, снова запрашивая от пользователя ввода строки.

В libGDX для получения введенных данных или уведомления или введенных данных, которые пользователь может отменить, нужно реализовать TextInputListener интерфейс.

public class MyTextInputListener implements TextInputListener {
   @Override
   public void input (String text) {
   }

   @Override
   public void canceled () {
   }
}

Метод input() будет вызван, когда пользователь ввел текстовую строку. Метод canceled() будет вызван, если пользователь закрыл диалоговое окно на персональном компьютере или нажал на кнопку возврата на Android устройстве.

Чтобы вызвать диалоговое окно, просто вызовите getTextInput() метод с использованием вашего обработчика:

MyTextInputListener listener = new MyTextInputListener();
Gdx.input.getTextInput(listener, "Dialog Title", "Initial Textfield Value");

В libGDX методы обработчика будут вызываться в потоке визуализации, прямо перед ApplicationListener.render() методом.

Обнаружение жестов

Сенсорные экраны хорошо подходят для ввода жестов. Жестом может быть использование двух пальцев для масштабирования, прикосновение или двойное прикосновение, долгое нажатие и так далее.

libGDX предоставляет GestureDetector (исходники) класс, который позволяет вам обнаружить следующие жесты:

  • tap: пользователь прикасается к экрану и затем поднимает палец. Палец не должен выходить за пределы указанной квадратной области вокруг исходного положения для регистрации tap жеста. Многократные последовательные tap жесты будут обнаружены, если пользователь выполняет tap жест втечении заданного интервала времени.
  • pan: пользователь двигает пальцем по экрану. Детектор сообщит координаты текущего прикосновения, а также разницу между позицией текущего и предыдущего прикосновения. Используется для реализации панорамирование камеры в 2D.
  • fling: пользователь передвигал палец по экрану, а затем поднял его. Используется для реализации жеста скольжения.
  • zoom: пользователь помещает два пальца на экран и перемещает их вместе друг от друга. Детектор сообщает о начальном и текущем расстоянии между пальцами в пикселях. Используется для реализации масштабирования камеры.
  • pinch: Похож на zoom. Детектор вместо расстояния будет сообщать о первоначальном и текущем положении пальцев. Используется для реализации масштабирования камеры и более сложных жестов, таких как вращение.

GestureDetector является обработчиком событий для жестов. Для перехвата жестов в libGDX игре или приложении, можно реализовать GestureListener интерфейс и передать его в конструктор GestureDetector класса. Затем детектор устанавливается как InputProcessor, либо как InputMultiplexer или в качестве основного InputProcessor:

public class MyGestureListener implements GestureListener {

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

    @Override
    public boolean tap (int x, int y, int count) {
        return false;
    }

    @Override
    public boolean longPress (int x, int y) {
        return false;
    }

    @Override
    public boolean fling (float velocityX, float velocityY) {
        return false;
    }

    @Override
    public boolean pan (int x, int y, int deltaX, int deltaY) {
        return false;
    }

    @Override
    public boolean zoom (float originalDistance, float currentDistance) {
        return false;
    }

    @Override
    public boolean pinch (Vector2 initialFirstPointer, Vector2 initialSecondPointer, Vector2 firstPointer, Vector2 secondPointer) {
        return false;
    }
}
Gdx.input.setInputProcessor(new GestureDetector(new MyGestureListener()));

GestureListener может сигнализировать о том, что он получил событие или что хочет передать его дальше следующему InputProcessor обработчику, возвращая из методов true или false соответственно.

Как и для событий, переданных обычному InputProcessor обработчику, в libGDX соответствующие методы будут вызваны в потоке визуализации перед непосредственным вызовом ApplicationListener.render() метода.

GestureDetector также имеет второй конструктор, который позволяет ему определить различные параметры для обнаружения жеста. Пожалуйста, для получения дополнительной информации обратитесь к Javadoc конструктора GestureDetector.

Обработка событий

В libGDX игре или приложении обработка событий позволяет получить более детальную и прежде всего хронологическую информацию о входных данных от пользователя. Обработка событий обеспечивает способ реализации взаимодействия с пользовательским интерфейсом, где важны конкретные последовательности ввода, например зажатие кнопки и затем отпускание означает, что пользователь нажал кнопку. Такое взаимодействие трудно реализовать с помощью опроса состояния клавиши.

Процессор ввода

Обработка событий осуществляется с помощью общего шаблона наблюдателя. Сначала мы должны реализовать интерфейс обработчик, называемый InputProcessor:

public class MyInputProcessor implements InputProcessor {
    @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 x, int y, int pointer, int button) {
        return false;
    }

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

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

    @Override
    public boolean touchMoved (int x, int y) {
        return false;
    }

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

Первые три метода позволяют перехватывать события клавиатуры:

  • keyDown(): метод вызывается, когда была нажата клавиша. Сообщает код клавиши, который можно найти в Keys классе.
  • keyUp(): метод вызывается, когда была отпущена клавиша. Сообщает код клавиши, как указано выше.
  • keyTyped(): метод вызывается, когда был сгенерирован Unicode символ при вводе с клавиатуры. keyTyped() метод может быть использован для реализации текстовых полей и аналогичных элементов пользовательского интерфейса.

Следующие три метода сообщают о событиях мыши и сенсорного экрана:

  • touchDown(): метод вызывается, когда палец коснулся экрана или была нажата кнопка мыши. Сообщает последние известные координаты, а также индекс указателя и кнопку мыши (для сенсорных экранов всегда Buttons.LEFT).
  • touchUp(): метод вызывается, когда палец был убран с экрана или была отпущена кнопка мыши. Сообщает последние известные координаты, а также индекс указателя и кнопку мыши (для сенсорных экранов всегда Buttons.LEFT).
  • touchDragged(): метод вызывается, когда палец перемещается по экрану или перемещается мышь с нажатой кнопкой. Какая была нажата кнопка – не сообщается, так как во время движения могло быть нажато несколько кнопок. В libGDX вы можете использовать Gdx.input.isButtonPressed() метод для проверки определенной кнопки.
  • touchMoved(): метод вызывается, когда мышь перемещается по экрану без нажатой кнопки. Это событие имеет значение только для персонального компьютера, и никогда не будет вызываться на устройствах с сенсорным экраном, где вы можете получить только touchDragged() событие.
  • scrolled(): метод вызывается, когда совершается прокрутка колеса мыши. Сообщает значение -1 или 1, в зависимости от направления вращения. Этот метод никогда не вызывается для устройств с сенсорным экраном.

Каждый из методов возвращает boolean значение. Почему это так, мы изучим в разделе о классе InputMultiplexer.

После реализации InputProcessor нужно сказать об этом libGDX, чтобы фреймворк мог вызвать ее, когда придет новое событие.

MyInputProcessor inputProcessor = new MyInputProcessor();
Gdx.input.setInputProcessor(inputProcessor);

С этого момента все новые события ввода будет сообщаться MyInputProcessor экземпляру.

InputAdapter

В libGDX существует InputAdapter класс, который реализует полностью интерфейс InputProcessor, возвращая false в каждом методе. Вы можете наследоваться от InputAdapter класса и переопределить необходимые методы. Также можно использовать анонимные внутренние классы.

Gdx.input.setInputProcessor(new InputAdapter(){
    public boolean touchDown(int x,int y,int pointer,int button){
        // код при нажатии
        return true; // возвращает true, сообщая, что событие было обработано
    }

    public boolean touchUp(int x,int y,int pointer,int button){
        // код при отпускании
        return true; // возвращает true, сообщая, что событие было обработано
    }
});

InputMultiplexer

Иногда хочется иметь несколько InputProcessor, например один процессор для пользовательского интерфейса, который лучше вызывать первым, а второй процессор для событий ввода, который управляет игровым миром. Для достижения этой цели можно воспользоваться InputMultiplexer классом libGDX фреймворка.

InputMultiplexer multiplexer = new InputMultiplexer();
multiplexer.addProcessor(new MyUiInputProcessor());
multiplexer.addProcessor(new MyGameInputProcessor());
Gdx.input.setInputProcessor(multiplexer);

Класс InputMultiplexer будет перенаправлять новые события первому InputProcessor. Если этот процессор вернет false из вызванного метода обработки события, это указывает на то, что событие не было обработано и мультиплексор передаст событие другому процессору. Благодаря этому механизму в libGDX, MyUiInputProcessor может обрабатывать любые события, которые исходят от элементов управления и передавать любые другие события в MyGameInputProcessor.

Пример непрерывной обработки ввода

Если вы хотите перемещать в игре персонажа, используя процессор ввода, то вы заметите, что персонаж будет двигаться только тогда, когда нажата клавиша. Для непрерывной обработки событий ввода или перемещения спрайта, вы можете добавить boolean флаг в класс персонажа.

public class Bob
{
    boolean leftMove;
    boolean rightMove;
    ...
    updateMotion()
    {
        if (leftMove)
        {
            x -= 5 * Gdx.graphics.getDeltaTime();
        }
        if (rightMove)
        {
            x += 5 * Gdx.graphics.getDeltaTime();
        }
    }
    ...
    public void setLeftMove(boolean t)
    {
        if(rightMove && t) rightMove = false;
        leftMove = t;
    }
    public void setRightMove(boolean t)
    {
        if(leftMove && t) leftMove = false;
        rightMove = t;
    }

Процессор ввода

...
    @Override
    public boolean keyDown(int keycode)
    {
        switch (keycode)
        {
        case Keys.LEFT:
            bob.setLeftMove(true);
            break;
        case Keys.RIGHT:
            bob.setRightMove(true);
            break;
        }
        return true;
    }
    @Override
    public boolean keyUp(int keycode)
    {
        switch (keycode)
        {
        case Keys.LEFT:
            bob.setLeftMove(false);
            break;
        case Keys.RIGHT:
            bob.setRightMove(false);
            break;
        }
        return true;
    }

Опрашивание событий (Polling)

Опрашивание в libGDX относится к механизму проверки текущего состояния устройства ввода, например, нажата ли определенная клавиша, где находится первый палец на экране и так далее. Это простой и быстрый способ обработки пользовательского ввода и его будет хватать для большинства аркадных игр.

Использование только опрашивания

Если вы решили положиться на использование только опрашивания, то можно пропустить некоторые события, например быстрое нажатие и отпускание клавиши. Если нужно убедиться в том, что определенная последовательность ввода была завершена, используйте обработку событий.

Опрос клавиатуры

Опрос ввода с клавиатуры осуществляется с помощью всего одной простой строки кода, как показано ниже.

boolean isAPressed = Gdx.input.isKeyPressed(Keys.A);

Параметр, передаваемый в этот метод – код клавиши. Вместо запоминания всех этих кодов, libGDX предоставляет статический Keys класс Input интерфейса, который содержит коды клавиш, вы можете использовать их. Смотрите коды клавиш в Keys классе.

Опрос мыши и сенсорного экрана

Есть несколько методов, относящихся к опросу мыши и сенсорного экрана. Для проверки одного или нескольких пальцев, находящихся в настоящее время на экране (что эквивалентно нажатию кнопки мыши), вы можете сделать следующее:

boolean isTouched = Gdx.input.isTouched();

Для мультитач экранов, возможно, вам будет интересно находиться ли определенный палец (указатель) в настоящее время на экране.

// Проверка есть ли сейчас прикосновение к экрану
boolean firstFingerTouching = Gdx.input.isTouched(0);
boolean secondFingerTouching = Gdx.input.isTouched(1);
boolean thirdFingerTouching = Gdx.input.isTouched(2);

Каждый палец, который коснулся экрана, получает так называемый индекс указателя. Первый палец, прикоснувшись, получает индекс 0, следующий получает индекс 1 и так далее. Если палец отрывается от экрана и прикасается снова, в то время как другие пальцы по-прежнему на экране, то палец получит первый свободный индекс. Например:

  1. первый палец коснулся -> 0
  2. второй палец коснулся -> 1
  3. третий палец коснулся -> 2
  4. второй палец отпущен -> 1 становится свободным
  5. первый палец отпущен -> 0 становится свободным, на данный момент только 2 индекс используется
  6. другой палец коснулся -> 0, так как это первый свободный индекс

На персональном компьютере или в браузере всегда будет только один палец.

Если вы хотите проверить, если пользователь коснулся и отпустил любой палец, вы можете использовать justTouched() метод:

// Проверка было ли прикосновение к экрану
boolean justTouched = Gdx.input.justTouched();

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

Отметим, что данный метод является ненадежным, поскольку он основан на опросе.

Чтобы получить координаты конкретного пальца вы можете использовать следующие методы:

int firstX = Gdx.input.getX();
int firstY = Gdx.input.getY();
int secondX = Gdx.input.getX(1);
int secondY = Gdx.input.getY(1);

Здесь мы получаем координаты прикосновения как индекс указателя 0 (0 по умолчанию) и индекса указателя 1. Координаты представлены в системе координат относительно экрана. Координаты (0, 0) находится в левом верхнем углу экрана, X-ось направлена ​​вправо, Y-ось направлена ​​вниз.

Кнопки мыши

На компьютере вы можете проверить, какие кнопки мыши нажаты в данный момент:

boolean leftPressed = Gdx.input.isButtonPressed(Input.Buttons.LEFT);
boolean rightPressed = Gdx.input.isButtonPressed(Input.Buttons.RIGHT);

Для использования других констант смотрите Buttons класс libGDX фреймворка.

Интерпретация прикосновений на Android

На Android происходит эмуляция только левой кнопки мыши. Любое событие о прикосновении будет интерпретироваться так, как если бы оно было сделано нажатием левой кнопки мыши. Сенсорные экраны, очевидно, не имеют понятия левой, правой и средней кнопки.