В прошлой серии мы сделали функцию, управляющую источником света и научились разбирать строки.
На всякий случай поясню одну афёру, которую я провернул в последнем варианте функции:
if (symbol == "+") {citime = citime/2;}
if (symbol == "-") {citime = citime*2;}
Чуть раньше в переменную citime было сохранено значение из xtime (времени на выполнение одного шага программы).
И при вызове LightColor используется именно эта переменная. Если в
тексте кода для лайта нет + или -, то citime = xtime и ничего
особенного не происходит. Если же попадается, например, минус - citime
будет умножено на два и все последующие переходы от цвета к цвету будут
выполняться медленнее в два раза. Естественно, такую ситуацию
исправляет плюс, идущий далее и делящий citime пополам. Пример кода для
лампочки:
nznz++nznznz--nznz
Получаем две редких вспышки, потом три частых, потом снова две редких.
Ну а теперь добавим финальный штрих. А именно, было бы неплохо иметь
возможность сразу установить цвет без плавного перехода. Самым
логичным, вроде, было бы выставить время перехода в 0, но тут есть один
большущий глюк. Если вам интересно его найти самим - пока не читайте
следующий абзац.
Итак, глюк.
Скорее всего при попытке поставить время, равное 0, игра вылетит в
консоль с сообщением об ошибке. Но ведь вроде бы ничего преступного в
желании перейти от цвета к цвету за 0 секунд нет. Кстати, можете
попробовать переход за -1 секунду - тоже странный результат. В чём же
всё-таки его причина. А причина в том, как мы считаем a в функции
LightColor. Напомню, a = GAME_FRAMETIME/xtime; и если xtime у нас равно
нулю - деление просто невозможно выполнить. А если время отрицательно -
то a тоже становится отрицательным и k вместо плавного перехода от 1 к
0 начинает так же плавно расти от 1 куда-то вверх. Цикл при этом,
естественно, не кончается, поскольку k > 0, а защита игры не
срабатывает как раз таки за счёт sys.waitFrame(); Самый простой способ
избавиться от этого глюка - рассмотреть в функции LightColor случай с
нулевым временем отдельно. Выглядит это примерно так:
if (xtime <= 0)
{
//Особый отдел следит за тобой.
}
a = GAME_FRAMETIME/xtime;
<...>
}
<...> - просто обозначение, что тут есть код, убраный для краткости. Итак, что же мы напишем в этот if?
С одной стороны можно грязно выругаться в консоль и завершить игру. Для этого даже есть специальная функция:
sys.error( string text );
После её выполнения игра вылетает в консоль, и в консоли появляется
сообщение Error: ..... (вместо троеточия тот текст, который вы указали
при вызове функции). Ну что же, с хорошей функцией познакомились.
Теперь всегда можно уронить игру... Но это не путь джедая, поэтому
просто обеспечим скрипту нормальное выполнение, даже если в параметрах
что-то не то. Самое правильное, что можно сделать при нулевом параметре
xtime - просто переключить источник света на новый цвет и всё. Пусть и
отрицательное время распознаётся так же. Значит в if просто нужно
вписать light.setColor(newcolor_x,newcolor_y,newcolor_z); Но как после
этого остановить этот скрипт, чтобы он не продолжал выполнение
остальной части, в которой реализован плавный переход. И если вы
внимательно читали всё предыдущее - наверняка скажете, что остальное
нужно запихать в else {} блок. Ответ, в принципе, верный. Но есть более
удобный вариант, а именно - команда return; Она прерывает выполнение
той функции, внутри которой была вызвана. Кроме этого она имеет ещё
один смысл, но к нему вернёмся позже. Итак, итоговый вариант:
if (xtime <= 0)
{
light.setColor (newcolor_x,newcolor_y,newcolor_z);
return;
}
a = GAME_FRAMETIME/xtime;
oldcolor = light.getColor();
for (k=1;k>0;k-=a)
{
color = oldcolor*k + newcolor*(1-k);
light.setColor (color_x,color_y,color_z);
sys.waitFrame();
}
light.setColor (newcolor_x,newcolor_y,newcolor_z);
}
С реализацией команды для установки ctime в ноль, я думаю, вы
справитесь сами. Но если не справитесь - всегда сможете посмотреть в
примере. Он традиционно в конце статьи. Но к этой команде нужна ещё
одна для восстановления исходного состояния ctime, поскольку на 0 плюс
и минус уже не действуют. Под это дело можно задействовать
дополнительную переменную (для хранения значения, которое было до 0)
или просто восстановить значение из xtime. Поскольку скрипт пишется в
познавательных целях, реализуем оба варианта. Для зануления я выбрал
знак 0, для восстановления старого значения - тоже ноль (первый ввод
нуля зануляет время, второй восстанавливает), а для восстановления к
нормальной скорости - *. Итак, заведём "спасалочку" для citime.
Например, float oldtime; И определимся с плаом действий. Для начала
нужно сохранить старое citime, а потом выставить ноль. При повторном
появлении нуля на горизонте нужно проделать нечто противоположное.
Возникает вопрос, как быстро и надёжно узнать, первый это ноль или
второй. Даю нахальную подсказку: после первогонуля у нас в citime что?
А после второго?
Итак, если citime не ноль - то сохраняем его и устанавливаем в ноль.
Если уже ноль - восстанавливаем. Надеюсь, что вы овладели if-ом в
достаточной мере, чтобы реализовать такую логику. Ну а про * и
размышлять особо нечего.
Хорошо бы сделать из этого скриптовую библиотеку.
Здесь, в общем, нет ничего сложного. Для начала создайте отдельный
файлик в папке script - в нём будет жить наш скрипт. Теперь пора
перенести туда обе функции. В общем минимальнейший скрипт-мод готов.
Для его использования достаточно в скриптовый файл вашей карты вписать
#include "путь до файла со скриптом"; и пользоваться нововведёнными
функциями. Обычно путь до файла выглядит так: "script/название.script",
если вы его конечно не запихали в другое место. Для завершённости мода
осталось только упаковать ваш файл в пак (как с картами) и написать
ридми. Но давно известен факт, что пользователь часто криворук. А наш
несчастный мод совсем беззащитен против подрывных действий
пользователей. А именно, попробуйте ответить на такие вопросы:
Что будет, если указать не источник света, а другой объект?
Что будет, если при работе функции лайт вдруг будет удалён (с триггера к примеру)?
Что будет, если указать несуществующий лайт?
Что случится, если программа для лайта будет состоять только из +,-,0,?* ?
Что случится при пустой программе?
Чего ещё можно сделать нестандартного с этим скриптом???
Уже из первых трёх вопросов следует, что нужно проверять - получен
ли объект и лайт ли это. Лично мне кажется, что в случаях 1, 3 и 5 нужно завершать игру с ошибкой.
Если вдруг произойдёт 2 - просто прекратить работу, а 4 - просто
игнорировать. Значит в начале каждой из функций будем проверять наличие
лайта. Проверка на наличие объекта выглядит так:
if (!light)
{
//Объекта нет
}
Обратите внимание на восклицательный знак перед объектом. Обычно этот
знак просто инвертирует условие, то есть !(a>5) означает, что a не
больше 5. Но с объектами он преобретает новый смысл. Если нужна
проверка на наличие объекта - придётся повторить этот значок ещё раз:
if (!(!light)). Соответственно, если объекта нет - вылетаем с ошибкой.
Как это сделать: sys.error("light parameter is empty in LightColor"); -
вообще в таких сообщениях полезно указывать информацию для
разработчика, например, название вылетевшей функции и причину вылета.
Теперь проверим лайт на его расовую принадлежность, так сказать. Для
этого каким-то образом нужно извлечь spawnclass объекта. Поскольку он
прописан в ключах - извлекать будем с помощью getKey(string Key); Эта
функция позволяет прочитать любой ключ объекта. Даже если этот ключ вы
сами создали в редакторе. В общем прекрасный способ общения со скриптом
из редактора, но и о нём, к сожалению, позже. А сейчас просто читаем
spawnclass и тут же, не отходя от кассы, сравниваем с "idLight". Примерно
так: light.getKey("spawnclass") == "idLight" И если это условие не
выполняется - опять в консоль. (Подсказка: если вам вдруг потребуется узнать имя класса какого-то
другого объекта - создайте его на карте и распечатайте spawnclass в
консоль.)
Ну а теперь всё же можно посмотреть на итоговый код:
if (!light)
{
sys.error("Incorrect light in LightColor\n");
}
if (light.getKey("spawnclass") != "idLight")
{
sys.error("Object: "+light.getName()+" is not a light, is a "+light.getKey("spawnclass")+"\n");
}
if (xtime <= 0)
{
light.setColor (newcolor_x,newcolor_y,newcolor_z);
return;
}
a = GAME_FRAMETIME/xtime;
oldcolor = light.getColor();
for (k=1;k>0;k-=a)
{
color = oldcolor*k + newcolor*(1-k);
light.setColor (color_x,color_y,color_z);
sys.waitFrame();
}
light.setColor (newcolor_x,newcolor_y,newcolor_z);
}
void LightProg(entity light, float xtime,vector newcolor,string lightcode)
{
float pos; //Тут будет храниться номер изучаемого в данный момент символа.
float lenstr; //Тут будет храниться длина строки. Желательно
пользоваться этим методом, вместо того, чтобы сравнивать счётчик в
цикле со strLength(). Это просто оптимизация.
string symbol;
float citime = xtime;
float savetime = xtime;
float multiply; // Дополнительно умножаем на это число, оно зависит от знака.
lenstr = sys.strLength(lightcode);
if (!light)
{
sys.error("Incorrect light in LightColor\n");
}
if (!lenstr)
{
sys.error("Empty programm in LightProg\n");
}
if (light.getKey("spawnclass") != "idLight")
{
sys.error("Object: "+light.getName()+" is not a light, is a "+light.getKey("spawnclass")+"\n");
}
for (pos=0;pos<lenstr;pos++)
{
symbol = sys.strMid(lightcode,pos,1); //берём один знак, начиная с позиции pos
if (symbol == "a") {LightColor(light,citime,newcolor * 0.038);}
if (symbol == "b") {LightColor(light,citime,newcolor * 0.076);}
if (symbol == "c") {LightColor(light,citime,newcolor * 0.114);}
if (symbol == "d") {LightColor(light,citime,newcolor * 0.152);}
if (symbol == "e") {LightColor(light,citime,newcolor * 0.19);}
if (symbol == "f") {LightColor(light,citime,newcolor * 0.228);}
if (symbol == "g") {LightColor(light,citime,newcolor * 0.266);}
if (symbol == "h") {LightColor(light,citime,newcolor * 0.304);}
if (symbol == "i") {LightColor(light,citime,newcolor * 0.342);}
if (symbol == "j") {LightColor(light,citime,newcolor * 0.38);}
if (symbol == "k") {LightColor(light,citime,newcolor * 0.418);}
if (symbol == "l") {LightColor(light,citime,newcolor * 0.456);}
if (symbol == "m") {LightColor(light,citime,newcolor * 0.494);}
if (symbol == "n") {LightColor(light,citime,newcolor * 0.532);}
if (symbol == "o") {LightColor(light,citime,newcolor * 0.57);}
if (symbol == "p") {LightColor(light,citime,newcolor * 0.608);}
if (symbol == "q") {LightColor(light,citime,newcolor * 0.646);}
if (symbol == "r") {LightColor(light,citime,newcolor * 0.684);}
if (symbol == "s") {LightColor(light,citime,newcolor * 0.722);}
if (symbol == "t") {LightColor(light,citime,newcolor * 0.76);}
if (symbol == "u") {LightColor(light,citime,newcolor * 0.798);}
if (symbol == "v") {LightColor(light,citime,newcolor * 0.836);}
if (symbol == "w") {LightColor(light,citime,newcolor * 0.874);}
if (symbol == "x") {LightColor(light,citime,newcolor * 0.912);}
if (symbol == "y") {LightColor(light,citime,newcolor * 0.98);}
if (symbol == "z") {LightColor(light,citime,newcolor);}
if (symbol == "z") {LightColor(light,citime,newcolor);}
if (symbol == "+") {citime = citime/2;}
if (symbol == "-") {citime = citime*2;}
if (symbol == "*") {citime = xtime;}
if (symbol == "0") {
if (citime == 0) {citime=savetime;} else {savetime=citime;citime=0;}
}
}
}
}
Вы можете заметить в самом начале слова namespace LightScript. Для чего
они нужны? Они нужны для того, чтобы одноимённые функции из разных
скриптов не заменяли друг друга, например, если бы кто-нибудь у себя на
карте использовал собственную функцию LightColor - возникли бы проблемы
при стыковке с этой библиотекой. А так для использования функций именно
нашей библиотеки перед их названием просто нужно дописать LightScript::
и никаких пересечений. На картах разработчиков тоже используются
namespace, благодаря чему многие могут спокойно переносить шлюзы и
прочие агрегаты на свои карты без особых проблем, даже не прикасаясь к
скриптам. И самое главное - пример вызова функции из скрипта: