Hello, this is a general question as i am new to godot.

So i have a Player scene. The main node is a characterbody3D. I want to build my scenes with composition, so i made a component node that will be used as a movementcontroller. I put a script on it that inherits from just node, but exposes a Characterbody3D, which then is manipulated inside the movementcontroller. So i assign the main node characterbody3D to it. Everything works nicely.
But now i want to add a PlayerScript to the mainnode (The characterbody3D), which will handle things like interacting etc.
If the Playerscript does not inherit from characterbody3D, the movementcontroller throws an error, because now for some reason the characterbody3D node is not counted as a characterbody3D, instead its the type of what the playerscript inherits from.

Is there an explanation for this?

  • xyz replied to this.
  • Moep MonoBehaviour is the base type for scripts (well, one of them); a developer would still typically get a component of type PlayerScript even though it initially inherited a MonoBehaviour. That is, you can't called a method on a MonoBehaviour if it's implemented by a PlayerScript - you need the correct type. That's the same in godot. At some point, you'll want to cast it to the correct type so here, godot and C# are using the type system to ensure everything is correct.

    If you're using Typed-things, then that's correct but you could use a base type if you really want (edit: see Inherits).

    Moep Post the exact error and code that caused it.

    E 0:00:00:0694 :0 @ System.Object System.Runtime.CompilerServices.CastHelpers.ChkCastAny(System.Void* , System.Object ): System.InvalidCastException: Unable to cast object of type 'PlayerScript' to type 'Godot.CharacterBody3D'.
    <C++ Error> System.InvalidCastException
    <C++ Source> :0 @ System.Object System.Runtime.CompilerServices.CastHelpers.ChkCastAny(System.Void* , System.Object )
    <Stack Trace> :0 @ System.Object System.Runtime.CompilerServices.CastHelpers.ChkCastAny(System.Void* , System.Object )
    VariantUtils.generic.cs:385 @ T Godot.NativeInterop.VariantUtils.ConvertTo<T >(Godot.NativeInterop.godot_variant& )
    MovementController_ScriptProperties.generated.cs:18 @ Boolean MovementController.SetGodotClassPropertyValue(Godot.NativeInterop.godot_string_name& , Godot.NativeInterop.godot_variant& )
    CSharpInstanceBridge.cs:57 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Set(IntPtr , Godot.NativeInterop.godot_string_name* , Godot.NativeInterop.godot_variant* )

    Happens in the MovementController if the playerscript on the Characterbody3D node is not inheriting from Characterbody3D.

    Movementcontroller code:

    `public partial class MovementController : Node
    {
    [Export] private CharacterBody3D body;
    [Export] private float speed = 10f;
    [Export] private float speedMod = 1f;
    [Export] private float rotationSpeed = 5f;
    [Export] private float gravity = -9f;
    [Export] private float drag;
    public override void _Ready()
    {

    }
    
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
    public override void _PhysicsProcess(double delta)
    {
    	Move(delta);
    	ApplyGravity(delta);
    	body.MoveAndSlide();
    }
    private void ApplyGravity(double delta)
    {
    	if (body.IsOnFloor())
    		return;
    
    	body.Velocity = new Vector3(body.Velocity.X, body.Velocity.Y + gravity * drag * (float)delta, body.Velocity.Z);
    
    
    
    }
    private void Move(double delta)
    {
    	//Input get action strength to get a vector
    	Vector2 input = InputManager.i.InputVector;
    	Vector3 move = new Vector3(input.X, 0.0f, input.Y);
    	move = move.Normalized();
    	body.Velocity = move * speed * speedMod * (float)delta;
    	if (move != Vector3.Zero)
    	{
    		float y = (float)Mathf.LerpAngle(body.Rotation.Y, Mathf.Atan2(move.X, move.Z), delta * rotationSpeed);
    		body.Rotation = new Vector3(body.Rotation.X, y, body.Rotation.Z);
    	}
    	
    }

    }
    `

      Moep and what of the PlayerScript? The error is here, it’s not the right type, especially if you claim it doesn’t inherit from CharacterBody3D. It should look like:

      public partial class PlayerScript : CharacterBody3D
      • Moep replied to this.

        spaceyjase

        Yeah, thats what i explained. Maybe to make it more clear:

        MovementController exports/exposes a Characterbody3D. Which i drop on it. But if the Characterbody3D has a script on it that does not inherit from Characterbody3D. The Movementcontroller thinks its of the type of the script instead of the Characterbody3D

        I'm confused on why Godot does this. I mean i can just have the script(in my case the playerscript) inherit from Characterbody3D and it will all work.
        It just felt wrong to me to have the main playerscript extend the characterbody3D, because thats the main node of the scene.

        I guess this is just something to get used to in the engine.

        What would be the right approach here design wise?

        Either having the playerscript that will not do anything with the extended functions of the characterbody3D on the node.
        Or create a new node playercomponent that i will put my playerscript on, which will be a child of the characterbody3D main node.

        Why is a problem for the Player class to inherit from CharacterBody3D if the node is actually of that type?

          xyz

          Im to used to only extend from monobehaviour and have many scriptcomponents on an object, instead of having 1 script on 1 object and the script also being bound to the type of the object.

          Its not like worse imo, just something to get used to.

            Moep MonoBehaviour is the base type for scripts (well, one of them); a developer would still typically get a component of type PlayerScript even though it initially inherited a MonoBehaviour. That is, you can't called a method on a MonoBehaviour if it's implemented by a PlayerScript - you need the correct type. That's the same in godot. At some point, you'll want to cast it to the correct type so here, godot and C# are using the type system to ensure everything is correct.

            If you're using Typed-things, then that's correct but you could use a base type if you really want (edit: see Inherits).