Accessing some (but not all) information from a PackedScene

Please excuse the long post. I was initially just going to ask about deriving from PackedScene, but I figured that including my other options and the surrounding context would make things clearer. I have some questions, but I’m also looking for thoughts and feedback in general.

Godot Version

4.2.1

Background

In my game, attack definitions (called GameActions) are defined as trees of nodes, which are then saved as Scenes. This will hopefully allow me to easily build modular attack systems with multiple parts.

For example, an attack that applies an effect to nearby enemies and heals the user might look like this:

RootNode      (GameActionRoot)
├AOE          (GameActionNode)
│└ApplyEffect (GameActionNode)
└HealPlayer   (GameActionNode)

Each node within that structure has its own properties – AOE might include a parameter for range and damage, for example.

AOE (GameActionNode):
    Range: 10
    Shape: Circle
    Damage: 0
    HitsTeam: Enemy

The problem

As well as the behaviors provided by the branch and leaf nodes, each attack definition also needs to include some information about the whole thing. Things like its icon, its tooltip, its name, etc. I initially simply put these in the RootNode.

Those parameters have to be accessed far before the complicated tree is relevant. The tree isn’t necessary until the attack is initialized, but the icon, name, and tooltip all need to be accessible to the UI.

But when I store the trees as a PackedScene, these variables are no longer accessible at all, until I unpack the entire thing. I’m not really sure what the best way around this is.

I’ve listed a few options below, and I’d like to hear other people’s thoughts.

2 Likes

Option 1: Just Unpack the thing

Unpack the tree immediately, the moment it’s put into the button.

Pros:

  • Easy

Cons:

  • Feels a bit silly to have an entire unnecessary tree structure being held by my lightweight AttackButton object.
  • Probably wouldn’t work in-editor. Attack buttons would only unpack attack definitions at runtime, meaning that in-editor they’d be missing their icons.
  • No way to determine at compile-time that the PackedScenes I’m providing are actually GameActions.

Option 2: Use a resource to hold additional information

Instead of giving the UI a reference to the GameAction PackedScene itself, create a resource that holds attack names, icons, tooltips for the UI, as well as a reference to the attack definition scene that this information refers to.

Like this:

class GameActionReference: Resource {
    [Export] string AttackName;
    [Export] string Tooltip;
    [Export] Texture2D Icon;
    [Export] PackedScene GameAction;
}

Pros:

  • Keeps UI lightweight
  • Separates appearance and functionality
  • Should probably work in-editor

Cons:

  • Have to make and keep track of two files for each GameAction now (the PackedScene and the Resource with a reference to it)
  • More chances to mess up
  • Still no way to ensure type safety

Option 3: Use Resources instead of nodes

Instead of using Godot’s Node structure to store my tree, make a new Resource type that can contain itself.

class GameAction: Resource {
    List<GameAction> children;
}

class GameActionRoot: GameAction {
   // icons, tooltips, etc.
}

Pros:

  • Type safety, finally
  • Don’t have to worry about accessing information – it’s all there, all the time.

Cons:

  • Can’t use Godot’s convenient hierarchy
  • Inspector gets much more cluttered
  • Lose Transform information, making code that handles previews at different locations more annoying to write

Option 4: Extend PackedScene??

Okay, I don’t even know if this is possible. But I was wondering if I could write my own extension of PackedScene, so that some information was available immediately, without unpacking the scene.

class GameActionPackedScene: PackedScene {
    // tooltips, icons, etc.
}

Pros:

  • Icons, textures, and tooltips immediately accessible
  • Only one file per attack definition
  • Type safety

Cons:

  • I have no idea if this is possible
  • If it is possible, I’d have no idea how to save a scene as one of these new objects.

Alright, that’s everything. If anyone has any advice on this sort of thing or has solved a similar problem, I’d love to hear it. I might be way overcomplicating all this…

Some more options I’ve found out about:

  • Adding metadata to a PackedScene. Could work, but seems like an abuse of the system. Currently bugged and nonfunctional anyway.

  • Adding a script to the outside of a PackedScene. I’ve seen some things indicating this may be possible, but I haven’t figured out how to do it. Maybe it’s only possible for Scenes in the hierarchy, and not PackedScene Resource files.

  • Using PackedScene.GetState() to access properties of packed nodes. This looks clunky and potentially difficult to use (properties are indexed by int, not by name), but if I can get it to work, this would be exactly what I’m looking for.

Thanks for the research, that may end up being useful to build heavily data-driven games later. I tend to use the resource-only approach in Unity (using Scriptable Object) which means UI widgets must place elements exactly the same way every time; but in fact, I had a similar issue to yours with monster icons which could differ in size, and I had to inject visual parameters into data (namely a widget offset XY)…

On another project, I’m having a look at how to check PackedScene in advance, before instantiation. In this case not just for type safety but also for dynamic type check: I support two different types of PackedScenes and I must behave differently depending on their type. Problem is, I must use a different API to instantiate the PackedScene, so instantiating first to know the root node type will make it too late to do that.

Otherwise, I found a proposal " Add a generic parameter to PackedScene (like Array) to allow for easier type safety" Add a generic parameter to PackedScene (like Array) to allow for easier type safety · Issue #6694 · godotengine/godot-proposals · GitHub where the OP suggests:
@export var my_scene : PackedScene[MyType]
and a comment suggests:
@export_scene(MyType) var my_scene : PackedScene

but this is not implemented yet.

EDIT: and I just saw the last comment is from you, so you were already aware of this thread!