Godot Version
v4.4.rc3.mono.official [15ff45068]
Question
So I’ve been refactoring my gdscript code for the last few days to C#. I have some experience with C# development but it’s a little bit different in Godot.
I mostly have issues with understanding and implementing asynchronous methods and signals.
I have this bug which happens when the dialogue ends in DialogueManager plugin, and it happens when I go through dialogue fast, like mashing the Enter key.
I understand that it’s a NullReferenceException
because something is null
, but I can’t figure out what.
Error:
E 0:00:13:857 void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread): System.NullReferenceException: Object reference not set to an instance of an object.
<C++ Error> System.NullReferenceException
<C++ Source> :0 @ void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread)
<Stack Trace> :0 @ void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread)
SignalAwaiter.cs:58 @ void Godot.SignalAwaiter.SignalCallback(nint, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_bool*)
Relevant code:
InteractionManager.cs(instantiated under Globals autoload)
public partial class InteractionManager : Node
{
public Player? Player { get; set; }
private List<InteractComponent>? _activeAreas;
private bool _canInteract;
public override void _Ready()
{
Player = GetTree().GetFirstNodeInGroup("player") as Player;
_activeAreas = new List<InteractComponent>();
_canInteract = true;
Global.InteractionManager = this;
}
public override void _PhysicsProcess(double delta)
{
if (_activeAreas?.Count > 0 && _canInteract)
{
_activeAreas = _activeAreas.OrderBy(area => Player?.GlobalPosition.DistanceTo(area.GlobalPosition)).ToList();
}
}
public async override void _Input(InputEvent @event)
{
if (@event.IsActionPressed("interact") && _canInteract)
{
GD.Print("Interact Pressed");
if (_activeAreas?.Count > 0)
{
_activeAreas[0].EmitInteractionStarted();
_canInteract = false;
await ToSignal(_activeAreas[0], InteractComponent.SignalName.InteractionEnded);
_canInteract = true;
}
}
}
public void RegisterArea(InteractComponent area)
{
_activeAreas?.Add(area);
}
public void UnregisterArea(InteractComponent area)
{
_activeAreas?.Remove(area);
}
}
InteractComponent.cs (instantiated under every “interactable”)
public partial class InteractComponent : Area3D
{
[Signal]
public delegate void InteractionStartedEventHandler();
[Signal]
public delegate void InteractionEndedEventHandler();
public override void _Ready()
{
BodyEntered += OnBodyEntered;
BodyExited += OnBodyExited;
}
public void EmitInteractionStarted()
{
EmitSignal(nameof(InteractionStarted));
}
public void EmitInteractionEnded()
{
EmitSignal(nameof(InteractionEnded));
}
private void OnBodyEntered(Node3D body)
{
if (body is Player) Global.InteractionManager.RegisterArea(this);
}
private void OnBodyExited(Node3D body)
{
if (body is Player) Global.InteractionManager.UnregisterArea(this);
}
}
TestNpc.cs (has InteractComponent instantiated under it)
public partial class TestNpc : CsgBox3D
{
private InteractComponent? _interactComponent;
public override void _Ready()
{
_interactComponent = GetNode<InteractComponent>("InteractComponent");
_interactComponent.InteractionStarted += async () => await OnInteract();
}
private async Task OnInteract()
{
Resource dialogue = GD.Load<Resource>("res://assets/dialogue/test_dialogue.dialogue");
DialogueManager.ShowExampleDialogueBalloon(dialogue, "start");
await ToSignal(DialogueManager.Instance, "dialogue_ended");
_interactComponent?.EmitInteractionEnded();
}
}