Материалы:
- балдежная статья от блинчика: Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции
- ОФФ ДОКА: Компонент Collection Proxy
- консультация LLM, просил критиковать код, так добавил флаг загрузку коллекции, функцию для снятия фокуса release_input_focus
Для начала создадим и включим одну коллекцию, а так же научимся ее выключать.
Cоздаем в Aseets:
- уже созданный main.collection
- созадем коллекцию start.collection
- controller.scipts
- создаем папку assets, в ней создаем main.atlas и закидываем box.png
Outline main коллекции:
- создаем go и называем controller
- в этом же го подключаем controller.scipts
- в этом же го создаем прокси коллекцию и выбираем в поле коллекции нашу start.collection и меняем id на start
Переходим в start.collection. Добавим спрайт, чтоб видеть, что коллекция загрузилась.
- создаем go
- в го создаем спрайт
- в image выбираем атлас main
- в default animation box.png
- в image выбираем атлас main
- в го создаем спрайт
Что происходит при создании коллекции? Создаем отдельный мир, для физики тоже, между коллекциями обьекты не могут общаться, например, в физике это два разных мира, коллизии не будут видеть друг друга. Так же память выделяется, создание десятков коллекций не гуд, тут лучше использовать фабрики коллекций.
Загружаем коллекцию controller.scipts:
function init(self)
msg.post(".", "acquire_input_focus")
print('hi')
-- загружаем коллекцию
-- есть еще async_load загрузка
-- обратно получим сообщение "proxy_loaded"
-- обрабатываем в on_message
msg.post("#start", "load")
end
-- self таблица скрипта
-- message_id как раз таки наша hash("proxy_loaded")
-- message таблица с данными, если есть
-- sender url отправителя
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_loaded") then
-- если нужно сначала вызвать метод init, а только потом показать обьекты коллекции на экране
msg.post(sender, "init")
-- включаем видимость
msg.post(sender, "enable")
-- выключаем скрипты через секунду
-- 1 - задержка в секундах
-- false/true - повторять таймер
-- функция
-- -- handle индентификатор, всегда 0 возвращаем????
-- -- timer.cancel(0) получилось отменить таймер
-- time_elapsed - время, которое прошло с последнего вызова:
-- 1.0004662275314, 1.0057771205902
timer.delay(1, true, function(self, handle, time_elapsed)
print("Прошла 1 секунда!")
-- можно вызвать сразу unload
-- но можно скрыть через disable
msg.post(sender, "disable")
-- запустить метод final
msg.post(sender, "final")
-- затем уже выгрузить все
msg.post(sender, "unload")
timer.cancel(0)
end)
end
if message_id == hash("proxy_unloaded") then
-- после выгрузки по таймеру:
print('коллекция выгружена')
end
end
Примерно такое будет в конце:
Cоздаем переключатель из двух кнопок для прыжков между коллекциями.
Подготовка:
- удаляем весь код в контроллере
- создаем еще одну коллекцию second, добавляем в нее go, в го спрайт, в спрайт вставляем атлас в image и выбираем в default animation box.png
- в main.controller создаем коллекцию, называем second и выбираем в свойствах collection проекцию second
- можем побегать по коллекциям старт/секонд и поменять расположение спрайтов, размер, чтоб коллекции друг от друга различались
В контроллере быстро проверяем работоспособность
function init(self)
msg.post("#start", "load")
msg.post("#second", "load")
end
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_loaded") then
msg.post(sender, "enable")
end
end
- Создаем новую папку menu
- в ней же коллекцию menu
- в коллекции го
- в этой же папке создаем menu.gui и menu.gui_script
- в menu.gui в свойствах Script выбираем наш созданыый menu.gui_script
- в го подключаем menu.gui через add Component File
- переходим в menu.gui, создаем два бокса в нодах, называем btn1, btn2, добавим обоим текст text1, text2
- добавялем фон в fonts default уже есть в редакторе
- выбираем у text1,text2 наш фон в Font
- идем в main.collection, в controller, создаем новую прокси коллекцию, называем menu, выбираем коллекцию menu в Collectoin в свойствах
- включим коллекцию menu на экране в controoler.script
- изменим цвет текста в menu.gui у кнопок в свойстве Color, на белом фоне не видно и в свойстве Text поменяем текст на btn1,btn2
- в текстуры menu.gui добавим main.atlas, перейдем к нодам btn1,btn2, выберем текстуры, включим side mode: manual для включения Slice 9, начинающий, точно не скажу, это когда можем создать маленькую текстурку и растягивать ее в разные стороны, если фон в центре одного тона, удобно для UI. На фото ниже видно как мы увеличили го без Slice 9 через scale и края поплыли в пиксели
function init(self)
-- включим для будущего первого шага ввод от мышки
msg.post(".", "acquire_input_focus")
msg.post("#start", "load")
msg.post("#second", "load")
msg.post("#menu", "load")
end
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_loaded") then
-- пока передаем всем коллекциям, потом исправим
msg.post(sender, "acquire_input_focus")
msg.post(sender, "enable")
end
end
вот что получается
Придумываем как переключать коллекции.
Шаг 1. Научимся отслеживать клик по кнопкам.
Переходим в menu.gui_script:
function init(self)
msg.post(".", "acquire_input_focus")
-- выбираем по id в self таблицы
self.btn1 = gui.get_node("btn1")
self.btn2 = gui.get_node("btn2")
end
-- action_id — это идентификатор из input bindings, например, touch
-- action это таблица, например координаты x,y там есть
-- action.x, action.y показывают x,y координат GUI
-- то есть координаты которые заданы GUI в game.project как я понял
function on_input(self, action_id, action)
-- нажали кнопку action.pressed
if action.pressed and gui.pick_node(self.btn1, action.x, action.y) then
print('кликнули на 1 кнопку')
end
if action.pressed and gui.pick_node(self.btn2, action.x, action.y) then
print('кликнули на 2 кнопку')
end
end
Шаг 2. Научимся отправлять сообщения в controller.script
menu.gui_script
function init(self)
msg.post(".", "acquire_input_focus")
self.btn1 = gui.get_node("btn1")
self.btn2 = gui.get_node("btn2")
end
function on_input(self, action_id, action)
if action.pressed and gui.pick_node(self.btn1, action.x, action.y) then
-- main - коллекция main
-- controller - гошка controller
-- controller название скрипта
-- switch - id сообщения на которое отсылаем в controller.script
-- option = 1 - таблица с переменной option=1
msg.post(msg.url("main:/controller#controller"), "switch", { option = 1 })
end
if action.pressed and gui.pick_node(self.btn2, action.x, action.y) then
msg.post(msg.url("main:/controller#controller"), "switch", { option = 2 })
end
end
в контроллере оставляем одну сцену menu с кнопками
function init(self)
msg.post(".", "acquire_input_focus")
msg.post("#menu", "load")
end
function on_message(self, message_id, message, sender)
-- получаем сообщение по ключу switch
if message_id == hash("switch") then
print('hello from menu.gui_script')
-- наш option=1
pprint(message)
end
if message_id == hash("proxy_loaded") then
msg.post(sender, "acquire_input_focus")
msg.post(sender, "enable")
end
end
Шаг 3. Включаем коллекции по клику.
-- табличка для прокси
local PROXIES = {
["1"] = "#start",
["2"] = "#second",
}
function init(self)
msg.post(".", "acquire_input_focus")
msg.post("#menu", "load")
end
function on_message(self, message_id, message, sender)
if message_id == hash("switch") then
-- в строку потому что в proxies у нас тоже строка, данные: 1,2
local option = tostring(message.option)
-- ключ сцены для 1 - #start и 2 - #second, данные: #start, #second
local keyScene = PROXIES[option]
-- отправляем на загрузку
msg.post(keyScene, "load")
end
-- тут загружаем коллекции
if message_id == hash("proxy_loaded") then
msg.post(sender, "acquire_input_focus")
msg.post(sender, "enable")
end
end
Шаг 4, нужно включать только одну коллекцию, другие выключаем.
Добавляем три функции и немного кода
local PROXIES = {
["1"] = "#start",
["2"] = "#second",
}
function init(self)
msg.post(".", "acquire_input_focus")
-- будем хранить загружаемые сцены
self.loaded = {}
-- текущая сцена
self.current = nil
-- загрузка на случай многократного вызова
self.loading = {}
msg.post("#menu", "load")
end
local function option_by_sender(sender)
-- k ключи 1,2
-- proxy #start, #second
-- msg.url("#") можно вернуть текущий url скрипта
-- msg.url(proxy) #start -> url: [main:/controller#start]
for k, proxy in pairs(PROXIES) do
if msg.url(proxy) == sender then
return k
end
end
return nil
end
-- убираем фокус у предыдущей сцены
-- при удалении коллекци в final движок сам это делает
-- но вроде как бест практика
-- и чтобы просто потом не забывать, когда буду делать разные вариации
local function release_previous_focus(self)
if self.current and PROXIES[self.current] then
msg.post(PROXIES[self.current], "release_input_focus")
end
end
local function enable_option(self, option)
-- возвращаем, если равен текущему
if option == self.current then return end
release_previous_focus(self)
msg.post(PROXIES[option], "enable")
-- если нужен ввод
msg.post(PROXIES[option], "acquire_input_focus")
for k, proxy in pairs(PROXIES) do
if k ~= option and self.loaded[k] then
msg.post(proxy, "disable")
-- если хотим экономить память, удаляем коллекцию
msg.post(proxy, "unload")
self.loaded[k] = nil
end
end
self.current = option
end
function on_message(self, message_id, message, sender)
if message_id == hash("switch") then
-- в строку потому что в proxies у нас тоже строка, данные: 1,2
local option = tostring(message.option)
-- ключ сцены для 1 - #start и 2 - #second, данные: #start, #second
local keyScene = PROXIES[option]
if not keyScene then return end
-- если сцена текущая, оставляем
if option == self.current then
return
end
-- если сцена загрузилась
if self.loaded[option] and not self.loading[option] then
enable_option(self, option)
else
self.loading[option] = true
-- отправляем на загрузку
msg.post(keyScene, "load")
end
end
if message_id == hash("proxy_loaded") then
-- включаем меню, sender и msg.url("#menu"): url: [main:/controller#menu]
if sender == msg.url("#menu") then
msg.post(sender, "enable")
msg.post(sender, "acquire_input_focus")
end
-- выясним, какая опция загрузилась
local option = option_by_sender(sender)
if option then
self.loading[option] = nil
self.loaded[option] = true
enable_option(self, option)
end
end
end
добавил еще одну кнопку для теста и проверил результат


