Знакомство с Монархом в Defold. Переходы в главное меню/игру/игровая пауза(попап)

Привет всем подвальным дефолдерам и не только!

Эта тема может послужить вам отправной точкой при знакомстве с Монархом (Monarch) в Defold. Как минимум, с помощью этого пошагового руководства вы сможете создать контроллер, отвечающий за переходы между сценами вашей простой игры:
gif_4
Я писал это руководство с той целью, чтобы новичок мог воспроизвести этот материал пошагово, некоторые моменты, связанные с Defold я не разбирал, предоставляя это для самостоятельной работы. Иногда я приводил простые аналогии, которые мне кажутся уместными.
Это руководство может подойти и для первого знакомства с GUI.

Исходный проект и “Пошаговое руководство” в READ.ME можете скачать здесь: defolder/forum/lessons/monarch/monarch_1 at main · dprogrb/defolder · GitHub

Готовый проект: defolder/forum/lessons/completed_project/cp_monarch_1 at main · dprogrb/defolder · GitHub


Монарх (Monarch) — это менеджер экранов (screen manager) для движка Defold, который упрощает навигацию между различными экранами игры (меню, уровни, настройки, попапы). Он работает на основе стека экранов : когда вы показываете новый экран, он добавляется на вершину стека, а при возврате назад верхний экран удаляется.

Установка Монарха


Вы можете использовать Monarch в своём проекте, добавив этот проект в качестве зависимости библиотеки Defold.
Как добавить библиотечную зависимость в проект на Defold

Ссылка на GitHub-репозиторий Monarch: GitHub - britzl/monarch: Monarch is a Defold screen manager with transition support and a straight forward API

В разделе “Installation” вы можете найти ссылки на все версии монарха:


После добавления Monarch в проект, у нас открывается новая способность в редакторе:
“Мы можем создавать несколько файлов одним кликом, и они будут находиться в одной коллекции”.
Создай папку “New Folder”:


Для примера, я назвал папку “test”.
Теперь кликни правой кнопкой мыши по папке test:

Выбери пункт “Create Monarch Scene From Directory”.
Теперь в директории test у нас несколько созданных файлов:
image
Если мы перейдём в test.collection, то увидим, что в ней создан игровой объект monarch и в него добавлен компонент gui:
image
Если мы посмотрим на gui, то к gui автоматически был прикреплен gui script:

Если мы посмотрим содержимое gui скрипта, то увидим там код:

Мы можем создать один файл, а потом использовать вышеприведенный способ, чтобы получить тоже самое:


image

Попробуйте сами поэкспериментировать.

Нюансы


Если создать попробовать создать Монарх сцену, опираясь на созданный gui script, то изменений в gui script не будет:


Не добавляется строка с добавлением модуля монарха:
image

В этом скрипте редактора можно изменить шаблон создания файлов:

Как вы могли понять. Каждый экран в Monarch состоит из трёх компонентов:

Collection (.collection) — содержит игровые объекты экрана.
GUI (.gui) — визуальный интерфейс экрана.
GUI Script (.gui_script) — логика интерфейса.

Пошаговое руководство

Если вы не скачали файл с github и решили пройти урок в своём проекте, то исходная файловая структура такова:


Структура узла в button.gui:

План работ

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

  • Переход с сцены main_menu в game.
  • Открытие game_pause.
  • Переход с сцены game_pause в сцены main_menu и game.

Создадим для каждой сцены шаблон из файлов:


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

Кликните по game.collection и collection, задайте имя game:

Для других коллекций сделайте аналогичные действия:

Создадим GUI макеты

Для начала перейдём в game.project и узнаем заданные настройки ширины и высоты экрана:


Теперь создадим корневой узел для макета в main_menu.gui (в следующий раз я не буду упоминать это действие):

Присвоим ему имя “root” и выставим такие значения:

Посмотрите, теперь наш корневой узел расположен по всему экрану:

Это необязательно, но можно ещё установить и Stretch в Adjust Mode:
image
Теперь в наш root поместим шаблон кнопки из папки gui/templates/button.gui:


Изменим ей id на button_start (для удобства):

Создадим в папке controller скрипт controller.script:

image
Перейдём в controller.collection и создадим там игровой объект и затем переименуем его на controller:


Добавим компонент controller.script:


В этой же коллекции controller.collection создадим игровой объект с id main_menu:
image
В этом игровом объекте мы создадим компонент Collection Proxy (Прокси Коллекцию):

В качестве коллекции в этом компоненте укажем коллекцию main_menu.collection:


Переименуем её на main_menu_proxy (для удобства):
image
Тем же способом, которым мы добавили в игровой объект компонент controller.script, добавим скрипт монарха screen_proxy:

В свойствах этого скрипта мы внесём изменения.
В параметр screen_proxy зададим имя нашей прокси-коллекции — main_menu_proxy.
В параметр screen_id зададим значения main_menu. С помощью этого имени мы будем обращаться к этой прокси при работе с API Монарха.

Перейдём в controller.script и вставим такой код:

-- controller.script

local monarch = require "monarch.monarch" -- [1]

function init(self) -- [2]
	msg.post(".", "acquire_input_focus") -- [3]
	msg.post("#", "start_main_menu") [] -- [4]

end

function on_message(self, message_id, message, sender) -- [5]
	if message_id == hash("start_main_menu") then -- [6]
		monarch.show(hash("main_menu")) -- [7]
	end
end

Сохраним проект (ctrl + S).
Соберем и запустим проект (ctrl + B):


Постараюсь объяснить по-простому:

  1. Делаем возможным использовать код, расположенный в этой папке:
  2. Функция, которая начинает своё выполнение кода, только в процессе создания игрового объекта.
  3. Отправляем сообщение игре: “Считывай данные с устройств ввода”. Как пример, с мыши или клавиатуры. Т.е если я кликаю на клавишу мыши или набираю что-то на клавиатуре, то компьютер это обрабатывает.
  4. Отправляем сообщение в этот же скрипт (для этого указана "#", чтобы скрипт обработал его).
  5. Функция для обработки сообщений, поступающих в этот скрипт (у нас это controller.script).
  6. Проверяем, если пришло сообщение с хэшем hash("start_main_menu").
  7. Используем функцию из файла monarch.lua (модуля), чтобы показать сцену main_menu (мы указывали в screen_proxy screen_id):
    image

Если привести аналогию с человеком.
Человек спит.
Во время пробуждения он говорит себе: “Я могу работать”.
А потом говорит себе: “Отрой глаза” и с помощью функции глаз он открывает глаза.

Если вы зайдёте в monarch.lua то увидите созданную там функцию show, которая позволяет открыть прокси-коллекцию:


Давайте внесём изменения в main_menu.gui, изменим цвет корневому узлу:

Теперь сохраним проект и снова его запустим:

Вы также можете изменить текст для текстового узла кнопки здесь:

Скрывать узлы с помощью свойства параметра:

Теперь создайте подобным образом другие сцены: game, game_pause:

Макет сцены game:



Макет game_pause.gui:


В controller.collection создайте игровые объекты, содержащие прокси-коллекции и скрипт screen.proxy для остальных сцен:

Перейдём в main_menu.gui_script и вставим такой код:

-- main_menu.gui_script

local monarch = require "monarch.monarch"

function init(self)
    self.button_start = gui.get_node("button_start/root")  -- [1]
    self.button_text = gui.get_node("button_start/text") -- [2]
    -- Настраиваем текст кнопки
    gui.set_text(self.button_text, "START") -- [3]
    -- Запрашиваем input focus
    msg.post(".", "acquire_input_focus")
end


function on_input(self, action_id, action)
    if action_id == hash("touch") then
        -- Проверяем клик по кнопке
        if gui.pick_node(self.button_start, action.x, action.y) then -- [4]
            if action.pressed then -- [5]
                gui.set_scale(self.button_start, vmath.vector3(0.97, 0.97, 1)) -- [6]
            elseif action.released then -- [7]
                gui.animate(self.button_start, gui.PROP_SCALE, 
                vmath.vector3(1.03, 1.03, 1), 
                gui.EASING_INOUTQUAD,
                0.3, 0, 
                function()
                    -- Вернем масштаб после анимации и переключим окно
                    gui.set_scale(self.button_start, vmath.vector3(1, 1, 1))
                    monarch.show(hash("game"))
                end,
                gui.PLAYBACK_ONCE_PINGPONG
            ) -- [8]
            end
            return true  -- [9]
        end
    end

end

  1. Получаем id узла и записываем его в переменную self.button_start.
  2. Получаем id узла и записываем его в переменную self.button_text.
  3. Устанавливаем свойство text равное “START” текстовому узлу, ссылающемуся на значение, хранящиеся в переменной self.button_text.
  4. Если кликнули по узлу button_start/root (значение хранится в self.button_start) был совершен "touch", то выполни определенные команды.
  5. Если клавиша из game.input_binding (touch) была нажата.
  6. Измени масштаб коревому узлу кнопки.
  7. Если клавиша из input_binding (touch) была отпущена.
  8. Проиграй анимацию для кнопки.
  9. Прекращай обрабатывать этот actions(touch).
    После клика на кнопку “START” вы переходите на коллекцию game.collection:
    gif_2
    Теперь перейдём в файл input_binding и добавим новую привязку ввода:
    image

    В game.gui_script добавим такой код:
-- game.gui_script

local monarch = require "monarch.monarch"

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

function on_input(self, action_id, action)
    if action_id == hash("esc") and action.pressed then -- [1]
        monarch.show(hash("game_pause"), {no_stack = true}) -- [2]
    end
end
  1. Если была нажат клавиша с привязкой на esc.
  2. Показываем сцену монарха, не помещая его в стек вызовов монарха.

Сохраним и запустим проект, нажмём на клавишу “ESC” на клавиатуре:
gif_3
Теперь перейдём в game_pause.gui_script и вставим такой туда код:

local monarch = require "monarch.monarch"

function init(self)
	-- Получаем узлы 
	self.button_continue_root = gui.get_node("button_continue/root")  -- root кнопки
	self.button_continue_text = gui.get_node("button_continue/text") -- text кнопки
	self.button_to_menu_root = gui.get_node("button_to_menu/root")  -- root кнопки
	self.button_to_menu_text = gui.get_node("button_to_menu/text") -- text кнопки
	-- Настраиваем текст кнопки
	gui.set_text(self.button_continue_text, "CONTINUE")
	gui.set_text(self.button_to_menu_text, "MENU")
	-- Запрашиваем input focus
	msg.post(".", "acquire_input_focus")
end

function on_input(self, action_id, action)
	if action_id == hash("esc") and action.pressed then
		monarch.hide(hash("game_pause")) -- [1]
	end
	if action_id == hash("touch") then
		-- Проверяем клик по кнопке
		if gui.pick_node(self.button_continue_root, action.x, action.y) then
			--print("Button picked!")
			if action.pressed then
				gui.set_scale(self.button_continue_root, vmath.vector3(0.97, 0.97, 1))
			elseif action.released then
				gui.animate(self.button_continue_root, gui.PROP_SCALE, 
				vmath.vector3(1.03, 1.03, 1), 
				gui.EASING_INOUTQUAD, 0.3, 0, 
				nil, gui.PLAYBACK_ONCE_PINGPONG)
				gui.set_scale(self.button_continue_root, vmath.vector3(1, 1, 1))
			end
			monarch.hide(hash("game_pause")) -- [1]
			return true  -- Потребляем input
		end

		if gui.pick_node(self.button_to_menu_root, action.x, action.y) then
			if action.pressed then
				gui.set_scale(self.button_to_menu_root, vmath.vector3(0.97, 0.97, 1))
			elseif action.released then
				gui.animate(self.button_to_menu_root, gui.PROP_SCALE, 
				vmath.vector3(1.03, 1.03, 1), 
				gui.EASING_INOUTQUAD, 0.3, 0, 
				nil, gui.PLAYBACK_ONCE_PINGPONG)
				gui.set_scale(self.button_to_menu_root, vmath.vector3(1, 1, 1))
			end
			-- Закрываем game_pause
			monarch.hide(hash("game_pause")) -- [1]
			monarch.show(hash("main_menu"), { clear = true }) -- [2]
			return true  -- Потребляем input
		end
	end
end
  1. monarch.hide(hash("game_pause")) закроет попап, показанный с no_stack.
  2. monarch.show(hash("main_menu"), { clear = true }) удалит все экраны из стека и покажет main_menu.

Сохраняем и запускаем проект:
gif_4

Всем спасибо за внимание, если есть какие-то вопросы, задавайте.
Если нашли ошибку или опечатку, буду благодарен за то, что сообщите о ней.

1 лайк