The Guild Trader: roguelite shopkeeper autobattler

Hi Everyone!
A while ago I put up a demo of the game we’re working on here: The Guild Trader (Demo) by nog1potje

In the game you sell gear to adventurers who must stop incoming threats from destroying your town. The adventurers will autobattle the threat after closing the shop. Please let us know what you think!

Currently working with an artist to completely overhaul the theme and look:

14 Likes

I love the idea and gameplay. Looking forward to seeing the game with the new art style and added polish.

1 Like

Looks great! I like the concept too!

1 Like


Work continues on updating the visuals. This is what the shop looks like in the current build.

Thematically it’s actually no longer a shop. In the game you play as a quartermaster who is helping heroes on their expeditions. The title for the game that we have in mind now is the Chopkeeper.

I’ve also put together an in-game animation tool based on Panthera and added some functionality to the playback engine. This tool allows me animate and preview tweens, flipbooks, shader constants, and overlay other animations. I’m now working to consolidate all animations.

12 Likes

That sounds both impressive and scary! Could you share your experience with it? I’m very intrigued!

2 Likes

For sure! The main reason I built it is due to frustration with the disjointed workflow when trying to animate a combination of flipbook, tweens, particles and shader constants. The only way to see the combined result was ingame.

Some things to know up front though:

  • The game purely built in gui nodes
  • I have a script which turns all .gui template files into a big lua table, this allows me to instantiate templates without using prefabs. There’s still work to do with regards to atlasses and other .gui defined data, but for now it works great

I replicated the panthera editor layout out of modified druid property panels with some added properties. The editor itself is a monarch screen that I can switch to when pressing F2.

Then you can pick a template and instantiate/load it:

Again it works a lot like panthera editor. You can add keys or tweens based on changes to the node that you can make in the properties panel on the right. You can also click on any timeline key and edit its values.

This editor changes the panthera animation data and keys, and when I save it converts the data back into a panthera compatible json. If there’s no exotic stuff in there, it will still load in panthera. However, I built this so I could animate shader constants as well.

I’ve also added the option to play and stop particlefx in there and vfx overlay animations:

The only edits I made to panthera itself were to the gui adapter. I saw a new version came out recently, but I haven’t updated yet.

Hope this helps. I still have figure out how and how much of this editor I’ll cut out when making release builds.

10 Likes

This is very cool! I’m happy that you able to manage such complex GUI with a Druid, good job!

The only edits I made to panthera itself were to the gui adapter. I saw a new version came out recently, but I haven’t updated yet.

Should be easy to migrate, in GUI adapter just two lines changes - Panthera Runtime v5 by Insality · Pull Request #16 · Insality/panthera · GitHub

This editor changes the panthera animation data and key

I think in this way, if you open the JSON in the Panthera 2.0 again and re-save, it will delete all custom data you added with a custom editor. Can be an issue here from this side.

I have a script which turns all .gui template files into a big lua table, this allows me to instantiate templates without using prefabs

Also thought about this sometimes, but still never tried. As I think it can be useful sometimes!

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.

3 Likes