The collection proxy component is used to load and unload new game “worlds” dynamically based on the content of a collection file. They can be used to implement switching between game levels, GUI screens, loading and unloading of narrative “scenes” throughout a level, loading/unloading of mini-games and more.
Defold organizes all game objects in collections. A collection can contain game objects and other collections (i.e. sub-collections). Collection proxies allow you to split your content into separate collections and then dynamically manage the loading and unloading of these collections through scripting.
Collection proxies differ from collection factory components. A collection factory instantiates the contents of a collection into the current game world. Collection proxies create a new game world at runtime and thus have different use-cases.
Add a collection proxy component to a game object by right-clicking a game object and selecting Add Component ▸ Collection Proxy from the context menu.
Set the Collection property to reference a collection that you wish to dynamically load into the runtime at a later point. The reference is static and makes sure that all the content of the referenced collection end up in the final game.
(You can exclude the content in the build and download it with code instead by checking the Exclude box and using the Live update feature.)
When the Defold engine starts it loads and instantiates all game objects from a bootstrap collection into the runtime. It then initializes and enables the game objects and their components. Which bootstrap collection the engine should use is set in the project settings. By convention this collection file is usually named “main.collection”.
To fit the game objects and their components the engine allocates the memory needed for the whole “game world” into which the contents of the bootstrap collection are instantiated. A separate physics world is also created for any collision objects and physics simulation.
Since script components need to be able to address all objects in the game, even from outside the bootstrap world, it is given a unique name: the Name property that you set in the collection file:
If the collection that is loaded contains collection proxy components, the collections that those refer to are not loaded automatically. You need to control the loading of these resources through scripts.
Dynamically loading a collection via proxy is done by sending a message called "load"
to the proxy component from a script:
-- Tell the proxy "myproxy" to start loading.
msg.post("#myproxy", "load")
The proxy component will instruct the engine to allocate space for a new world. A separate runtime physics world is also created and all the game objects in the collection “mylevel.collection” are instantiated.
The new world gets its name from the Name property in the collection file, in this example it is set to “mylevel”. The name has to be unique. If the Name set in the collection file is already used for a loaded world, the engine will signal a name collision error:
ERROR:GAMEOBJECT: The collection 'default' could not be created since there is already a socket with the same name.
WARNING:RESOURCE: Unable to create resource: build/default/mylevel.collectionc
ERROR:GAMESYS: The collection /mylevel.collectionc could not be loaded.
When the engine has finished loading the collection, the collection proxy component will send a message named "proxy_loaded"
back to the script that sent the "load"
message. The script can then initialize and enable the collection as a reaction to the message:
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_loaded") then
-- New world is loaded. Init and enable it.
msg.post(sender, "init")
msg.post(sender, "enable")
...
end
end
"load"
"proxy_loaded"
when it’s done."async_load"
"proxy_loaded"
when it’s done."init"
init()
functions are called at this stage."enable"
The Name set in the collection file properties is used to address game objects and components in the loaded world. If you, for instance, create a loader object in the bootstrap collection you may need to communicate with it from any loaded collection:
-- tell the loader to load the next level:
msg.post("main:/loader#script", "load_level", { level_id = 2 })
To unload a loaded collection, you send messages corresponding to the converse steps of the loading:
-- unload the level
msg.post("#myproxy", "disable")
msg.post("#myproxy", "final")
msg.post("#myproxy", "unload")
"disable"
"final"
final()
functions are called at this stage."unload"
If you don’t need the finer grained control, you can send the "unload"
message directly without first disabling and finalizing the collection. The proxy will then automatically disable and finalize the collection before it’s unloaded.
When the collection proxy has finished unloading the collection it will send a "proxy_unloaded"
message back to the script that sent the "unload"
message:
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_unloaded") then
-- Ok, the world is unloaded...
...
end
end
Collection proxy updates can be scaled by altering the time step. This means that even though the game ticks at a steady 60 FPS, a proxy can update at a higher or lower pace, affecting things such as:
dt
passed to update()
You can also set the update mode, which allows you to control if the scaling should be performed discretely (which only makes sense with a scale factor below 1.0) or continuously.
You control the scale factor and the scaling mode by sending the proxy a set_time_step
message:
-- update loaded world at one-fifth-speed.
msg.post("#myproxy", "set_time_step", {factor = 0.2, mode = 1}
To see what’s happening when changing the time step, we can create an object with the following code in a script component and put it in the collection we’re altering the timestep of:
function update(self, dt)
print("update() with timestep (dt) " .. dt)
end
With a time step of 0.2, we get the following result in the console:
INFO:DLIB: SSDP started (ssdp://192.168.0.102:54967, http://0.0.0.0:62162)
INFO:ENGINE: Defold Engine 1.2.37 (6b3ae27)
INFO:ENGINE: Loading data from: build/default
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0.016666667535901
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0
DEBUG:SCRIPT: update() with timestep (dt) 0.016666667535901
update()
is still called 60 times a second, but the value of dt
changes. We see that only 1/5 (0.2) of the calls to update()
will have a dt
of 1/60 (corresponding to 60 FPS)—the rest is zero. All physics simulations will also be updated according to that dt and advance only in one fifth of the frames.
You can use the collection time step functionality to pause your game, for instance while showing a popup or when the window has lost focus. Use msg.post("#myproxy", "set_time_step", {factor = 0, mode = 0})
to pause and msg.post("#myproxy", "set_time_step", {factor = 1, mode = 1})
to resume.
See set_time_step
for more details.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB