Using Factories for GUI Collections / Screen Handling

I’m pretty new to game dev, so I’m just looking for a sanity check on my GUI / screen handling method. Any additional feedback would be great as well.

For background, I’m making a business sim / tycoon type game and, at the moment, most of the game takes place in GUI Scenes. There will be some regular game scenes for later action/physics elements, but that’s a minority of the game – most of the game is GUI.

It seems that most people like to use collection proxies for screen switching, but since I’m bouncing back between many GUI screens and don’t need the duplicate game worlds from proxies, I wanted to save the resources overhead so I figured I would use collection factories instead.

As I have it set up now, my main collection has a home screen GUI and a loader GO which hosts the collection factories from the various other GUIs.

Right now, the loader creates the other GUIs on init, and gets the hash URL from the generated table for later use. (side bar: I remember reading somewhere that something with URLs is disabled at bundling… would this be affected?)

function init(self)
	--create instance from collection factory, then get hash from table and save to variable
	local gui_menu2_table = collectionfactory.create("loader#gui_menu2")
	self.gui_menu2 = gui_menu2_table[hash("/gui_menu2")]

When the user presses a button on the home screen, the home screen sends a message to the loader, the loader then calls a simple function which, for now, forwards the message along to the target GUI.

-- onmessage call "show" function
function on_message(self, message_id, message, sender)
	if message_id == hash("show_gui_menu2") then
		show_gui_menu2(self)
	end 
end

--show function sends simple show message to target

local function show_gui_menu2(self)
	msg.post(self.gui_menu2, "show_gui_menu2")
end

I have it set up this way in case I decide to remove the GUIs from creating at loader init later on: e.g., I could easily switch to async loading or load resources on startup but only create when first called, or otherwise only load on first call, if the game starts running slowly later in development.

I could also easily swap out a collection factory for a collection proxy for certain calls if I need a game world later.

A couple questions I have:

  • Is there a better way to do this?
  • Am I overthinking the collection proxy issue?
  • Am I overthinking the message passing to the generated GUIs?
  • Is there a simpler function to get the hashed URL in one step rather than get the table, then convert?
  • Should I be unloading / deleting the factory-generated GUIs whenever they hide? I’m not sure how much memory that would save. They aren’t that complex at the moment.

I know my current code works on build to swap between GUIs, but my game is currently pretty empty anyway. I’m hoping it scales as my game grows and uses less resources than if I had used collection proxies due to less overhead.

I suppose I could probably make the loader a more generic message forwarder too, to send any message to the factories instead of just the ones the loader is programmed for.

The game is very early in development so I’m basically just writing the gui / screen handlers at the moment while I work out the core game mechanics in blocks / pseudo-code.

Another option, I guess, is just to throw everything in the main collection but then I have no control over dynamic loading if things get slow, and everything is just overlaid in the defold window.

And the last option is to stop trying to reinvent the wheel and just use collection proxies like everyone else.

Thoughts?

1 Like

While this is true that every loaded collection gets its own physics world it doesn’t necessarily have much impact. Especially if you’re not using physics and have stripped the physics systems from the engine (using an app manifest). Still, it’s good that you think about performance in the way that you do!

And collection factories is a good alternative. (I have plans to add support for collection factories to my screen manager, Monarch, as well)

[quote=“unsymmetricalstudios, post:1, topic:25622”]
Right now, the loader creates the other GUIs on init, and gets the hash URL from the generated table for later use.[/quote]

Do you create all of them at once? Why not create and delete as needed? Make sure to disable those that you don’t use! Check the profiler to make sure you don’t have stuff loaded that you don’t need!

No, URLs aren’t disabled. You will however not get anything human readable from tostring(hash(“foobar”)) in a release build since reverse hashing is disabled.

Your solution sounds good.

Yes. Use the profiler to measure if you’re uncertain. Are you targeting mobile, web or desktop? (or perhaps all?)

No, sounds reasonable to me.

You mean this line: self.gui_menu2 = gui_menu2_table[hash("/gui_menu2")] ?

You should at least disable them. But that will still keep any assets in memory. This could be a problem or not. Depends on if you have many unique assets per screen. Use the profiler!

Don’t. Your solution sounds good, and it will be fairly future proof as you add more and more content.

2 Likes

Thanks for the thorough reply!

To address a few points:

As I mentioned, right now I don’t have much content yet so I load them all at once, but I plan to implement loading the first time each one is called and seeing how that works.

As for the disabiling of each GUI, I have every GUI on init set self.active = false, then the main GUI sends a message to itself to turn on. I’ll probably implement a splash screen / start screen and maybe a loading screen before the home in the future to allow for some initialization delay.
As far as I know, “enable”/“disable” only applies to proxies, right?

I could delete the generated GUI when hidden, but was curious if rapidly creating and deleting them was more resource intensive than just loading them and activating/deactivating as necessary?

Yes, this one and the line above it. Is there a more elegant way to create the instance from the factory and extract the hash of the instance url in one step? In other words, can I eliminate the “gui_menu2_table” intermediate variable?

Thanks again for the feedback! I looked at monarch before. I liked it but I’m not sure I would use the stack feature for non-popups and I definitely have some nested menus which you mentioned elsewhere in the forums that monarch didn’t support. Besides, this is a good exercise for myself!

While I have your attention, if I am checking at startup for a previous save file to check if it’s the user’s first run, is it better practice to do that initialization on the home screen or some separate splash screen, or does it not matter? This may be a case-by-case basis thing I suppose.

Edit: Forgot a few things:

  • I will definitely be using the profiler during development.
  • This is primarily for mobile, hence the laser focus on resource management. Plus, I want to learn to do it right the first time.
  • I may port to desktop later on but more likely I will use what I learned to make a more full-featured game extending the concepts of the mobile version.

Thanks again!

No, you can enable/disable most (all?) components. If you have stuff offscreen then make sure to send a “disable” message to the game object! If you simply move a gui offscreen it will still be rendered. If you disable it it will have almost no impact on performance.

Spawning and deleting things generally have very little impact in Defold. If you use dynamic resource loading it could have a greater impact when the ref count reaches zero and stuff gets unloaded. The next time you spawn an instance the resources will have to be loaded again.

You could turn it into a one-liner:

self.gui_menu2 = collectionfactory.create("loader#gui_menu2")[hash("/gui_menu2")]

I should add an option to not add a screen to the stack (https://github.com/britzl/monarch/issues/26)

What do you mean by this?

Indeed! It’s a great way to learn Lua and the engine. I don’t discourage it at all!

Doesn’t really matter. It shouldn’t have any effect on performance. By the way, there’s DefSave from the Asset Portal if you don’t want to deal with save states yourself.

Yes, use it often! If your game is gui heavy you really need to keep draw calls in check (use layers to reduce draw calls). Every time you add new content, double check the draw calls in the profiler to ensure that they are what you expect (excellent post on draw calls: Draw calls and Defold).

Ok, interesting. What is the difference between self.active = false and msg.post("#", “disable”)?
edit: I was already doing this! The message disables the gui and sets self.active = false. I was confusing the self.active variable for the “enable”/“disable”. I understand it now. Thanks!

Are you suggesting I should spawn and delete the GUIs but be careful not to unload the resources? Or should I force it to unload / reload the resources with dynamic loading?
I’m not sure which case you are suggesting is better.

For example, if I had a sub_gui, and then that me had a child gui (e.g. a dependent GUI menu that only is called from that one GUI).
There was a previous post somewhere that had something similar so I assume this but, to be honest, I’m not exactly sure if this is really an issue.

I’m probably going to be using Playfab.

Thanks for all of the feedback! I think I am on the right track!