Разбор проектов на Defold 10. Боец комбо

Всем привет :waving_hand:
Продолжаем серию разборов чужого кода с этой страницы: Публичный пример Defold .

Сегодня разбираем пример от britzlБоец комбо.
Попробуйте этот проект в действии: запустите пример .
Исходная папка с проектом: ссылка на github .
Исходная папка с публичными примера Defold на github: ссылка на github.

Обращение к новичкам:

Я предполагаю, что у вас уже установлен Defold, если нет, перейдите по этой ссылке: Добро пожаловать в Defold.
Также, будет плюсом, если вы хотя бы поверхностно знакомы со строительными блоками Defold. Если нет, ознакомиться с основными концепциями Defold можно на официальном сайте — перейдите по этой ссылке.6.

Пример того, как скачать и открыть готовый проект:

Скачиваем архив с примерами проектов из github
Распаковываем скачанный ZIP архив примеров в любую папку во вашему усмотрению.
Переходим в папку examples.
Ищем проект play_animation.
Открываем game.project.

Внимание: скриншоты представлены ниже, это пример скачивания и открытия проекта, в этом примере мы рассматриваем пPreformatted textроект с названием play_animation, потому название папок проекта будет отличаться.


Скриншот 3. Открываем разархивированную папку(наименование может отличаться от вашего названия скаченной папки)
Скриншот 4. Открываем папу с примерами.

Этот пример представляет собой простую реализацию распознавания комбо-атак по последовательности нажатий клавиш.

Рассмотрим файлы проекта:

attack.font — шрифт, который используется метками.
bg.atlas — содержит фоновое изображение.

ryu.atlas — содержит изображения игрового персонажа и анимации.

fighter_combo.collection — содержит в себе два игровых объекта с компонентами метка (attack, instructions), bg — содержит спрайт фонового изображения, ryu — содержит скрипт персонажа и спрайт.

Рассмотрим ryu.script:
local LEFT = hash("left")
local RIGHT = hash("right")
local UP = hash("up")
local DOWN = hash("down")
local PUNCH = hash("punch")
local KICK = hash("kick")

Создаются хэши для возможных кнопок управления(понадобятся для сравнения строк).

local ATTACKS = {
	{ name = "HIGH KICK", anim = hash("hkick"), actions = { UP, KICK } },
	{ name = "PUNCH", anim = hash("punch"), actions = { PUNCH } },
	{ name = "LOW PUNCH", anim = hash("lpunch"), actions = { DOWN, PUNCH } },
	{ name = "HADOUKEN", anim = hash("hadouken"), actions = { DOWN, RIGHT, PUNCH } },
}

Это список возможных атак. У каждой атаки есть:

  • name: отображаемое имя
  • anim: анимация (используется в sprite.play_flipbook)
  • actions: список нужных последовательных нажатий

local function find_attack(actions)

Функция find_attack(actions) проверяет, соответствует ли переданная последовательность actions какой-то из комбо-атак в ATTACKS.

  • Сначала проверяется длина последовательности
  • Потом — поэлементное сравнение
  • Если всё совпадает — возвращается объект атаки
    Функция ищет точное совпадение с одной из заранее заданных атак.
for _, attack in ipairs(ATTACKS) do
  • Цикл по всем атакам из таблицы ATTACKS.
  • Каждая attack — это таблица вида { name, anim, actions }.
if #attack.actions == #actions then

Сначала сравнивается длина, например, если у текущей атаки actions длиной 3, а у игрока — 2, то это не совпадение.

local match = true
for i = 1, #attack.actions do
	if attack.actions[i] ~= actions[i] then
		match = false
		break
	end
end

Создаём флаг match, предполагая, что будет совпадение.
Проходим по каждой кнопке (i-тый элемент) из комбо.
Если хоть одна кнопка не совпадает по порядку, выставляем match = false и прерываем цикл.

if match then
	return attack
end

Если всё совпало — возвращаем найденную атаку.
Это завершает выполнение функции.

return nil

Если не нашли совпадений ни для одной атаки — возвращаем nil.


local function play_animation(self, anim)
	if self.anim ~= anim then
		self.anim = anim
		sprite.play_flipbook("#sprite", anim)
	end
end

Если текущая анимация отличается от текущей, проигрывается новая анимация.


function init(self)
	msg.post(".", "acquire_input_focus")
	play_animation(self, hash("idle"))
	self.actions = {}
	self.last_input_time = 0
end

Вызывается при старте объекта:

msg.post(".", "acquire_input_focus") — захватывает ввод.
play_animation(self, hash("idle")) — устанавливаем анимацию в idle.
self.actions — инициализирует список текущих нажатий.
self.last_input_time — инициализирует время последнего нажатия.


function on_message(self, message_id, message, sender)
	if message_id == hash("animation_done") then
		self.anim = nil
		label.set_text("/attack#label", "IDLE")
		play_animation(self, hash("idle"))
	end
end

Обрабатывается событие завершения анимации:

self.anim = nil — сбрасывает self.anim.
label.set_text("/attack#label", "IDLE") — устанавливает на метке слово “IDLE”.
play_animation(self, hash("idle")) — устанавливает анимацию idle.


function on_input(self, action_id, action)
	if action_id and action.pressed then
		print(action_id)
		local now = socket.gettime()
		if now - self.last_input_time > 0.35 then
			self.actions = {}
		end
		self.last_input_time = now
		table.insert(self.actions, action_id)

		local attack = find_attack(self.actions)
		if attack then
			label.set_text("/attack#label", attack.name)
			play_animation(self, attack.anim)
		end
	end
end

Обрабатывает нажатия клавиш:

  1. Проверка на нажатие (action.pressed)
  2. Сравнение с последним вводом — если прошло больше 0.35 секунд, сбрасывается список действий
  3. Добавляется новое действие в self.actions
  4. Проверяется, соответствует ли текущая последовательность одной из атак
  • Если да, то проигрывается анимация и пишется название на экран
if action_id and action.pressed then

Проверяем, есть ли действие (action_id) и было ли это нажатие кнопки.

print(action_id)

Печатаем в консоль, какое действие было нажато — нужно для отладки.

local now = socket.gettime()

Получаем текущее время в секундах (с точностью до миллисекунд).
Функция socket.gettime() возвращает время в формате UNIX timestamp (например, 1716732768.34).

if now - self.last_input_time > 0.35 then
	self.actions = {}
end

Если прошло больше 0.35 секунд с момента последнего ввода, значит это новая комбо, и мы обнуляем список действий.
Это предотвращает “смешивание” предыдущего ввода с новым.

self.last_input_time = now — обновляем момент последнего нажатия, чтобы отслеживать интервалы между нажатиями.

table.insert(self.actions, action_id) — добавляем текущее нажатие в список действий self.actions.

local attack = find_attack(self.actions) — проверяем, соответствует ли текущая последовательность кнопок какой-то атаке.

if attack then
	label.set_text("/attack#label", attack.name)
	play_animation(self, attack.anim)
end

Если нашли атаку:

  • Пишем название на экран через метку attack#label.
  • Запускаем анимацию этой атаки.

Надеюсь, кому-нибудь этот материал будет полезен.
Всем спасибо за внимание :light_blue_heart:

Другие разборы:
  1. Разбор проектов на Defold 1.Tilemap Collisions [tilemap]
  2. Разбор проектов на Defold 2. Параллакс [parallax]
  3. Разбор проектов на Defold 3. Движение игровых объектов [animation, movement, input]
  4. Разбор проектов на Defold 4. Воспроизвести анимацию [animation, movement, input]
  5. Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции [proxy-collection, gameloop, collection, gui]
  6. Разбор проектов на Defold 6. Пауза [пауза]
  7. Разбор проектов на Defold 7. Простая кнопка [простая кнопка]
  8. Разбор проектов на Defold 8. Фабрики и свойства [фабрики и свойства]
  9. Разбор проектов на Defold 9. Селектор уровня [селектор уровня].
2 Likes

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

The main language of communication on this forum is English. Your posts are very useful for Russian-speaking newcomers, but unfortunately they are inaccessible to the vast majority of forum users. As it happens, we have a category for Chinese, but we would prefer that communication here should be in English only, so that all members of the development team can help users. I would recommend posting such tutorials on third-party resources, and sharing them with the Russian-speaking community, for example in:

6 Likes

В данном примере работает только кнопка Z - это только у меня так или у всех?

In thid example work only Z button for me. Its correct ?

Попробуй эти комбинации для ударов:
Вниз, z = низкий удар
Вверх, x = высокий удар
Вниз, верно, z = Hadouken