2
\$\begingroup\$

While learning about the Godot engine, I am trying to write a simple spaceships game.

A spaceship, as I have found out, can be defined as a scene of its own, with the following internal structure:

RigidBody2D            // the root of the scene
 |- Sprite2D           // the image representing the spaceship
 |- CollisionPolygon2D // the collision shape

Now, I can add this "Spaceship" scene into my main game scene, which then looks like this:

Node2D
 |- Spaceship             // instance of the above scene
     |- RemoteTransform2D // makes the camera follow the ship

In here, I have attached a C# script on the Spaceship node that does a simple check for input and calls methods like AddConstantCentralForce and AddConstantTorque n its _Process method to move the ship according to player input:

Node2D
 |- Spaceship + InputControl.cs
     |- RemoteTransform2D

So far, this works fine.

Problem: I also need (?) to attach a script to the root node of my Spaceship scene. That is because I want to dynamically add some additional nodes in there, depending on what is happening in the game, and I think the root object of the scene would be the appropriate place to handle this logic:

RigidBody2D + ManageShip.cs
 |- Sprite2D
 |- CollisionPolygon2D

But if I do this, it has no effect - the script on the root node of the Spaceship scene is replaced by the script on the Spaceship node in my main scene.

Possible alternatives:

  • I do not want to integrate the input control script into the Spaceship scene, because only the player ship should be controlled like this, whereas I want to reuse the Spaceship scene also for enemy ships.
  • I have tried to wrap the Spaceship node in a Node2D node in my main scene, but then I cannot attach my input control script because Node2D does not have the methods like AddConstantCentralForce:
    Node2D
     |- Node2D + InputControl.cs
         |- Spaceship
             |- RemoteTransform2D
    
  • I have tried to wrap the Spaceship node in a RigidBody2D node instead, but then, my input seems to have no effect because that RigidBody2D itself has no mass and/or shape (as is also pointed out by the Godot IDE with a warning symbol):
    Node2D
     |- RigidBody2D + InputControl.cs
         |- Spaceship
             |- RemoteTransform2D
    

What node type should I use instead? Is there a scene design guideline by Godot for this?

Is the solution maybe to wrap Spaceship in just a Node2D and rewrite the input control script to find the nested RigidBody2D and call the methods on that instance?

But then, it seems that it would be my Spaceship scene flying around in this wrapping node rather than in the global space. That doesn't seem right. Or, looking at it differently, that would mean I could just as well handle input in a script on the main scene's root node (or anywhere else in the scene) and just notify the player object about the input. This will work, but is it the intended way to use Godot?


To elaborate on the workaround I suggested in the end, I have now implemented it like this:

Right below my main scene root, I have added a Node2D and attached InputControl.cs to it. On that node, I have added a metadata value named PlayerNode of type NodePath and assigned my Spaceship node from the same scene to it.

Node2D
 |- Spaceship + (whatever script is attached in the scene)
     |- RemoteTransform2D
 |- Node2D + InputControl.cs

The InputControl class now inherits from Node2D and has a Player property of type RigidBody2D that is used as the target of my calls to ship movement methods. I initialize the object as follows:

public override void _Ready()
{
    var player = GetNode(GetMeta("PlayerNode").AsNodePath()) as RigidBody2D;
    if (player == null)
    {
        throw new InvalidOperationException("Player object not found.");
    }

    Player = player;
}

This works, is easy to implement (once you know what API methods to use) and it's easy to understand, still - being a total beginner in Godot - I wonder whether this is the way we're supposed to use Godot?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I have a very similar answer for the question What's the appropiate way to achieve composition in Godot?. It is not accepted, so I can't vote to close this as duplicate. Please refer to it for a more elaborate use case. I'll focus on your use case for this answer.


This is the gist: A node can manipulate its parent.

More precisely: Create a new script that handles input, and manipulate its parent based on said input. This new script is meant to attached to a node placed as child of your spaceship. So spaceships that do not have it, won't get input.

You can think about it as adding a component to your spaceship.

This approach has these advantages over the alternatives:

  • If you want a different set of features you can have a different set of children component.
  • The components are children, so they will naturally be moved with their parent when you are reorganizing the scene tree.
  • In fact, components can be bundled in an scene with them. And you can take advantage of scene inheritance (open the context menu on your scene on the FileSystem dock, and select "New Inherited Scene").

If you really really don't want to do that, I have a couple alternatives for you:

  • Yes, incorporating the input into your script would be the first idea to come to mind. I understand that you do not want to do that. A work-around would be to export StringName for the input actions, and if they are not set, do not run the input part of your code.

    The drawback of this is that it makes your code more complex, and as you continue to add more optional features it will become harder to maintain.

  • Alternatively you can resource to inheritance. Since Godot will be replacing the script you set in the scene with the one you set where you instance it... You can create a new class for it, which extends the one you use in the scene, and add whatever additional features you want.

    On one hand you cannot toggle individual features during runtime. On the other, you need a new class for each combination of features you want.

Yet, right now you only have one optional feature, so these approaches are viable to you.


I'm aware that the Godot community has been saying to call down and signal up. I have repeated this advice myself. And yes, today I'm telling you to call up.

So, do not follow "call down and signal up" blindly. Understand it instead: If your nodes depend on other nodes that are not their children, when you are moving them in the scene tree, you might not notice if you place them such that they won't be able to find the nodes they need to work. And this will result in a runtime error much later.

Solutions to that problem include:

  • Exporting a Node※ (Godot will keep it updated as you move nodes inside the scene).
  • Using unique node names※ (Its relative position does not matter as long as it is in the same scene)
  • Exporting a NodePath (Godot will keep it updated as you move nodes inside the scene).

※: These are a much more recent development than the "call down and signal up" advice.

Futhermore you can use a configuration warning (this requires a @tool script) to have your node tell you if it cannot find the nodes it need to work (this is similar to the RigidBody2D telling you that it does not have collision shapes, or a PhysicalBone2D telling you it is not a child of an Skeleton2D).

Note that exporting a NodePath is similar to what you are doing in your workaround, except you are using metadata for it instead of exporting a property (i.e. with [Export]).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .