Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции

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

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

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

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

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

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

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


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

Структура проекта:

После запуска проекта, мы видим кнопку "play", кликнув по которой мы переходим на уровень. На этом уровне имеются персонажи, они исчезают, если кликать по ним. Удалив последнего, можно перейти в меню всё с той же кнопкой"play".

В этом примере показано главное меню и игра, которые определены в отдельных коллекциях и загружаются и выгружаются с использованием прокси-серверов коллекций через скрипт контроллера.

Рассмотрим файловую структуру проекта:

controller.collection — является основной коллекцией проекта и именно она загружается при старте игры.


Если у вас не видно фонового изображения, смените значение Z position для игрового объекта bg, находящегося в этой коллекции, например, на -0.5.

gameproxy и menuproxy — являются прокси-коллекциями, которые будут загружаться скриптом controller.script

Прокси-коллекция — это ссылка на другую коллекцию, которая находится в отдельном .collection файле и подключается к основной коллекции через collectionproxy компонент. Прокси-коллекции используются для того, чтобы разделять сцены/уровни по коллекциям. Для оптимизация памяти — неактивные уровни не загружаются. Для упрощения переходов между экранами.

controller.script — в этом скрипте происходит централизованное управление логикой игры. Этот скрипт отвечает за переключения между игровым меню и игровым уровнем.

В нашем случае, после того, как игрок нажмёт на "play". Будет осуществлён переход с "menyproxy" на "gameproxy".

game.collection — коллекция игрового уровня, которая будет загружаться после того, как в menu.collection игрок нажмёт на кнопку "play". Содержит в себе игровой объект go, который включает в себя компонент фабрики (прототип thing.go) , и скрипт, отвечающий за логику уровня.

menu.collectrion — коллекция, отвечающая за меню. Загружается при старте игры controller скриптом. Имеет в себе игровой объект menu, включающий в себя только gui-компонент.

menu_and_game.atlas — содержит изображения, которые будут использоваться в меню и в уровне.

thing.go — прототип игрового объекта, фабрика при создании будет создавать игровой объект по этому шаблону.

menu.gui — визуальная сцена интерфейса, содержащая в себе только один шаблон кнопки — "play".

button.gui — кнопка "play". Созданная с помощью строительного компонента — node. С типом узла — box. И расположенного внутри text-нода.

Рассмотрим controller.script:

controller.script — управляющий скрипт, который загружает и переключает между двумя сценами через прокси-коллекции: меню (#menuproxy) и игру (#gameproxy).

msg.post(".", "acquire_input_focus")
msg.post("#menuproxy", "load")

msg.post(".", "acquire_input_focus") — запрашиваем фокус ввода, чтобы скрипт мог обрабатывать события от клавиатуры, мыши, касания и т.д, в нашем примере, чтобы мы могли кликать.
msg.post("#menuproxy", "load") — загружает коллекцию menu.collection c id menuproxy

msg.post(".", "release_input_focus")

Освобождает фокус ввода, когда объект будет уничтожен.

function on_message(self, message_id, message, sender) — это обработчик сообщений, реагирует на события, приходящие в скрипт.

if message_id == hash("show_game") then
		msg.post("#gameproxy", "load")
		msg.post("#menuproxy", "unload")

Загружается игровая сцена (#gameproxy).
Когда приходит сообщение show_gameзагружает прокси-коллекцию gameproxy. А та, уже в свою очередь, по id открываетgame.collection.

Выгружается меню (#menuproxy).

elseif message_id == hash("show_menu") then
    msg.post("#menuproxy", "load")
    msg.post("#gameproxy", "unload")

Аналогично, но наоборот: загружаем меню, выгружаем игру.

elseif message_id == hash("proxy_loaded") then
    print("proxy_loaded", sender)
    msg.post(sender, "enable")

После загрузки прокси приходит сообщение proxy_loaded.
Выводим лог.
Активируем сцену командой enable.
Без enable коллекция не будет обновляться и отображаться.

elseif message_id == hash("proxy_unloaded") then
    print("proxy_unloaded", sender)

Просто логируем, когда коллекция выгрузилась.

Как я полагаю, согласно информации с этой страницы: Компонент Collection Proxy.

Желательно, чтобы код выглядел вот так:
function on_message(self, message_id, message, sender)
	if message_id == hash("show_game") then
		msg.post("#gameproxy", "load")
		msg.post("#menuproxy", "final")	
		msg.post("#menuproxy", "disable")
		msg.post("#menuproxy", "unload")
	elseif message_id == hash("show_menu") then
		msg.post("#menuproxy", "load")
		msg.post("#gameproxy", "final")	
		msg.post("#gameproxy", "disable")
		msg.post("#gameproxy", "unload")
	elseif message_id == hash("proxy_loaded") then
		print("proxy_loaded", sender)
		msg.post(sender, "init")
		msg.post(sender, "enable")
	elseif message_id == hash("proxy_unloaded") then
		print("proxy_unloaded", sender)
	end
end
Рассмотрим game.script:

game.script — реализует простую игру, где нужно кликнуть по всем инопланетянам, чтобы завершить игру.

math.randomseed(os.time())

math.randomseed(os.time()) делает math.random() непредсказуемым.

В Lua, как и в большинстве языков, генератор случайных чисел math.random() по умолчанию использует одинаковое начальное значение (seed), что делает случайные числа повторяемыми при каждом запуске игры. Чтобы избежать этого и получить разные значения каждый раз, нужно задать seed случайно, и чаще всего используется текущее время:

self.things = {}
for i = 1, 5 do
	local id = factory.create("#factory", vmath.vector3(math.random(40, 996), math.random(40, 600), 0))
	table.insert(self.things, id)
end

Создаём таблицу, которая будет хранить пришельцев.
В цикле создаём 5 игровых объектов через factory.create() в случайных позициях.
Каждый объект сохраняется в self.things.

function update(self, dt)
	msg.post("@render:", "draw_text", { text = "Click and remove all the aliens to return to the menu", position = vmath.vector3(20, 30, 0) } )
end

Каждый кадр отрисовываем через render текст-подсказку на экране.

function on_input(self, action_id, action)
	if action.released then
		for i=1,#self.things do
			local id = self.things[i]
			local pos = go.get_position(id)
			if action.x > (pos.x - 30) and action.x < (pos.x + 30) and action.y > (pos.y - 35) and action.y < (pos.y + 35) then
				go.delete(id)
				table.remove(self.things, i)
				break
			end
		end
		if #self.things == 0 then
			msg.post("controller:/controller", "show_menu")
		end
	end
end

Если клавиша отпущена, то заходим в цикл и проходим по таблице с пришельцами.
Получаем id и позицию n-пришельца.
Проверяем, если клик попал в прямоугольную область вокруг объекта (ширина: 60, длина: 70), то удаляем игровой объект. Выходим из цикла.
Если все объекты были уничтожены, размер таблицы равен 0, то отправляем сообщение контроллеру о том, что нужно показать menu.collection.

Рассмотрим menu.gui_script:

Обратите внимание, menu.gui_script — это GUI-скрипт, управляющий кнопкой "play" в меню.

if action.pressed and gui.pick_node(gui.get_node("play/bg"), action.x, action.y) then
	msg.post("controller:/controller", "show_game")
end

Проверяем, произошло ли нажатие: action.pressed и точка касания находится внутри ноды: play/bg. Если попадание по ноде есть, отправляем сообщение контроллеру: “запусти игру”.

Надеюсь, кому-нибудь этот материал будет полезен.
Также я буду рад получить обратную связь, чтобы в дальних постах я смог улучшить качество изложений/объяснений.
Всем спасибо за внимание :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]
2 Likes