C# custom signals with arguments, can't find the correct syntax to create a callable

Godot Version

4.6.1 stable.mono.official

Question

Hey folks, I’m struggling to find the correct syntax to connect to a custom signal callback with parameters in C#. I have tried several different approaches including lambdas and .Bind() but lambdas are not valid and .Bind() is not being found with Intellisense ( coding in rider )

I want to collect the custom signals in a SignalBus. This extends node and is pre-loaded with configuration, it’s not in the node tree.

public partial class SignalBus: Node
{
[Signal]
public delegate void PickupEventHandler(PhysicsBody2D pickedUpBody);
}

My player class emits the event on colliding with something in the collectible layer

private void HandleCollectible(PhysicsBody2D collider)
{
    EmitSignal(SignalBus.SignalName.Pickup, collider);
}

My GameController class handles the actual event. This is attached to the root node in Godot. The one with no arguments attaches fine, although currently that signal is declared in the PlayerController. I wanted to move all the custom signals to the signalbus to keep things clean.

private Callable gameOverCallback;
private Callable pickupCallback;


public override void _Ready()
{
    //works fine
    player = GetNode<Node>("level1/Player");
    gameOverCallback = Callable.From(OnGameOver);
    player.Connect(PlayerController.SignalName.Died, gameOverCallback);

    //tried various approaches already, can't get the callback formatted
    pickupCallback = Callable.From(physics)=> OnPickup(physics));
    player.Connect(...);
}

    //callback function, I want to pass the collider to delete the last pickup
    private void OnPickup(PhysicsBody2D pickedUpBody)
    {
        if (!IsInstanceValid(pickedUpBody)) return;
        Score += 10;
        pickedUpBody.QueueFree();
    }

Sharing the working callback too just in case it’s helpful

private void OnGameOver()
{
if (IsInstanceValid(player))
{
player.Disconnect(PlayerController.SignalName.Died, gameOverCallback);
}
GD.Print(“Game Over”);
}

Hey, thanks for the detailed post.

I believe there’s only two ways to connect to a Signal in C#:

  • Via the use of Object.Connect()
  • Via the delegate instance that’s created when marking the delegate definition as [Signal]

If you have a reference to your SignalBus instance, you are able to connect any signature-compliant method to the signal – including lambdas.

SignalBus signalBus;

public override void _Ready()
{
    signalBus.Pickup += HandleCollectible;

    // OR

    signalBus.Pickup += () => { EmitSignal(SignalBus.SignalName.Pickup, collider); };
}

private void HandleCollectible(PhysicsBody2D collider)
{
    EmitSignal(SignalBus.SignalName.Pickup, collider);
}

I feel like I don’t understand the reasoning behind your choice of architecture here, and that this choice is the reason for your problem.

Why do you want to move all your signals into a single, seperate class if the signals are inherently supposed to originate from a specific instance of an object (in this case, an in-game pickup)?

Signals (or more generally, delegates) are supposed to allow the architecture to create loosely coupled classes such that class instances can come and go as they please without the need for the emitter (invoker) to implement class-specific logic. By bunching up all your signals into one class, you’re essentially just creating another interface layer through which classes must communicate. It’s an odd choice from my POV.

If you want, I’d love an explanation as to why you went with this approach.

I’m looking at the code, and the callback you comment as “can’t get the callback formatted” has incorrect syntax, right? You’re missing a parenthesis.

Would it be possible to get a peek at the different approaches you’ve tried that failed to work as intended? I use signals and/or delegates all the time and always find a way to connect to them no matter the context.


I hope this helps. Let me know if you have any more questions.

1 Like

Thanks so much I appreciate the detailed reply.

The reason I went with this approach (signal wise) is when I was c reating the first signal from the player I could not use the C# += to connect to it from the GameController. I believe that only works for signals declared and handled in the same class. I’m only guessing, however I couldn’t get any of the official documentation examples for C# to work for a signal that wasn’t in the file. I was rebuilding and I could see the custom signal on the object but C# did not see it for the += approach.

The SignalBus is a bit of a misnomer, what I want is to centralise all of the signals I have created and deal with them within whatever class needs to know about it. The reason for the bus was twofold

  1. Keep all of my custom signals in one file so I can scan them when I create a new signal and see if I already have an implemented signal I can re-use

  2. I was going to try and abstract out the connect and disconnect in the signalbus if I was able to get it working so that the signal handlers only had to worry about the game logic, not the signal management

This is my first project in Godot in C# so nothing in here is final, I’m really just experimenting by making some simple games with mechanics I want to bring into my bigger idea

I’ll get some more examples of things that didn’t work after work and link them here. Again I super appreciate your detailed response and if this is just me unknowingly fighting with the accepted pattern I’m absolutely happy to change it up. I just felt that the class that emits a signal shouldn’t need to necessarily declare it or you end up in inheritence hell. I might be thinking too much outside GameDev though, my instinct is to use interfaces, enums and dependency injection where I can.

Edit to add - I’m a java native dev. I’ve used C# at work for a few years now but it’s been in the context of net core so I could absolutely just be forcing java things into C#. For instance I had no idea you could connect signals natively in C# with += until I was troubleshooting my first signal in the playercontroller

I can assure you that this is not the case. Any delegate instance (including those created by the [Signal] attribute) can be subscribed to as long as you have a reference to an instance of the class that defines the signal.

Given the issues you’ve experienced in your code editor (Rider), perhaps the intellisense is not working completely with the attributes provided by Godot? I don’t use Rider so I don’t know how to fix that issue, but yeah…

I understand where you’re coming from given that you couldn’t (previously) subscribe to delegates across classes. However, you should let signals (delegates) be emitted from the class who’s in charge of producing the “event”, and then, as you say, let other classes respond to that signal (delegate) by subscribing methods to it. Unless you have a good reason to generalize the signals in question, you shouldn’t be creating a separate class to redistribute the signal. I hope that makes sense.

You have to think in more Object-Oriented terms. A signal (delegate) is, conceptually, an object that sends out a signal (is invoked) when something happens (e.g. when a specific pickup is picked up, or a specific collision occurs with another physics body). By removing the signal from the class that knows when it should occur, you’re separating the concepts in the abstract. You shouldn’t be creating abstract signals and applying them to the objects that can make use of them; you should define objects with their own signals so another object can respond to those signals if it needs to.

I believe it’s a backwards way of working. Although, there are cases where abstracting the interface to similar objects makes sense – but I don’t think this is one of those cases given the simple nature of the signals in question. Correct me if I’m wrong.

I’m not sure how you can work with signals without, at some point, connecting and disconnecting from them. If you don’t define what’s connected to a signal, do you even need one? Maybe you can elaborate on this point?

I completely get where you’re coming from. Inheritance hell is usually a product of over-analyzing a class to the point of having too many inheritance layers though. If the inheritance is warranted, there’s no problem. What is warranted varies by case, so yeah.

Godot’s RigidBody3D class inheritance:
PhysicsBody3D < CollisionObject3D < Node3D < Node < Object

I would recommend reading Godot Docs | Godot’s design philosophy to get a sense of why everything is the way it is. I read this before using the engine for the first time, and it was a great conceptual primer.


Let me know when you get those examples!

Thanks once again for the response, I’ll peruse properly later but I was able to get it working thanks to the context you provided.

My first attempt to reproduce one of the problems I was seeing earlier led to a different IDE error and solved it for me.

The problem at the heart of everything was that I was mixing C# Object.Callable and Godot.Callable depending on which signature I was trying so I was trying to provide the function reference to the Godot callable which expects a string. The intellisense in Rider is very good with Godot, there’s actually a built-in plugin provided by JetBrains and I’ve been hugely impressed since the last time I tried unity with resharper and Visual Studio about 10 years ago :blush:

I haven’t tested yet but my reading of the docs shows that it should pass any amount of arguments through to the callback. I assume I can use things like [required] to ensure a param is passed and throw an exception if not to keep myself honest.

For anyone else who struggles with this the syntax is this (untested as of now, mornings are my game dev time)

pickupCallback = new Callable(this, nameof(OnPickup));
player.Connect(SignalBus.SignalName.Pickup, pickupCallback );

The fix doesn’t work as SignalBus isn’t in the tree which I believe is what you were trying to tell me with the pattern being wrong.

I moved to try and use c# signalling and had a few issues like declaring player as a node instead of my C# class but eventually I got the expected pattern to where the syntax doesn’t complain.

I haven’t been able to test because now I have to look up instantiating the player scene and getting a reference to that controller and need to head out the door.

I’ve posted the example and my current code below, I’d appreciate any feedback you might have. I’ve put some of my assumptions under the declaration example which cover some of the problems I was having

This is the example in the docs for reference

public override void _Ready()
{
    var button = new Button();
    // C# supports passing signals as events, so we can use this idiomatic construct:
    button.ButtonDown += OnButtonDown;

    // This assumes that a `Player` class exists, which defines a `Hit` signal.
    var player = new Player();
    // We can use lambdas when we need to bind additional parameters.
    player.Hit += () => OnPlayerHit("sword", 100);
}

private void OnButtonDown()
{
    GD.Print("Button down!");
}

private void OnPlayerHit(string weaponType, int damage)
{
    GD.Print($"Hit with weapon {weaponType} for {damage} damage.");
}

Signal Declaration and Emit in PlayerController

public partial class PlayerController : CharacterBody2D {

    [Signal] 
    public delegate void DiedEventHandler();

    public override void _PhysicsProcess(double delta){
        if (IsDeathCollision) {
            EmitSignal(SignalName.Died);
        }
    }
}

My assumptions -

  • You can have the signal in another class if you like but you must reference an instance of the class, not the static type
  • When using C# += you need to connect directly to the signal, not the static string that you use to emit the Signal. The Signal is the bit before EventHandler() in your delegate. If it can’t be found you are likely trying to attach to the static class.

GameController handling the signal (and eventually counting lives, instantiating the player and level, etc)

public partial class GameController:Node2D {
    private PlayerController player;

    public override void _Ready()
    {
        player = new PlayerController();// for example, I haven't gotten to instantiating a player and dealing with a game reset 
        player.Died += OnPlayerDied;
    }

    private void OnPlayerDied(){
        if(IsInstanceValid(player)){
            player -= OnPlayerDied;
            //cleanup logic
        }
    }
}

Game inspector. GameController is attached to root, PlayerController attached to Player. ScreenBoundary is just 4 normals at screen side set to the death collision layer.

1 Like

Okay, I’m gonna start by reviewing your ‘assumptions’.

You have to think of the signal like any other variable. I’m a little stumped on the fact that this is a surprise given my previous attempts at an explanation. The signal (delegate) declaration (e.g. DiedEventHandler()) is not the variable you’re operating on when referencing player.Died. That variable is an instance of the delegate that’s auto-instantiated by the [Signal] attribute.

I think you’re conflating concepts here due to your unfamiliarity with both the C# language and the Godot engine. As described previously, there are two different ways to connect to a Godot-signal in C#:

// Subscribe to the signal's delegate
player.Died += Foo;

// Connect to the signal
player.Connect(PlayerController.SignalName.Died, Foo);

They both achieve the same thing.

This is another indicator of your unfamiliarity with C# – I guess? The static class (and its static members) are only accessed when the class is used as such. You can never accidentally access a static member from a reference to an instance of that class. C# is statically typed so it’s impossible.

public partial class PlayerController : Node
{
    public static int staticNumber;
    public int number;
}

# This is how you reference the static class (and its members).
PlayerController.staticNumber;

var player = new PlayerController(); // This is a class INSTANCE
// This is how you reference the class instance's members.
player.number;

Next up: code review.

Nothing wrong from a syntax POV, but the way you’re handling the collision response is a little odd. I can’t really comment on it though given that your snippet doesn’t show how and when IsDeathCollision is set. There’s a system that I’m unaware of.

Again, nothing wrong from a syntax POV. You’re not adding the player to the scene tree though, so the PlayerController instance will not be part of the game. However, even if you add your player to the scene tree, it won’t function properly because PlayerController is a CharacterBody2D which requires an instance of a CollisionShape2D to describe its collision shape.


I think you should end the topic here. The problem of connecting signals in C# has been more than answered with the root cause of the problem being your inexperience with the C# language – not Godot. For added reference, here are some pages on working with Godot’s signals, and delegates in C#:

If you’re interested, I can get on a call with you to discuss the misconceptions you have (see DM). Otherwise, have fun with Godot!

Absolutely and thank you again for all of your help on this I really appreciate it. I have really bad social anxiety so I might just take these links and really take the time to go through it all at the weekend. I might send you a message after if I’m still confused but hopefully it will all click together for me.

1 Like