Всем привет
Продолжаем серию разборов чужого кода с этой страницы: Defold public example.
Сегодня разбираем пример от britzl — Game object movement.
Попробовать этот проект в действии: запустить пример.
Исходная папка с проектом: ссылка на github.
В этом примере показаны два разных способа анимации положения игрового объекта:
- Использование go.animate() для перемещения по прямой линии. Для перемещения нужно щелкнуть ЛКМ.
- Использование процедурной анимации, где игровой объект перемещается в зависимости от ввода пользователя. Нажмите «Влево», «Вправо», «Вверх», «Вниз» для перемещения.
Первоначальный обзор примера:
Имеется сцена с фоном, на которой размещены два игровых объекта. Одному персонажу можно поменять позицию по нажатию ЛКМ, другому изменить своё местоположение в пространстве с помощью нажатой клавиши клавиатуры.
Рассмотрим game_object_movement.atlas
У нас имеется одно изображение для фона и несколько для анимации.3.
Рассмотрим иерархию игровых объектов в game_object_movement.collection
У нас имеется 3 игровых объекта. Для их визуального представления используется компонентsprite
. В качестве фона выступает игровой объект bg
, он является статичным на протяжении всего жизненного цикла игры. Игровые объекты, такие как go_animate
и go_update
, из-за добавленных к каждому соответствующих скриптов go_animate.script
и go_update.script
будут иметь разное поведение во время игры. Один объект будет перемещаться к указанной ЛКМ позиции, другой будет перемещаться с помощью клавиш клавиатуры.
Запустите проект, нажав f5
или (ctrl + B)
.
Если фоновое изображение не отображается:
Измените
z-position
игрового объекта bg
с -1.0 на -0.9:Теперь фоновый спрайт отображается на экране:
Может быть полезно: Компоненты игрового объекта.
Рассмотрим game.input_binding
Обратите внимание на то, что в game.input_binding
указаны привязки ввода и для геймпада:
Разберём код в файле `go_animate.script`
Что делает этот код:
- принимает нажатие (клик мыши или тап на экране),
- при клике двигает объект в точку нажатия с помощью go.animate(),
- пишет текст на экране “Click to use go.animate()” каждый кадр.
При старте сцены захватывает ввод для этого объекта ("."
означает текущий объект).
При уничтожении объекта освобождает фокус ввода.
Может быть полезно:
Устройтво ввода в Defold
Жизненный цикл приложения
function init(self)
msg.post(".", "acquire_input_focus")
end
function final(self)
msg.post(".", "release_input_focus")
end
Каждый кадр отправляет сообщение в рендер о том, чтобы нарисовать текст: “Click to use go.animate()” в позиции (100, 0).
function update(self, dt)
msg.post("@render:", "draw_text", { text = "Click to use go.animate()", position = vmath.vector3(10, 100, 0) } )
end
Может быть полезно:
Рендер пайплайн
Функция, которая срабатывает при любом вводе (мышь, экран, клавиши):
function on_input(self, action_id, action)
Проверяется: событие — это touch
и оно отпущено (а не нажато).
if action_id == hash("touch") and action.released then
Попробуйте заменить
action.released
, наaction.pressed
. В чём отличие?
Рассуждения
Когда ты используешь action.pressed
, это значит:
Событие происходит в момент нажатия пальцем/мышью.
Когда ты используешь action.released
, это значит:
Событие происходит в момент отпускания пальца/мыши
Логика здесь такая:
- Мы хотим узнать точку, где клик закончился (отпустили палец).
- Только после отпускания у нас есть точные координаты, где игрок захотел переместить объект.
- И только после отпускания создаём анимацию
go.animate()
.
Если бы мы делали на pressed
, то:
- Срабатывало бы сразу при первом касании,
- Координаты могли бы быть не окончательными,
- Пользователь ещё мог “передумать” или “передвинуть” палец.
go.cancel_animations(".", "position")
Останавливаем анимацию “position”:
go.animate(".", "position", go.PLAYBACK_ONCE_FORWARD, vmath.vector3(action.screen_x, action.screen_y, 0), go.EASING_OUTCUBIC, 1, 0, function(self, url, property)
print("go.animate() done")
end)
Часть | Что делает |
---|---|
go.animate |
Функция запуска анимации свойства игрового объекта |
"." |
ID объекта: "." значит текущий объект, на котором висит скрипт |
"position" |
Какое свойство анимируем? Здесь — позицию объекта |
go.PLAYBACK_ONCE_FORWARD |
Как анимировать: один раз вперёд, без возврата |
vmath.vector3(action.screen_x, action.screen_y, 0) |
Конечное значение позиции: экранные координаты клика |
go.EASING_OUTCUBIC |
Как будет двигаться объект: плавное замедление в конце |
1 |
Длительность анимации: 1 секунда |
0 |
Задержка перед стартом анимации: 0 секунд |
function(self, url, property) |
Функция-обработчик, вызывается когда анимация завершена |
print("go.animate() done") |
Выводит сообщение в консоль после завершения анимации |
Что происходит в итоге?
- Когда отпускаешь кнопку мыши или палец,
- Объект начинает плавно перемещаться в точку касания,
- Перемещение длится 1 секунду с красивым замедлением в конце (
OUTCUBIC
), - После завершения движения печатается
"go.animate() done"
в консоль.
Что значит function(self, url, property)
?
Это функция-коллбек (обратный вызов), которая будет автоматически вызвана в нашем коде, когда анимация завершится.
Эта функция принимает три аргумента:
Аргумент | Что это такое |
---|---|
self |
Ссылка на текущий скриптовый объект (тот же self , что в скрипте) |
url |
URL объекта, на котором анимация была запущена |
property |
Имя свойства, которое анимировали (например, "position" ) |
Может быть полезно: API reference (go)
return true
Означает следующее:
- Ты говоришь движку Defold: “Я обработал это событие ввода (
touch
+released
) полностью.” - После возврата
true
другие скрипты или объекты не получат это событие. - То есть, событие не будет передано дальше по иерархии объектов.
Если бы мы не написали return true
:
- Событие продолжило бы передаваться дальше другим объектам,
- И, возможно, другой скрипт тоже начал бы обрабатывать нажатие,
- Могли бы возникнуть конфликты или дублирование действий.
В Defold в функции on_input(self, action_id, action)
возврат true
:
- останавливает дальнейшую обработку события,
- гарантирует, что другие объекты и компоненты больше не получат это событие.
Разберём код в файле `go_update.script`
local HASH_LEFT = hash("left")
local HASH_RIGHT = hash("right")
local HASH_UP = hash("up")
local HASH_DOWN = hash("down")
Переменные HASH_LEFT, HASH_RIGHT, HASH_UP, HASH_DOWN — это хэш-значения, которые соответствуют действиям, связанным с клавишами стрелок. Хэши используются для повышения производительности и предотвращения проблем с поиском строк.
Те самые строки, которые были указаны в game.input_binding
.
self.speed = vmath.vector3(0, 0, 0)
Инициализация переменной self.speed
, которая определяет скорость движения объекта, установлена в 0 по всем осям (x, y, z).
go.set_position(go.get_position() + self.speed * dt)
Далее обновляется позиция объекта, прибавляя скорость, умноженную на время кадра (dt
), чтобы движение было плавным и независимым от частоты кадров.
if action_id == HASH_LEFT then
if action.pressed then
self.speed.x = -200
elseif action.released then
self.speed.x = 0
end
return true
elseif ...
Если нажата стрелка влево (HASH_LEFT
), объект начинает двигаться влево, устанавливая скорость по оси x в -200.
С другими событиями примерно также, меняется только переменная скорости.
Для чего нужно dt?
dt
(delta time) — это параметр, который представляет собой время, прошедшее с последнего кадра игры. В контексте игры, где каждый кадр может быть обработан с разной частотой (в зависимости от производительности компьютера или устройства), использование dt
позволяет сделать движение и другие действия в игре независимыми от частоты кадров.
Когда вы обновляете позицию объекта или выполняете другие действия, такие как движение, важно учитывать, что частота кадров может быть различной. Если вы не будете использовать dt
, объект может двигаться с разной скоростью на разных устройствах или при разных частотах кадров.
Пример:
Если в коде для движения объекта используется просто фиксированное значение скорости, например:
go.set_position(go.get_position() + self.speed)
То объект будет двигаться на фиксированное расстояние за каждый кадр. Однако если на одном устройстве частота кадров высокая (например, 60 FPS), а на другом — низкая (например, 30 FPS), то объект будет двигаться быстрее на первом устройстве и медленнее на втором. Это делает поведение игры нестабильным на разных устройствах.
Используя dt
, вы можете учитывать время, прошедшее с последнего кадра, и корректировать движение объекта:
go.set_position(go.get_position() + self.speed * dt)
Здесь self.speed * dt
позволяет двигаться на одинаковое расстояние в зависимости от времени, прошедшего с последнего кадра, и независимо от частоты кадров. Например, если в одном кадре прошло 1/60 секунды (при 60 FPS), объект переместится на меньшее расстояние, чем если прошло 1/30 секунды (при 30 FPS). Это делает движение объекта плавным и стабильным, независимо от того, сколько кадров в секунду обрабатывает система.
Как это работает?
dt
— это время, которое прошло с предыдущего кадра.- Умножив скорость на
dt
, вы получаете перемещение, которое корректируется в зависимости от времени, прошедшего между кадрами.
Таким образом, использование dt
гарантирует, что движение объекта будет одинаковым на всех устройствах, при любых частотах кадров.
Надеюсь, кому-нибудь этот материал будет полезен.
Также я буду рад получить обратную связь, чтобы в дальних постах я смог улучшить качество изложений/объяснений.
Всем спасибо за внимание
Другие разборы:
- Разбор проектов на Defold 1.Tilemap Collisions [tilemap]
- Разбор проектов на Defold 2. Parallax [parallax]
- Разбор проектов на Defold 3. Движение игровых объектов [animation, movement, input]
- Разбор проектов на Defold 4. Воспроизвести анимацию [animation movement input]