#pun-title h1 span {display: none;}

Союз | Union

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Союз | Union » Картостроение и скриптовка » Как перестать бояться и начать скриптить


Как перестать бояться и начать скриптить

Сообщений 1 страница 10 из 10

1

Проблема скриптования стоит ещё более остро, чем рисование новых карт, так что эта тема призвана подтянуть общее понимание того, как писать качественные и работоспособные скрипты.

   Можно подумать, что чтобы написать качественный сценарий - нужно иметь высшее образование в области программирования, но спешу порадовать вас - это не так :)

   Однако, здесь я не буду досконально разжёвывать как написать ф-цию и вообще запустить скрипт - на форуме такая информация уже имеется и причём очень хорошо изложенная, здесь я лишь дам ссылки и, может быть некоторые комментарии к тому, что было написано ранее.
Итак, начнём.
   А с чего вообще начинается написание скрипта? Конечно же, с самого файла. По сути, скрипт - текстовый файл расширения lua с прописанным внутри кодом. Для написания своего сценарий сгодится даже обычный блокнот, но лучше использовать специальную программу, которая будет в этом архиве. Кроме того, там вы сможете найти "библию" любого картодела по скриптам, где описаны все применяемые команды/операторы(их так же можно именовать функциями) и справочник по редактору карт.
   Товарищ Reks, ныне ушедший от нас, уже давно создал тему, в которой очень доступно и понятно объяснил самые основы, поэтому привожу ссылки на самые нужные нам сообщения: 1, 2, 3
   Для начала внимательно прочитайте и переварите написанное. В принципе, это всё довольно легко и просто.

   От себя лишь добавлю теоретическую часть для понимания, как вообще происходит работа скрипта, и зачем нужен оператор Suicide()

   Язык lua таков, что он выполняется циклически, то есть весь файл работает сразу от начала и до конца, и так происходит много-много раз. В большинстве языков программирования выполнение идёт построчно, то есть есть одна главная программа, которая построчно выполняется. В нашем случае такого нет, у нас всё работает по функциям. "В чём разница?", - спросите вы. А в том, что мы имеем инициализирющую функцию под названием Init, из которой начинают запускаться другие функции. Init запускается вместе с миссией и его не надо вызывать самостоятельно. Если мы запускаем свою какую-нибудь функцию(слишком много функций, но вы терпите %-) ), например, RunScript("Attack1", 3000), то эта самая Attack1 будет запускаться каждые 3000 миллисекунд, при условии, если внутри не будет прописан Suicide(), то есть:

function Init()
   RunScript("Attack1",3000);
   RunScript("Attack2",3000);
end;

function Attack1()
   DicplayTrace("Началась атака 1");
end;

function Attack2()
   DicplayTrace("Началась атака 2");
   Suicide();
end;

В данном примере, при запуске карты, на экране сообщение "Началась атака 1" будет появляться каждые 3 секунды, а сообщение "Началась атака 2" будет выведена лишь единожды.

   Suicide() - это такая замечательная штука, которая не даёт функциям зацикливаться, но с ней есть несколько тонкостей, которые важно понимать для построения правильного алгоритма.
Когда выполняется этот оператор, ф-ция моментально выключается, однако, её можно будет вновь использовать для вызова. Вызов функции двумя RunScript подряд будет приводить к зацикливанию даже при наличии в ней Суицидов. Например, если в Init записать RunScript("Attack2",3000); дважды.
   Если вам нужно вызвать функцию определённое кол-во раз, то стоит дописать параметр после время вызова, который будет означать кол-ва срабатываний нужной функции RunScript("Attack2",3000,3);
   А вот если нужен постоянный запуск, например для проверки какого-то условия через if, то в такой функции Suicide() не нужен, а вернее, нужен, но в другом месте.

function Init()
   RunScript("Proverka1",3000);
end;

function Proverka1()
   if (GetNUnitsInArea(0,"zona") > 0) then
      DisplayTrace("В зоне");
      Suicide();
   end;
end;

В данном случае Proverka1 будет запускаться каждые 3 секунды, пока не выполнится условие, то есть хотя бы один юнит стороны 0 не окажется в зоне "zona". Без Суицида проверка будет работать дальше и выдавать сообщение "В зоне" каждые 3 секунды, пока хотя бы один юнит находится в зоне. А вот если Суицид поместить между двумя end;, то проверка отработает лишь один раз после первого запуска и больше работать не будет, по сути своей это будет одноразовая проверка условия(не циклическая). На этом принципе строится выполнение простейших заданий, то есть пока врага сколько-то или он где-то, то происходит проверка на наличие юнитов в скриптовой группе или зоне(зависит от задания).
   Это самые базовые знания для создания простеньких сценариев плана захватить то, уничтожить это, удержать вот здесь, доставить туда. На простеньком примере опишу каждое из таких заданий (открывайте руководство по луа и разбирайте все ключевые слова по ходу):

function Init()
RunScript("Obj0", 3000);
end;

function Obj0()
ObjectiveChanged(0,0) -- Выдача первого(нумерация начинается с нуля) задания, у нас это будет, допустим, захват деревни
RunScript("Obj0end", 3000); -- Запуск проверки выполнения первого задания
Suicide();
end;

function Obj0end()
if GetNUnitsInArea(1,"Village") < 2 then -- Если в зоне Village, которую мы рисуем в редакторе карт, юнитов врага меньше 2, то выполняется задание и выдаётся следующее. Желательно не писать в одной функции два и более ObjectiveChanged, иначе не будет появлятся окошко с заданием(не влияет на геймплей)
    ObjectiveChanged(0,1); -- Отображение завершения первого
    RunScript("Obj1", 3000); -- Запуск второго задания
    Suicide();
end;
end;

function Obj1()
ObjectiveChanged(1,0); -- Выдача второго задания, допустим, уничтожить стоящую на месте немецкую колонну
RunScript("Obj1end", 3000); -- Запуск проверки второго задания
Suicide();
end;

function Obj1end()
if GetNUnitsInScriptGroup(100) < 1 then --Если юнитов(грузовиков, например) с номером 100 меньше 1(можно так же писать "== 0", но если случайно потерять одно из двух "=", то скрипт не запустится вообще), то задание выполняется
    ObjectiveChanged(1,1);
    LandReinforcement(1); --Подкрепление для врага, задаётся в редакторе карт(см. свиток Reinforcement Groups в редакторе карт)
    RunScript("Attack", 10000); --Команды для юнитов подкрепления нужно задавать не сразу, а через некоторое время, потому что юниты развёртываются не мгновенно, а постепенно, и если команда будет задана пока юнита нет на карте, то он её не получит вообще.
    RunScript("Obj2", 3000);
    Suicide();
end;
end;

function Obj2()
ObjectiveChanged(2,0); -- Выдача третьего задания, допустим, удерживать деревню в течении 5 минут
RunScript("Obj2end", 300000); -- Запуск проверки третьего задания(по факту там нет проверки, а сразу выполение через 5 минут)
RunScript("Proval", 10000); -- Запуск проверки провала задания
Suicide();
end;

function Attack()
Cmd(19,10000,1,GetScriptAreaParams("Village")); -- Вызов бомбардировщиков, второй параметр для команд с авиацией всегда равен 10000, GetScriptAreaParams можно применять вместо координат
Cmd(3,20,GetScriptAreaParams("AttackPos")); -- Предположим, что все юниты в подкреплении имели номер 20, все атакующие будут идти в одну точку. Как сделать атаку более реалистичной будет расказано в следующем посте.
Suicide();
end;

function Proval()
if GetNUnitsInArea(0,"Village") < 2 then -- Если юнитов игрока в деревне будет меньше 2-х, то поражение
    ObjectiveChanged(2,2); -- Невыполнение задачи
    Loose(); -- Вызов окошка поражения
    Suicide();
end;
end;

function Obj2end()
ObjectiveChanged(2,1); -- Выполнение третьего задания
KillScript("Proval"); -- Принудительная остановка проверки на невыполнение
RunScript("Obj3", 3000); -- Запуск четвёртого задания
Suicide();
end;

function Obj3()
ObjectiveChanged(3,0); -- Выдача четвёртого задания, допустим, доставить прибывшего офицера к штабу
LandReinforcement(2); -- Прибытие офицера
RunScript("Obj3end", 3000); -- Запуск проверки четвёртого задания
Suicide();
end;

function Obj3end()
if GetNScriptUnitsInArea(80,"Shtab") > 0 then -- Если офицер с сриптовым номером 80 прибудет в зону "Shtab", то победа
    ObjectiveChanged(3,1); -- Выполнение четвёртого задания
    Win(0); -- Вывод окошка победы
    Suicide();
end;
end;

Такой скрипт пишется за полчаса. Как видим, не используя чего-то сверхсложного можно получить вполне интересную миссию ^^
    В следующих сообщениях постараюсь осветить такую тему, как переменные и использование готовых кусков скрипта, которые я буду приводить далее.
    Здесь можете задавать любые вопросы касательно скриптов, идей для каких-либо алгоритмов, работы каких-то конкретных функций и т.д.

+5

2

Для создания простых последовательных сценариев достаточно знание базовых приёмов, описанных выше, но что если хочется чего-нибудь необычного или нелинейного? Вот как раз для таких целей нам и пригодятся переменные.
   Что же такое эти самые переменные? Это ничто иное, как ячейки данных, с произвольным значением. Или, проще говоря, это те самые переменные из алгебры, в которые можно подставлять различные числа. У нас на вооружении имеются два типа переменных: локальные и глобальные.
   Начнём с локальных. Область их видимости, т.е. где эти переменные будут работать, ограничивается функцией, в которой они прописаны. Общий синтаксис выглядит так:

function test()
local a,b;
a = 10;
b = 20;
...

В справочнике по луа есть более подробные данные о синтаксисе и правильности применения переменных, настоятельно рекомендую ознакомиться с данным разделом. Тут я перейду к практическому использованию этой темы.
   Чем же на практике нам помогут переменные? А тем, что они позволяют считать нам какие-то определённые юниты, давать им импровизированные состояния и передавать информацию в другие части скрипта или оптимизировать код, в общем расширяют наши возможности по созданию необычных алгоритмов, которые было бы либо трудно реализовать без них, либо вообще невозможно. Тут без примеров не обойтись, и начнём мы, пожалуй, с простеньких оптимизаций, заодно расскажу о такой крайне полезной штуке, как цикл for.
   Допустим, у нас есть 20 пар юнитов типа танк-сквад. Каждый сквад должен следовать за своим танком, но как удобнее это сделать? Написать 20 строчек Cmd(39,*номер сквада*,*номер танка*)? А если таких пар 40? или 100? Не очень удобно копировать и вставлять одно и то же, но для решения этой задачи у нас есть очень хороший инструмент. Цикл for - это по сути счётчик. При нём обязательно есть переменная, которая увеличивается(или уменьшается) с каждым циклом. Присвоим сквадам чётные номера начиная со 100, а танкам нечётные, таким образом получится, что у нас заняты все номера от 100 до 139. Этим мы и воспользуемся. Цикл for выглядит следующим образом:

function Attack()
local i;
   for i = 1,20,1 do
      Cmd(39, 98+i*2, 99+i*2);
      Cmd(3, 99+i*2, GetScriptAreaParams("zona"..i));
   end;
   Suicide()
end;

Что же в этой абрадакабре происходит? А происходит вот что: объявляется переменная i, после чего запускается цикл for. Первый параметр - это начальное значение счётчика, которое будет применено на первой итерации цикла, второе значение - конечное значение i, то есть последнее значение перед выходом из цикла, третий параметр - приращение, другими словами, насколько будет увеличиваться переменная i после каждой итерации. А теперь к тому, что вообще происходит. Вот выполнился вход в цикл, переменная i имеет значение 1. Теперь берётся первая команда Cmd. Внутри неё, как видите, находятся не просто скриптовые номера, а выражения. Посчитаем же их: 98+1*2 = 100, 99+1*2 = 101. Как раз номера первой пары юнитов. Теперь пехота следует за танком. Следующая строчка задаёт координаты атаки танка, разберём и её: во втором параметре такое же выражение, как и в первом, то есть равно 101, а вот дальше идёт что-то необычное. На карте мы имеем зоны zona1, zona2, zona3... для каждой пары юнитов. Здесь же мы при помощи цикла задаём каждому танку свою зону всего лишь в одной строчке. В скобочках строка "zona" "складывается" со значением i и в итоге получается "zona1". Две точки означают сложение строк(об этом тоже подробнее рассказано в руководстве по луа). Далее первая итерация цикла заканчивается, переменная i увеличивается на 1 и становится равной 2. Цикл начинается снова, но теперь i = 2. Номера юнитов становятся равны 98+2*2=102, 99+2*2=103, а зона атаки "zona2" и таким образом итерации продолжаются дальше, пока i не станет равна 20, тогда произойдёт последняя итерация и цикл завершится. В результате мы имеем вместо 40 строк кода всего лишь 5, все сквады следуют за своими танками, а все танки идут в атаку к своим зонам. И чем больше пар, тем лучше мы экономим своё время и место в скрипте, и выглядит это намного приятней, согласитесь :)

   Следующий интересный предмет, который мы разберём, будет рандом. Рандом в переводе с английского - случайность, а в контексте скриптования это означает механизм создания случайных событий. Разберёмся же, зачем нам это нужно. Иногда хочется внести какой-то непредсказуемости в сценарий, чтобы игрок не мог предугадать, откуда пойдёт атака, или же у игрока выпадала случайная авиация для пользования, вот тут-то рандом нам и пригодится. Синтаксически эта конструкция выглядит так: local x = RandomInt(n); где n - любое целое (не дробное) число. В результате переменная x примет любое случайно значение в диапазоне от 0 до n-1. Пример скрипта для случайной атаки тут. Так же рандом можно применять для более случайного момента вызова функции внутри RunScript, например, RunScript("Attack", 60000 + RandomInt(120000)); В результате скрипт атаки запустится в случайном промежутке времени между одной и тремя минутами.

   Локальные переменные годятся для того, чтобы подсчитать что-то в пределах одного запуска функции, но мы не сможем сосчитать с её помощью, сколько раз функция запускалась или что-то, что выходит за рамки одной функции, и тут нам на помощь приходит глобальная переменная.
   Глобальные переменные в этом плане очень хорошие помощники, потому что область их видимости - весь скрипт, то есть применить их можно буквально из любого места. Создание и задание им значений происходит через ключевое слово SetIGlobalVar("имя переменной", значение);, а считывание через GetIGlobalVar("имя переменной", 0); Второе ключевое слово никак не изменяет переменную, а лишь возвращает её значение или же 0, если переменной с таким именем не существует. Применять это можно очень много где, и у меня не хватит фантазии, чтобы описать все возможности этого инструмента, постепенно вы сами научитесь читать чужие скрипты и сможете увидеть много новых фишек или же сами начнёте придумывать свои алгоритмы, а здесь я ограничусь небольшим примером. Допустим, у нас есть 4 задания, которые можно выполнять в любом порядке, а победа должна быть присвоена игроку, когда тот выполнит все 4. Наш код будет выглядеть так:

function Init()
SetIGlobalVar("temp.Objectives",0); -- Переменная, которая будет считать кол-во выполненных заданий
RunScript("Obj0", 3000);
RunScript("Obj1", 6000);
RunScript("Obj2", 9000);
RunScript("Obj3", 12000);
RunScript("ToWin", 3000);
end;

function ToWin()
if GetIGlobalVar("temp.Objectives", 0) == 4 then -- Проверка того, что все задания выполнены
    Win(0);
    Suicide
end;
end;

function Obj0()
ObjectiveChanged(0,0)
RunScript("Obj0end", 3000);
end;

function Obj0end()
if GetNUnitsInArea(1,"Village1") < 2 then
    ObjectiveChanged(0,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1); -- При выполнении задания переменная увеличивается на единицу. Значение внутри SetIGlobalVar задаётся при помощи GetIGlobalVar("temp.Objectives",0) + 1, то есть берётся текущее значение этой переменной и к ней прибавляется единица.
    Suicide();
end;
end;

function Obj1()
ObjectiveChanged(1,0)
RunScript("Obj1end", 3000);
end;

function Obj1end()
if GetNUnitsInArea(1,"Village2") < 2 then
    ObjectiveChanged(1,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1);
    Suicide();
end;
end;

function Obj2()
ObjectiveChanged(2,0)
RunScript("Obj2end", 3000);
end;

function Obj2end()
if GetNUnitsInArea(1,"Village3") < 2 then
    ObjectiveChanged(2,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1);
    Suicide();
end;
end;

function Obj3()
ObjectiveChanged(3,0)
RunScript("Obj3end", 3000);
end;

function Obj3end()
if GetNUnitsInArea(1,"Village4") < 2 then
    ObjectiveChanged(3,1);
    SetIGlobalVar("temp.Objectives", GetIGlobalVar("temp.Objectives",0) + 1);
    Suicide();
end;
end;


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

Отредактировано ИС 3 (2017-02-16 21:50:37)

+2

3

Переходим к самому вкусному. Сразу говорю, что нижеописанные инструменты ещё не изучены по полной программе, так что содержимое может дополняться и меняться.
   Для начала экскурс в программирование. Что мы вообще называем функцией? В классическом понимании, функция - это подпрограмма, которая возвращает значение. Кроме того, существует термин процедура - подпрограмма, которая НЕ возвращает значение. В нашем конкретном случае такого понятия, как процедура, формально не существует, но её роль у нас выполняет функция, то есть функция в луа универсальна, она  может как возвращать значение, так и не возвращать. В функции или процедуры, обычно, входит ряд параметров(но может и не входить), это мы можем видеть на примере любых ключевых слов из справочника, скажем, ChangePlayer(номер, сторона), что по факту является процедурой, потому что не возвращает значения, или GetNUnitsInScriptGroup(номер) - функция, которая возвращает кол-во юнитов в сценарной группе. В данном случае, номер и сторона как раз и являются параметрами, и что самое главное, мы сами можем писать такие же процедуры и функции самостоятельно! Дальше речь пойдёт именно о процедурах. Увы, мы не можем влезать в код игры, а можем лишь оптимизировать и упрощать с их помощью построение скрипта. Самая главная прелесть этого метода в том, что мы прописываем в скрипте нашу процедуру лишь единожды, а потом используем её из любого места скрипта. Но у этого метода есть много тонкостей и работа с ним отличается от простого использования функций без параметров, как мы обычно делаем. Далее расскажу на конкретных примерах, начну с простенького:

function Init()
RunScript("prepAt", 3000);
end;

function prepAt()
local i;
i = 1; -- Номер первого юнита
At(i);
i = 2; -- номер второго юнита
At(i);
Suicide();
end;

function At(i) -- Наша процедура
Cmd(0,i,GetScriptAreaParams("123"..i)); -- Зоны 1231 и 1232 для первого и второго юнита соответственно
end;

У нас есть наша процедура At(i), которая даёт команду юниту на движение в центр зоны с номером "123"..i . Запуск таких процедур отличается от запуска обычных функций тем, что не нужно прописывать RunScript. Соответственно, процедура стартует сразу же и её запуск нельзя отложить, а что самое главное, она не уходит в цикл! Да, в ней можно не писать Суицид, однако опять же, существует ряд тонкостей, о них попозже, пока только главный принцип. Итак, у нас запускается сразу две функции At(i) и причём они не пересекаются и не зацикливаются. Это был мой первый эксперимент на проверку работоспособности концепции в целом. В справочнике по луа об этом упоминается лишь пара слов без подробностей, а сам такой метод можно найти только на картах товарищей Jukov и Ilyaka (может быть, есть ещё у кого-то, но я не видел). Но это лишь простенькое использование, дальше - больше.
   В процедурах всё таки можно использовать зацикливания, но для этого нужны дополнительные манипуляции. Дальше вы посмотрите на скрипт атаки для одной пары типа танк-сквад с использованием координат(то есть танк будет ждать, пока пехота его догонит) на одну зону. Такие скрипты я давно использую на своих картах, но копировать и вставлять такой большой кусок для каждой(!) пары на карте было весьма утомительно, а новый метод позволит значительно уменьшить визуальную загруженность кода и сделает его более понятным и удобным. Проблема состояла в том, что сложные блоки скриптов зачастую циклические и требует постоянной проверки, а процедуры не умеют зацикливаться, что же тогда делать? А выход оказался довольно прост. Ниже представлен скрипт для двух пар:

function Init()
RunScript("Attack1", 5000); -- Вспомогательная функция для первой пары
RunScript("Attack2", 5000); -- Вспомогательная функция для второй пары
SetIGlobalVar("const",400); -- Константа, задающая максимальное расстояние между сквадом и танком, прописывается один раз в скрипте обязательно!!!
end;

function AttTInf(t,inf,zone) -- Сама наша процедура, как я и говорил, в ней даже можете не разбираться. Никто вам не запрещает, и очень даже желательно понять, как она работает, но в этом посте разжёвывать я её не буду. Входные параметры такие: t - номер танка, inf - номер сквада, zone - название зоны.
local tx, ty, infx, infy, S, n;
if (GetNUnitsInScriptGroup(t) > 0) then
    if (GetNUnitsInScriptGroup(inf) > 0) then   
        tx, ty = GetObjCoord(t);
        infx, infy = GetObjCoord(inf);
        n = (tx - infx)*(tx - infx) + (ty - infy)*(ty - infy);
        if (n > GetIGlobalVar("const",0)*GetIGlobalVar("const",0)) then
            Cmd(50,t);
        else
            Cmd(3,t,GetScriptAreaParams(zone));
        end;
    else
    Cmd(3,t,GetScriptAreaParams(zone));
    end;
else
    if (GetNUnitsInScriptGroup(inf) > 0) then
    Cmd(3,inf,GetScriptAreaParams(zone));
    else
    Suicide();
    end;
end;
end;

function Attack1()
local t,inf,zone;
t = 100;
inf = 101;
zone = 100;
Cmd(39, inf, t); -- Задаём команду пехоте следовать за танком
AttTInf(t,inf,zone); -- Запуск процедуру
end;

function Attack2()
local t,inf,zone;
t = 102;
inf = 103;
zone = 102;
Cmd(39, inf, t);  -- Задаём команду пехоте следовать за танком
AttTInf(t,inf,zone); -- Запуск процедуру
end;

Сходу напрашивается вопрос: зачем нам нужны вспомогательные функции для каждой пары, не проще ли запустить процедуры через for сразу в одном месте, и почему в них нет суицида? Это и есть те самые тонкости, о которых я упоминал. Как я уже и говорил, процедуры не зацикливаются, и поэтому нам нужно зациклить их вызов, для этого и нужны вспомогательные функции без Суицидов. Кроме того, есть такая неприятная особенность, что одна процедура, запущенная несколько раз из одной функции, при срабатывании Suicide() внутри процедуры, убивается сразу для всех запусков, то есть если мы вызовем все процедуры через for и у одной из них отработает Suicide(), то все вызванные процедуры завершат свою работу тут же. Для этого, мы и выносим вызов каждой из них в отдельную функцию, потому что тогда процедуры становятся "как бы" разными и работают независимо друг от друга. В общем так этот метод работает. Кстати, эта первая процедура, которую вы можете использовать в своих картах. Сами вспомогательные процедуры свободно можно вызывать через for, после чего нужно будет накопировать столько функций Attack, сколько будет пар юнитов, к каждой прописать нужные параметры. Да, всё равно это копирование одного куска, но сравните копирование 50 строк и 8 по нескольку раз. Разница очевидна.
   Теперь небольшая ремарочка по поводу функций(те которые возвращают значение). В справочник есть следующие строки:

Функция, возвращающая значение:
function “имя_функции” (…)
тело функции;
return возвращаемое_значение;
end;

Это значит, что теоретически можно будет делать некие алгоритмы, которые будут возвращать число, например, a = Func(i), где Func(i) некая функция, в которой что-то обсчитывается и в результате получается число, которое можно положить в переменную(Так работают все функции, типа GetNUnitsInArea(), GetNUnitsInScriptGroup() и т.д.) . Пока я не вижу реального применения этого метода, но желающие могут поэкспериментировать в этом направлении.
   На этом моё изложение теории заканчивается. Опять же повторюсь, если возникнут вопросы, то пишите здесь, постараюсь разжевать подробней и с примерами. Часть с применением кусков скриптов на практике всё же перенесу в следующий пост.

+3

4

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

Клац

Ниже приведён скрипт для одной атаки на n пар атакующих. Для следующих атак применяйте сквозную нумерацию зон.

function Init()
RunScript("prepAt1", 3000); -- Скрипт запуска вспомогательных функций для одной атаки
SetIGlobalVar("const",400); -- Та же константа, что и в примере до этого, не забывать про неё!
end;

function prepAt1()
local i;
for i = 1,n,1 do -- Здесь n - это количество пар в атаке, не забудьте указать! Для следующего скрипта атаки prepAt2 начальное i будет равно n+1, то есть for i = 4,..., если в прошлой атаке было 3 пары
SetIGlobalVar("zone"..i.."01",i*100+1); -- В этих глобальных переменных мы будем держать информацию о текущих зонах для каждой пары. Зоны на карте в этом случае будут называться так: "101", "102" ... "109" для первой пары, "201","202"..."209" для второй и т.д. Кол-во зон определяется в функциях Attack.
RunScript("Attack"..i, 5000); -- Функции будут носить названия Attack1, Attack2, ... AttackN, где N - поседняя пара
end;
Suicide();
end;

function Attack1() -- Будет копироваться для вызова процедуры для каждой пары атакующих(для каждой меняйте индекс в названии и параметры внутри)
local t,inf,startZ,finZ;
t = 100; -- номера юнитов используйте по вкусу, для каждого юнита свой номер!
inf = 101;
startZ = 101; -- Первая зона для первой пары
finZ = 103; -- Третья зона. Их может быть до 9 штук. Напоминаю, что следующая пара будет иметь зоны "201","202"..."209"
Cmd(39, inf, t); -- Задаём команду пехоте следовать за танком
AttTInfZ(t,inf,startZ,finZ); -- Запуск процедуры
end;

function Attack2()
.
.
.
function AttackN() -- Последняя пара
.
.
.
function AttTInfZ(t,inf,startZ,finZ) -- Собственно сама процедура, её нужно вставить в скрипт лишь один раз.
local tx, ty, infx, infy, S, n;
if (GetNUnitsInScriptGroup(t) > 0) then
    if (GetNUnitsInScriptGroup(inf) > 0) then   
        tx, ty = GetObjCoord(t);
        infx, infy = GetObjCoord(inf);
        n = (tx - infx)*(tx - infx) + (ty - infy)*(ty - infy);
        if (n > GetIGlobalVar("const",0)*GetIGlobalVar("const",0)) then
            Cmd(50,t);
        else
            if GetNScriptUnitsInArea(t,GetIGlobalVar("zone"..startZ,0)) > 0 then
            if GetIGlobalVar("zone"..startZ,0) == finZ then
                Suicide();
            else
                SetIGlobalVar("zone"..startZ, GetIGlobalVar("zone"..startZ,0) + 1);
                Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
            end;
            else
            Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
            end;
        end;
    else
    if GetNScriptUnitsInArea(t,GetIGlobalVar("zone"..startZ,0)) > 0 then
        if GetIGlobalVar("zone"..startZ,0) == finZ then
        Suicide();
        else
        SetIGlobalVar("zone"..startZ, GetIGlobalVar("zone"..startZ,0) + 1);
        Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
        end;
    else
        Cmd(3,t,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
    end;
    end;
else
    if (GetNUnitsInScriptGroup(inf) > 0) then
    if GetNScriptUnitsInArea(inf,GetIGlobalVar("zone"..startZ,0)) > 0 then
        if GetIGlobalVar("zone"..startZ,0) == finZ then
        Suicide();
        else
        SetIGlobalVar("zone"..startZ, GetIGlobalVar("zone"..startZ,0) + 1);
        Cmd(3,inf,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
        end;
    else
        Cmd(3,inf,GetScriptAreaParams(GetIGlobalVar("zone"..startZ,0)));
    end;
    else
    Suicide();
    end;
end;
end;

+4

5

судя по всему, все задавлены интеллектом Докладчика и отходят от шока, вопросы будут задавать позже  :love:

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

кубик в копилку: счетчик времени на исполнение задания

таймер

объявление глобалок в предыдущих функциях
SetIGlobalVar("temp.start",1); --переключатель к стартовому условию
SetIGlobalVar("temp.timer",11); --установка таймера, минуты

function Init()
RunScript("defendZone", 60000);
end;
function defendZone()
if GetIGlobalVar("temp.start",0)==1 then --стартовое условие для начала отслеживания
if GetNUnitsInArea(0,"zone")<1 then -- ключевое условие задания = старт таймера
    if GetIGlobalVar("temp.timer",0)==0 then -- время закончилось
    RunScript("toLoose",5000); -- наказание
    else
    DisplayTrace("Срочно верните охранение на занятые позиции!");
    DisplayTrace("Иначе Вы будете отстранены от командования!");
    a=GetIGlobalVar("temp.timer",0)-1; -- счетчик
    DisplayTrace("На выполнение осталось %g мин!",a);
    SetIGlobalVar("temp.timer",a);
    end;
    else
    SetIGlobalVar("temp.timer",11); -- восстановление таймера, минуты
end;
end;
end;
function toLoose() -- функция наказания
Loose(0);
suicide();
end;

Работает это просто,
1. таймер начинает контроль когда глобальная переменная temp.start  равна 1 (можно название заменить на Objective0 тогда контроль начнет работать после выполнении первого задания (Objective6 - седьмого и т.п.).
2. Затем начинается отслеживание ключевого условия (их может быть много через and, or) для старта счетчика минут, в нашем случае это отсутствие юнитов Игрока в zone (область рисуется в редакторе)
3. Если время кончается, а ключевое условие не выполнено, то Игрок получает наказание.
3а. Если Игрок выполняет условие (занял зону), то таймер обновляется и так до бесконечности.
4. Если по сценарию проверка уже не нужна, не забудьте прописать в какой нибудь другой функции далее по сюжету, строку
KillScript("defendZone");

+5

6

Возможно, очень глупый вопрос, наверняка где-то здесь уже был задан, но:
Как заставить юнит окопаться по прибытию в место назначения?
Я пишу в скрипте:

Код:
Cmd (0, 102, 860, 568);
QCmd (45, 102, 860, 568);

Он доезжает до точки, но не окапывается!
Пробовал и без координат - безрезультатно!

Отредактировано BalashovM (2017-05-25 01:49:44)

0

7

BalashovM
Скорее всего эта команда не реализована для использования в скриптах, так что дать её можно только через редактор карт.

0

8

ИС 3 написал(а):

BalashovM
Скорее всего эта команда не реализована для использования в скриптах, так что дать её можно только через редактор карт.

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

0

9

BalashovM написал(а):

Но вот только зачем разрабы ее тогда включали в список возможных команд?

А где она упоминается? В справочнике Жукова о ней не написано.

0

10

ИС 3 написал(а):

А где она упоминается? В справочнике Жукова о ней не написано.

В оригинальном мануале от разрабов, к редактору карт. Список команд, команда №45 - окопаться.

0


Вы здесь » Союз | Union » Картостроение и скриптовка » Как перестать бояться и начать скриптить