Factory components are used to dynamically spawn game objects from a pool of objects into a running game.
When you add a factory component to a game object you specify in the Prototype property what game object file the factory should use as a prototype (also known as “prefabs” or “blueprints” in other engines) for all new game objects it creates.
To trigger the creation of a game object, call factory.create()
:
-- factory.script
local p = go.get_position()
p.y = vmath.lerp(math.random(), min_y, max_y)
local component = "#star_factory"
factory.create(component, p)
factory.create()
takes 5 parameters:
url
[position]
vector3
. If you do not specify a position, the game object is spawned at the position of the factory component.[rotation]
quat
.[properties]
[scale]
number
(greater than 0) which specifies uniform scaling along all axes. You can also provide a vector3
where each component specifies scaling along the corresponding axis.For example:
-- factory.script
local p = go.get_position()
p.y = vmath.lerp(math.random(), min_y, max_y)
local component = "#star_factory"
-- Spawn with no rotation but double scale.
-- Set the score of the star to 10.
factory.create(component, p, nil, { score = 10 }, 2.0) -- <1>
-- star.script
go.property("score", 1) -- <1>
local speed = -240
function update(self, dt)
local p = go.get_position()
p.x = p.x + speed * dt
if p.x < -32 then
go.delete()
end
go.set_position(p)
end
function on_message(self, message_id, message, sender)
if message_id == hash("collision_response") then
msg.post("main#gui", "add_score", {amount = self.score}) -- <2>
go.delete()
end
end
Defold does not currently support non uniform scaling of collision shapes. If you provide a non uniform scale value, for instance vmath.vector3(1.0, 2.0, 1.0)
the sprite will scale correctly but the collision shapes won’t.
Defold’s addressing mechanism makes it possible to access every object and component in a running game. The Addressing manual goes into quite a bit of detail how the system works. It is possible to use the same addressing mechanism for spawned game objects and their components. It is quite often enough to use the id of the spawned object, for instance when sending a message:
local function create_hunter(target_id)
local id = factory.create("#hunterfactory")
msg.post(id, "hunt", { target = target_id })
return id
end
Message passing to the game object itself instead of a specific component will in fact send the message to all components. This is usually not a problem but it’s good to keep in mind if the object has a lot of components.
But what if you need to access a specific component on a spawned game object, for instance to disable a collision object or change a sprite image? The solution is to construct a URL from the game object id and the id of the component.
local function create_guard(unarmed)
local id = factory.create("#guardfactory")
if unarmed then
local weapon_sprite_url = msg.url(nil, id, "weapon")
msg.post(weapon_sprite_url, "disable")
local body_sprite_url = msg.url(nil, id, "body")
sprite.play_flipbook(body_sprite_url, hash("red_guard"))
end
end
When you call factory.create()
you get back the id of the new game object, allowing you to store the id for future reference. One common use is to spawn objects and add their id’s to a table so you can delete them all at a later point, for instance when resetting a level layout:
-- spawner.script
self.spawned_coins = {}
...
-- Spawn a coin and store it in the "coins" table.
local id = factory.create("#coinfactory", coin_position)
table.insert(self.spawned_coins, id)
And then later:
-- spawner.script
-- Delete all spawned coins.
for _, coin_id in ipairs(self.spawned_coins) do
go.delete(coin_id)
end
-- or alternatively
go.delete(self.spawned_coins)
It is also common that you want the spawned object to be aware of the game object that spawned it. One case would be some type of autonomous object that can be spawned only one at a time. The spawned object then needs to inform the spawner when it is deleted or inactivated so another one can be spawned:
-- spawner.script
-- Spawn a drone and set its parent to the url of this script component
self.spawned_drone = factory.create("#dronefactory", drone_position, nil, { parent = msg.url() })
...
function on_message(self, message_id, message, sender)
if message_id == hash("drone_dead") then
self.spawed_drone = nil
end
end
And the spawned object’s logic:
-- drone.script
go.property("parent", msg.url())
...
function final(self)
-- I'm dead.
msg.post(self.parent, "drone_dead")
end
By checking the Load Dynamically checkbox in the factory properties, the engine postpones the loading of the resources associated with the factory.
With the box unchecked the engine loads the prototype resources when the factory component is loaded so they are immediately ready for spawning.
With the box checked, you have two options for usage:
factory.create()
when you want to spawn objects. This will load the resources synchronously, which may cause a hitch, then spawn new instances.
function init(self)
-- No factory resources are loaded when the factory’s parent
-- collection is loaded. Calling create without having called
-- load will create the resources synchronously.
self.go_id = factory.create("#factory")
end
function final(self)
-- Delete game objects. Will decref resources.
-- In this case resources are deleted since the factory component
-- holds no reference.
go.delete(self.go_id)
-- Calling unload will do nothing since factory holds no references
factory.unload("#factory")
end
factory.load()
to explicitly load the resources asynchronously. When the resources are ready for spawning, a callback is received.
function load_complete(self, url, result)
-- Loading is complete, resources are ready to spawn
self.go_id = factory.create(url)
end
function init(self)
-- No factory resources are loaded when the factory’s parent
-- collection is loaded. Calling load will load the resources.
factory.load("#factory", load_complete)
end
function final(self)
-- Delete game object. Will decref resources.
-- In this case resources aren’t deleted since the factory component
-- still holds a reference.
go.delete(self.go_id)
-- Calling unload will decref resources held by the factory component,
-- resulting in resources being destroyed.
factory.unload("#factory")
end
It is possible to change which Prototype a factory can create by checking the Dynamic Prototype checkbox in the factory properties.
When the Dynamic Prototype option is checked the factory component can change prototype using the factory.set_prototype()
function. Example:
factory.unload("#factory") -- unload the previous resources
factory.set_prototype("#factory", "/main/levels/enemyA.goc")
local enemy_id = factory.create("#factory")
When the Dynamic Prototype option is set the collection component count cannot be optimized, and the owning collection will use the default component counts from the game.project file.
The project setting max_instances in Collection related settings limits the total number of game object instances that can exist in a world (the main.collection loaded at startup or any world loaded via a collection proxy). All game objects that exist in the world are counted against that limit and it does not matter if they are placed by hand in the editor or spawned in runtime through a script.
If you set max_instances to 1024 and have 24 manually placed game objects in your main collection, you can spawn an additional 1000 game objects. As soon as you delete a game object, you are free to spawn another instance.
It may seem like a good idea to save spawned game objects in a pool and reuse them. However, the engine is already doing object pooling under the hood so additional overhead will only slow things down. It is both faster and cleaner to delete game objects and spawn new ones.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB