How do I call a method from a collided KinematicBody2D using the body_entered(object body) signal in C#?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By vvsuu

Noob to programming and godot here

Trying out the body_entered signal in C#, but I can’t seem to access
a method from the collided KinematicBody2D.

The collision seems to work correctly as “collided” is printed when the 2 collision shapes interact, so I’m a little confused.

I’ve found that I can access the foo() method when I set the type of “body” to the name of the class foo() is created in

private void _on_Area2D1_body_entered(KinematicBody2D2test body)
{
    GD.Print("collided");
    body.foo();
}

From what I understand, the PhysicsBody2D node should be passed to body_entered as an argument which should
allow me to access the foo() method using body.foo();.

I’d like to be able to create foo() in other KinematicBody2D nodes and access it without having to hardcode the class name by replacing _on_Area2D1_body_entered(object body) with _on_Area2D1_body_entered(KinematicBody2D2test body).

This is the error I get:

Area2D.cs(27,8): error CS1061: 'object' does not contain a definition for 'foo' and no accessible extension method 'foo' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?) 

Code of the area2d node:

using Godot;
using System;

public class Area2D : Godot.Area2D
{
	//Area2D2 SecondArea2D2 = new Area2D2();

	// Declare member variables here. Examples:
	// private int a = 2;
	// private string b = "text";

	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		
	}

//  // Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(float delta)
	{
		
	}

	private void _on_Area2D1_body_entered(object body)
	{
		GD.Print("collided");
		body.foo();
	}
}

Code of the kinematicbody2d that holds the foo method:

using Godot;
using System;

public class KinematicBody2D2test : KinematicBody2D
{
	// Declare member variables here. Examples:
	// private int a = 2;
	// private string b = "text";
	// Called when the node enters the scene tree for the first time.
	
	public void foo()
	{
		GD.Print("this was called from Area2D1");
	}
	
	public override void _Ready()
	{

	}
//  // Called every frame. 'delta' is the elapsed time since the previous frame.
//  public override void _Process(float delta)
//  {
//      
//  }
}

Scene hierarchy:
hirachy of nodes

Does CallDeferred give the same error?

Cam | 2023-02-05 05:47

Testing Call might be worth it too. Prefer CallDeferred unless it is important that the function is called immediately, to avoid engine issues.

Cam | 2023-02-05 06:14

Not 100% sure on how to use CallDeferred on body.foo
I’ve tried KinematicBody2D2test.CallDeferred("foo"); which works as it calls directly from the KinematicBody2D2test class, but I don’t think I’m able to use call deferred when calling foo directly from body CallDeferred("body.foo");.

I might be missunderstanding how to use call deferred.
Calling the function immediately isn’t important.

vvsuu | 2023-02-05 07:47

I don’t have a C# environment set up to test with, but it should be body.CallDeferred("foo"). You are correct, using the className.method would call it directly from the class, which should only work for static functions.

Cam | 2023-02-05 08:49

Using body.CallDeferred("foo") seems to produce a similar error error CS1061: 'object' does not contain a definition for 'CallDeferred' and no accessible extension method 'CallDeferred' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

I suppose that means that nothing at all is being parsed, despite the code executing.

vvsuu | 2023-02-06 07:49

:bust_in_silhouette: Reply From: juppi

You can use the is operator to check the type and then cast it into a variable:

public class Enemy : Area2D
{
    private void _on_Area2D_body_entered(Node body)
    {
        if (body is KinematicBody2D kinematicBody2D)
        {
            kinematicBody2D.Foo();
        }
    }
}

If you want to Extend the KinematicBody2D class itself with new methods and not use a child class, you have to use Extension Methods:

public static class KinematicBody2DExtensions
{
    public static void Foo(this KinematicBody2D kinematicBody2D)
    {
        GD.Print("Foo");
    }
}
:bust_in_silhouette: Reply From: vvsuu

I’ve managed to figure out how to solve my issue.

I’ve been a bit slow to understand this, but the issue is that the program is unable to
find the function during compile time as object body is only set during run time. Running body.GetType() shows that the correct PhysicsBody2D is being passed so it’s more of a limitation than anything particularly wrong with my setup.

body_entered(body) works with gdscript as gdscript doesn’t compile and is interpreted similar to python. A solution is for the program to know to update the type during runtime, which the dynamic type allows us to do.

To use the dynamic type I had to get Microsoft.CSharp via a reference in my csproj file

<ItemGroup>
	  <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
  </ItemGroup>

and then set body to the dynamic type.

private void _on_Area2D1_body_entered(dynamic body)
{
	GD.Print("collided");
	body.foo();
}

A note: Using dynamic will prevent the game from running on IOS according to this post: C# Dynamic keyword missing references for csproj · Issue #18371 · godotengine/godot · GitHub

Supposedly reflection is another way to achieve dynamic type setting, but I haven’t looked into it.

Thanks for the help, I’ve learned allot from this.