Можно и анимацией - но анимацией то ты все равно управляешь посредством скрипта!
Я для себя сделал вывод - самый лучший способ двигать любой анимированный узел на модели (форточку, дверь, створки жалюзи, вентилятор...) - это привязать к ним контрол и уже контрол скриптом двигать на нужные значения. А не ломать каждый раз голову - на какой кадр (а точнее - временную метку) сдвинуть анимацию и в какой момент ее сбрасывать. Удобнее, знаете ли.
Как вариант - если не хочется загромождать локомотив лишними контролами - сделать то же самое переменной. С точки зрения скрипта, разница между контролом и переменной только в одном - контролам можно переопределять значение через InputMapper, когда пользователь орудует клавиатурой или мышью. Если переменную определить вне функций Initialize и Update, она будет глобальной и может использоваться для хранения значений между тактами (вызовами функции Update движком). Т.е. тот же контрол, но в который нельзя влезть ручками.
Почему удобнее линковать с анимированными деталями контролы, а не переменные?
1. На этапе отладки можно назначить в Input Mapper управляющие клавиши и подвигать стрелку прибора руками (если это не системный контрол вроде BrakeCylinderPressureBAR - их двигать нельзя).
...и посему крайне желательно ВСЕ приборы в кабине привязывать к СОБСТВЕННЫМ контролам, а системные к ним линковать в скрипте - непосредственно или через дополнительную логику. У меня так реализован редукционный клапан питательной магистрали, проверка плотности тормозной магистрали, сверхзарядка ПМ и УР (только визуальная, ни на что не влияет в тормозах. Для простоты.). Тогда на этапе отладки можно с клавиатуры двигать любые стрелки приборов (для сверки значений с тем, что видно на шкале), зажигать лампочки, имитировать рост температуры охлаждающей жидкости и т.п.
2. Потому, что из контролов удобно создавать логические цепочки if-elseif-else , AND, OR и производные от них, собирая схемы локомотивов (точнее - их поведенческие алгоритмические модели). И их значения видно в отладочном окне ControlStateDialog.
3. Потому, что их значения используются как триггеры для звуковой модели локомотива или вагона. Переменную посадить на триггер нельзя. А контрол - пожалуйста. И если у вас вентиль привода жалюзи должен сделать "ПШШШШ", когда створки повернутся на половину хода, то это можно сделать одним контролом. Не создавая дополнительных переменных (кроме тех, что выше по логической цепочке - есть ли питание вентиля от цепей управления, есть ли воздух в магистрали управления, не держит ли створки термостат и т.п. сахарок)
Рассмотрим еще такое понятие, как "виртуальный контрол". Это контрол, не имеющий интерфейса ввода и вывода. Т.е. к нему ни прикреплен Input Mapper, ни прилинкован анимированный узел модели для индикации состояния. Однако! Такой контрол может управляться скриптом - и следовательно, использоваться для управления звуковыми событиями или процессами в схеме локомотива.
Например, на два виртуальных контрола у моего ТУ7А посажена смена звуков дизеля в зависимости от позиции. Так как звуки у меня записаны очень по хитрому, чтобы проигрывать без склеек переходы между позициями и циклы. Также на контролы посажены стрелки индикаторов температуры и давления - а к ним скриптом подключаются РАЗНЫЕ переменные, рассчитанные в схеме. Температура левого/правого моноблока, до и после дизеля, температура масла/воды. Все, как у реального тепловоза.
Сегодня вечером постараюсь выложить функции.
Добавлено спустя 3 часа 8 минут 51 секунду:Итак, обещанные функции.
UpdateControl (time, name, index, target, speed) и UpdateControlWithTremor (time, name, index, target, speed, gain, freq) - для КОНТРОЛОВ
UpdateVariable (time, name, target,speed) - для ПЕРЕМЕННЫХ . Варианта с дребезгом нет - так как не используется для вывода данных на приборы. А для расчетов в системе. Тремор
добавлять следует только к тому, что этот тремор физически испытывает - в данном случае, к стрелкам приборов.
Использование: применяется для перевода значений контролов и переменных в заданные с заданным темпом единиц/секунду (именно в СЕКУНДУ, а не В ТАКТ, чем так грешат дефолтные скрипты - скорость процессов в которых ой как зависит от ФПС сцены!). Поэтому можно использовать в том числе для расчета переходных процессов в тормозах, если они у вас не дефолтные.
----------------------------------------------------------
UpdateControl (time, name, index, target, speed)
time - системная переменная времени, имеет значение прошедшего времени с прошлого такта скрипта. НЕ ИЗМЕНЯТЬ. При использовании в теле функции подставить имя переменной времени из родительского цикла. Обычно это inteval.
name - имя контрола, который обновляется данным вызовом функции.
index - индекс контрола, который обновляется данным вызовом функции. Добавлено для совместимости формата данных с системными функциями TS вроде GetControlValue.
target - значение, которое нужно присвоить контролу. Направление движения определяется автоматически. Если значение выходит за диапазон контрола, будет достигнуто соответствующее предельное значение.
speed - скорость, с которой нужно обновить значение. Каждую секунду (игровую), к текущему значению контрола будет добавляться либо вычитаться значение speed, промежуточные значения (с частотой тактирования скрипта - читай, ФПС) рассчитываются в теле функции автоматически. Потому движение будет плавным.
Код функции. Вставить в Simulation скрипт, вне всех методов (т.е. в самом начале либо самом конце файла, вне операторных скобок)
- Код: Выделить всё
function UpdateControl (time, name, index, target, speed)
-- функция апдейта контрола на новое значение с задаваемой скоростью единиц/сек.
-- name - имя контрола
-- index - индекс контрола
-- target - целевое значение
-- speed - скорость перевода
n_value = Call ("*:GetControlValue", name, index);
n_step = time*speed;
if ( n_step > math.abs(n_value - target) ) then -- устранение "дребезга" вокруг конечного значения.
n_step = math.abs(n_value - target);
end
if (speed > 0) then
if (n_value > target) then
Call("*:SetControlValue", name, index, n_value - n_step);
end
if (n_value < target) then
Call("*:SetControlValue", name, index, n_value + n_step);
end
if (n_value == target) then
Call("*:SetControlValue", name, index, n_value);
return 1;
end
else return -1;
end
end
Пример использования в теле скрипта:
- Код: Выделить всё
if (AirSpeed >= ApplicationRate) then
UpdateControl (inteval, "TrainBrakeControl", 0, 0.42, 0.1*AirSpeed);
end
Комментарий: в данном блоке кода производится сравнение скорости воздушного потока со темпом разрядки УР темпом служебного торможения. Если проверка возвращает True, значение СИСТЕМНОГО контрола (прошу это отметить) TrainBrakeControl с индексом 0 (а обычно 1,2 и так далее у локомотива и не бывает) приводится к значению 0.42 темпом (0.1 х AirSpeed) единиц/сек.
Во, заодно раскрыл секрет, как у меня тормоза разряжают фиксированным темпом вне зависимости от ФПС игры - кто-то интересовался, я помню.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Продолжим. Вторая функция.
UpdateControlWithTremor (time, name, index, target, speed, gain, freq)
time - системная переменная времени, имеет значение прошедшего времени с прошлого такта скрипта. НЕ ИЗМЕНЯТЬ. При использовании в теле функции подставить имя переменной времени из родительского цикла. Обычно это inteval.
name - имя контрола, который обновляется данным вызовом функции.
index - индекс контрола, который обновляется данным вызовом функции. Добавлено для совместимости формата данных с системными функциями TS вроде GetControlValue.
target - значение, которое нужно присвоить контролу. Направление движения определяется автоматически. Если значение выходит за диапазон контрола, будет достигнуто соответствующее предельное значение.
speed - скорость, с которой нужно обновить значение. Каждую секунду (игровую), к текущему значению контрола будет добавляться либо вычитаться значение speed, промежуточные значения (с частотой тактирования скрипта - читай, ФПС) рассчитываются в теле функции автоматически. Потому движение будет плавным.
gain - амплитуда синусоидальных колебаний, накладываемых на мгновенное значение на данном такте. Можно добавить генератор случайных чисел, но будьте осторожны - чтобы не зашкалил прибор. Это АБСОЛЮТНЫЕ значения, т.е. в тех же единицах, что и target.
freq - частота синусоидальных колебаний, накладываемых на мгновенное значение на данном такте. Герц, то есть колебаний в секунду. Обычно кратно RPM или аналогичному значению. У спидометра - частоте пульсаций в датчике скорости.
Код функции. Вставить в Simulation скрипт, вне всех методов (т.е. в самом начале либо самом конце файла, вне операторных скобок)
- Код: Выделить всё
TremorCounter = { -- счетчики для тремора стрелок приборов
["EngineRPM"] = 0,
["GMPPress F"] = 0,
["WaterTemp F"] = 0,
["OilPress F"] = 0,
["GMPPress R"] = 0,
["WaterTemp R"] = 0,
["OilPress R"] = 0,
["Speedometer F"] = 0,
["Speedometer R"] = 0
}
-- В этом блоке ПЕРЕЧИСЛИТЬ ОБЯЗАТЕЛЬНО имена всех контролов в указанном формате, которые будут обновляться функцией UpdateControlWithTremor . Это счетчики колебаний синусоиды.
-- нужны для предотвращения сбоя фазы колебаний и защиты от переполнения переменной. Если кто-то найдет способ этот блок не использовать (см. код функции), это будет здорово.
function UpdateControlWithTremor (time, name, index, target, speed, gain, freq)
-- функция апдейта контрола на новое среднее значение с задаваемой скоростью единиц/сек и колебаниями.
-- name - имя контрола
-- index - индекс контрола
-- target - целевое среднее значение
-- speed - скорость перевода
-- gain - амплитуда синусоидальных колебаний
-- freq - частота синусоидальных колебаний
n_value = Call ("*:GetControlValue", name, index);
n_step = time*speed;
n_inc = 1.0;
if (TremorCounter [name] > 10) then -- защита от переполнения переменной-счетчика.
TremorCounter [name] = 0;
end
TremorCounter [name] = TremorCounter [name] + n_inc*time;
tremor = gain* math.sin (freq * TremorCounter [name]);
if ( n_step > math.abs(n_value - target) ) then -- устранение "дребезга" вокруг конечного значения.
n_step = math.abs(n_value - target);
end
if (speed > 0) then
if (n_value > target) then
Call("*:SetControlValue", name, index, n_value - n_step + tremor);
end
if (n_value < target) then
Call("*:SetControlValue", name, index, n_value + n_step + tremor);
end
if (n_value == target) then
Call("*:SetControlValue", name, index, n_value + tremor);
return 1;
end
else return -1;
end
end
Пример использования в теле скрипта:
- Код: Выделить всё
if (PKM_Front == 0.25) then
UpdateControl (inteval, "WaterTemp F", 0, WaterTemp, 200);
UpdateControlWithTremor (inteval, "OilPress F", 0, OilPress, 20, 0.03, EngineRPM*0.05);
UpdateControlWithTremor (inteval, "GMPPress F", 0, OilPressGMP, 20, 0.02, EngineRPM*0.05);
-- этот код НЕПОЛНЫЙ, здесь длинная цепочка if-elseif-else-end . Не используйте его "как есть"
end
Расшифровка: Если передний переключатель индикаторов установлен в положение "1" , обновляем показания датчика температуры воды переднего пульта (эта функция расписана выше и вам уже знакома).
Также обновляем :
- Индикатор давления масла переднего пульта на контроле "OilPress F", индексом 0, до значения переменной OilPress, темпом 20 Атм/сек. На результат накладывать синусоидальные колебания
амплитудой в 0.03 Атм , с частотой в 0.05 от частоты вращения коленчатого вала дизеля (стрелка упругая, она не будет колебаться на 1-ой гармонике в 500-1500 колебаний в секунду)
Аналогично обновляем индикатор давления масла в гидропередаче, но до другого значения, вибрация чуть слабее (у прибора меньше диапазон, и чем меньше диапазон, тем меньше надо делать амплитуду), но с той же частотой.
Последняя и достаточно простая функция - так как переменные тоже иногда нужно обновлять постоянным темпом (зависящим от системных часов, а не ФПС), по аналогии с функцией для
контролов написана и функция для переменных. Позволяет строить линии задержки (ставите проверку на значение переменной - и увеличиваете ее медленно функцией), рассчитывать
переходные процессы, а можно использовать в сочетании с SetControlValue аналогично функции UpdateControl - результат будет аналогичный.
UpdateVariable (time, name, target,speed)
time - системная переменная времени, имеет значение прошедшего времени с прошлого такта скрипта. НЕ ИЗМЕНЯТЬ. При использовании в теле функции подставить имя переменной времени из родительского цикла. Обычно это inteval.
name - имя переменной, которая обновляется данным вызовом функции.
target - значение, которое нужно присвоить переменной. Направление движения определяется автоматически. Внимание! У переменных нет диапазонов, кроме предельных значений типа - переменная может переполниться, если вы не
контролируете процесс обновления значения условиями!!! Зазеваетесь - получите температуру масла в -65535 градусов.
speed - скорость, с которой нужно обновить значение. Каждую секунду (игровую), к текущему значению будет добавляться либо вычитаться значение speed, промежуточные значения (с частотой тактирования скрипта - читай, ФПС) рассчитываются в теле функции автоматически.
Тело функции:
- Код: Выделить всё
function UpdateVariable (time, name, target,speed)
-- функция апдейта переменной на новое значение с задаваемой скоростью единиц/сек.
-- name - имя контрола
-- target - целевое значение
-- speed - скорость перевода
--Эта функция возвращает значение!
n_step = time*speed;
n_value = 0;
if ( n_step > math.abs(name - target) ) then -- устранение "дребезга" около конечного значения.
n_step = math.abs(name - target);
end
if (speed >0) then
if ( name > target) then
n_value = name - n_step;
end
if ( name < target) then
n_value = name + n_step;
end
if ( name == target) then
n_value = name;
end
else return -1;
end
return n_value;
end
Эта функция возвращает значение, в отличие от предыдущих! И ее можно использовать в Call-aх для мгновенной передачи рассчитанных значений в контролы. Пример использования:
- Код: Выделить всё
elseif (GetPower < 0.5) then -- дизель остановлен
-- остывание жидкостей до минимальной температуры сезона
WaterTemp = UpdateVariable (inteval, WaterTemp, MinTemp, 0.010);
OilTemp = UpdateVariable (inteval, OilTemp, MinTemp, 0.012);
OilTempGMP = UpdateVariable (inteval, OilTempGMP, MinTemp, 0.012);
В общем, все понятно из комментариев - переменным, используемым в скрипте для рассчета температуры воды, масла и масла гидропередачи, назначаются новые значения, равные температуре
жидкостей у "холодного" тепловоза. Проще говоря - они остывают до уличной температуры, заданным темпом. Вода - помедленнее, масло, как более теплоемкое - быстрее. 0.010 и 0.012 градуса в секунду. Как можно было заметить выше, именно эти значения передаются (тоже с плавным ходом) на приборы в кабине. Т.е. водичка остывает медленно - стрелка прибора тоже реагирует не мгновенно, а со своей скоростью. Если приборы отключить, они тоже упадут на ноль не мгновенно, а мягко и лампово. Как и положено настоящим.
Надеюсь, я сделал вашу жизнь веселее. Пользуйтесь, на здоровье.
Добавлено спустя 49 минут 5 секунд:П.С.
Обратите внимание - функции UpdateControl и UpdateControlWithTremor не возвращают рабочих значений, а обновляют значения контролов, принятых в аргументах. Т.е. в любимых олдфагами терминах называются ПРОЦЕДУРАМИ. Ладно, на самом деле - возвращают (-1) при неправильном вводе параметров. Но это обычно нигде не используется. Примерная аналогия в ООП - методы для записи значений в классе или его конструктор.
Функция же UpdateVariable должна использоваться только как часть математического или логического выражения и возвращает ОБНОВЛЕННОЕ ЗНАЧЕНИЕ той самой переменной, которую вы обновляете, т.е является "истинной" ФУНКЦИЕЙ. Забудете его забрать - так оно у вас и останется необновленным. Почему я сделал так, а не написал эту функцию по полной аналогии? А ХЗ - работает, и работает надежно. А переписывать скрипт мне не хочется. Возможно, в последующих локомотивах полностью перепишу по аналогии с функцией для контролов - чтобы выполнялась как процедура, а не вызывалась в рассчетах.
Также ВАЖНО: значения параметра Speed , передаваемого в тело функции, должно быть БОЛЬШЕ НУЛЯ. Это вам не консольный код и не приложение, которое может ругнуться "ой, мне прислали какую-то херню". Функция просто не выполнится (код -1), значение останется прежним.