Dynamic generation with Collection proxies?

TL;DR
What is the best way of creating reusable collections for spawning procedurally generated levels that can be customized when you instantiate them? That is, using the same collection to generate different level types, as opposed to having one collection for each type of level.

Long version:
I’m struggling on with my procedural generation experiments in Defold and keep running into problems with they way I’d like to code, and the way Defold/lua is designed to work (I think).

I have a procedurally generated Galaxy map, with planets of different types. When the player lands on a planet I would like to generate a random world based on the type of planet. So a desert planet would have dry biomes, a jungle planet lots of forest and so on.

I use Collection Proxies for switching between the Galaxy map and the World map (planet surface). When the player goes to a planet, I Unload the galaxy collection and Load the Worldmap collection via proxies. This works fine, but I would like to be able to tell the Worldmap-GO which kind of world to generate. And I can’t figure out how achieve this.

I have tried sending messages to the worldmap instance after it’s been loaded, but that means I need to know it’s ID and I don’t know how to get that in runtime. If I print msg.Url() form Worldmap I can see that the id of the instance is “#worldmap1”, but I don’t know how to get that Id in runtime from my main controller. The 1 on the end is of course an incremental id, and I wouldn’t want to hardcode that.

Is there a (good) way to send messages or context data to the instances created by the Collection proxy?

Or is there a different way of solving this altogether?
I’ve seen some people suggesting using global Lua modules for shared state, but that seems so centralised. I’d rather have some way of distributing state to each GO so they can be more autonomous and easier to reuse.
What would be the “Defold-way” of doing this?

You can use Monarch that have that possibility to send data between screens which are also collection proxies. Or longer way: if I imagine it correctly, you have a main collection loaded at the beginning that could now have a subscription handling script - whenever a new world you are loading, send its msg.url() in init to the main script, which id is static, so you can then send any data to the loaded world :wink:

1 Like

Ah, I see. So in the init of my collection I would send a message to main which in turn would give me my data? Seems a bit roundabout. But it’s logically rather close to what I was trying: send a message from main to the instantiated collection.
I like the sound of that! It’s a bit like a pub-sub relationship.
I’ll try it out :slight_smile: Thanks!

1 Like

Indeed :wink: if you want to set up a “conversation” between two instantiated collections only, just collect their urls in main collection and send to each other, like “changing handshakes”, so they know about themselves :slight_smile:

So, you want to recreate No Man’s Sky if I am getting you right.
For that type of thing I would rather suggest using lua modules to store current scene, rather than making gos handle them. Instead, make them handle the functionality of creating the level.
Store the data, basically name or the id of the level you want to generate, and access them using GOs. The GOs will then make descisions using the data you stored, and then use it to create the various structures of the level.
{Meanwhile, spoiler request, is your game 2D or 3D, and if 2D then sidescroller or top down?(I am just to curious about this, as this reminds me of an old game I was working on In July)}

So, I make a Lua module with variables for global data and then call accessor functions on that? Or what is the best way of using a state module like that?
Would you keep the all the game state in modules?

Regarding the game, I wouldn’t say I’m aiming for a new No Man’s Sky :slight_smile: So far I’m thinking a 2D top down exploration sand box game. Travel a random galaxy, land on planets, excavate ore, find artifacts, enter locations. All procedurally generated.

My vision of this includes some kind of randomized difficulty made up of several different difficulty factors that vary for different locations/planets/regions. So you get the risk-reward push your luck gameplay where you can pick a more difficult planet that gives greater rewards.
But really I’m mostly trying out Defold and procedural generation.

And this game is similar to an OLD game I was working on in 1992 :smiley: But that time I was using 68k assembler. Good old times!
(Defold is rather easier to work with :slight_smile: )

Here is an (ugly) result of my Defold struggles so far

2 Likes

Lua modules can be used for many different things: keeping track of game state, encapsulation of code and/or data, oop (gasp!) and so on.

In your case you could stick your state data directly on the module and make all of the data “public” or you could use getters and setters.

I’m still too un-fluent in Lua… Even though I’ve must’ve spent HOURS reading about it :smiley:

Does a Lua module never get reloaded after the game has started? So it keeps its state? Or do I need to make the variables static (local?)?

A Lua module is typically defined like this:

-- mymodule.lua
local M = {}

local privatedata = "foo"

M.publicdata = "bar"

local function privatefunction()
	print("I'm private")
end

function M.publicfunction()
	print("I'm public")
end

return M


-- use it like this
local mymodule = require "mymodule"

print(mymodule.publicdata) -- "bar"
mymodule.publicfunction() -- "I'm public"

Once you’ve required it once the returned value (the M table) will get cached and every subsequent require call for the same module will return the same value (the M table). You can unload a module, but that is usually not done.

1 Like

And that cached data will also reflect edits made to it? Changing state I mean.

Yes, you’re manipulating the M table directly. It’s not a copy or anything like that. All loaded modules has their values stored in the package.loaded table and any subsequent require call returns the same value, which in the case of a table will be the same table, not a copy.

2 Likes

Thanks! Sounds really useful :slight_smile: