RSS
Меню сайта

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

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

Copyright C4TNT© 2008

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

Скриптуем #6
Механик уже ломится в дверь, а нам ещё нечем его удивить. Поэтому срочно включаем едитор и рисуем.

Для начала соберём какую-нибудь простую механизму.


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



Назовите левую подставку под ящик func_mover_1, а правую - func_mover_2. (Эта полочка состоит из двух половинок)

Принцип действия устройства будет таким: при срабатывании триггера обе створки полочки поворачиваются вокруг своей оси и ящик с них падает. Сами оси в принципе особого интереса не представляют, поскольку не движутся.
Кстати, чуть не забыл, origin обеих створок нужно выставить по центру, вокруг которого они будут вращаться. В нашем случае этот центр снаружи створки. Но если плохо себе это представляете - смотрите карту-пример. Полочка готова, осталось научиться её вращать. Смотрим файл doom_events.script и видим там целый набор для вращения чего угодно:

scriptEvent    void     rotateUpTo( float axis, float angle ); -вращение вокруг заданной оси на некоторый угол. Вращение происходит в сторону увеличения угла.
scriptEvent    void     rotateDownTo( float axis, float angle ); -вращение вокруг заданной оси на некоторый угол. Вращение происходит в сторону уменьшения угла.
scriptEvent    void     rotateTo( vector angles ); -такое вращение, после которого углы поворота объекта станут равны указаным в параметре.
scriptEvent    void     rotate( vector angleSpeed ); -Вращение с определённой угловой скоростью.
scriptEvent    void     rotateOnce( vector angles ); -Поворот на указанный угол.
Нам сподручнее всего последний вариант. Но каким же образом в игре задаются углы? А задаются они так: первым идёт угол вращения вокруг оси Z, потом Y, а потом X. У меня на карте ось вращения должна быть параллельна X, соответственно я буду использовать третью цифру, две остальные изменять не буду. Если у вас полочка повёрнута по другому - используйте вторую цифру. А если вы сделали полочку с осями, не параллельными сетке - придётся её переделать, поскольку вращать такое довольно сложно.

Итак, пока весь код сводится к простому повороту на 90 градусов обеих полочек:

void DropBox ()
{
    func_mover_1.rotateOnce('0 0 90');
    func_mover_2.rotateOnce('0 0 -90');
}
Возможно вам потребуется поменять знаки для того, чтобы полочка открывалась вниз а не вверх. Но у меня при тестировании всё сложилось просто прекрасно и заработало прямо таки с первого раза, очень редкое явление, между прочим. Но даже если вам повезло не сразу - думаю всё же вы с ней уже справились. Но у этой конструкции есть один недостаток: такое ощущение, что створка открывается как дверь, а не падает. Попробуем её ускорить из редактора, для этого у каждой створки установите поле move_time = 0.5. Смотрим - получилось лучше, но всё равно мало. Попробуем 0.4 Вроде бы так лучше, но есть одно но. Такое вращение выглядит несколько неестественно, так, как будто створка не обладает упругостью и инерцией. В реальности она бы пролетела ещё чуть-чуть и потом некоторое время качалась на петле. К сожалению многие дизайнеры карт останавливаются на достигнутом, а это не впечатляет. Особенно в сравнении с картами разработчиков самой игры, где такие мелочи учтены.

Приделываем энерцию.


Подправим угол раскрытия створки:

void DropBox ()
{
    func_mover_1.rotateOnce('0 0 135');
    func_mover_2.rotateOnce('0 0 -135');
}
Итак, створка пролетела позицию, осталось нарисовать затухающие колебания. Но во первых для этого нам нужно будет распаралелить процесс затухания так, чтобы каждая створка раскачивалась отдельно. Если вы помните, для этого предназначена команда thread, но она работает только с функциями. А значит будем писать функцию. Можно написать две разные функции - по одной на створку, но зачем делать двойную работу, ведь объект-створку можно передать параметром так же, как мы это делали с лампочками. Осталось продумать, что функция будет получать в параметрах: во первых, мы передаём объект, во вторых, нам нужно будет знать угол, в котором створка остановится и в третьих - было бы очень полезно иметь возможность как-то влиять на скорость "успокоения" створки. Остальное можно узнать прямо у объекта, но если чего-то не хватает - дополним по ходу дела.

void BobAndDecay (entity obj, vector endang, float decay)
{
}
Итак, какова же логика работы этой функции. В самом начале считаем, что объект уже стоит в правильном положении и готов раскачиваться. Финальный угол знаем. Для такого раскачивания нам нужно узнать, на сколько и в какую сторону нужно повернуть объект, чтобы он занял финальную позицию. Для нахождения этого числа нужно из финального угла вычесть тот, в котором объект находится сейчас. Но тут прячется одно большое НО. Поскольку углы задаются числом от 0 до 360 - это вычисление будет правильным далеко не всегда. (Например 135-90 = 45 - правильно, 359 - 90 = 270 - больше чем пол оборота, а должно быть меньше) На этот случай разработчики заготовили две полезные функции: anglemod360 и anglemod180 - они доводят углы "до кондиции". Первая помещает угол в интервал от 0 до 360, а вторая - в интервал от -180 до 180. При этом "смысловая" нагрузка угла не страдает. Нам очень подходит вторая функция, поскольку она приводит результат вычислений к таким удобным значениям. Но ещё не всё, ведь объекты вращаются независимо от скрипта, и скрипт их не ждёт. А подождать нужно, иначе мы попытаемся вращать створку в противоположную сторону раньше, чем она закончит первое вращение. Можно сделать это с помощью wait, но есть более удобная функция sys.waitFor( entity obj). Эта функция ждёт, пока объект не завершит все свои действия. Получить угол поворота объекта - проще простого, для этого есть функция getAngles().

Итак, пробуем:

void BobAndDecay(entity obj, vector endang, float decay)
{
vector toAngle;

    sys.waitFor(obj);
    toAngle = anglemod180(endang - obj.getAngles());
    obj.rotateOnce(toAngle);
}

void DropBox ()
{
    $func_mover_1.rotateOnce('0 0 135');
    $func_mover_2.rotateOnce('0 0 -135');

    BobAndDecay($func_mover_1,'0 0 90',0.8);
}
Попробовали, получили интересный результат, но слегка не такой - створка качнулась и попала на финальную позицию сразу. Но это логично, так как мы её повернули на то расстояние, которое её отделяло от финальной позиции. Значит поворачивать её нужно на больший угол. Для этого уже вычисленный угол поворота (toAngle) нужно просто умножить на некоторое число. Это число должно быть больше 1 (поскольку створка должна пролететь финальную позицию) и меньше двух (иначе она будет раскачиваться всё сильнее и сильнее). Вспомним, что decay у нас должен регулировать процесс раскачивания объекта. Если считать decay значением от 0 до 1 (своеобразный коэфициент трения) то можно записать такую небольшую формулку: toAngle = toAngle * (2-decay); Чем больше decay - тем меньше пролетит створка.

Пробуем ещё раз. Теперь вы, скорее всего, увидели нечто более похожее на правду, но створка качается по инерции только один раз и потом застывает. Такая беда от того, что у нас в функции нет цикла. Если не верите - можете вызвать BobAndDecay два раза подряд вместо одного - теперь створка качнётся по инерции два раза. Но for нам не совсем подходит, поскольку точное число таких раскачиваний выяснить довольно проблематично. Правильнее бы было завершить цикл когда toAngle будет близка к '0 0 0' и движение будет уже незаметным. Для подобных случаев есть цикл while(условие) {код} Код в таком цикле будет выполняться, пока верно условие. Нужно придумать, как бы выяснить, что угол приближается к финальному. Лучший вариант - найти разницу углов (она уже есть - toAngle), найти в ней наибольшее число и сравнивать с чем-нибудь маленьким, например 0.005.

Напишем свою первую функцию, которая умеет возвращать значение. Эта функция будет находить наибольший по модулю элемент вектора и возвращать его. Для этого нужно поступить так же, как и при создании обычной функции, но вместо void вписать тип возвращаемого значения. В нашем случае это float.

float MaxVec (vector v)
{
    float pmax;
    if (v_x < 0) {v_x = -v_x;} // Если в векторе есть отрицательные значения - меняем их знак
    if (v_y < 0) {v_y = -v_y;}
    if (v_z < 0) {v_z = -v_z;}

    pmax = v_x; //Эти три строки находят максимум
    if (v_y > pmax) {pmax = v_y;}
    if (v_z > pmax) {pmax = v_z;}
    return pmax;
}

В этом примере вы можете увидеть второй смысл команды return - она используется для возвращения значения. Функция на этом, естественно, завершается. Если функция возвращает значение - она должна завершаться именно через return, иначе в консоли вас ждёт error.
И финальный код с этой функцией:

float AbsMaxVec (vector v)
{
    float pmax;

    if (v_x < 0) {v_x = -v_x;}
    if (v_y < 0) {v_y = -v_y;}
    if (v_z < 0) {v_z = -v_z;}

    pmax = v_x;
    if (v_y > pmax) {pmax = v_y;}
    if (v_z > pmax) {pmax = v_z;}
    return pmax;
}

void BobAndDecay(entity obj, vector endang, float decay)
{
vector toAngle;
float maxDist;

    toAngle = anglemod180(endang - obj.getAngles());
    maxDist = AbsMaxVec(toAngle); //Находим maxDist уже сейчас, чтобы while было что сравнивать

    while (maxDist > 1) //Цикл, кончается когда отличие финального угла от нынешнего будет меньше одного градуса
    {
        sys.waitFor(obj);
        toAngle = anglemod180(endang - obj.getAngles());
    maxDist = AbsMaxVec(toAngle);
        toAngle = toAngle * (2-decay);
        obj.rotateOnce(toAngle);
    }
    toAngle = anglemod180(endang - obj.getAngles()); //Поворачиваем объект в финальную позицию, прямо как в самом первом варианте этой функции.
    obj.rotateOnce(toAngle);
}

Эта функция уже работает и её можно применять. Теперь вам осталось только дописать в DropBox нужные строчки и попробовать ловушку. Надеюсь, что триггер на карте вы уже давно поставили. Итоговый вид DropBox такой:

void DropBox ()
{
    $func_mover_1.rotateOnce('0 0 135');
    $func_mover_2.rotateOnce('0 0 -135');

    thread BobAndDecay($func_mover_1,'0 0 90',0.4);
    thread BobAndDecay($func_mover_2,'0 0 -90',0.4);
}
Но в силу вышеназванных причин у вас он может слегка отличаться. Новую функцию BobAndDecay можно использовать с любым mover-ом, и вы даже можете её добавить в скриптовую библиотеку. Только в этом случае будет полезно добавить ей проверки таким же образом, как в прошлой статье LightProg-у.

Тестовую карту вы можете скачать здесь: карта. Триггер, запускающий ловушку, расположен около двери. Ну а почётное право положить туда что-нибудь тяжёлое я предоставлю вам. А вот уже и механик идёт.
Категория: Doom3 | Добавил: c4tnt (18.11.2008)
Просмотров: 580 | Комментарии: 1 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Вход:

Поиск

Ссылки


Статистика

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


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