Factory components

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.

Factory component

Factory component

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)

Spawned game object

factory.create() takes 5 parameters:

url
The id of the factory component that should spawn a new game object.
[position]
(optional) The world position of the new game object. This should be a vector3. If you do not specify a position, the game object is spawned at the position of the factory component.
[rotation]
(optional) The world rotation of the new game object. This should be a quat.
[properties]
(optional) A Lua table with any script property values to initiate the game object with. See the Script property manual for information on script properties.
[scale]
(optional) The scale of the spawned game object. The scale can be expressed as a 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>
  1. Sets the “score” property of the star game object.
-- 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
  1. The “score” script property is defined with a default value.
  2. Reference the “score” script property as a value stored in “self”.

Spawned game object with property and scaling

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.

Addressing of factory created objects

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

Tracking spawned and parent objects

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

Dynamic loading of factory resources

By checking the Load Dynamically checkbox in the factory properties, the engine postpones the loading of the resources associated with the factory.

Load dynamically

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:

Synchronous loading
Call 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
Asynchronous loading
Call 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

Dynamic prototype

It is possible to change which Prototype a factory can create by checking the Dynamic Prototype checkbox in the factory properties.

Dynamic prototype

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.

Instance limits

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.

Max instances

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.

Pooling of game objects

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.