Переменные
Использование в скриптах
Переменные скриптов в EyeAuras
Зачем нужны переменные вне кода?
Обычный скрипт живёт недолго: запускается, выполняет задачу и завершается. Но иногда нужно хранить настройки и результаты дольше — между перезапусками скрипта и даже делиться ими с другими скриптами. Для этого в EyeAuras есть «внешние» переменные — на уровне скриптов, аур и папок.
Основные сценарии:
- хранение настроек, которые редко меняются
- обмен данными между скриптами (например, конфиг в одном, логика в другом)
- просмотр значений в UI на вкладке Variables у ауры или папки
Где могут храниться данные
- Локальные переменные — живут внутри метода. Обычный вариант:
var x = 123; - Свойства sandbox — поля/свойства класса вашего скрипта. Они сохраняются, пока вы не измените код (после изменения скрипт перекомпилируется, и свойства сбрасываются):
int MyCounter { get; set; } // ... MyCounter = 16; Log.Info($"Counter: {MyCounter}"); - Переменные Script/Aura/Folder — долговременное хранилище, видимое в UI и доступное другим скриптам. Именно о них и пойдёт речь в этой статье.
Общий принцип работы с переменными скрипта
В «сыром» виде переменные можно читать и записывать через индексатор по строковому ключу:
Variables["myCounter"] = 1;
Log.Info($"Counter: {Variables["myCounter"]}");
Минусы: теряется тип, приходится вручную делать приведение. В C# это неудобно и ненадёжно.
Решение — ScriptVariable<T>: вы один раз задаёте имя и тип, а дальше работаете типобезопасно.
var myCounter = Variables.Get<int>("myCounter");
myCounter.Value = myCounter.Value + 1;
Log.Info($"Counter value: {myCounter}"); // 2, 3, 4, ...
Снаружи это выглядит как обычная переменная: её можно читать, писать и логировать. Но внутри значение хранится в общем «хранилище переменных», доступном и другим скриптам.
Как получить переменную
- Переменная текущего скрипта:
var v = Variables.Get<float>("speed"); v.Value = 1.5f; - Переменная другой ауры или папки по пути:
var auraVar = Variables.Get<int>(@".\Gameplay\Bosses\Shaper", "attempts"); auraVar.Value++; - Переменная, если у вас уже есть
IHasVariables:var aura = AuraTree.GetAuraByPath(@".\Something"); var auraVar = Variables.Get<string>(aura, "status"); Log.Info($"Status: {auraVar.Value}");
Самое важное поведение ScriptVariable<T>
Ниже — простые правила, которые лучше понять один раз, чем потом долго отлаживать.
-
Value- Читать всегда безопасно: исключения не выбрасываются.
- Если значения нет или тип не подходит — возвращается
default(T)(0для чисел,falseдляbool,nullдля ссылочных типов). - Присваивание просто записывает значение в хранилище.
var i = Variables.Get<int>("i"); Log.Info(i.Value); // 0, если переменная не задана i.Value = 42; // записали 42 -
HasValuetrue, если запись существует в хранилище (даже если для ссылочного типа тамnull).false, если записи нет вообще.
var s = Variables.Get<string>("name"); Log.Info(s.HasValue); // false, записи нет s.Value = null; Log.Info(s.HasValue); // true, запись есть, но значение null -
TryGetValue(out T value)- Возвращает
true, только если запись существует, значение НЕnullи его можно привести/конвертировать кT. - Возвращает
false, если записи нет, значениеnullили тип не подходит.
var f = Variables.Get<float>("ratio"); if (f.TryGetValue(out var val)) { Log.Info($"ratio = {val}"); } else { Log.Info("ratio is not set or null or cannot be converted to float"); } - Возвращает
-
GetOrThrow()- Единственный «небезопасный» способ чтения.
- Выбрасывает
InvalidOperationException, если записи нет, значениеnull(для ссылочных типов) или его нельзя привести/конвертировать кT.
var required = Variables.Get<int>("port"); var port = required.GetOrThrow(); // выбросит исключение, если нет записи, null или неверный тип -
Remove()- Удаляет запись из хранилища. Возвращает
true, если запись существовала, иfalse, если нет. - После
Remove()чтениеValueвернётdefault(T),HasValueстанетfalse, аListen()получит событие «сброс к значению по умолчанию».
var v = Variables.Get<int>("x").Set(10); v.Remove(); // true Log.Info(v.Value); // 0 Log.Info(v.HasValue); // false - Удаляет запись из хранилища. Возвращает
-
Неявное преобразование к
T- Можно писать просто:
int i = myIntVar;
var myIntVar = Variables.Get<int>("score").Set(5); int i = myIntVar; // 5 var missing = Variables.Get<int>("missing"); int j = missing; // 0 - Можно писать просто:
-
Строковое представление
v.ToString()показывает имя и текущее значение, либо"<not set>", если записи не существует.
var a = Variables.Get<int>("a"); Log.Info(a.ToString()); // "a = <not set>" a.Value = 10; Log.Info(a.ToString()); // "a = 10"
Типы и конвертация: что происходит на самом деле
ScriptVariable<T> сначала пытается прочитать значение «как есть», а затем — сконвертировать его. Конвертация выполняется через внутренний репозиторий конвертеров (IBindingValueConverterRepository).
- Если значение уже имеет тип
T— оно возвращается как есть. - Если это другой тип, но есть конвертер в
T— значение конвертируется и возвращается. - Для строкового назначения (
T == string) подходит почти любой тип — будет возвращёнvalue.ToString(). - Для
Nullable<T>конвертация идёт к базовому типу. - Если значение равно
null,TryGetValueвернётfalse, аValue—default(T). - Если конвертации нет —
Valueвернётdefault(T),TryGetValueвернётfalse, аGetOrThrowвыбросит исключение.
Примеры:
// 1) Типы совпадают — всё просто
Variables.Get<int>("n").Set(123);
Log.Info(Variables.Get<int>("n").Value); // 123
// 2) Чтение как другого типа
((IScriptVariable)Variables.Get<int>("mix")).Value = "abc"; // записали строку в обход типобезопасности
var v = Variables.Get<int>("mix");
Log.Info(v.Value); // 0 — default(int)
Log.Info(v.TryGetValue(out var x)); // false
// v.GetOrThrow(); // throws InvalidOperationException
// 3) Почти всё можно превратить в string
((IScriptVariable)Variables.Get<string>("text")).Value = 42;
Log.Info(Variables.Get<string>("text").Value); // "42"
Совет: если вам нужна конкретная конвертация, зарегистрируйте конвертер в IBindingValueConverterRepository. После этого TryGetValue и Value начнут возвращать нужный вам тип.
Подписка на изменения: Listen()
Если нужно реактивно отслеживать изменения переменной, используйте Listen().
- Возвращает
IObservable<T>. - Сразу отправляет текущее значение (или
default(T), если записи нет). - Затем отправляет только действительно новые значения (
DistinctUntilChanged). Remove()отправляет событие «сброса» кdefault(T).
Пример:
var hp = Variables.Get<int>("hp");
var observed = new List<int>();
using var sub = hp.Listen().Subscribe(x => {
Log.Info($"HP: {x}");
observed.Add(x);
});
hp.Value = 100; // HP: 100
hp.Value = 100; // not emitted (distinct)
hp.Value = 90; // HP: 90
hp.Remove(); // HP: 0 (default)
Для ссылочных типов первым значением будет null, затем новые значения, а потом снова null при Remove() или если вы сами присвоите null.
Переменные аур и папок, иерархия значений
Каждая аура и папка тоже является контейнером переменных. Здесь действует иерархия: дочерние элементы «видят» значения родителей и могут их переопределять.
Есть два способа доступа:
- По пути:
var auraVar = Variables.Get<int>(@".\Something", "myCounter"); auraVar.Value = 16; - Через объект:
var aura = AuraTree.GetAuraByPath(@".\Something"); var auraVar = Variables.Get<int>(aura, "myCounter"); auraVar.Value = 16;
Почему это важно: можно задать «значение по умолчанию» на папке — и все вложенные ауры смогут его читать. А там, где нужно, переопределить локально. Это очень удобно для общих настроек: целевое окно, input simulator и т. п.
Нюанс: переменные с префиксом default. часто используются как «базовые значения» по всему дереву. Например, если переопределить default.WindowSelector.TargetWindow, это повлияет на выбор окна во всех вложенных элементах.
Операторы и удобные мелочи
Для удобства доступны операторы и неявные преобразования.
- Неявное преобразование к
T:var score = Variables.Get<int>("score").Set(5); int current = score; // 5 - Сравнение по значению:
var a = Variables.Get<int>("a").Set(3); var b = Variables.Get<int>("b").Set(3); if (a == b) { /* same values */ } if (a != b) { /* different values */ } - Числовые сравнения (когда
T— сравнимый числовой тип):var x = Variables.Get<int>("x").Set(2); var y = Variables.Get<int>("y").Set(3); if (y > x) { /* yes, 3 > 2 */ } if (x < 3) { /* yes */ }
Типичные сценарии
- «Конфигурационный скрипт» записывает параметры, а «рабочий скрипт» их читает.
- Счётчики запусков, задержки, интервалы — сохраняются между запусками.
- Кэш промежуточных результатов, который нужен нескольким скриптам.
Пример «config -> worker»:
// Config script
Variables.Get<string>("target").Set("Shaper");
Variables.Get<int>("maxAttempts").Set(5);
// Worker script
var target = Variables.Get<string>("target").GetOrThrow();
var maxAttempts = Variables.Get<int>("maxAttempts").GetOrThrow();
for (var i = 0; i < maxAttempts; i++)
{
Log.Info($"Try {i+1} on {target}");
}
Частые ошибки и как их избежать
-
«Почему
Valueвернул0/false/null, а не выбросил исключение?»- Это ожидаемое поведение:
Valueникогда не выбрасывает исключения. Если нужна строгая проверка, используйтеGetOrThrow().
- Это ожидаемое поведение:
-
«
TryGetValueвернулfalseдляnull— это баг?»- Нет. Для
TryGetValuenullозначает «значения нет». Для ссылочных типовHasValueпри этом будетtrue(запись существует), аTryGetValue—false(потому что реального значения нет).
- Нет. Для
-
«Почему
Listen()не отправил то же значение второй раз?»- Подряд идущие одинаковые значения отфильтровываются.
-
«Я удалил переменную, что отправит
Listen()?»default(T). Дляintэто0, дляstring—nullи так далее.
Рекомендации
- Объявляйте
ScriptVariable<T>один раз и переиспользуйте его. - Для «обязательных» параметров читайте через
GetOrThrow(). - Для «необязательных» значений лучше использовать
TryGetValue, а не сравнение сdefault(T). - Не путайте
set nullиRemove():set null— запись остаётся (HasValue == true), ноTryGetValue == false.Remove()— запись удаляется (HasValue == false), аValueвозвращаетdefault(T).
- Для подписок не забывайте вызывать
Dispose()у возвращаемогоIDisposable(обычно удобно черезusing var ...). - Давайте переменным понятные имена и придерживайтесь единого формата, например
snake_caseилиcamelCase.
Краткая шпаргалка по API
var v = Variables.Get<int>("n");
// чтение/запись
int x = v.Value; // безопасно, default если нет значения/неверный тип
v.Value = 10; // запись
// строгий сценарий
var required = v.GetOrThrow(); // исключение, если нет значения/неверный тип/null
// аккуратное чтение
if (v.TryGetValue(out var val)) { /* ok */ } else { /* no value */ }
// подписка на изменения
using var sub = v.Listen().Subscribe(x => Log.Info($"n = {x}"));
// удаление
var removed = v.Remove();
// прочее
bool has = v.HasValue; // существует ли запись?
string name = v.Name;
IHasVariables src = v.Source; // откуда читает/куда пишет