I’ve been making a target system where the TargetableCharacter that is clicked has an InputEvent called Clicked that runs when it is clicked. The PlayerTarget class is a singleton and an instance of it is created when a TargetableCharacter is clicked, this represents the players current target which there can be only one of at a time.
I was wondering if this is standard practice because from reading up on singletons it seems like they should be set once and remain constant throughout the whole game, not created and discarded every time the player changes target.
I had hoped to create a ‘blank’ currentTarget when the PlayerTarget class Ready() runs, but I couldn’t see a way to subscribe to the clicked event without creating a target, which I don’t think is possible because the player hasn’t chosen one yet.
Is this singleton usage standard or is there a way to subscribe the PlayerTarget to the Clicked event?
using System;
using Godot;
public partial class PlayerTarget : Node
{
public static PlayerTarget Instance { get; private set; }
public event Action<TargetableCharacter> TargetChanged;
public TargetableCharacter CurrentTarget;
public override void _Ready()
{
Instance = this;
}
public override void _Process(double delta)
{
}
public void SetCurrentTarget(TargetableCharacter target)
{
CurrentTarget = target;
TargetChanged?.Invoke(target);
}
}
//just the relevant parts of this class for this query
public partial class TargetableCharacter : CharacterBody2D
{
public event Action<TargetableCharacter> Clicked;
public override void _InputEvent(
Viewport viewport,
InputEvent @event,
int shapeIdx)
{
if (@event is InputEventMouseButton mouse &&
mouse.Pressed &&
mouse.ButtonIndex == MouseButton.Left)
{
PlayerTarget.Instance.SetCurrentTarget(this);
}
}
You heard correctly. Singletons are often times made once and remain alive for a very long time. Even though it is not always the case that they remain alive for the duration of the application and sometimes they do get destroyed and then remade (still keep in mind that there can always only be one instance)
For your case I do not see a reason for using a singleton. I don’t know what your game is like and the context of this part, but it seems to me that you are running into a problem of not knowing how to get a reference to PlayerTarget while inside TargetableCharacter. I see that PlayerTarget is a node so you could maybe try getting it with get_nodeif it is in an awkward place for that, you could try with an @exported variable. If that is cumbersome a script that created TargetableCharacters could also automatically assign them a reference. In any case I would not use singletons for this problem.
Sorry for the late reply and thank you for the explanation.
I’m new to all this so sorry if this explanation is wonky. I want the Player Target node to listen to the Targetable Character’s “Clicked” action. I was hoping that when a Targetable Character is clicked, their name, hp, etc to would be sent to Player Target, and from there send it on to other classes.
The only way I’ve been able to do that is to use getNodesinGroup() to make a group of all the Targetable Character nodes contained in the scene tree inside of the Player Target class, otherwise I get a null reference.
Is this standard practice? It works, but it feels counter-productive to create a list of Targetable Character nodes when all the Player Target needs is the clicked Targetable Character’s name, hp, etc. I feel like I’m missing something very obvious.
Here’s the Player Target node with the Singleton stuff removed.
using System;
using Godot;
public partial class PlayerTarget : Node
{
public event Action<TargetableCharacter> TargetChanged;
public TargetableCharacter _target;
public override void _Ready()
{
foreach (Node node in GetTree().GetNodesInGroup("TargetableCharacter"))
{ TargetableCharacter target = node as TargetableCharacter;
if (target != null) { target.Clicked += HighlightChanged; }
}
}
public override void _Process(double delta)
{
}
public void HighlightChanged(TargetableCharacter target)
{
_target = target;
TargetChanged?.Invoke(target);
}
}