Valid approach? Base scenes for enemy types, and variations are defined with data Resources

Godot Version

4.5 stable

Question

I’m just wondering if this is a valid, not-overdesigned approach in general, or maybe at least for my game specifically.

In a nutshell: Data for specific enemies stored in .tres file Resources. A handful of scenes for each type of enemy that don’t have data in them, but read from said Resources to create themselves when spawned.

Explanation

I’m making a simple platformer that procedurally generates stages and randomly places enemies from a list of possible enemies. But enemies can be easily categorized into a few types, and there are going to be quite a lot of them, so it seems like a better approach to just have one base scene for each type, then have all the enemy-specific data in Resources. It’s also nice to have all the data for each enemy in a Resource, so I can even have stuff in there like its description and name, which would only be used for UI things.

So here’s what I have so far. I’ve got a basic enemy scene called an IdlerEnemy:

And this root node extends the script for an Enemy, which is literally just this:

class_name Enemy
extends BaseCharacter

@export var data: EnemyData

Now, EnemyData is not finished yet, but this is what it has so far:

class_name EnemyData
extends CharacterData

enum spawn_locations {
	GROUND,
	AIR,
	WATER
}

@export var base_scene: PackedScene
@export var spawn_location: spawn_locations
@export var level: int
@export var att_setup: CharacterAttributesSetup
@export var item_drops: Dictionary[float, Resource] #change later
@export var sprite_frames: SpriteFrames
@export var collision_shape: RectangleShape2D

(CharacterData just has a variable for the name, and extends Resource)

So whenever I make a new enemy, I just create a new .tres file from this EnemyData, and I choose the base scene for the base_scene variable.

Now, my character spawning class that actually spawns the characters in can have a nice, safely-typed array of EnemyData, rather than an array of PackedScenes, and it can read the enemy data like the spawn location without actually instantiating the scene first.

So it does something like this (not precisely what I have but the same idea):

var spawn_amount: int = stage.spawn_amount
while spawn_amount > 0:
     var enemy_data: EnemyData = possible_enemies.pick_random()
     #I randomly generate my stages, so 
     #I need to check if there is water before placing any water enemies:
     if enemy_data.spawn_location == enemy_data.spawn_locations.WATER and water_exists == false:
          possible_enemies.erase(enemy_data)
          continue
     var enemy_instance = enemy_data.base_scene.instantiate()
     enemy_instance.data = enemy_data
     add_child(enemy_instance)
     spawn_amount -= 1

When the enemy is added to the scene, it just uses the data to set itself up, like this:

So! I was thinking lately (from a discussion I had in another post I made) that just making a whole separate scene for each enemy might be easier. But I changed a few things since then so that the data-driven approach feels like it works just as fine and might actually be better for my game specifically. Here are my thoughts:

Whole new scene for every single enemy

Pros:

  1. Simpler to actually create, everything is done in the editor and left in the editor
  2. If I decide to change a specific enemy a little bit later, easier to make tiny changes to it

Cons:

  1. PackedScenes. With my random generation approach, I would have to first instantiate the scene, then check if it could even be spawned, and then if not, remove it from the array and queue_free() it. PackedScenes are also not safely typed as Enemies, and less readable in the editor. I also know that I’ll want things like a library where players can read about all the enemies, and I don’t want to instantiate them all just to get their data there, either.
  2. Lots of redundant scenes. If you have a Cat, an OrangeCat, and a GrayCat, and all that’s different is their color and stats, seems wasteful to have a scene for each.

Data-driven

Pros:

  1. Much easier to keep track of the actual in-game functionality of all the enemies, since you only have a handful of scenes. If I want to change the way all my Cats work later, I can just make one change to the base scene and be done.
  2. Much easier to read from the data for things like the UI and random spawning. Safely typed Resources that only have what you need in them before you instantiate anything.
  3. Very easy to make new enemies. Just create a new .tres file from the Resource and fill in the blanks, easy-peasy.
  4. More readable in the editor and better for the editor, too. If I want to create my list of possible enemies for a stage in the editor, I am safely required to drag in EnemyData (rather than any PackedScene), and I don’t get weird pictures that PackedScenes give you.
  5. Just feels way more organized to me overall.

Cons:

  1. Maybe overly complex?? It feels pretty readable and simple to me, but I’m not sure now after a recent discussion.
  2. Maybe I’m doing it in a weird way and there’s a better way to handle data vs. scenes?

That’s all, what do you think? Am I doing anything stupid? I really want to have my data for my enemies separated somewhere for this game, so I feel like the data-driven approach makes more sense now. I just want to ask before I go jumping in the deep end and making a bunch more.

(If you’re one of the people that helped me on my other post: I DID decide to use specific scenes for my PLAYER characters, thanks to that discussion. The player characters are all different enough to make unique scenes. The enemies are way more bare-bones and simple, though.)

From a development standpoint there aren’t really any major downsides to either solution, unless you want to get very specific and nitpick. But both should perform just about the same, there isn’t really any imposed solution, so you should pick which one is more comfortable and easier for you to put in the game. Godot is very flexible when it comes to those things. Handling data separately from your scene via a resource is pretty convenient, but simply using separate scenes works just as well.

Thank you! I just want to ask for clarification; there really isn’t that much of a performance impact from instantiating scenes just to read from them, then queue_freeing them when they’re no longer needed? I feel like that would have terrible performance, but I don’t know anything. Like instantiating all the enemies of your game so the player can look through their data in a book or something.

I don’t think I’ll be going with that now, but I was just curious.

It really depends on the size of the scenes and how fast/often/when you’re doing it.
If the scenes are small enough (in this case since it’s just a single enemy it should be very small) it would be a marginal performance difference. Especially if it’s only done once.
Resources are the kind of thing you want to be loading to get data from constantly, but if the scene is small enough it shouldn’t make much of a difference from my experience.

You could in theory use both solutions as well, have different scenes for each enemy variant to spawn, but use resources to store each entry in the book.

1 Like

Interesting! I did consider doing both solutions too, like each scene has a data resource associated with it, but it just seemed like a bit much work for little gain. At least in my case where I’ll have several dozens of simple enemies. But I think it could totally work in other cases.

Anyway, thank you for your input!

Yeah, again, in Godot there aren’t really any imposed solutions for most things, so do whatever feels best for you. If it doesn’t pose any errors or major performance impact then you can’t really say it’s wrong!

1 Like

The loading of a resource can be the time consuming part - what if the resource is a 500Mb texture volume animation for example.

Some of this seems blatantly obvious when its over explained, so please forgive the following paragraph.
Scenes are usually associated with the role of resource containers, when a scene gets loaded all the resource instances get loaded too - so obviously its intuitive to dynamically load resources instead. The scene file is essentially a tree of nodes. Resources can also reference other resources, but the resource needs to be associated with one of the nodes in a scene to appear in the game.

One problem is that decimating the project into resource loader scripts makes the resources used by each scene invisible in the editor, and so they are more difficult to trace and the memory used is probably dynamic and could be stored in a different script memory pool to statically defined memory.

I dont actually know how gdscript manages memory, i am just thinking of answers using knowledge from other programming languages.

You could try making things load faster using binary resources and scenes … resources could be “.res” instead of “.tres” and scenes “.scn” instead of “.tscn”.