В ходе моего текущего проекта я обнаружил, что переписываю аналогичный шаблон во множестве разных классов для выдачи сигнала при каждом изменении переменной. В качестве примера целочисленной переменной этот код выглядит так:
[Signal] public delegate void ValueChangedEventHandler(int newValue);
private int _value;
public int Value
{
get => _value;
set
{
_value = value;
EmitSignal(SignalName.ValueChanged, Value);
}
}
К 20-й переменной, которую я создал с использованием этого формата, я начал уставать от беспорядка и повторений, поэтому решил, что могу создать вспомогательный класс, который обертывает эту функциональность. Я выполнил это с помощью:
using Godot;
public partial class NotifyOnChange<[MustBeVariant]T> : RefCounted
{
[Signal] public delegate void ChangedEventHandler(Variant newValue);
private T _value;
public T Value
{
get => _value;
set
{
_value = value;
EmitSignal(SignalName.Changed, Variant.From(Value));
}
}
public NotifyOnChange() { }
public NotifyOnChange(T value)
{
Value = value;
}
}
Это работает довольно хорошо и делает почти то, на что я надеялся, но у меня есть одна неприятная проблема, и я надеюсь, что есть обходной путь. Самой важной частью этой системы является сигнал, поскольку именно с ним будут взаимодействовать многие другие сценарии. Однако единственный способ объявить этот сигнал без ошибок — придать ему тип Variant. Это означает, что в каждом скрипте, подписывающемся на этот сигнал, сигнатура метода, ожидаемая для методов, подписанных на этот сигнал, будет выглядеть так:
private void OnValueChanged(Variant newValue)
{
// Logic here...
}
Моя проблема в том, что каждый из этих методов будет иметь параметр типа Variant, требующий каждый раз приведения к нужному типу. Это добавляет накладные расходы, поскольку мне нужно будет запомнить, какого типа должно быть значение, и я обнаружил, что это добавляет больше хлопот, чем пользы, которую я получаю от использования этой системы.
Итак, мой вопрос:
Есть ли способ объявить сигнал, используя общий тип, например:
[Signal] public delegate void ChangedEventHandler(T newValue);
Попытка объявить сигнал таким образом в классе, который я показал выше, приводит к тому, что Godot не генерирует серверную часть сигнала и в целом, похоже, не работает. Я нахожу такое поведение странным, поскольку я пометил общий параметр T атрибутом [MustBeVariant] в объявлении класса, поэтому я ожидаю, что не должно возникнуть никаких проблем с использованием T вместо Variant для типа параметра сигнала.
Есть ли что-то, чего мне здесь не хватает, или это просто то, что пока невозможно в Годо?





Для всех, кто сталкивался с этим, я до сих пор не нашел способа сделать это с помощью сигналов Godot, но мне удалось заставить эту функциональность работать с помощью событий C#.
Это было достигнуто путем простого объявления делегата с параметром типа T, а затем создания события с использованием этого делегата.
Чтобы максимизировать совместимость и эффективность, я создал систему, которая позволяет использовать как событие универсального типа, так и сигнал вариантного типа. В целом это выглядит так:
using Godot;
public partial class NotifyOnChange : RefCounted
{
[Signal] public delegate void VariantChangedEventHandler(Variant newValue);
private Variant _variantValue;
public Variant VariantValue
{
get => _variantValue;
set
{
_variantValue = value;
EmitSignal(SignalName.VariantChanged, VariantValue);
}
}
public NotifyOnChange() { }
public NotifyOnChange(Variant value)
{
VariantValue = value;
}
}
public partial class NotifyOnChange<[MustBeVariant]T> : NotifyOnChange
{
public delegate void ChangedEventHandler(T newValue);
public event ChangedEventHandler Changed;
public T Value
{
get => VariantValue.As<T>();
set => VariantValue = Variant.From(value);
}
public NotifyOnChange()
{
VariantChanged += newValue => Changed?.Invoke(newValue.As<T>());
}
public NotifyOnChange(T value)
{
Value = value;
}
}
Хотя было бы здорово получить от Godot официальную поддержку сигналов общего типа, этот обходной путь работает отлично и обеспечивает большую гибкость!