Создаём прототип для игры с механикой свайпов

Всем привет!

В этом руководстве мы создадим прототип для игры, основанной на механике свайпов:
Так просто?!

Ссылка на git-репозиторий готового проекта:

План работ:

1. Создаём проект и добавляем необходимы ресурсы(ассеты) в проект:

Создаём проект и открываем его:


Создаём папку assets и создаём здесь два каталога json и png:

В папке json создаём card_data.json файл:


Добавим туда json код:

[
	{
		"id": 1,
		"title": "Name Card 1",
		"description": "Description for 1 card",
		"image": "card_1.png"
	},
	{
		"id": 2,
		"title": "Name Card 2",
		"description": "Description for 2 card",
		"image": "card_2.png"
	},
	{
		"id": 3,
		"title": "Name Card 3",
		"description": "Description for 3 card",
		"image": "card_3.png"
	},
	{
		"id": 4,
		"title": "Name Card 4",
		"description": "Description for 4 card",
		"image": "card_4.png"
	},
	{
		"id": 5,
		"title": "Name Card 5",
		"description": "Description for 5 card",
		"image": "card_5.png"
	},
	{
		"id": 6,
		"title": "Name Card 6",
		"description": "Description for 6 card",
		"image": "card_6.png"
	},
	{
		"id": 7,
		"title": "Name Card 7",
		"description": "Description for 7 card",
		"image": "card_7.png"
	},
	{
		"id": 8,
		"title": "Name Card 8",
		"description": "Description for 8 card",
		"image": "card_8.png"
	}
]

Если вы не знакомы с JSON: Знакомство с JSON в Defold

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

Теперь в каталог png добавим ассеты для спрайтов, которые будут использоваться в игровом объекте карточки:


Я использую все те же простые кубики, когда-то нарисованные Честером.

Создадим атлас и добавим туда добавленные изображения для карточек:




2. Пишем логику для свайпа:

Сначала создадим каталог core. В этой папке будем хранить основную логику игры.


Создадим скрипт game:

В этом скрипте, в функции обработки ввода напишем такой код:

function on_input(self, action_id, action)
	-- Проверяем, что событие ввода связано с касанием экрана (touch)
	if action_id == hash("touch") then
		-- Если палец только что коснулся экрана (нажали)
		if action.pressed then
			-- Запоминаем координату X начального касания
			self.start_x = action.x
			-- Если палец отпущен и есть сохранённая начальная координата
		elseif action.released and self.start_x then
			-- Вычисляем разницу по X между текущей точкой и началом касания
			local dx = action.x - self.start_x
			-- Порог, который определяет минимальное смещение для распознавания свайпа
			local threshold = 30
			-- Проверяем, что смещение по горизонтали больше порога
			if math.abs(dx) > threshold then
				-- Если смещение вправо (положительное)
				if dx > 0 then
					print("Swipe right")
				else
					-- Если смещение влево (отрицательное)
					print("Swipe left")
				end
			end
			-- Сбрасываем сохранённое значение начала касания, чтобы обработать новые свайпы
			self.start_x = nil
		end
	end
end

В файле game.input_binding по умолчанию на левую кнопку мыши стоит привязка ввода “touch”. По крайней мере, на момент написания этого поста(версия Defold 1.11.0):


В main.collection создадим игровой объект game и добавим в качестве компонента скрипт game.script:



У вас должно получиться что-то подобное:

Сохраним проект(ctrl + S), соберём его(ctrl + B):

Теперь сделайте свайп курсором. Ничего не происходит.
Это потому что в начале инициализации скрипта мы не отправили запрос на получение ввода данных из скрипта. Исправим это в функции init():

function init(self)
	msg.post(".", "acquire_input_focus")
end

Прежде чем собрать проект заново, давайте изменим разрешение дисплея игры. Для этого перейдём в game.project. Найдём вкладку Display:


Установим такие значения для ширины и высоты дисплея:

Сохраним проект(ctrl + S), а затем запустим проект(ctrl + B) снова.
Во время свайпов вы должны увидеть сообщение в консоле:

3. Загрузим данные из JSON в Lua Module:

В папке core создадим подкаталог card. В нем создадим луа-модуль card.lua:

Если вы не знаете что такое модуль: Что такое модуль, что такое require в Defold

Вставим такой блок кода:

local M = {}
	-- Загрузка и парсинг JSON файла из ресурсов по пути "/assets/json/card_data.json"
	local json_text = sys.load_resource("/assets/json/card_data.json")
	if json_text then
		-- Парсим JSON текст в Lua таблицу и сохраняем в M.cards
		M.cards = json.decode(json_text)
	else
		-- Если файл не загружен, выводим сообщение об ошибке и инициализируем пустую таблицу
		print("Ошибка загрузки JSON файла")
		M.cards = {}
	end
	
	-- Инициализация текущего индекса карты (по умолчанию 5)
	M.current_index = 5
	
	-- Функция возвращает текущую карту из списка по индексу
	function M.get_current_card()
		return M.cards[M.current_index]
	end
	
	-- Функция меняет текущую карту при свайпе влево или вправо
	-- direction: "left" или "right" - направление свайпа
	function M.swipe_change(direction)
		if direction == "right" then
			M.current_index = M.current_index + 1 -- сдвигаем индекс карты вправо
			if M.current_index > #M.cards then
				-- Если индекс превысил количество карт, ограничиваем последний элемент
				-- Чтобы зациклить, можно раскомментировать строку ниже
				-- M.current_index = 1 -- переход к первой карте
				M.current_index = #M.cards -- оставляем последний индекс
			end
		elseif direction == "left" then
			M.current_index = M.current_index - 1 -- сдвигаем индекс карты влево
			if M.current_index < 1 then
				-- Если индекс стал меньше 1, ограничиваем первый элемент
				-- Для зацикливания можно раскомментировать строку ниже
				-- M.current_index = #M.cards -- переход к последней карте
				M.current_index = 1 -- остаемся на первой карте
			end
		end
		-- Выводим в консоль текущую карту для отладки
		pprint(M.cards[M.current_index])
	end

return M

Теперь в game.script самой первой строкой кода оставим такую записть:

local CARD = require("core.card.card")

Затем обновим обработчик ввода on_input(...), добавив несколько вызовов функций из нашей таблицы card.lua:

function on_input(self, action_id, action)
	-- Проверяем, что событие ввода связано с касанием экрана (touch)
	if action_id == hash("touch") then
		-- Если палец только что коснулся экрана (нажали)
		if action.pressed then
			-- Запоминаем координату X начального касания для определения свайпа
			self.start_x = action.x
		-- Если палец отпущен и уже была сохранена стартовая координата
		elseif action.released and self.start_x then
			-- Вычисляем смещение по оси X от точки начала касания до текущей точки
			local dx = action.x - self.start_x
			-- Задаём порог смещения, который считается свайпом
			local threshold = 30
			-- Проверяем, превысило ли абсолютное смещение порог
			if math.abs(dx) > threshold then
				-- Если смещение положительное — свайп вправо
				if dx > 0 then
					print("Swipe right")
					-- Вызываем функцию переключения карты вправо
					CARD.swipe_change("right")
				else
					-- Если смещение отрицательное — свайп влево
					print("Swipe left")
					-- Вызываем функцию переключения карты влево
					CARD.swipe_change("left")
				end
			end
			-- Сбрасываем сохранённое начальное положение для обработки следующих свайпов
			self.start_x = nil
			-- Выводим в консоль текущую карту для отладки
			print(CARD.get_current_card())
		end
	end
end

Сохраним и соберём проект. Теперь после свайпа влево или вправо выводится информация о карточке. Та самая информация, которая лежит в card_data.json.


4. Создадим игровой объект card и добавим ему визуальное представление:

В папке card создадим игровой объект card.go:


Создадим компонент sprite:

Выберем в качестве источника изображений для спрайта main.atlas.
Выберем какое-нибудь изображение в качестве анимации:

Теперь добавим 2 компонента label, как добавляли компонент sprite:

Перейдём в main.collection и добавим игровой объект в игровой объект game:



Сохраним и соберём проект:

Установим позиции для игрового объекта такими, чтобы он был расположен по центру(я просто поделил ширину и высоту дисплея на 2):
image
Сохраним и соберём проект:

Теперь нам необходимо изменять визуальное представление карточки во время свайпов по экрану. Для этого создадим в модуле card(card.lua) несколько функций, которые будут отправлять сообщения нашему игровому объекту card.go, а именно, его скрипту(который мы создадим). И уже через компонент скрипт будет изменять визуальное представление других компонентов в игровом объекте card.go.

	-- изменяем изображение спрайта для текущей карты
	function M.update_sprite(go_url)
		local card = M.get_current_card()
		if card and card.image then
			-- Убираем расширение .png из имени файла для flipbook
			local anim_id = hash(card.image:gsub("%.png$", ""))
			-- Отправляем сообщение игровому объекту-спрайту сменить анимацию
			msg.post(go_url, "play_animation", { id = anim_id })
		end
	end

	-- Функция изменения description для текущей карты
	function M.update_description(go_url)
		local card = M.cards[M.current_index]
		msg.post(go_url, "update_description", {description = card.description})
	end

	-- Функция изменения title для текущей карты
	function M.update_title(go_url)
		local card = M.cards[M.current_index]
		msg.post(go_url, "update_title", {title = card.title})
	end

В папке card создаём скриптовый файл card.script:


В функции обработки сообщений запишем такой код:

function on_message(self, message_id, message, sender)
	if message_id == hash("play_animation") then
		sprite.play_flipbook("#sprite", message.id)
	elseif message_id == hash("update_description") then
		label.set_text("#card_description", message.description)
	elseif message_id == hash("update_title") then
		label.set_text("#card_title", message.title)
	end
end

Добавим скрипт в качестве компонента игрового объекта card.go:

Теперь перейдём в game.script и изменим условия в обработчике ввода on_input(...):

-- Функция обработки ввода пользователя
function on_input(self, action_id, action)
	-- Проверяем, что событие ввода связано с касанием экрана
	if action_id == hash("touch") then
		-- Если палец только что коснулся экрана (начало касания)
		if action.pressed then
			-- Запоминаем координату X начального касания для определения свайпа
			self.start_x = action.x
			-- Если палец отпущен и есть сохранённая начальная точка касания
		elseif action.released and self.start_x then
			-- Вычисляем смещение по оси X от начала касания до текущей позиции отпуска
			local dx = action.x - self.start_x
			-- Порог смещения для распознавания действия свайпа
			local threshold = 30
			-- Проверяем, превышает ли абсолютное смещение порог
			if math.abs(dx) > threshold then
				-- Если смещение вправо (положительное значение)
				if dx > 0 then
					print("Swipe right")
					-- Вызываем функцию смены карты вправо
					CARD.swipe_change("right")
				else
					print("Swipe left")
					-- Если смещение влево (отрицательное значение)
					-- Вызываем функцию смены карты влево
					CARD.swipe_change("left") 
				end
				-- Сбрасываем начальную точку касания для следующего события
				self.start_x = nil
				-- Обновляем внешнее состояние карточки: спрайт, описание, заголовок
				CARD.update_sprite(self.go_url)
				CARD.update_description(self.go_url)
				CARD.update_title(self.go_url)
			end
		end
	end

Обновим функцию инициализации:

function init(self)
	-- Запрашиваем захват фокуса ввода для текущего game object, чтобы получать события ввода
	msg.post("", "acquire_input_focus")
	-- Инициализируем начальное значение для координаты X начала свайпа (nil — свайп не начался)
	self.start_x = nil

	-- Сохраняем URL объекта карточки для удобства обращения к нему позже
	self.go_url = msg.url("/card")

	-- Обновляем визуальные элементы карточки: спрайт, описание и заголовок с использованием модуля CARD
	CARD.update_sprite(self.go_url)
	CARD.update_description(self.go_url)
	CARD.update_title(self.go_url)
end

Сохраним и запустим проект:
Новый проект

Всем спасибо за внимание!
Если есть какие-то вопросы, пишите!

2 лайка