I primarily started doing it because I wanted to rely less on strings to grab nodes. Something you also tried to solve with the SCHEME workflow in older versions of Druid.
Ok, big explainer. My script setup works like this:
- A module requires every relevant .lua file like so:
local to_load_list = {
item_slot_equipment = require("src.items.item_slot_equipment"),
item_slot_inventory = require("src.items.item_slot_inventory"),
tooltip_item = require("src.items.tooltip_item"),
areamap = require("src.map.areamap"),
...
}
return to_load_list
- A loader goes over each of these and injects stuff. For instance, modules can have a dependencies field, which the loader will look for to inject into the module. This solves any kind of circular reference issue.
for name, module in pairs(modules) do
if type(module) == "table" and module.dependencies then
for _, dependency_name in pairs(module.dependencies) do
if modules[dependency_name] then
module[dependency_name] = modules[dependency_name]
else
log:warning("Warning: Dependency " .. dependency_name .. " not found for " .. name)
end
end
end
end
- The loader also finds modules which are druid widgets, and then wraps their init function to inject all gui nodes into a table. I use the generated file which contains all gui template data in lua form to figure out which node names I’m looking for. Roughly like this:
for name, module in pairs(modules) do
if type(module) == "table" and module.init then
// Set aside the original init
local original_init = module.init
// A predefined scheme can override the node names, ui.templates contains all templates
local scheme = module.scheme or ui.templates[name].nodes
if scheme then
module.scheme = scheme
module.init = function(self, ...)
self.nodes= {}
// Widget instantiated based on prefab
if self._meta and self._meta.nodes and next(self._meta.nodes) then
for node_name, node_data in pairs(scheme) do
local node = self:get_node(node_name)
self.nodes[node_name] = gui_node.make_node(node)
end
// Widget requires nodes to be made
else
// Makes the nodes using gui.new_box_node() etc
ui.widget_helper.create_nodes_from_template(name, self.nodes, "prefab_" .. name .. "/")
// Add data expected by druid
if self._meta and self.nodes.root then
self._meta.nodes = self.nodes.root:get_tree()
self._meta.template = "prefab_" .. name
end
end
// Back to the original
original_init(self, ...)
end
end
end
end
- This does require my widget module names to be the same as the .gui template files.
- I also wrap nodes into a gui_node class for even further QoL
In the end this allows me to do:
---@class some_widget: druid.widget
---@field nodes some_widget_nodes
local some_widget = {}
function some_widget:init()
self.nodes.icon:set_enabled(false)
end
return some_widget
Which feels very nice and clean, and gives me great autocomplete as some_widget_nodes annotations are also generated.
Not sure if anyone would find it helpful to pull this stuff out of my project and share it separately. However, I do feel I’d be nice if the Defold engine did more of this. Maybe I should work on that someday.
And yeah, Druid is nice, great work! There was a bit of a learning curve with regards to the things it does automagically. Like the stuff it does chaining template name when working with components inside components. When I was still learning Defold itself and tried having subcomponents work on nodes which weren’t actually inside subtemplates/prefabs it was tricky. Tricky to figure out how Druid keeps track of those nodes and why it wouldn’t see the ones I tried to give it access to.
A lot of that has cleared up with me learning, but also 1.1 was a very nice upgrade. It took a bit of work to convert all custom components to widgets. Technically I didn’t need to do that, but the widget workflow is just so much cleaner and easier to maintain.