RSS
Меню сайта

Категории каталога
Doom3 [11]
Статьи о разработке карт к Doom3
Общее [7]
Статьи про общие моменты маппинга

Наш опрос
Навигация в "Файлах"
Всего ответов: 21

Copyright C4TNT© 2008

Главная » Статьи » Маппинг » Doom3

Скриптуем #5

Небольшая ретроспектива


В прошлой серии мы сделали функцию, управляющую источником света и научились разбирать строки.
На всякий случай поясню одну афёру, которую я провернул в последнем варианте функции:

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 случай с нулевым временем отдельно. Выглядит это примерно так:

void LightColor(entity light, float xtime,vector newcolor)
{
    float k;
    float a;
    vector oldcolor;
    vector color;

    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; Она прерывает выполнение той функции, внутри которой была вызвана. Кроме этого она имеет ещё один смысл, но к нему вернёмся позже. Итак, итоговый вариант:


void LightColor(entity light, float xtime,vector newcolor)
{
    float k;
    float a;
    vector oldcolor;
    vector color;
   
    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", если вы его конечно не запихали в другое место. Для завершённости мода осталось только упаковать ваш файл в пак (как с картами) и написать ридми. Но давно известен факт, что пользователь часто криворук. А наш несчастный мод совсем беззащитен против подрывных действий пользователей. А именно, попробуйте ответить на такие вопросы:
  1. Что будет, если указать не источник света, а другой объект?
  2. Что будет, если при работе функции лайт вдруг будет удалён (с триггера к примеру)?
  3. Что будет, если указать несуществующий лайт?
  4. Что случится, если программа для лайта будет состоять только из +,-,0,?* ?
  5. Что случится при пустой программе?
  6. Чего ещё можно сделать нестандартного с этим скриптом???
Уже из первых трёх вопросов следует, что нужно проверять - получен ли объект и лайт ли это. Лично мне кажется, что в случаях 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 в консоль.)

Ну а теперь всё же можно посмотреть на итоговый код:

namespace LightScript {
void LightColor(entity light, float xtime,vector newcolor)
{
    float k;
    float a;
    vector oldcolor;
    vector color;
  
    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, благодаря чему многие могут спокойно переносить шлюзы и прочие агрегаты на свои карты без особых проблем, даже не прикасаясь к скриптам. И самое главное - пример вызова функции из скрипта:

LightScript::LightProg($light_1,0.5,'0.9 0.9 1',"+++zazyzdzh--g+a-z");

Пример тут

Ну а в следующий раз займёмся главной особенностью дума, а именно, движущейся механикой.
Категория: Doom3 | Добавил: c4tnt (18.11.2008)
Просмотров: 930 | Комментарии: 2 | Рейтинг: 5.0/1
Всего комментариев: 1
1 Rajvo  
1
Очччень полезные заметки про скрипты! Продолжать в том же духе! biggrin

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Вход:

Поиск

Ссылки


Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Работаем на керосине