Всем Приветствие! Сегодня я бы хотел рассказать о том, как реализовать управление свайпами в игровом проекте для Android на движке Godot Engine.
Меня зовут Пётр, и я являюсь одним из активных пользователей игрового движка Godot Engine.
В русскоязычном сегменте огромнейший дефицит материалов по данному инструменту, что меня сильно удивляет, так как он является одним из самых быстро-развивающихся игровых движков.
Конечно, он во многом уступает таким движкам, как Unity, UE, и подобным, да и игру класса «AAA» на нём не сделаешь.
Однако! Он бесплатный (полностью), кросс-платформенный (полностью), а его вес составляет порядка 60 мегабайт, в противовес тому же Unity.
Движок запускается на большинстве устройств, даже если последние из совсем уж бюджетного ряда. Так же «из коробки» он уже русифицирован, имеет на борту все необходимые инструменты, не требует наличия дополнительного ПО, и не съедает в запущенном виде всю оперативную память.
На Хабре я уже видел пару статей по нему, и это очень мало, так как он настолько дружелюбен к разработчику, что пройти мимо него и не попробовать — большое упущение.
Тема данного поста — реализация управления свайпами (жестами) в Android-проекте.
Вообще, опыт моего использования движка довольно обширен, и, если тема получит отклик, я могу оформить целый обучающий курс. Данным же постом я хочу хотя бы немного привлечь ваше внимание к движку.
В качестве языка программирования можно использовать, опять же, из коробки, два варианта: GDScript и C#. Я буду использовать первый.
Интерфейс основного редактора выглядит так:
В нем можно работать одновременно с 3D, 2D, скриптами и вообще всем, что может потребоваться разработчику.
В движке используется подход, при котором ваша игра представляет из себя набор сцен, вложенных друг в друга. И тут может возникнуть некоторая путаница, потому термин «сцена» я использую для узлов, которые представляют из себя именно игровые сцены (ситуации, окна, состояния игры (меню, игра, т.д.)), а для остальных случаев я использую термин «префаб», позаимствованный у Unity.
Поскольку в данном посте я буду рассматривать частный случай, то и разворачивать некоторые темы я не буду. Если что-то неясно — есть комментарии.
Так вот, основной сценой для демонстрации у нас будет game.
Структура её выглядит так:
Корневой узел game хранит в себе вложенные:
— world, — хранит в себе данные уровня
— — level, — набор объектов окружения (блоки, кольца, препятствия)
— — player, — объект игрока
— — InterpolatedCamera, — плавная камера, следящая за игроком
— gui, — интерфейс, задействован не будет
Имена объектам и структура произвольная и это только частный случай.
Возле некоторых объектов вы можете видеть значки, говорящие о том, что этот объект является префабом (вложенной сценой) и у него так же может быть добавлен скрипт.
Так, нажав на значок «скрипт», мы попадаем в редактор сценариев, по сути просто переключается режим работы движка.
В данном режиме работы можно редактировать поведение объектов. Собственно, в данном случае это скрипт объекта world, который при загрузке объекта в игру устанавливает в объект камеры (InterpolatedCamera) устанавливает цель для слежения.
extends Spatial
func _ready():
$InterpolatedCamera.target = '../player/camera' # путь такой, потому, что объекты на одном уровне относительно друг друга (камера и игрок). У объекта player узел "camera" это просто pivot. Точка в пространстве. Сама же камера будет плавно стремиться к данной точке.
Синтаксически GDScript похож на Python. Пройти краткое обучение GDScript на русском языке можно тут: GDScript Book
С объектом world понятно, он просто устанавливает камере цель для слежения. Следующий объект (уже дочерний для world), — это level. Его структура выглядит так:
По сути, это просто расставленные объекты с физическими свойствами, не имеющие скриптов и поведения. Кроме объектов «cell». Это вращающиеся колечки, исчезающие при соприкосновении с объектом игрока.
Больше всего сейчас интересен объект player, в котором содержится логика управления свайпами для сенсорных экранов.
Выглядит данный префаб так:
Корневой объект имеет скрипт, к которому мы перейдем немного позже.
Сначала рассмотрим вложенные объекты.
- camera, — тот самый указатель для камеры, которая будет плавно к нему стремиться, дабы сгладить движение. Не имеет никакого поведения и просто существует в рамках объекта player.
- CollisionShape, — это физическое тело объекта. Именно он реагирует на столкновения и позволяет «осязать» объект. Для примера я выбрал его в виде куба, хотя поддерживаются самые разные формы, вплоть до произвольных. По сути для физической формы объектов ограничений нет совсем.
- MeshInstance, — это визуальное представление объекта. В данном случае это шар, покрытый текстурой. Сделан в Blender 3D меньше, чем за минуту.
- Tween, специальный аниматор для плавных изменений численных свойств объектов. Он отвечает за анимированные сдвиги влево и вправо при свайпах.
Ну и теперь рассмотрим уже сам скрипт в корневом объекте. Он получается самым объемным во всей «игре».
Ну и его описание и расшифровка.
extends KinematicBody # Для поведения используем тип физического управляемого объекта
# Определение констант
const GRAV = 0.5 # Скорость падения (по правде просто интерполяция по оси Y, т.к. писать формулу физики просто дольше и сложнее, а визуально будет так же)
const SPEED = 2 # Скорость движения
const SWIPE_SPEED = 30 # Скорость смещения при свайпах влево и вправо
const JUMP_SPEED = 10 # Скорость прыжка при свайпе вверх
onready var ball = $MeshInstance # Берем визуальный шар в переменную для облегчения обращений
onready var tween = $Tween # То же самое для аниматора чисел
var vel = Vector3() # Определение переменной с движущими силами
var swipe = '' # Тут будет храниться необходимое значение текущего свайпа
func _ready():
pass # функция нотовности к работе в примере не пригодится; можно не определять даже
# А вот функция фиксированного обновления состояния (60FPS) очень нужна
func _physics_process(delta): # delta - Это Delta Time фактор, хранящий время, прошедшее с прошлого кадра
vel.y -= GRAV # Первым делом толкаем объект вниз (чтобы пытался падать)
vel.z = -SPEED # Вторым делом двигаем его вперед. Используются значения констант
ball.rotate_x(-delta * SPEED * 2) # Так же визуально делаем так, чтобы шар "катился"
if swipe && swipe != 'swiped': # Если обнаружен новый свайп и он ещё не обработан
if swipe == 'up' && is_on_floor(): # Если свайпнули вверх и объект на плоскости (поверхности)
vel.y = JUMP_SPEED # Меняем скорость падения на константу, и вуаля - объект прыгает
elif swipe == 'left' || swipe == 'right': # Если свайпнули влево или вправо
tween.interpolate_property(self, "translation", # Плавно изменяем значение положения текущего объекта
translation, translation+Vector3(-2 if swipe == 'left' else 2,0,0), 0.2, # Берем его текущее положение, прибавляем к нему вектор с некоторым значением по X. Оно рассчитывается исходя из направления свайпа. -2 если влево, иначе 2
Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) # Устанавливаем тип интерполяции и "резкость" анимации
tween.start() # Стартуем изменение значения
swipe = 'swiped' # и помечаем свайп, что он отработан
vel = move_and_slide(vel, Vector3.UP) # Для учета физики требуется вызвать специальную функцию, которая обновит данные в векторе сил для движения и так же начнет двигать объект с учетом наших потуг
# Следующая функция отвечает за обработку сенсорных событий
func _input(e):
if e is InputEventScreenDrag: # Проверяем, что событие именно движение пальца по экрану
if !swipe: # Если в данный момент свайп не производится
if e.relative.y < -SWIPE_SPEED: # Проверяем скорость движения пальца по оси Y
swipe = 'up' # и если она меньше (потому что вверх идет на уменьшение) отрицательной скорости свайпа (что переводится, как если она больше или равна необходимой для учета свайпа), то устанавливаем свайп как "вверх (UP)"
elif e.relative.x < -SWIPE_SPEED: # Если палец идет по другой траектории, то проверяем уже ось X. Если палец двигается влево - то влево
swipe = 'left'
elif e.relative.x > SWIPE_SPEED: # Если вправо,
swipe = 'right' # то вправо
elif e is InputEventScreenTouch: # Затем обработаем событие отпускания экрана
if !e.pressed: # Когда игрок убирает палец с экрана
swipe = '' # Очищаем значение свайпа
Каждую строчку описал, надеюсь, со скриптами всё плюс минус ясно.
Если русским языком, то мы при движении пальцем по экрану регистрируем направление движения и скорость. Если палец движется с нужной скоростью, засчитываем свайп. Исходя из того, по какой оси и в какую сторону шло движение, имеем три вариант: ВВЕРХ, ВЛЕВО, ВПРАВО.
Записываем полученное значение в переменную, которую тут же подхватывает событие обновления состояния объекта, и выполняет с ним нужные действия, после чего помечает событие, как отработанное и ожидает следующее.
Я взял не самый простой способ реализации свайпа, однако довольно надежный и работающий в большинстве ситуаций. Само собой, зависит от жанра и типа игры.
В результате проделанной работы мы получаем пример того, как легко реализовать управление свайпами буквально за десять минут.
Суммарно на всё у меня ушло минут 30, это с учетом создания моделек в Blender.
Ну и по традиции…
Автор: Петр