Всем привет
Продолжаем серию разборов чужого кода с этой страницы: Публичный пример Defold.
Сегодня разбираем пример от britzl — Магнит монет.
Попробуйте этот проект в действии: запустите пример.
Исходная папка с проектом: ссылка на github.
Исходная папка с публичными примера Defold на github: ссылка на github.
Обращение к новичкам:
Я предполагаю, что у вас уже установлен Defold, если нет, перейдите по этой ссылке: Добро пожаловать в Defold.
Также, будет плюсом, если вы хотя бы поверхностно знакомы со строительными блоками Defold. Если нет, ознакомиться с основными концепциями Defold можно на официальном сайте — перейдите по этой ссылке. 10.
Пример того, как скачать и открыть готовый проект:
Скачиваем архив с примерами проектов из github
Распаковываем скачанный ZIP архив примеров в любую папку во вашему усмотрению.
Переходим в папку examples
.
Ищем проект play_animation
.
Открываем game.project
.
Внимание: скриншоты представлены ниже, это пример скачивания и открытия проекта, в этом примере мы рассматриваем проект с названием play_animation, потому название папок проекта будет отличаться.
Этот пример от britzl демонстрирует, как реализовать эффект «магнита», притягивающего монеты к кораблю.
Рассмотрим powerup.go:
Игровой объект, содержащий спрайт и объект столкновения, маски и группы настроены на взаимодействие с player.
Тип объекта столкновения стоит как
Kinematic
, т.к этот объект не должен подчиняться полностью физическим силам и этот игровой объект будет управляться через скрипт. А вообще, можно установить тип Trigger
.
Рассмотрим coin_magnet.collection:
Имеются три игровых объектов в качестве фона:
bg1
, bg2
, bg3
.Игровой объект
player
, который содержит в себе визуальное представление(sprite
), две фабрики, которые отвечают за создание монет и усилителей. Объекты столкновений для магнита и корабля. Скрипт для обработки логика игры.
Рассмотрим coin.go:
Всё аналогично предыдущему игровому объекту, за исключением того, что к маске добавляется и другой тег
coinmagnet
для магнита.
Рассмотрим coin_magnet.script:
-- Список имён игровых объектов с фонами
local backgrounds = { "bg1", "bg2", "bg3" }
-- Высота одного фонового изображения. Используется, чтобы фоны циклично повторялись(эффект бесконечного скроллинга).
local background_size = 256 * 3
-- Интервал появления монет (каждые 0.15 сек).
local coin_spawn_interval = 0.15
-- Интервал появления усилений(каждые 6 секунд)
local powerup_spawn_interval = 6
-- Сколько секунд работает магнит после активации
local magnet_lifetime = 10
Функция init(self):
-- захватывает ввод, чтобы можно было обрабатывать клавиши.
msg.post(".", "acquire_input_focus")
-- устанавливает начальное значение для случайных чисел
math.randomseed(os.time())
-- Скорость прокрутки фона, монет и усилений вниз
self.speed = 400
self.powerups = {} -- список усилений на сцене
self.coins = {} -- все монеты на сцене
self.coins_to_collect = {} -- монеты, которые магнит притягивает
-- Таймеры для спавна монет и усилений
self.coin_spawn_timer = coin_spawn_interval
self.powerup_spawn_timer = powerup_spawn_interval
-- Направление движения игрока (изменяется в on_input)
self.player_direction = vmath.vector3()
-- Таймер для работы магнита. Пока 0 — магнит не работает.
self.magnet_timer = 0
-- Выключаем магнит: он невидим и неактивен по умолчанию.
msg.post("coinmagnet", "disable")
msg.post("#coinmagnetcollisionobject", "disable")
Функция on_input(sefl, action_id, action):
- Эта функция автоматически вызывается Defold’ом, когда игрок нажимает клавишу или совершает другое действие.
action_id
— имя действия (например,"left"
,"right"
,"touch"
), изinput_binding
.action
— таблица с данными о событии (нажата/отпущена, позиция и т.д.).
if action_id == hash("left") then
Проверка: было ли действие “left” (нажатие влево)?
if action.released then
self.player_direction.x = 0
else
self.player_direction.x = -1
end
Если кнопку отпустили — движение останавливается.
Если нажали и держим — двигаем игрока влево (по X со знаком минус).
elseif action_id == hash("right") then
if action.released then
self.player_direction.x = 0
else
self.player_direction.x = 1
end
То же самое, но направление вправо (по оси X со знаком +1).
В update(self, dt)
есть строка:
local player_pos = go.get_position() + self.player_direction * 200 * dt
То есть, переменная self.player_direction.x
(−1, 0 или 1) умножается на скорость, и игрок перемещается влево, стоит на месте или вправо.
Функция update(sefl, dt):
-- move background
for _,bg in pairs(backgrounds) do
local pos = go.get_position(bg)
pos = pos + vmath.vector3(0, -self.speed * dt, 0)
if pos.y <= -background_size then
pos.y = pos.y + #backgrounds * background_size
end
go.set_position(pos, bg)
end
Этот код отвечает за движение фона вниз и создание эффекта бесконечного прокручивающегося фона.
В начале файла была создана таблица backgrounds
, именно по этой таблице и мы шагаем в цикле. Каждый шаг (bg
) — это имя игрового объекта, указанного в таблице backgrounds
.
-- получаем текущую позицию фона bg
local pos = go.get_position(bg)
-- смещаем фон вниз по оси Y, со скоростью self.speed. Умножение на dt (дельта времени) нужно для плавного движения независимо от FPS.
pos = pos + vmath.vector3(0, -self.speed * dt, 0)
-- проверяем: если фон вышел ниже нижнего края экрана (например, у < -768), то пора переместить его вверх, чтобы он снова появился сверху
if pos.y <= -background_size then
-- фон перекидывается вверх на высоту всех фонов, создавая эффект зацикливания (например, 3 фона по 768 —> перенос вверх на 2304 пикселя).
pos.y = pos.y + #backgrounds * background_size
end
-- устанавливаем новую позицию для текущего фона bg
go.set_position(pos, bg)
Этот блок кода:
- Плавно двигает все фоновые объекты вниз.
- Когда фон выходит за экран снизу, он “телепортируется” обратно наверх.
- В результате получается бесконечная вертикальная прокрутка.
-- проходим по всем монетам в таблице self.coins
-- coin — это ID Game Object монеты
for coin,_ in pairs(self.coins) do
-- получаем текущую позицию этой монеты
local pos = go.get_position(coin)
-- смещаем монету вниз по оси Y со скоростью self.speed
-- dt — дельта времени, чтобы движение было плавным независимо от FPS
pos = pos + vmath.vector3(0, -self.speed * dt, 0)
-- устанавливаем новую позицию монеты
go.set_position(pos, coin)
-- если монета ушла слишком низко (за пределы экрана), она:
-- удаляется как из сцены, так и из таблицы self.coins
if pos.y <= -50 then
go.delete(coin)
self.coins[coin] = nil
end
end
Этот блок кода:
- Плавно двигает все монеты вниз.
- Автоматически удаляет монеты, которые вышли за нижний край экрана, чтобы:
- избежать утечек памяти,
- не нагружать игру лишними объектами.
for powerup,_ in pairs(self.powerups) do
local pos = go.get_position(powerup)
pos = pos + vmath.vector3(0, -self.speed * dt, 0)
go.set_position(pos, powerup)
if pos.y <= -50 then
go.delete(powerup)
self.powerups[powerup] = nil
end
end
Этот код делает то же самое для усиления(магнита), что и ранее объясненный код для монеты.
-- уменьшает таймер каждую секунду (-dt)
self.coin_spawn_timer = self.coin_spawn_timer - dt
if self.coin_spawn_timer <= 0 then
-- сбрасывается в начальное значение
self.coin_spawn_timer = coin_spawn_interval
-- создаётся монета через фабрику на случайной позиции(вне экрана сверху)
local id = factory.create("#coinfactory", vmath.vector3(math.random(20, 620), 1500, 0), nil, {}, 0.25)
-- новый объект заносится в таблицу, чтобы отслеживать его
self.coins[id] = true
end
self.powerup_spawn_timer = self.powerup_spawn_timer - dt
if self.powerup_spawn_timer <= 0 then
self.powerup_spawn_timer = powerup_spawn_interval
local id = factory.create("#powerupfactory", vmath.vector3(math.random(20, 620), 1500, 0))
self.powerups[id] = true
end
Почти тоже самое, только используется другая фабрика и объекты спавнятся реже.
local player_pos = go.get_position() + self.player_direction * 200 * dt
go.set_position(player_pos)
- Получаем текущую позицию игрока.
- Прибавляем направление движения (
-1
,0
,1
) по X, умноженное на скорость (200) иdt
. - Перемещаем игрока по X (влево или вправо).
self.player_direction
устанавливается в on_input()
при нажатии клавиш.
for coin,_ in pairs(self.coins_to_collect) do
local coin_pos = go.get_position(coin)
local delta = player_pos - coin_pos
go.set_position(coin_pos + delta * 5 * dt, coin)
if vmath.length(delta) < 30 then
go.delete(coin)
self.coins_to_collect[coin] = nil
end
end
- Проходим по всем монетам, которые должны притягиваться магнитом (
self.coins_to_collect
). - Вычисляем вектор от монеты к игроку (
delta
). - Двигаем монету к игроку с небольшим множителем (плавное движение).
- Если расстояние до игрока меньше 30 — удаляем монету (считаем, что игрок её “собрал”).
Может быть полезно: математика или Математика в Defold. Часть 1: Действия с векторами - #2 от пользователя vovs03
if self.magnet_timer > 0 then
self.magnet_timer = self.magnet_timer - dt
if self.magnet_timer <= 0 then
msg.post("coinmagnet", "disable")
msg.post("#coinmagnetcollisionobject", "disable")
end
end
- Если магнит активен (таймер > 0), уменьшаем таймер.
- Когда он становится 0 или меньше:
- Отключаем объект
coinmagnet
. - Отключаем его collision object, чтобы он больше не притягивал монеты.
Функция on_message(self, message_id, message, sender):
Эта функция вызывается каждый раз, когда объект получает сообщение. В данном случае — при столкновении (collision).
message_id
: тип сообщения (например,"collision_response"
).message
: содержит данные о столкновении (с кем, какой группой и т.д.).sender
: кто прислал сообщение.
function on_message(self, message_id, message, sender)
-- обрабатываем столкновения
if message_id == hash("collision_response") then
-- столкновение с монетой (если игрок столкнулся с монетой)
if message.group == hash("coin") then
-- удаляем монету из таблицы(больше не отслеживаем её)
self.coins[message.other_id] = nil
-- проверяем: работает ли магнит?
if self.magnet_timer > 0 then
-- добавляем монету в таблицу, чтобы она притягивалась к игроку
self.coins_to_collect[message.other_id] = true
-- отключаем у этой монеты компонент столкновения(чтобы больше не сталкивалась)
local url = msg.url(message.other_id)
url.fragment = "collisionobject"
msg.post(url, "disable")
else
-- если магнит не включён, просто удаляем моенту
go.delete(message.other_id)
end
-- столкновение с усилителем(магнитом)
elseif message.group == hash("powerup") then
-- удаляем из списка и из игры
self.powerups[message.other_id] = nil
go.delete(message.other_id)
-- активируем магнит на заданное количество секунд
self.magnet_timer = magnet_lifetime
-- включаем магнит и его коллизию — теперь он может притягивать монеты
msg.post("coinmagnet", "enable")
msg.post("#coinmagnetcollisionobject", "enable")
end
end
end
Надеюсь, кому-нибудь этот материал будет полезен.
Всем спасибо за внимание
Другие разборы:
- Разбор проектов на Defold 1. Tilemap Collisions [tilemap]
- Разбор проектов на Defold 2. Параллакс [parallax]
- Разбор проектов на Defold 3. Движение игровых объектов [animation, movement, input]
- Разбор проектов на Defold 4. Воспроизвести анимацию [animation, movement, input]
- Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции [proxy-collection, gameloop, collection, gui]
- Разбор проектов на Defold 6. Пауза [пауза]
- Разбор проектов на Defold 7. Простая кнопка [простая кнопка]
- Разбор проектов на Defold 8. Фабрики и свойства [фабрики и свойства]
- Разбор проектов на Defold 9. Селектор уровня [селектор уровня].
- Разбор проектов на Defold 10. Боец комбо [ввод и анимация]
- Разбор проектов на Defold 11. Coin Magnet (Магнит монет) [фабрика]
- Разбор проектов на Defold 12. Simple Ai (простой ИИ) [ИИ][AI]