Proxy


Setup

The setup consists of several collections and game objects.

proxy

proxy.collection
This is the bootstrap collection specified in game.project. Contains:
  • A Script that handles loading and unloading of collection proxies
  • Four Collection proxies referencing a menu collection and three level collections.

menu

menu.collection
This collection contains a menu. Contains:
  • A GUI with some box and text nodes that acts as buttons.
  • A GUI script that handles the logic of clicking on the buttons and sending messages back to the proxy.collection.

level

level1-3.collection
Collections representing the levels of a game. Contains:
  • Script with logic to send a message back to the proxy.collection to show the menu again.

Scripts

controller.script

local function show(self, proxy) -- <5>
	if self.current_proxy then -- <6>
		msg.post(self.current_proxy, "unload") -- <7>
		self.current_proxy = nil
	end
	msg.post(proxy, "async_load") -- <8>
end

function init(self)
	msg.post(".", "acquire_input_focus") -- <1>
	self.current_proxy = nil -- <2>
	msg.post("#", "show_menu") -- <3>
end

function on_message(self, message_id, message, sender)
	if message_id == hash("show_menu") then -- <4>
		show(self, "#menuproxy")
	elseif message_id == hash("show_level1") then
		show(self, "#level1proxy")
	elseif message_id == hash("show_level2") then
		show(self, "#level2proxy")
	elseif message_id == hash("show_level3") then
		show(self, "#level3proxy")
	elseif message_id == hash("proxy_loaded") then -- <9>
		self.current_proxy = sender -- <10>
		msg.post(sender, "enable") -- <11>
	elseif message_id == hash("proxy_unloaded") then
		print("Unloaded", sender)
	end
end

--[[
1. Acquire input focus for this game object. This is required for input to be able to propagate into any of the collection proxies on the same game object as this script.
2. Create a variable `current_proxy` to track which collection proxy that is loaded.
3. Post a `show_menu` message to the `on_message` function of this script. This load and show the first screen.
4. Message handler that will react to `show_menu`, `show_level1`, `show_level2` and `show_level3` messages and load the appropriate collection proxy.
5. A helper function to unload any currently loaded collection proxy and load a new collection proxy.
6. Check if a collection proxy is loaded.
7. Send an `unload` message to the currently loaded collection proxy. This will immediately unload the proxy and all of its resources. A `proxy_unloaded` message will be sent when it has been unloaded.
8. Send an `async_load` message to the collection proxy that should be loaded. This will start loading the collection proxy and all of its resources. A `proxy_loaded` message will be sent when it has been loaded.
9. Handle the `proxy_loaded` message. This is sent when a collection proxy has finished loading. The collection and all resources will be loaded but no game objects or components will be active/enabled.
10. Store the url of the proxy that was loaded. This is used in the helper function to unload it when showing another collection proxy.
11. Enable the loaded collection proxy. This will activate/enable all game objects and components within the collection.
--]]

menu.gui_script

local function create_layout(items, bl, tr, max_dy)
	local xi = math.ceil(math.sqrt(items) / 1.5)
	local yi = math.ceil(items / xi)

	local dx = (tr.x - bl.x) / xi
	local dy = (tr.y - bl.y) / yi
	
	if dy > max_dy then
		dy = max_dy
	end
	
	local layout = {}
	local c = items
	for y = 1,yi do
		for x = 1,xi do
			local xp = bl.x + dx / 2 + dx * (x - 1)
			local yp = tr.y - dy / 2 - dy * (y - 1)
			table.insert(layout, vmath.vector3(xp, yp, 0))
		end
	end
	return layout
end

local function create_cat_nodes(self, layout)
	-- create category nodes
	self.categories = {}
	local c = 1
	for t, cat in ipairs(self.index) do
		local p = layout[c]
		local n = gui.new_text_node(p, cat)
		gui.set_color(n, vmath.vector4(0.2, 0.2, 0.2, 1.0))
		gui.set_font(n, "text64")
		gui.set_scale(n, vmath.vector3(0.5, 0.5, 1.0))
		local m = gui.get_text_metrics_from_node(n)
		local size = vmath.vector3(m.width, m.height, 1)
		gui.set_size(n, size)
		table.insert(self.categories, { node = n, category = cat, pos = p, size = size * 0.5 })
		c = c + 1
	end
end

local function create_example_nodes(self, category, layout)
	-- create example nodes
	self.examples = {}
	local c = 1
	for t, ex in ipairs(self.index[category]) do
		local p = layout[c]
		local n = gui.new_text_node(p, ex)
		gui.set_color(n, vmath.vector4(0.2, 0.2, 0.2, 1.0))
		gui.set_font(n, "text48")
		gui.set_scale(n, vmath.vector3(0.5, 0.5, 1.0))
		local m = gui.get_text_metrics_from_node(n)
		local size = vmath.vector3(m.width, m.height, 1)
		gui.set_size(n, size)
		local example = hash(category .. "/" .. ex)
		table.insert(self.examples, { node = n, example = example })
		c = c + 1
	end
end

local function show_categories(self)
	self.state = "categories"
	local closenode = gui.get_node("close")	
	gui.set_enabled(closenode, false)
	-- delete example nodes
	for i, ex in ipairs(self.examples) do
		gui.delete_node(ex.node)
	end
	self.examples = {}
	
	for i, cat in ipairs(self.categories) do
		gui.set_enabled(cat.node, true)
		gui.set_position(cat.node, cat.pos)		
	end
end

local function cat_expand(self)
	local closenode = gui.get_node("close")
	gui.set_enabled(closenode, true)
	
	local ex = self.index[self.current_category]
	local layout = create_layout(#ex, self.bl, self.tr, 100)
	create_example_nodes(self, self.current_category, layout)
end

local function show_category(self, category)
	self.state = "category"
	self.current_category = category
	for i, cat in ipairs(self.categories) do
		if cat.category == category then
			local pos = vmath.vector3(50, 600, 0)
			local m = gui.get_text_metrics_from_node(cat.node)
			pos.x = pos.x + cat.size.x / 2 + 20
			gui.animate(cat.node, "position", pos, gui.EASING_INOUTQUAD, 0.3, 0, cat_expand)
		else
			gui.set_enabled(cat.node, false)			
		end
	end	
end

function init(self)
	self.index = { "basics", "physics", "animation", "gui", "input", "particles", "sound", "render", "debug", "collection", "sprite" }
	self.index["basics"] = { "simple_move", "message_passing", "follow", "parent_child", "spawn", "z_order" }
	self.index["physics"] = { "dynamic", "kinematic", "raycast", "trigger" }
	self.index["animation"] = { "spinner", "flipbook", "tween", "spine" }
	self.index["gui"] = { "button", "stencil", "load_texture", "pointer_over"}
	self.index["input"] = { "move", "text", "down duration" }
	self.index["particles"] = { "particlefx", "modifiers" }
	self.index["sound"] = { "music", "fade_in_out" }
	self.index["render"] = { "camera" }
	self.index["debug"] = { "physics", "profile" }
	self.index["collection"] = { "proxy" }
	self.index["sprite"] = { "size" }
			
	self.examples = {}
	self.categories = {}

	self.bl = vmath.vector3(50, 50, 0)
	self.tr = vmath.vector3(670, 560, 0)
	
	local cat_layout = create_layout(#self.index, self.bl, self.tr, 100)
	create_cat_nodes(self, cat_layout)
	
	show_categories(self)
	
	msg.post(".", "disable")
	msg.post("#", "release_input_focus")
end

function on_message(self, message_id, message, sender)
	if message_id == hash("show") then
		msg.post(".", "enable")
		msg.post("#", "acquire_input_focus")
	elseif message_id == hash("hide") then
		msg.post(".", "disable")
		msg.post("#", "release_input_focus")
	end
end

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed then
		if self.state == "categories" then
			for i, cat in ipairs(self.categories) do
				if gui.pick_node(cat.node, action.x, action.y) then
					show_category(self, cat.category)
				end
			end		
		end
	
		if self.state == "category" then
			local n = gui.get_node("close")	
			if gui.pick_node(n, action.x, action.y) then
				show_categories(self)
			end
			
			for i, ex in ipairs(self.examples) do
				if gui.pick_node(ex.node, action.x, action.y) then
					msg.post("/loader#script", "load_example", { example = ex.example })
				end
			end			
		end
	end
end

function on_reload(self)
	msg.post(".", "disable")
end

level.script

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

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.released then
		msg.post("proxy:/controller", "show_menu")
	end
end

If you want to play with these examples, you can get the project on Github.

Do you want to see more examples? Why not write a few yourself and submit a pull request? We love contributions.