Howdy C# brothers!
I was working on a basic mechanic where an Area2D emits a signal when the player enters it, as defined in the ChaseRange script. The Enemy script subscribes to the event and triggers an action when it receives the signal. The problems is that I can’t access the ChaseRangeEntered event through the Area2D (_area2D.ChaseRangeEntered += FollowPlayer;) in the enemy script.
using Godot;
using System;
public partial class ChaseRange : Area2D
{
public Action ChaseRangeEntered;
public CharacterBody2D _player;
public override void _Ready()
{
_player = GetTree().GetFirstNodeInGroup("Player") as CharacterBody2D;
BodyEntered += OnChaseRangeBodyEnteredSignal;
if (_player is null)
{
GD.Print("Player not found in the group.");
}
}
public void OnChaseRangeBodyEnteredSignal(Node2D body)
{
if (body == _player)
{
ChaseRangeEntered?.Invoke();
}
}
}
Enemy script
using Godot;
using System;
public partial class Enemy : CharacterBody2D
{
private CharacterBody2D _player;
private int _speed = 100;
private Area2D _area2D;
public override void _Ready()
{
_player = GetTree().GetFirstNodeInGroup("Player") as CharacterBody2D;
_area2D = GetNode<Area2D>("ChaseRange");
_area2D.ChaseRangeEntered += FollowPlayer;
}
private void FollowPlayer()
{
Velocity = Position.DirectionTo(_player.Position) * _speed;
MoveAndSlide();
}
}
The main thing I’m noticing here is that in Enemy you’re storing the area as just an Area2D instead of casting it to ChaseRange - ChaseRangeEntered only exists on ChaseRange and not on the Area2D superclass, so trying to access it just from Area2D won’t work. You can put as ChaseRange2D in the GetNode for the chase range, or add the [GlobalClass] annotation to ChaseRange to just put it in the generics.
If you want to make ChaseRangeEntered a full proper signal that integrates with Godot instead of just an event, you’ll need to make it a delegate void with the [Signal] annotation and call it with EmitSignal(SignalName.ChaseRangeEntered) - you can see the C# signal docs page for more info.
None of your solutions seemed to work. I tried to use _area2D = GetNode<Area2D>("ChaseRange") as ChaseRange;, the [GlobalClass], and public delegate void ChaseRangeEnteredEventHandler with the EmitSignal(SignalName.ChaseRangeEntered). The way I set up the scripts was inspired by https://youtu.be/o5M-x3Z7720, specially the last part about the Action keyword. In the video he doesn’t use any casting or special syntax. It got to be some silly mistake on my part.
My first guess was the missing [GlobalClass] and not using it. These are just guesses, but that how I’ve always done it.
Instead private Area2D _area2D use private ChaseRange _chaseRange
And get it by: _chaseRange = GetNode<ChaseRange>("ChaseRange");
Making it a global class also allows you to create it as a new node. Like the built in types.
Regarding MoveAndSlide(); This should be called every physics frame to move your character. But your chase range entered gets only called once when the player enters.
EDIT:
Just tested it for my game:
[GlobalClass]
public partial class Health : Node
{
public Action Changed;
public void TakeDamage(int amount)
{
Changed?.Invoke();
}
[GlobalClass]
public partial class Character : CharacterBody3D
{
private Health _health;
public override void _Ready()
{
_health = GetNode<Health>("Health");
_health.Changed += OnHealthChanged;
}
private void OnHealthChanged()
{
GD.Print("Changed");
}
Using ChaseRange instead of Area2D solved the issue, thanks. Do you my mind explaining or pointing to some resource about why the class name have to be used in this case?
I knew I would have some problems with that, but I was occupied with the event error. Any suggestions on how to make it work?
That is something I need to look up myself. I’m not sure from where I know it. Maybe I saw an example somewhere in the docs or I’m just used to it from Unitys GetComponent().
Maybe set a flag when player enters and exits the area. Bool followPlayer = true when entering and followPlayer = false when leaving area. And then in physics process update your movement.
Pseudo code:
physics process:
if follow player:
update velocity
move and slide
Yeah, because the type of the _area2D field is still Area2D - once the ChaseRange gets put into an Area2D field it doesn’t know it’s a ChaseRange anymore when it tries to compile, even if it is at runtime. That’s the big thing with languages that try to have a more static typing system like C#; you need to keep track of what things actually are while you’re writing it. It would work perfectly if you changed the type of _area2D to ChaseRange.