вторник, 28 октября 2008 г.

Оружейная. Делаем дубину.

Интерес к оружию у большинства людей сидит буквально в крови, поэтому ничего удивительного, что большинство резидентов, мало-мальски освоившись в-мире, начинают вооружаться до зубов - мало ли, супостат нападет, а не нападет, пусть будет, ибо запас карман, простите, инвентарь, не тянет.



В SL существует масса оружия, от холодного до стратегического, подробнее об можно прочитать в обзорах заслуженного ветерана и владельца русского военного острова Fynist Island Dmitry Willis'а, где он рассказывает о видах всевозможных стрелялок и пулялок, а также где такое можно приобрести, ну а мы в рамках цикла "Оружейная" научимся самостоятельно изготавливать себе всевозможные военные побрякушки. Начнем мы с холодного оружия и для начала изготовим дубину для битья супостатов по бокам. Сразу скажу, что принцип действия любого оружия в SL один и тот же независимо от визуального воплощения - нечто вылетает из него, долетает до врага и входит с ним в соприкосновение; а дальше выполняет деструктивные методы - push, impulse или damage.

Итак, создадим некую дубину - пусть она у нас будет иметь вид классической бейсбольной биты.



Далее нужно приладить ее к руке - "одеть" на правую руку и отрегулировать положение.





Теперь нужно научиться махать нашим дубинатором, для чего открываем редактирование и создаем там скрипт, который позволит нам при нажатии определенных клавиш проигрывать различные анимации, имитирующие удары. Анимации могут быть сделаны вами самостоятельно или взяты откуда-либо - особой роли не играет. Лишь бы они адекватно отражали то, что вы хотите получить - наглядность нанесения ударов. Для простоты сейчас мы воспользуемся стандартной анимацией sword_strike_R, имитирующей косые рубящие удары с плеча.

Итак, рассмотрим основные части нашего будущего скрипта:

1. Захват и опрос клавиш управления при "одевании" дубины;
2. Проигрывание анимации в момент удара;
3. Нанесение урона врагу;
4. Спецэффекты (звуки, брызги крови etc).


"Одеванием" заведует событие attach(key id), возникающее при одевании чего либо на аватар, либо снятии этого чего-либо. Параметр, который появляется при этом событии - ключ-идентификатор аватара id. Таким образом, если предмет одет и возникло событие, то в переменной id появляется значение ключа, а если же предмет снят, то id будет равно значению NULL_KEY. Проверяя это значение по возникновению события attach мы всегда можем узнать, одели или сняли предмет.

В первую очередь, при одевании предмета, нужно проверить, если ли у нас право захватить клавиши управления и проигрывать анимации. Для этого существует оператор llGetPermissions(), возвращающий целое число, биты которого говорят о том, какие права в данный момент установлены. Нас интересуют следующие битовые флаги - PERMISSION_TRIGGER_ANIMATION, позволяющий проигрывать анимации, и PERMISSION_TAKE_CONTROLS, позволяющий перехватывать события нажатия кнопок. Если такие права у нас есть, то выполняем оператор llTakeControls(), в котором указываем, какие именно кнопки мы хотим захватить. Если же по каким-то причинам у нас нет прав на эти действия, то запрашиваем оные, исполняя llRequestPermissions(). Этот оператор вызовет появление в верхнем правом углу диалогового окна, предлагающего вручную разрешить применить к вашему аватару действия по захвату кнопок управления и назначения анимации.

К слову сказать, при "одевании" необходимые нам права выделяются автоматически, но на всякий случай мы предусматриваем корректную обработку возможной ошибки, связанной с выделением прав.

Ну и, если предмет был снят, нужно "освободить" клавиши, выполнив оператор llReleaseControls().

attach(key id)
{
if(id != NULL_KEY)
{
integer perm = llGetPermissions(); // запрашиваем права
if(perm == (PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION))
{
// если с правами все ОК, "захватываем" управление
llTakeControls(CONTROL_ML_LBUTTON | CONTROL_LBUTTON | CONTROL_UP | CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT, TRUE, TRUE);
}
else
{
// если же нет, то просим нам дать эти права
llRequestPermissions(id, PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION);
}
}
else
{
llReleaseControls();
}
}


Еще один момент - при событии "одевания" и после ответа на запрос прав возникает еще одно событие - run_time_permissions(integer perm), где в perm хранится состояние выделенных прав. Нужно его также корректно обработать.

run_time_permissions(integer perm)
{
if (perm)
{
llTakeControls(CONTROL_ML_LBUTTON | CONTROL_LBUTTON | CONTROL_UP | CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT | CONTROL_DOWN, TRUE, TRUE);
}
}


Теперь нужно описать, какие действия будет выполнять наш скрипт при нажатии на определенные клавиши. Для удобства использования сделаем так, чтобы при простом нажатии на клавиши со стрелочками аватар вел себя как обычно до тех пор, пока не нажата и удерживается кнопка мыши - лишь в этом случае при нажатии на стрелочки аватар начинал бы орудовать дубиной.

Событие, возникающее при нажатии на клавишу, называется control(key owner, integer level, integer edge). При его возникновении определяются параметры owner, содержащее ключ вашего аватара, level - битовое число, "знающее", какая кнопка нажата и не отпущена и edge, которое, в свою очередь "знает", какая кнопка удерживалась и теперь отпущена. Все действия, интересные нам должны обрабатываться только при нажатой и удерживаемой кнопке мыши - level & (CONTROL_ML_LBUTTON | CONTROL_LBUTTON) и если это так, выясняем, нажимается ли еще что-то в данный момент. Если нажимается, проигрываем анимацию удара при помощи оператора llStartAnimation().

control(key owner, integer level, integer edge)
{
if(level & (CONTROL_ML_LBUTTON | CONTROL_LBUTTON))
{
if(edge & (CONTROL_UP | CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_ROT_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT))
{
llStartAnimation("sword_strike_R");
}
}
}


После описания обработчика этих событий нашей дубиной можно вполне спокойно размахивать, пугая народ, но вот бить по-настоящему ей пока еще нельзя - этому ее нужно еще "научить". Существует несколько способов нанесения урона, но для простоты и понятности в дальнейшем мы рассмотрим сейчас эмиссионный способ, в котором нечто невидимо вылетает из нашей дубины и поражает противника. Согласен, не совсем корректно по отношению к природе дубины, но во-первых, это один из самых распространенных методов действия оружия, а во-вторых, когда в следующей статье мы будем делать пистолет, принцип стрельбы пульками будет уже вами освоен.

Итак, нам нужно создать некий объект, который бы при ударе вылетал из дубины и бил прямехонько в супостата. Делать мы это будем при помощи оператора llRezObject(), который позволяет выкидывать в пространство предмет, хранимый в инвентаре родительского объекта, по определенному вектору с определенной силой. Для этого мы сначала определяем, в какой стороне от нас враг и куда нужно пулять. Делается это операторами llGetRot() и llRot2Fwd(), первый из которых возвращает вращение вашего аватара (а предполагается, что вы бьете врага, стоя к нему лицом), а второй преобразует это вращение в направление. Кроме того, неплохо было бы знать и положение в пространстве нашего аватара, что мы сделаем при помощи оператора llGetPos(). Выделим это в отдельную пользовательскую функцию kick():

kick()
{
rotation rot = llGetRot();
vector pos = llGetPos();
vector dir = llRot2Fwd(rot);
pos += dir;

llRezObject("kick_bullet", pos, dir, ZERO_ROTATION, 0);
}

Самое время переходить к изготовлению пули. В качестве нашего kick_bullet будем использовать обыкновенный шарик небольшого диаметра. Собственно, форма объекта может быть любой - все равно в нашем случае он будет невидимым. Также нужно решить, как именно будет наносится урон врагу. Я бы рекомендовал использовать энергию импульса и damage, что позволит пользоваться нашей дубиной даже там, где выключен push, ну и на территориях, где разрешен damage (на так называемых бэттлграундах) и "убивать" ей супостатов.

Создаем скрипт в пуле и прописываем там, что нужно делать, если пуля попала во врага. Событие соприкосновения с чем-либо называется collision_start(integer num), где num - количество столкновений. Кроме того, нам интересно еще одно событие, возникающее при начале движения пули - moving_start(). Во-первых, мы должны придать пуле способность наносить повреждения - damage. Делается это при помощи оператора llSetDamage(), придать ей свойство физического объекта STATUS_PHYSICS оператором llSetStatus() и во-вторых, увеличить силу физического удара при помощи llSetForce(), чтобы нашим врагам мало не казалось. Для этого мы в событии collision_start() определяем массу врага через llGetObjectMass() и расчитываем необходимую силу удара.

Теперь немного об утилизации отработанных пулек. Для того, чтобы не засорять окружающее пространство, добавляем в пулю такой параметр, как lifetime. Это - количество времени, которое проживет пуля с момента выстрела (начала своего движения в пространстве). Кроме того, это время попутно нам сокращает радиус действия нашей дубины, что, согласитесь, немаловажно для реалистичности ее использования. Итак, в событии moving_start() мы запускаем таймер llSetTimerEvent(lifetime), который сработав, вызовет событие timer(), в котором при помощи оператора llDie() пуля самоуничтожится. Переменная shot введена для удобства настройки боевых качеств пули - если вы ее просто выложите на землю, то она не будет самоуничтожаться, а даст вам возможность спокойно настроить необходимые параметры скрипта. Ну и еще маленькая тонкость - чтобы наши пульки, вылетев за границу мира, не падали нам в папку Lost And Found и не засоряли инвентарь, добавим статус STATUS_DIE_AT_EDGE, который будет их удалять при пересечении границы мира.

vector velocity;
float damage = 20;
float lifetime = 0.27;
integer shot = FALSE;

default
{
state_entry()
{
llSetDamage(damage);
llSetAlpha(0.0, ALL_SIDES);
llSetStatus(STATUS_PHYSICS, TRUE);
llSetStatus(STATUS_DIE_AT_EDGE, TRUE);
llSetBuoyancy(1.0);
llSetForce(<10, 0, 0>, TRUE);
}

collision_start(integer total_number)
{
key kd = llDetectedKey(0);
float fMass = llGetObjectMass(kd);
integer mass = llRound(fMass);
llSetForce(<10*mass,0,0>,TRUE);
}

timer()
{
if(shot) llDie();
}

moving_start()
{
llSetTimerEvent(lifetime);
velocity = llGetVel() * 5;
float vmag = llVecMag(velocity);
if (vmag > 0.1){
shot = TRUE;
}
}
}


Ну вот, собственно, и все. Сохраняем скрипт, забираем пулю с земли и кладем ее в инвентарь дубины. После этого хватаем дубину в руки и идем гонять нехороших злых аватаров. Что касается спецэффектов в виде брызг крови, отлетающих зубов и т.п., то это несложно реализовать, добавив в событие collision_start() соответствующие функции. Какие именно - это мы рассмотрим в следующей статье цикла, когда будем создавать огнестрельное оружие, где спецэффекты более обоснованы. ;)

Немного от автора.
Этот материал в первую очередь предназначен для людей, способных к самообучению. Поэтому я специально не привожу полные листинги скриптов - моя цель состоит именно в том, чтобы читатель разобрался в принципе действия такого оружия, в принципе работы событий в скриптах и т.п., тогда ему не составит никакого труда сделать что-то свое, придав ему именно те свойства, которые требуются. А нахаляву метод "копипаста" не прокатит. ;)


ЛИТЕРАТУРА:
http://wiki.secondlife.com/
http://rpgstats.com/wiki/index.php?title=Main_Page

1 комментарий: