Пускаем снаряд по кругу

Здрасьте! Похоже, новый цикл статей нарисовался. Называется «не знаю, нафига это надо, но прикольно». Тут я предлагаю делиться всякими фишками, которые удалось создать для себя/своего проекта или просто ради интереса «а чё будет, если.. а как бы можно было…?». Аналогия. Я ещё постараюсь разбирать, как я до такого докатился. Итак, погнали.

Прикол-то в чём… Делал я разные ловушки, а потом вспомнил, что в С.Т.А.Л.К.Е.Р.-е были аномалии вроде огненной или электрической фигни, летавшей по определённому маршруту. Самый простой и гибкий метод — создать актор, прикосновение к которому будет дамажить, да и заставить его летать по маршруту, как уже учил Bolon667 в статье про скайбоксы. А что, если создать снаряд (например, выстрел BFG) и заставить его летать по кругу, а в случае его уничтожения — спавниться снова и продолжать свой грязный и губительный для всего живого полёт?

Скачайте эти две карты.

Каждая из них представляет скрипт, который спавнит проджектайл и заставляет его лететь по кругу. Если снаряд пропадёт, то из точки его спавна появится новый. Два скрипта потому, что у обоих методов есть достоинство и недостаток, поэтому они могут подойти каждый — для своей ситуации.

Мордой вперёд.

script 1 ENTER
{
if (ThingCount(T_NONE, 678) == 0) 
{ 
Thing_Spawn(16, T_rocket, 0, 678); 
} 
else 
{
int a = GetActorAngle(678);
SetActorAngle(678, a - 0.125);
ThrustThing(GetActorAngle(678) >> 8, 15, 1, 678);
Delay(5);
Thing_Stop(678);
}
restart;
}

Что мы тут видим? А видим мы условие, при котором спавнится снаряд:

if (ThingCount(T_NONE, 678) == 0) 
{ 
Thing_Spawn(16, T_rocket, 0, 678);

Перевод на русский: если каких-либо штук с тидом 678 на уровне — ноль, то есть, нету (ваще нигде), тогда спавним на точке с тидом/тэгом 16 ракету, повёрнутую мордой в направлении «ноль», то есть, на восток, и присваиваем ракете тид 678…

else 
{
int a = GetActorAngle(678);
SetActorAngle(678, a - 0.125);
ThrustThing(GetActorAngle(678) >> 8, 15, 1, 678);
Delay(5);
Thing_Stop(678);
}
restart;
}

…и в том случае, если предмет с тэгом 678 на карте ееееесть… Мы задаём переменную «а», которая имеет значение угла поворота этого самого предмета; затем мы присваиваем этому предмету другой угол поворота, а именно — поворачиваем на 45 градусов по часовой стрелке (а — 0.125); после чего толкаем этот предмет мордой (куда бы она ни была повёрнута) ВПЕРЁД силой, равной 15 со снятым ограничением на применяемую энергию (1). После чего, спустя 5 тиков, сбрасываем скорость снаряда и перезапускаем весь этот алгоритм. Ещё раз:

int a = GetActorAngle(678); — узнаём угол поворота объекта здесь и сейчас, и присваиваем ему переменную «а».

SetActorAngle(678, a — 0.125); — поворачиваем предмет на 45 градусов

ThrustThing(GetActorAngle(678) >> 8, 15, 1, 678); толкаем его в этом направлении, и вот тут я кое-что попытаюсь объяснить.

«GetActorAngle(678) >> 8» эта функция задаёт направление, в котором предмет толкнёт невидимая сила. Можно функции писать целиком вместо цифры/переменной и даже производить с ними арифметические операции. И вот это вот «>> 8» — это битовый сдвиг, нужный для преобразования значения, которое нам возвращает GetActorAngle. Преобразуем из целочисленного в байтовое. Важно — зачем это надо…

Дело в том, что GetActorAngle нам число с фиксированной точкой, где поворот на 45 градусов — это 0.125, а на 360 градусов — 1.0. При этом (оооооххх) ПОДРАЗУМЕВАЕТ машина другой тип чисел — целое число, значение которого (для фиксированного 0.125, оно же — 45 градусов) будет выглядеть, как 8192. А принимают значения всякие функции, типа «заспавнить предмет» (где мы указываем и направление его морды) — в байтовом виде. А оно у нас — это 0 (восток), 32 (северо-восток), 64 (север) и так далее (все положительные значения крутят против часовой стрелки, типа…). Потому-то нам и нужно, чтобы возвращённое значение равнялось принимаемому. Ну что, не-программисты, поняли что-нибудь? Во-во. И я. Факт в том, что мы должны ПРЕОБРАЗОВАТЬ полученное целое значение в байтовое. Для этого надо понять, как соотносятся эти числа. Здесь есть таблица, в которой мы видим соответствие угла поворота и его представление разными типами чисел. Мы видим, что, например, 32 = 0,125=8192=»45 градусов». Следовательно, чтобы из 8192 получить 32, его надо разделить на некое число. Делим 8192 на 32 и получаем 256. То есть теперь мы можем написать GetActorAngle(678) / 256, и всё заработает. Но есть в программировании такие операции, как сдвиг. Это деление (>>) или умножение (<<) на 2 в некоей степени. Так, 256=2 в 8 степени. Эту самую степень мы и указываем в сдвиге, то есть X >> 8 = X / 28 = X / 256.

Поверили, что я в этом разбирался? Да я просто в этой же статье прочитал, как с помощью сдвига на 8 преобразовать фиксированное значение в байтовое, а то, что у нас именно фиксированное число, прочитал в той же вики в статье про GetActorAngle, хаааааххххх))))). Простите. Так вот и живём.

Кстати, переменную «а» тоже не обязательно создавать, можно просто написать

SetActorAngle(678, GetActorAngle(678) - 0.125);

Так тоже работает, но с переменной нагляднее, хоть и более громоздко.

И я ещё должен пояснить за Delay(5) и Thing_Stop(678). С задержкой всё понятно — без хотя бы однотиковой задержки умрёт любой цикл, а в случае с перезапуском скрипта у меня просто вешает игру намертво; также, чем меньше задержка, тем как-бы выше скорость, но меньше радиус вращения. Калибруя задержку и силу толчка, можно регулировать радиус и скорость, но это уже пробуйте сами, пожалуйста. А вот Thing_Stop…

Дело в том, что каждый снаряд имеет некую потенциальную энергию, вектор которой — это его лицевая сторона, и даже если вы его создаёте функцией «заспавнить предмет», либо «заспавнить снаряд с нулевой скоростью», при любом перемещении снаряда эта энергия проснётся и приплюсуется к вашей, которую вы задаёте функцией thrust. По кругу снаряд лететь не будет, товарищи, он будет лететь по траектории шва «иголка назад», поэтому эту его энергию нужно сбрасывать каждый виток цикла. Вы можете поставить его после толчка, как у меня, можете перед взятием значения угла, работает и так, и так, но остановка должна происходить каждый раз, когда мы перемещаем снаряд, а не единожды, после спавна. Почему так — пёс его знает…

Подытожим: этот скрипт гоняет по кругу лицом вперёд и хвостом назад. По кругу? Нет, братцы, по восьмиугольнику. Увы, сторон у акторов тут 8, поэтому самый компромиссный и наглядный вариант — по движению на каждый 45-градусный разворот. Однако ничто не мешает выставить 0.005 вместо 0.125, силу толчка сменить на 5, а задержку выставить в 1 тик. Теперь ракета будет вращаться плавно, но периодически будет лететь чуть-чуть «полубоком». Как лучше — вам решать. Вот «плавный» скрипт:

Thing_Stop(678);
SetActorAngle(678, GetActorAngle(678) - 0.005);
ThrustThing(GetActorAngle(678) / 256, 5, 1, 678);
Delay(1);

Альтернативный вариант для односторонних объектов

Того же эффекта, но без поворота актора по направлению движения, можно добиться и так:

int i = 0;
script 1 ENTER
{
if (ThingCount(T_NONE, 678) == 0) {
Thing_Spawn(16, T_BFGSHOT, 64, 678);
i=0;
} else {
i--;
ThrustThing(i, 10, 0, 678); Delay(1);
Thing_Stop(678);
}
restart;
}

Что тут происходит: Мы задаём переменную i, равную нулю, и она глобальная. Кстати, глобально можно ей значение не присваивать, об этом позже.

Знакомый нам синтаксис if-else; также создаём объект всякий раз, когда его нет. И всякий этот раз обнуляем i. А когда объект есть, начинается цикл, каждый виток которого этот наш непонятный i уменьшается на единичку. Как и со сдвигами, в языках программирования прибавление и вычитание единицы можно заменить инкрементом (++) или декрементом (—) соответственно. То есть это i— = i-1;

Потому что на самом деле эти функции приводят переменную к следующему значению в ряду, частью которого она является. Так, наша переменная выражена целым числом, возможный ряд: 0, 1, 2, 3, 4, 5, 6 и так далее. Т.о. 0++=1, 1++=2, 2++=3…

Ничего на самом деле не знаю, а понтуюсь… Да, я такой.

Ну а дальше всё просто: толкаем предмет в направлении нашего i с силой 10, после однотиковой задержки сбрасываем его внутреннюю дурь (Thing_Stop) и перезапускаем цикл. В этом случае происходит уменьшение значения i исходя из полученного в предыдущем витке цикла. Таким образом наш объект постоянно перемещается на угол меньший (или больший, смотря какое направление относительно часовой стрелки нужно), чем нулевой, но вокруг своей оси не вращается. То есть, как я уже намекнул, (i—) — летит по часовой стрелке, а (i++) — полетит против часовой.

Также можно калибровать скорость. Задайте силу толчка =1, а значение угла (i) разделите на 15:

ThrustThing(i / 15, 1, 0, 678);

И ваш шарик будет двигаться по аналогичной траектории, но мееедленно. Умножьте i на 3, а силу задайте в 25 — шар полетит по схожей орбите, но быстро. Калибруем на глаз, уж простите, закономерности я не установил. Но кто знает, может, именно вы её найдёте?

Ну и в завершение — третья карта. Принципиальная разница — лишь в оформлении снаряда. Предлагаю самим понять, что там происходит. Но согласитесь — весело выглядит. Пссс… кстаааати.. а подставьте монстра вместо фаербола и зацените зрелище… А ещё обратите внимание, как используется принцип массивов, спасибо Болону за напутствие. Псссс… для этого нужно нажимать много раз кнопку на карте, где она есть…

Ну а если вы зайдёте в группу в ВК, то можете там в обсуждениях найти вариант, как сделать снаряд, который движется по точкам следования. Рекомендую, там мы отвечаем на вопросы, если можем. Если не можем, то тоже отвечаем. Всем спасибо!

avatar

Пожалуйста отключи блокировщик рекламы, или внеси сайт в белый список!

Please disable your adblocker or whitelist this site!