Factories, factories everywhere. Is this the way?

I understand it’s an optimization, that each factory acts as a pool of objects of its kind. But if the game starts growing and its content along with it, factory numbers start growing alarmlingly.

This is the state of our current game:

imagen

And it doesn’t look like much, but that’s because we are doing some nasty things to keep factory numbers low. For instance, the interactable_factory creates interactables, which are devices the user can interact with, and look like this:

imagen

It contains, among other scripts, the interactable script, which sends a message to its own go with the interactable data in its init function. Interactables can be goal_devices, spawners and vending_machines. Now, each script receives that message and if the interactable is not of its kind, it disables itself. It’s the interactable_factory which tells each interactable what it is (levels are created procedurally!) So a goal_device checks data and if it’s a goal device it works, otherwise deactivates itself and ignores all future messages.

May be ugly, but it allows us to keep things under a single game object. The alternative is a different .go - even when it basically does the same thing with little differences - and a specific factory for each one. Ouch.

Same for projectiles, we have a single projectile.script file that handles different kinds of projectiles, and a single projectile.go that configures itself with the correct atlas, sprite animation and such. Instead of a factory for each kind of projectile, it’s again centraliced in a single projectile_factory.script and a single projectile.go. In other game engines we would probably use different components, plugging them in on the fly to achieve different behaviours. Here we initialize the script this way:

It works because they basically use the same collider. Otherwise… PHEW. We would need a different go for each one or we have to do something like we did with factories: different collider shapes that we deactivate when not needed, or something like that.

I guess it depends a lot on the game you are making, and this solution currently suits us and our project, but I would love to know how you organice yours when things grow! Do you really have one single .go file for each thing, no matter how alike they are, and its own factory?

1 Like

In my opinion having many factories producing small game objects is the way to go.
And you can cache the method function in init() as self.method so you don’t have to do the lut lookup in update()

2 Likes

So imagine we have 15 projectiles in their own go. Once they are done, with their own factory, we suddently want to add something to all of them. Maybe a trail, for instance. You would have to change every single object one by one to add it… Wouldn’t be able to do it by code because we can’t just plug components.

We also have like 14 enemies. They are all the same thing: a script, a collider, a sprite. But their behaviour is different. We went the all-in-one-script way, so everything is in a single enemy.script file (yup, it’s growing). Otherwise… it would be 14 go (that look exactly the same with different atlases) and 14 factories?

Same with players! We have 4. We recently added an enemy we can’t jump on, because it kills us (it’s a cactus). We added the enemy.go a new collider in the hazzard group, and made the player collider interact with that group too. If we had had 4 different player .go files we would have been forced to change them all, right?

And you are doing this because why? There is nothing in our documentation which recommend to keep the number of factories as few as possible. But sure, I can see that it can quickly become unmanageable in a very large game.

I would recommend to have one factory per group of objects, assuming that the objects are fairly similar. It looks like what you are doing here:

I think this is totally valid. And a nice and kind of data driven approach. And like @sergey.lerg mentioned I’d probably cache the object specific functions on init. And probably move the object specific lifecycle functions into Lua modules.

local lut = {
	[projectile_type.rocket] = require("projectiles.rocket"),
	[projectile_type.bullet] = require("projectiles.bullet"),
	...
}
2 Likes

This is not directly about the number of factories, but you may want to consider using collections and collectionfactories instead of gameobject and gameobjectfactories for non-trivial entities.

You may have noticed that when you create a prototype .go file, you’re not able to add gameobject children to the root gameobject (you’re only able to add components). This can cause headaches in the future; it’s not too hard to imagine scenarios where you want to add gameobject children to your player gameobject prototype, e.g. a visual effect with its own transform, sprite component etc. that you want to have attached to the player.

4 Likes

You can use collectionfactories instead of regular factories or you can use a tail factory and spawn it along with your projectiles and use go.set_parent() to link them.

@britzl We are doing it to keep things easy to locate and maintain, we know there’s nothing related to ‘keeping number of factories as few as possible’ : )

The worst drawback I see from our current approach is the enemy.script file growing larger than 1000 lines. We also think moving functionality to Lua modules and using one or the other depending on the kind of enemy, is the way to go here. But we did it that way because of the running joke: successful games (like Celeste) MUST have at leas a file with +5000 lines of code :smiley: Yes, the long enemy script is not an issue for now (it encapsulates states of a finite state machine and behaviours, which control the FSM for each rat, so it’s easy to follow)

@GooseSwanson So far we have not needed it. We can instantiate things we need elsewhere. For instance, the Plague Doctor rat spawns a floating skull that represents a disease you can catch. These two could be a collection, but we are not going to do it just for a single rat.

1 Like

:+1:

That is as a good a reason as any!

1 Like