Учебник по созданию скриптов
Для начала скажу, что, приступая к изучению встроенного в ОФП языка скриптов, Вы уже должны прекрасно уметь работать во встроенном редакторе ОФП. Вы должны знать, как создавать и называть объекты, что такое триггер и маркер и зачем они нужны и т.д.…
Что же такое скрипт? Скрипт – внешний файл, написанный на встроенном в игру языке программирования, таком же, как и Turbo Pascal или Basic. В этом файле находится нечто напоминающее план действий для игры - последовательность команд и операторов. С помощью этого планы Вы “говорите” игровому движку, что ему надо сделать. В целом скрипт – файл, содержащий некую информацию для игры.
Зачем же нужны скрипты, если можно использовать триггеры, вейпоинты и другие встроенные в сам редактор ОФП средства? Во-первых, скрипты выглядят намного красивее, чем нагромождение триггеров и вейпоинтов. Во-вторых, скрипты – универсальны, то есть, один раз написав скрипт, Вы можете использовать его в разных миссиях. В-третьих, скрипты меньше загружают процессор. И, в-четвертых, скрипты позволяют сделать гораздо больше, чем те же триггеры.
Ну, предположим мы написали очень интересный скрипт, но как теперь его использовать в игре? Как я уже говорил скрипт – это всего лишь файл с расширением “sqs”. Например, myscript.sqs. Что бы использовать скрипт в игре нужно его скопировать в каталог с вашей миссией. Обычно это каталог “Operation Flashpoint\Users\{ваше имя в игре}\{название вашей миссии}”. Например “C:\Program Files\Codemasters\Operation Flashpoint\Users\Sanek\MyMission.Intro”.
| Название
миссии |
Остров |
| Eden |
Malden |
Kolgujev |
Desert
Desert Island
|
Nogova |
| Название каталога с миссией |
| Inprison |
Inprison.Eden |
Inprison.Abel |
Inprison.Cain |
Inprison.Intro |
Inprison.noe |
| Ambush |
Ambush.Eden |
Ambush.Abel |
Ambush.Cain |
Ambush.Intro |
Ambush.noe |
| Test |
test.Eden |
test.Abel |
test.Cain |
test.Intro |
Test.noe |
Итак, скрипт переписан в каталог с миссией и его осталось только лишь вызвать из игры. Но как? А вот так:
[arg1,arg2,arg3…argN] exec “ScriptName.sqs”
Arg1,arg2,arg3 и argN – аргументы передаваемые в скрипт. Аргументы, скажем так, это те объекты, числа, переменные и т.п. с которыми мы будем работать в скрипте (конечно помимо этих переменных в скрипте будут использоваться и другие). Аргументы передаются в скрипт из игры. То есть если в игре создана какая-нибудь переменная или объект, то они могут быть переданы в скрипт, для этого их нужно лишь указать в списке аргументов. Exec – ключевое слово, а “ScriptName.sqs” – название вашего скрипта. Скрипты можно вызывать из полей, On Activation, On Deactivation, Script(поле в вейпоинте). Приступим к рассмотрению нашего первого скрипта!
Hello.sqs
;этот скрипт выводит на экран сообщение “Hello!”
;вывод на экран сообщения
TitleText [“Hello!”, ”plain down”]
;выход
Exit
;Запуск скрипта: [] exec "Hello.sqs"
Вот и наш первый скрипт. Рассмотрим его повнимательнее. Для начала запомните символ “;” – этот символ, поставленный в начале строки позволяет делать комментарии – то есть текст, который не будет считываться игрой. Комментарии помогут Вам самим не запутаться при написании скрипта и облегчат другим понимание вашего скрипта. Но все же основная роль этого символа – разделение команд:
Unit1 dofire Unit2; Unit2 dofire Unit1
Хотя чаще всего каждую новую команду пишут с новой строчки и тогда “;” не ставится.
В следующей строке мы видим команду “TitleText”. Эта команда позволяет выводить на экран во время игры различные сообщения. Она имеет следующий синтаксис:
TitleText [“Необходимый текст в кавычках”,”Позиция текста на экране”, скорость]
Существуют две известные мне позиции: “PLAIN” – в центре экрана или “PLAIN DOWN” – внизу экрана. Скорость – скорость появления текста. Чем больше число – тем дольше текст будет появляться на экране. Если скорость не указана она будет установлена по-умолчанию.
Далее идет ключевое слово “Exit”. Это ключевое слово завершает выполнение скрипта. Причем это ключевое слово не обязательно должно стоять в конце скрипта, оно может быть и в середине скрипта.
Теперь посмотрим на работу скрипта в самой игре. Для чего сначала создадим в редакторе миссию и поставим всего лишь один юнит. Затем можно сохранять миссию. Пускай она называется Test а остров, на котором мы ее делали – Desert Island (следовательно, каталог с миссией будет называться Test.Intro ). Далее ОФП (Alt+Tab) и найдем каталог с миссией и в этом каталоге создадим файл (можно Блокнотом или встроенным в файловую оболочку редактором – как правило, с помощью нажатия Shift+F4) с именем “Hello.sqs”. Наберем в этом файле текст вышеописанного скрипта (я настоятельно Вам рекомендую самим набирать текст, а не копировать его через буфер обмена – это поможет Вам лучше его понять и запомнить команды) и сохраним его в каталоге с вашей миссией. Теперь снова вернемся к ОФП. В поле Initalization поставленного юнита пропишем следующую строку:
[] exec “Hello.sqs”
В данной строке вызова скрипта отсутствуют аргументы для передачи в скрипт (какой-либо текст в квадратных скобках). Так как нашему первому скрипту не нужны аргументы, он вызывается, так, как описано выше, то есть вместо аргументов ставится пробел (или ничего не ставится).
Наконец сохраните миссию и нажмите кнопку Preview и….. Вы сами все видели!
Теперь, когда Вы уже убедились в существовании и работоспособности скриптов приступим к более подробному их изучению.
Сначала я хочу немного рассказать о том, с чем Вам придется работать, создавая скрипты. В первую очередь это, конечно же, переменные. Переменная – это ячейка памяти, имеющая имя и в которой хранится какая-то информация. Переменные создаются тогда, когда Вы их прописываете в скрипте. Значениями переменных можно легко оперировать. Переменные бывают глобальные и локальные. Локальные переменные – это те переменные, которые видны только в пределах этого скрипта. То есть параллельно может выполняться скрипт с переменной имеющей точно такое же имя и любое значение (то есть это две переменные с одинаковым именем – у них могут быть разные значения). Локальную переменную можно узнать по символу подчеркивания “_” стоящему перед именем переменной. Символ подчеркивания пишется слитно с именем переменной. Например, _testvar . При каждом запуске скрипта локальные переменные обновляются! Глобальная переменная – переменная видимая во всей игре. С такой переменной можно работать как из любого скрипта, так и из любого поля (типа Initialization или Condition) в самой игре. Перед именем глобальной переменной подчеркивание не ставится. Имя глобальной переменной должно быть уникальным во всей игре. Пример использования переменных:
….
;часть скрипта пропущена
;глобальной переменной a присваивается значение 5
a = 5
;глобальной переменной b присваивается значение 4
b = 4
;локальной переменной _b присваивается значение суммы значения ;переменной a и 2. ;_b и b совершенно разные переменные! Теперь _b ;равно 7, а b равно 4(значение не ;изменилось).
_b = a + 2
;значение глобальной переменной b увеличивается на 1
b = b + 1
;думаю все и так понятно. * - знак умножения, / - знак деления.
_c = (a*b)/c
;после такой записи локальной переменной _Soldier1 присваивается ;значение глобальной переменной Sold1(если Sold1 - переменная) ;или же переменная _Soldier1 становится как бы ссылкой на ОБЪЕКТ ;Sold1. То есть если Sold1 – солдат в игре (его имя Sold1) то совершая ;какие-либо действия с _Soldier1(перемещая или убивая его) в игре ;все эти действия будут происходить с солдатом Sold1.
_Soldier1 = Sold1
;пример для предыдущего комментария. Этой командой мы убиваем солдата Sold1 хотя команда относится к _Soldier1(ведь _Soldier1 = Sold1 ).
_Soldier1 setdammage 1
exit
Значением переменной может быть число, логическая переменная True(истина) или False(ложь), ссылка на объект, строка или массив. Строка – текст, заключенный в кавычки. Строки можно складывать:
Hint “Vasia hochet domoi!”
Hint (“Vasia” + “ ” + “hochet” + “ ” + “domoi” + “!”)
Name = “Vasia”
Znak = “!”
Hint (Name + “ ” + “hochet” + “ ” + “domoi” + Znak)
Hint – ключевое слово которое выводит на экран подсказку. Имеет следующий синтаксис:
hint {строка(текст в кавычках) или переменная со значением строки}
Все три приведенные в примере команды приведут к одному и тому же примеру: на экране появится подсказка с надписью “Vasia hochet domoi!”
Массив – набор однотипных или разнотипных элементов. В игре он обозначается как перечисление через запятую элементов массива заключенное в квадратные скобки. Массивы бывают одномерными и многомерными. Одномерный массив имеет несколько элементов одного типа (например, только числа, или только строки) или разных типов (одновременно и числа, и строки).
_array1 = [“Ivan”,”Sergei”,”Alexander”] – определение(создание) одномерного массива с одинаковым типом переменных.
_array2 = [“Ivan”,true,1] - определение одномерного массива с различными типами переменных.
Вы можете также создавать многомерные массивы. Они могут быть составлены из элементов различных типов. Чтобы создать многомерный массив, необходимо вместо одного или нескольких элементов уже созданного массива записать новый массив.
_array3 = [["Ivan","Sergei",1],”Alexander”,true,10] - определение многомерного массива.
Для выбора определенного элемента из массива используется команда select . Она имеет следующий синтаксис:
MassivName select n
Где n – порядковый номер элемента массива. ЗАПОМНИТЕ: Нумерация элементов в массиве начинается с 0!
_elem1 = _array1 select 0 – выбор ПЕРВОГО элемента из массива _array1 и сохранение его в переменную _elem1.
_elem2 = _array1 select 1 – выбор второго элемента из массива _array1 и сохранение его в переменную _elem2.
Посмотрим на выше описанный массив _array3 и выберем второй элемент из массива первого элемента (сложно звучит, нужным элементом будет строка “Sergei”).
_elem3 = (_array1 select 0) select 1
Для того чтобы узнать количество элементов в массиве используют команду count.
Count MassivName
Пример:
NumberOfElems = count _array1 – получение количества элементов в массиве _array1 и сохранение значения в переменную NumberOfElems.
Также можно складывать массивы и находить их разность.
Нахождение суммы массивов:
_array1 = ["One", "Two"]
_array2 = ["Three","Four"]
_summaArray = _array1 + _array2
Теперь массив summaArray будет содержать элементы "One", "Two","Three","Four".
Нахождение разности массивов:
_array1 = ["One", "Two","Three"]
_array2 = ["One"]
RaznostArray = _array1 - _array2
Массив RaznostArray будет содержать элементы "Two","Three".
Для того чтобы убрать один элемент из массива нужно просто его вычесть из этого массива:
Убираем элемент “Sergei” из массива _array1
_array1 = [“Ivan”,”Sergei”,”Alexander”,”Vovan”]
_array1 = _array1 – (_array1 select 1)
Теперь массив _array1 содержит элементы “Ivan”,”Alexander”,”Vovan”.
Для того чтобы проверить, находится ли какое-либо значение в массиве, используют ключевое слово In. Так же это ключевое слово используется, для того чтобы проверить находится ли данный юнит в данном транспортном средстве. Пример:
10 in SomeArray – проверяет наличие числа 10 в массиве SomeArray
MyUnit in MyTank - проверяет наличие солдата MyUnit в технике MyTank
Иногда Вам нужно будет, чтобы все юниты из массива сделали одно и то же действие. Для подобных случаев существует ключевое слово foreach. Это ключевое слово имеет такой синтаксис:
“_x {какое-то действие или команда}” foreach MassivName
Где _x – специальная переменная, которая последовательно принимает значение каждого из элементов массива MassivName. Пример:
"_x dowatch ap" foreach Unitsgroup1 – команда заставляет всех юнитов из массива Unitsgroup1 выполнить команду dowatch ap (команда Unit1 dowatch ap заставляет следить Unit1 за ap – более подробно об этой и других командах Вы узнаете из списка команд на русском или на английском (более полная версия))
Для наглядности покажу, что примерно делает компьютер, выполняя это команду:
_x = Unitsgroup1 select 0
_x dowatch ap
_x = Unitsgroup1 select 1
_x dowatch ap
_x = Unitsgroup1 select 2
_x dowatch ap
…… и так далее
Однако это довольно “капризная” команда и у меня она срабатывала не всегда, поэтому я вместо этой команды использую другой способ “перебора” всех значений массива, о котором Вы узнаете из скрипта “Exitfromhome.sqs”.
Довольно часто Вам при работе с массивами понадобится команда случайного выбора. Это команда random. Она имеет следующий синтаксис:
Random n
Это команда будет генерировать случайное число от 1 до n. Чтобы она использовала и число 0, необходимо из условия генерации вычитать 0.5 (по какой-то причине вычет 1 приводит к ошибке деления на ноль! - это одна из странностей игры):
Выбор случайного элемента из массива mylist и сохранение его в переменную nextelement (с учетом 0-го элемента!):
nextelement = (random (count mylist)) - 0.5
Также стоит отметить знаки сравнения и присваивания.
= - знак ПРИСВАИВАНИЯ. Например, a = b, теперь переменной a присвоено значение b. Не путайте этот знак со знаком сравнения.
== - знак СРАВНЕНИЯ. Var1 == Var2. Эта запись возвращает значение True(истина) если Var1 равно Var2 и значение False(ложь) если Var1 не равно Var2. Например, если b = 1 и c = 1 то выражение a = (b == c) приведет к тому, что a станет равным True.
!= - знак ОТРИЦАНИЯ ЗНАЧЕНИЯ. Var1 != Var2. Эта запись возвращает значение True(истина) если Var1 не равно Var2 и значение False(ложь) если Var1 равно Var2. Например, если b = 1 и c = 1 то выражение a = (b == c) приведет к тому, что a станет равным False.
> - ПРОВЕРКА ЗНАЧЕНИЙ, КАКОЕ БОЛЬШЕ. Var1 > Var2. Эта запись возвращает значение True(истина) если Var1 больше чем Var2 и значение False(ложь) если Var1 меньше чем Var2. Например, если b = 2 и c = 1 то выражение a = (b > c) приведет к тому, что a станет равным True.
Действие знаков >=, <, <= можно понять по аналогии.
Давайте вернемся к скрипту “Hello.sqs”. В этот скрипт не передавалось никаких аргументов (помните, мы запускали его с пустыми квадратными скобками) и, следовательно, мы не изменяли состояние каких-либо объектов в игре – мы просто выводили на экран заранее написанное в скрипте сообщение. Но подумайте сами, для того чтобы изменить выводимое сообщение надо изменять сам скрипт. То есть скрипт не универсален. Для того чтобы сделать скрипт более универсальным, нужно передавать в него аргументы. Как мы уж знаем, чтобы передать в скрипт аргументы их нужно перечислить в квадратных скобках при вызове скрипта. Но как же использовать в скрипте переданные аргументы. Для этого используется специальная локальная переменная _this. Локальная переменная "_this" зарезервирована для ссылки на список аргументов, переданных в скрипт. Это может быть как один аргумент (если, например, скрипт вызывался так unit exec “Script.sqs” – однако такой вызов применяется очень редко, как правило, даже одиночный аргумент заключают в квадратные скобки), так и массив. Пример:
[arg1,arg2,arg3] exec “Script.sqs” – так вызывался скрипт
_argmassiv1 = _this – получение всего массива аргументов. Теперь переменная _argmassiv приняла значение(стала равной) массива [arg1,arg2,arg3].
_argument1 = _this select 0 – получение первого аргумента и сохранение его значения в переменную _argument1
_argument2 = _this select 1 - – получение второго аргумента и сохранение его значения в переменную _argument2
_argument3 = _this select 2 - – получение третьего аргумента и сохранение его значения в переменную _argument3
Теперь _argument1 равно arg1
_argument2 равно arg2
_argument3 равно arg3
Изменим наш скрипт так, чтобы в него передавалась строка, которая будет выведена на экран и позиция самого текста. Наш скрипт будет выглядеть так:
“Hello2.sqs”
;получение аргументов (строки передаются обязательно в кавычках)
_string = _this select 0
_position = _this select 1
;вывод текста на экран
TitleText [_string, _position]
;выход
Exit
;Запуск скрипта: [“Текст сообщения”, “Позиция текста на экране”] exec "Hello2.sqs"
Этот скрипт вызывается так (не забудьте, что скрипт должен находиться в каталоге с вашей миссией):
[“Текст сообщения”, “Позиция текста на экране”] exec “Hello2.sqs”
Например:
[“Hello World!!!”,”Plain Down”] exec “Hello2.sqs”
В предыдущем скрипте мы передавали в качестве аргументов строки (текст в кавычках) потому что команда titletext требует данные именно такого типа. То есть если бы Вы написали команду так titletext [5,”Plain Down”] или так titletext [peremen,”Plain Down”] (где значение переменной peremen не строка) то игра выдала бы Вам ошибку. Ключевое слово hint тоже требует именно строку. А что если Вам нужно сделать например таймер? Вот таймер-то и будет объектом следующего скрипта.
Ключевое слово format, позволяет конвертировать любые типы данных в строку. Синтаксис:
Format[“{текст %1 текст %2 текст %n}”, ARG1,ARG2,ARGn]
Где %1,%2…%n – ссылки на аргументы, которые идут через запятую после закрытия кавычек. То есть вместо %1 в отформатированной строке будет значение переменной (или число, если ARG1 - число) ARG1, а вместо %2 соответственно ARG3. В кавычках нельзя использовать запятые.
Можно также вставлять знаки перевода строки и разбивать текст на несколько строк. Для этого используется символ \n. Пример:
Groupid = Alfa
Dist = 1000
Hint format[“Hey %1 were are you? \n %2 meters from you!”, groupid, dist]
Тогда отформатированная строка будет выглядеть так:
“Hey Alfa were are you?
1000 meters from you!”
Ключевое слово format используют не только с hint:
TitleText [format [“Hey %1 were are you? \n %2 meters from you!”, groupid, dist],”PLAIN”]
Теперь приступим к скрипту:
"Timer.sqs"
; получение аргумента
_timeleft = _this select 0
;метка “loop”
#loop
;уменьшение значения переменной _timeleft на единицу
_timeleft = _timeleft – 1
;вывод подсказки
hint format["Time left %1 seconds",_timeleft]
; пауза в 1 секунду
~1
;проверка условия и при правильности условия переход на метку “loop”
? (_timeleft > 0) : goto "loop"
;присвоение переменной timeendfromscript значения True
timeendfromscript = True
;выход
exit
;Запуск скрипта: [количество_секунд] exec "timer.sqs"
Ну что же – поехали! Первая строчка Вам уже знакома – это получение аргументов. В качестве аргумента должно быть число – время, которое будет отсчитывать таймер(или оставшееся время). А вот вторая строчка – это уже что-то новое. “#loop” – метка. Метка необходима для быстрого перехода к определенному месту в скрипте. Она ставится в любом месте скрипта как отдельная строка. Любая метка должно начинаться с символа #. Затем (слитно или раздельно с решеткой) пишется имя метки. Для перехода на метку используется команда goto. Синтаксис команды:
Goto “LabelName”
Где LabelName – имя метки без решетки! После этой команды начинается выполняться следующая строка после метки, на которую указывает goto. То есть если метка идет в скрипте выше чем команда goto указывающая на эту метку, то получается как бы замкнутый круг – цикл. Вот общий пример цикла в ОФП:
#Label
~1(Время для паузы)
{какие то команды}
goto “Label”
Пауза обязательно должна присутствовать в цикле. Иначе компьютер, как правило, зависает. В зависимости от мощности компьютера и сложности действий в скрипте величина паузы колеблется от 0.0001 до бесконечности.
В цикле у нас выполняются всего две команды – hint и простое уменьшение значения переменной _timeleft на единицу. А вот потом в теле цикла идет такая строчка: ?(_timeleft > 0) : goto "loop".
Это условие. Вопросительный знак – символ постановки условия в ОПФ. После “?” идет само условие, именно оно и будет проверяться. Как правило, условие пишут в круглых скобках (хотя можно и без них) : это помогает при чтение, да и просто выглядит красивее. После условия идет двоеточие и после него те действие, которые будут выполняться при верности условия (условие возвращает значение True). Если условие неверно (возвращает False) то просто начинается выполнение следующей строчки скрипта. Вот общий пример условия в ОФП:
?(условие – одно или несколько): {действия, выполняемые в случае верности условия}
Условий может быть несколько. Для того чтобы их комбинировать используются логические операторы AND(или &&) - И,OR(или II) - ИЛИ и NOT(или !) - ОТРИЦАНИЕ. Например:
?((a>1) AND (b>1)): c = a + b – значение True будет возвращено, если оба условия верны.
?((a>1) OR (b>1)): c = a + b - значение True будет возвращено, если хотя бы одно из условий будет верно.
?(NOT(a>1) AND NOT(b>1)): c = a + b – значение True будет возвращено, если оба условия будут не верны.
Если после двоеточия должно быть несколько команд или действий, то они отделяются друг от друга точкой с запятой (;). Например:
?(NOT(a>1) AND NOT(b>1)): c = a + b; b = b +1
Существует также другой способ постановки условия:
@(одно или несколько условий)
Дойдя до такого условия, выполнения скрипта приостанавливается. Но как только условие будет верным (вернет значение True) выполнение скрипта продолжится со следующей строчки после условия. Этот вид условия используется намного реже, чем первый, но иногда он заметно упрощает скрипт.
И все же вернемся к нашему скрипту. В условии у стоит “_timeleft > 0” – то есть если оставшееся время будет меньше или равно 0, то условие возвратит False и выполнится следующая после условия строчку. Эта строчка exit – выход из скрипта. Если же условие возвращает True, то выполняется действие после двоеточия. Это действие – переход(goto “Loop”) на метку “Loop”.Если бы в цикле не было условия с возможностью выхода из цикла, то он (цикл) стал бы бесконечным (а в нашем скрипте вдобавок таймер перешел бы за ноль и начал считать отрицательное время) и постоянно грузил бы процессор компьютера(и в свою очередь начались бы “тормоза” в игре ). Поэтому старайтесь не делать бесконечных циклов (хотя иногда это просто необходимо).
Вроде бы все, но как теперь “связать” этот скрипт с игрой. Например, чтобы при окончании отсчета таймера срабатывал какой-то триггер (в поле OnActivation триггера можно написать необходимые действия или просто сделать триггер, при активизации которого заканчивается игра – тип триггера END или LOSE). Многие скрипты должны иметь такую связку. В скрипте практически все переменные локальные и, следовательно, с ними нельзя работать через редактор в игре. Именно для связки скрипта с встроенным в ОФП редактором используют несколько глобальных переменных, которые в свою очередь проверяются в редакторе через триггеры. В нашем скрипте это переменная “timeendfromscript”. Я специально использую длинные и как бы говорящие о своей роли (timeendfromscript – “конец времени из скрипта”) переменные, для того чтобы не создать две переменные с одним именем. Также можно создавать обратную связь. То есть наоборот в скрипте проверяется переменная (например так ?(endtimefromgame == true):goto “SomeLabel”) которая изменяется через триггеры(вейпоинты и т.д.) в игре. Попробуйте сами изменить скрипт “Timer.sqs” так чтобы при срабатывании триггера отсчет времени прекращался.
Чтобы проверить работу скрипта “Timer.sqs” необходимо создать миссию, сохранить ее под каким-нибудь именем, создать файл “Timer.sqs” и набрать в нем код скрипта, а затем переписать этот файл в каталог с миссией. Теперь в редакторе(в этой миссии) создаем юнита и называем его “aP”. Затем в поле Initialization этого юнита нужно прописать [15] exec “Timer.sqs”. Создаем триггер, в поле Condition которого пишем timeendfromscript == true(или просто timeendfromscript). В поле OnActivation пишем “aP setdammage 1”. Этот триггер и будет отслеживать конец выполнения скрипта и при активации (как только закончится скрипт) убивать игрока. Смотрим!
Я писал: “…в поле Condition … пишем timeendfromscript == true(или просто timeendfromscript)”. Действительно если в поле триггера Condition написать просто имя переменной, то игра будет проверять эту переменную на значение True.
Еще я упоминал команду setdammage . Эта команда устанавливает юниту или другому объекту уровень повреждений. Синтаксис команды:
UnitName setdammage n
Где UnitName – имя юнита, n – уровень повреждений от 0 до 1, причем 0 – полностью здоров, а 1 – максимальный уровень повреждений. Для проверки уровня повреждений используют команду getdammage.
getdammage UnitName
Где UnitName – имя юнита. Эта команда возвращает уровень повреждений. Например:
aP setdammage 0.5 – установка юниту aP уровня повреждений 0.5
Povregdenie = getdammage aP – получение значения повреждений юнита aP и сохранения значения в переменную Povregdenie.
Теперь значение переменной Povregdenie равно 0.5
Приступим к рассмотрению следующего скрипта! Вот собственно и он:
“Salut.sqs”
;получение аргумента(солдат)
_Unit = _this select 0
;прекратить выполнение скрипта до тех пор, пока дистанция между ;игроком и юнитом _unit не станет меньше 5 метров
@(_unit distance player < 5)
;устанавливает юниту _unit режим поведения SAFE(все спокойно, оружие за спину)
_unit setbehaviour "SAFE"
;заставляет юнит _unit следить за игроком
_unit doWatch player
;пауза в 5 секунд
~5
; Заставляет юнит _unit проиграть анимацию "effectstandsalute"
_unit playmove "effectstandsalute"
; заставляет юнит _unit не за кем не следить(следить за отсутствием объекта)
_unit dowatch objnull
;выход
exit
Сначала рассмотрим новые для нас команды, а потом уже поймем, что же делает этот скрипт. Первые две строчки нам уже хорошо известны это получение аргумента (для правильной работы этого скрипта аргументом должен быть юнит-солдат) и установка условия. В условии у нас присутствует новая для нас команда – это distance. Нетрудно догадаться, что же делает эта команда, но все же… Синтаксис:
ObjectName1 distance ObjectName2
Эта команда возвращает значение расстояния между двумя юнитами или объектами. Следовательно, условие в скрипте поставлено, так что скрипт приостанавливает свое выполнения до тех пор, пока дистанция между юнитом _Unit и юнитом player не будет меньше 5 метров. Однако Вы наверно заметили что юнит player не получается как аргумент а значит либо в редакторе должен быть юнит с именем player либо.… Да это не простое имя юнита. Ключевое слово player заменяет имя игрока (то есть того юнита, за которого играет человек). Вы можете выполнять любые команды с ключевым словом player вместо имени юнита, и все ваши действия будут отражаться на игроке. Например:
Player setdammage 1
Такая строчка убьет игрока. А в нашем условии проверяется дистанция между игроком и юнитом _Unit. В следующей строчке мы опять встречаем новую команду – setbehaviour .
UnitName setbehaviour “{тип поведения}”
Эта команда устанавливает для юнита Unit определенный тип поведения. Доступные типы поведения: SAFE - все спокойно, можно идти не спеша, оружие за спину, AWARE - будь на чеку, передвигайся ползком (но не заставляет искать укрытие), COMBAT - к бою, враг наступает, передвигаться ползком или бегом, CARELESS - не обращать внимания ни на что, даже если рядом приземлиться граната, STEALTH - укрыться и передвигаться ползком.
Зачем нужно “успокаивать” юнита я объясню чуть позже. Следующая команда, требующая объяснения:
UnitName1 doWatch UnitName2
Эта команда заставляет юнит Unit1 следить за юнитом Unit2. То есть Unit1 всегда будет стараться смотреть на юнита Unit2. В предпоследней строчке вместо второго юнита стоит ключевое слово ObjNull . ObjNull – обозначает отсутствие объекта, то есть в нашей команде(смотри предпоследнюю строчку скрипта) использование ObjNull заставит юнита _unit следить не за юнитом игрока, а за “никем” (то есть вообще не за кем не следить ).
Итак, мы подошли к очень интересной команде, а именно playmove. Эта команда заставляет юнит (человека, а не технику) проигрывать выбранную анимацию. Команда имеет такой синтаксис:
UnitName playmove ”{название анимации}”
Команда Playmove с плавным переходом из предыдущего положения начинает проигрывать указанную анимацию. Вот несколько примеров:
_unit playmove "effectstandsalute"
_unit2 playmove "CombatToCrouch"
_unit3 playmove "Stand"
_unit4 playmove "EffectStandSitDown"
Однако существует одна проблема. Не все анимации работают с этой командой. В игре есть также другая команда для работы с анимацией. Это switchmove . Синтаксис у этой команды такой же, как и у предыдущей. Эта команда проигрывает анимацию сразу же (без плавного перехода). Некоторые анимации работают только с Playmove, другие только с switchmove, а также есть анимации работающие с обеими командами. Пример команды switchmove:
_unit1 switchMove "FXStandDip"
_unit2 switchmove "EffectStandSitDown"
_unit3 switchmove "CommandEngageAtWill"
Вообще эти команды довольно капризные. Иногда для проигрывания анимации, например, требуется установить юниту тип поведения (setbehaviour) “SAFE”. Для демонстрации различий между Playmove и Switchmove попробуйте обе эти команды (на разных юнитах) с анимацией EffectStandSitDown". При использовании switchMove юнит мгновенно окажется в положении сидя. При использовании playMove юнит повесит оружие за спину и плавно сядет на землю. Как определить с какой командой работает та или иная анимация? Да просто попробовать!
Итак что бы проверить скрипт ставим на карте два юнита: одного – игрока(можно без имени) а второго – солдата с именем Sold1. Игрок должен стоять более чем в 5 метрах от второго юнита иначе будет не интересно. Теперь в поле Initialization одного из юнитов пишем: [Sold1] exec “Salut.sqs”. Запускаем и подходим игроком ко второму юниту. Этот юнит должен повернуться к игроку(или не совсем к игроку – багги движка) и отдать честь! Этот скрипт пустяк, но как приятно когда играешь в миссию проходишь мимо солдата на базе, а он отдает тебе честь! Вот из-за таких, (и не только) вроде бы пустяшных скриптов, ваша миссия приобретает красоту и оригинальность.
Если Вы играли в официальную кампанию (и не только) то Вы, наверное, замечали, что в некоторых миссиях Вы начинаете в одном месте, а потом (как правило, после скриптовой сцены) мгновенно появляются в другом. Это очень полезный прием. Например, в миссии главная задача - уничтожить базу противника. Зачем заставлять игрока ехать(лететь, ползти) с одного конца острова на другой, если можно его мгновенно переместить в нужную точку (конечно перемещение должно происходить как было замечено ранее в момент скриптового ролика). Именно для этих целей используются две команды: setpos и getpos . Эти команды работают с координатами. В ОФП координаты – массив такого типа [x,y,z]. Где х – координата по оси x, y – координата по оси y и z – координата по оси z.
Ось Y всегда направлена на север, а X на восток. Вы конечно видели карту в ОФП: она поделена на квадраты горизантальными и вертикальными линиями. Горизантальные – ось X, а вертикальные – ось Y. Высоту конечно на двухмерной карте показать сложно, но я думаю Вы сами догадаетесь где эта ось Z(высота).
Для получения координат объекта используют команду getpos.
Getpos ObjectName
Эта команда возвращает вышеупомянутый массив координат. С этим массивом работают как и с любым другим(см. пример). Команда setpos используется для мгновенного перемещения объекта:
ObjectName setpos [x,y,z]
В обойх премерах ObjectName – любой объект в игре. Примеры использования этих команд:
_posUnit = getpos _unit1 – получение координат объекта _Unit и их сохранение в локальную переменную _posUnit.
posUnitx = _posUnit select 0 – получение координаты X из массива координат _posUnit и сохранение ее значения в глобальную переменную posUnitx.
posUnity = _posUnit select 1 – получение координаты Y
posUnitz = _posUnit select 2 - получение координаты Я
Получить координаты можно и таким путем:
posUnitx = getpos _unit1 select 0 - получение координаты X объекта _unit1 и сохранение ее значения в глобальную переменную posUnitx.
Получение координат Y и Z:
posUnity = getpos _unit1 select 1
posUnitz = getpos _unit1 select 2
_Unit2 setpos [1234,1234,1234] – перемещения объекта _Unit2 в координаты [1234,1234,1234].
_Unit2 setpos getpos _unit1 - перемещения объекта _Unit2 в координаты объекта _unit1
_unit2 setpos [(getpos _unit1 select 1),( getpos _unit1 select 2), (getpos _unit1 select 1) + 10] - перемещения объекта _Unit2 в координаты объекта _unit1 и на 10 метров выше чем _unit1.
Если в массиве координат не указать высоту, то объект по умолчанию будет перемещен на нулевую высоту (будет стоять на земле).
Вот небольшой скрипт показывающий работу команд setpos и setpos.
“Privazka.sqs”
;Получение аргументов: _Privazka – объект который привязывается к объекту _Vedushii. ;_movex и _movey смещения координат объекта _Privazka по-отношению к координатам ;объекта _Vedushii.
_Privazka = _this select 0
_movex = _this select 1
_movey = _this select 2
_movez = _this select 3
_Vedushii = _this select 4
#loop
;Установка объекта _Privazka в координаты объекта _Vedushii с учетом смещения.
_Privazka setpos [(getpos _Vedushii select 0) + _movex, (getpos _Vedushii select 1) + _movey, (getpos _Vedushii select 2) + _movez]
;проверка здоровья объекта _Privazka. Если объект разрушен скрипт прекращает свое ;выполнение.
?(getdammage _Privazka == 1): exit
; необходимая – малая (что бы ведомый объект перемещался плавно за ведущим) пауза. ;Попробуйте изменить на 1 и увидите о чем я.
~0.003
goto "loop"
Этот скрипт привязывает один объект к другому. То есть если ведущий объект двигается то вместе с ним на определенном расстоянии двигается и ведомый объект. Для проверки скрипта создайте на карте два солдата одного игрока а другого с именем Sold1. Запустите скрипт (например из Initialization поля) так: [Sold1,10,10,1,player] exec “Privazka.sqs”. Теперь запустите миссию. Солдат двигается точно так же как и Вы только в 10 метрах спереди и в 10 метрах справа и на высоте в 1 метр!
Вот еще один скрипт также показывающий работу setpos. Этот скрипт позволяет делать выход юнитов из домов в которые заходить нельзя(например, казармы на базах).
“Exitfromhome.sqs”
;Получение аргументов: _Exitpoint – невидимый объект, точка выхода из дома ; _group – ;группа которая будет выходить из дома.
_Exitpoint = _this select 0
_Group = _this select 1
; получение массива всех юнитов из группы _group и сохранение этого массива в переменную _units
_Units = units _Group
;получение количества юнитов в массиве _Units и сохранение значения в ;переменную _num
_num = count _Units
_i = 0
~10
#loop
; сохранение первого юнита (ссылки на первый юнит) в массиве в переменную _unit
_unit = _Units select _i
; перемещение юнита _unit в точку выхода из дома
_unit setpos (getpos _Exitpoint)
; увеличение значения переменной _i на единицу
_i = _i + 1
;проверка равенства значения переменной _i числу юнитов в массиве(переменная _num)
?(_i == _num ): goto "exit"
~1.5
goto "loop"
#exit
exit
Рассмотрим этот скрипт. Принцип действия прост. В качестве аргумента передается группа (название группы, а не массив юнитов) которая будет выходить из дома. Эта группа должна быть где-то, где игрок не сможет ее увидеть, например, на острове в океане. Также в качестве аргумента передается невидимый объект (H(Invisible) или Game Logic) – точка выхода из дома. Этот объект ставится как можно ближе к нарисованному на казарме выходу. После получается (с помощью команды units) массив всех юнитов заданной группы. И затем каждый юнит из массива перемещается в точку выхода.
Новая команда:
Units GroupName
Эта команда возвращает массив всех юнитов группы. Теперь я расскажу об одном полезном и часто используемом приеме работы с массивом. В этом скрипте есть переменная _i равная 0. Зачем? Это своеобразный счетчик. В цикле присутствует строчка, которая увеличивает значение переменной _i на единицу. То есть после каждого выполнения действий в цикле значение этой переменной увеличивается на единицу. А так как изначально значение _i равно 0 то оно и будет показывать, сколько раз выполнились действия в цикле. Посмотрим на первую строчку в цикле. В этой строчке из массива _Units выбирается элемент, чей номер равен значению счетчика. То есть каждый раз при выполнении цикла будет выбираться тот элемент, который идет за элементом, выбранным при предыдущем выполнении цикла. Но так как количество элементов в массиве ограничено, а при выборе несуществующего элемента (например, в массиве 10 элементов, а компьютер пытается выбрать 11) происходит ошибка, то нам следует ограничить выполнение цикла. Цикл должен выполниться столько раз, сколько и элементов в массиве. Для этого в цикле и установлено условие. Значение переменной _num – количество юнитов в массиве (получено с помощью команды count: _num = count _Units ). В условие проверяется равенство значения переменной _i (номер выбираемого из массива элемента или количество “проходов” цикла) значению переменной _num (количество элементов в массиве) и если переменные равны (то есть значение переменной _i – номер выбираемого элемента – равно числу элементов в массиве, то есть элемент номер _i - последний), то выполнение скрипта прекращается. Этот прием очень часто применяется, когда для всех элементов массива нужно выполнить какие-то одинаковые действия. В нашем случае это перемещение в точку выхода из дома.
Для того чтобы посмотреть работу скрипта нужно найти какую-нибудь базу на карте, поставить в месте предполагаемого выхода Game Logic с именем Gl1. Поместить группу юнитов куда-нибудь подальше. В Initialization поле лидера группы прописать gr1 = group this . Теперь создайте триггер с активацией по каналу радио Альфа и с запуском скрипта в поле OnActivation: [gr1,Gl1] exec “ Exitfromhome.sqs ”.
Теперь запускаем миссию и нажимаем 0-0-1, вызывая радио. Да, не забудьте поставить самого игрока перед домом откуда выходит группа (игрок не в той группе которая выходит). Смотрим!
Вышеописанные команды перемещают объекты мгновенно из одной точки в другую, а как же быть, если нужно плавно за определенное время переместить объект из одной точки в другую? Зачем? Ну, мне лично однажды нужно было сделать движущиеся мишени, а применений собственно можно придумать множество (была бы фантазия, а в этом деле без нее никуда): самонаводящаяся ракета, красиво (а главное реалистично) взлетевшая после взрыва машина и т.д.… Так вот когда передо мною возникла эта проблема я, как и практически каждый окончивший среднюю школу человек начал размышлять над тригонометрическими выражениями: зная угол (определяется командой getdir: getdir ObjectName - возвращает угол поворота в градусах) и гипотенузу – путь который должен пройти объект, в прямоугольном треугольнике нужно найти катеты – в игре смещения по оси X и Y.
Стрелка (дистанция) – путь, который должен пройти объект (гипотенуза), φ – известный (с помощью getdir) угол. Итак, формулы:
Смещение по оси X = Дистанция * Cos φ
Смещение по оси Y = Дистанция * Sin φ
Найдя смещения, разделим их не определенное количество маленьких частей-кусочков (чтобы движение было плавным), это количество зависит от дистанции и нужного времени перемещения. Получив длину одного кусочка для каждой из осей, будем в цикле прибавлять эти кусочки к текущим координатам (X и Y) объекта и в результате получится плавное движение. Такой прием (нахождение смещений с помощью дистанции и угла) очень часто применяется в скриптах для нахождения смещений по осям, например, когда нужно найти координаты точки, находящейся в метре от юнита, но строго впереди него (если взять точку с координатами просто больше на один метр по оси Y относительно юнита, то не учитывается направление взгляда; то есть юнит, например, смотрит на юг, а точка всегда будет на севере). НО, во-первых, этот прием довольно громоздкий и сложный, а, во-вторых, ну …. многие просто не любят тригонометрию! Сейчас я предложу довольно интересный способ решения этой (плавное перемещение объекта) задачи, хотя и выше описанный способ иногда имеет место быть. Скажу также, что подобного я не в одних скриптах не встречал.
Небольшое вступление. Итак, Вы, наверное, знаете, что в ОФП можно создавать скриптовые сцены – что-то типа фильмов или роликов на движке игры. Помните, в кампании перед некоторыми миссиями или во время миссий у игрока отбиралась возможность управлять игрой, и начинался показ такого ролика. Создание таких роликов основано на управление (создание, перемещение, уничтожение и т.д. ) камерами. Если Вы еще не умеете делать скриптовые ролики, то уверен, что научитесь, так как это очень легко. Я не буду Вам объяснять, как именно создавать ролики, а всего лишь покажу один нестандартный способ использования камеры, но если Вы все же хотите научиться создавать ролики, то поищите на просторах Интернета руководства и учебники, уверяю Вас - они там есть. Итак, камеры. Для того чтобы создать камеру нужно написать в скрипте (или редакторе) подобную строчку:
CameraName = “camera” camcreate [x,y,z]
Где CameraName – имя переменной (глобальная или локальная) в которой будет храниться ссылка на камеру для дальнейшего управления. [x,y,z] – как Вы поняли координаты, в которых создается камера. Вместо массива координат можно, конечно же, написать что-то типа getpos SomeObject (получение координат объекта). Затем камере необходимо установит какой-либо эффект. Это делается с помощью команды cameraeffect . А именно:
CameraName cameraeffect [{“Эффект”},{“Позиция”}]
Значения различных эффектов (например “ZOOMIN” – в кавычках) и позиций Вы сможете узнать в специальных руководствах. В нашем же скрипте нам понадобится только эффект "terminate" (отключить эффекты) и позиция "back" (сзади). При установлении таких эффектов камера будет существовать, как некий объект в игре, но вид не будет переключен на эту камеру (то есть на экране останется вид “из глаз” и игрок может управлять своим юнитом). Камера создана, установлен эффект что дальше? А дальше, в этом вся фишка, эта камера будет использоваться для получения нужных координат. Например, заставив камеру двигаться по нужной траектории (об этом ниже) перемещать в цикле (с маленькой паузой) какой-либо объект по координатам камеры. Вот примеры использования команд управления камерой (только необходимые для нашего скрипта):
Примечание: предполагается, что используемые камеры созданы и для них установлены эффекты ["terminate","back"].
Устанавливает камеру в координаты [x,y,z]:
_cam camsetpos [x,y,z]
(или _cam camsetpos getpos SomeObject)
Устанавливает для камеры как цель объект SomeObject или точку с координатами [x,y,z] Камера всегда будет направлена на свою цель (если она указана). Цель можно менять:
_cam camsettarget SomeObject
(или _cam camsettarget [x,y,z])
Устанавливает камеру в координаты [x,y,z] относительно своей цели. Если цель не указана, команда верно не работает. Эту команду используют для нахождения координат относительно юнита с учетом направления его взгляда.
_cam camsetrelpos [x,y,z]
Важно: Все действия установленные этими командами вступают в силу только после команды camcommit:
{Действия управления камерой}
_cam camcommit {время за которое установится действие}
Например:
_cam camsetpos getpos SomeObject1 – камере дается указание перейти в координаты объекта SomeObject1
_cam camcommit 0 – мгновенно (время - 0) устанавливает камеру в координаты объекта SomeObject1
_cam camsetpos SomeObject2 - камере дается указание перейти в координаты объекта SomeObject2
_cam camcommit 10 – за 10 секунд плавно переносит камеру в координаты объекта SomeObject2 из координат объекта SomeObject1
Иногда, бывает полезна команда camcommitted. Эта команда возвращает значение True, если все изменения сделанные командами управления вступили в силу, и False, если еще не вступили.
@(camcommited _cam)
Такая строка приостановит выполнение скрипта до тех пор, пока изменения сделанные командами управления камерой не вступят в силу.
Но вернемся к скрипту, вот собственно и он:
“Move.sqs”
;получение аргументов.
;_target – объект, который перемещают
;_moveto – координаты, в которые перемещают объект, вместо координат можно передать ;получение координат getpos SomeObject (чаще всего получают координаты GameLogic’а (На ;усмотрение игры – в Буке) – или другого невидимого объекта)
;_time - время перемещения объекта.
_target = _this select 0
_moveto = _this select 1
_time = _this select 2
;создание и установка эффектов для камеры
_cam = "camera" camcreate [0,0,0]
_cam cameraeffect ["terminate", "back"]
;мгновенное перемещение камеры в координаты перемещаемого объекта
_cam camsetpos getpos _target
_cam camcommit 0
;плавное перемещение камеры в координаты объекта _moveto за время _time
_cam camsetpos _moveto
_cam camcommit (_time)
#loop
;пауза
~0.01
; перемещение объекта _target в координаты камеры _cam
_target setpos [getpos _cam select 0, getpos _cam select 1]
;проверка жив ли перемещаемый юнит, и если мертв то выход из скрипта
?(getdammage _target == 1): goto "exit"
goto "loop"
#exit
exit
Сразу после получения аргументов следуют строчки, в которых создается камера и ей устанавливается нужный эффект. В нашем случае, как уже говорилось раньше, эффекты отключены (["terminate", "back"]), так как не нужно переключать вид на эту камеру (при использовании для камеры других эффектов на экране Вы увидите вид именно из этой камеры). Далее камера мгновенно (так как время в команде camcommit приводящей изменения в силу установлено на 0) перемещается в координаты того объекта, который будет передвигаться. Затем камере дается указание двигаться в координаты _moveto, то есть в точку конечного назначения. Так как в команде camcommit время – значение переменной _time (время движения – передается в скрипт как аргумент, то есть устанавливается пользователем) то камера пройдет путь от начального положения перемещаемого объекта в конечную точку за время – значение переменной _time. Камера двигается, несмотря на продолжение скрипта. В цикле с паузой в 0.01 (для плавного движения) происходит постоянное перемещение объекта _target в координаты камеры. Так как камера движется во время выполнения цикла то и объект постоянно перемещается в новые координаты. Происходит примерно то же что и в скрипте “Privazka.sqs”, только объект “привязывается” к движущейся в нужную точку невидимой камере.
Для проверки поставьте двух солдат на карте (еще одного - игрока). Одного пусть зовут Sold1 а второго Sold2. Запустите скрипт так:
[Sold1, getpos Sold2, 20] exec “move.sqs”
Смотрим!
Если Вы заметили, при перемещении объекта в координаты камеры, не устанавливается высота (координата Z), и, следовательно, объект всегда будет двигаться по земле. Для того чтобы при перемещении использовалась координаты Z нужно в строчку изменения координат объекта добавить и координаты высоты камеры - getpos _cam select 2.
Эта строчка
_target setpos [getpos _cam select 0, getpos _cam select 1]
должна выглядеть так:
_target setpos [getpos _cam select 0, getpos _cam select 1, getpos _cam select 2].
Но такой способ изменения режима работы скрипта не очень удобен. Во-первых, нужно менять сам код скрипта, а во-вторых, человек не умеющий писать скрипты может просто не понять, что и где нужно изменить. Поэтому сейчас на примере этого скрипта покажу, как изменять режим работы скрипта через передачу аргумента. Итак, нам, например, нужно сделать три режима работы скрипта: “LAND”, “AIR”, “AUTO”. Где “LAND” – передвижения объекта по земле. “AIR” – передвижение объекта точно по траектории камеры, причем объект будет двигаться не только по земле и воздуху, но и под землей, если на пути движения будут горы и т.п. “AUTO” – передвижение объект либо по земле, либо по воздуху, но не под землей. Как это сделать? Просто нужно сделать получение еще одного аргумента, значение которого и будет режим работы скрипта. А в зависимости от полученного режима работы будет выполняться один из трех циклов. Смотрим:
“Move2.sqs”
;получение аргументов.
;_target – объект, который перемещают
;_moveto – координаты, в которые перемещают объект, вместо координат можно передать ;получение координат getpos SomeObject (чаще всего получают координаты GameLogic’а (На ;усмотрение игры – в Буке) – или другого невидимого объекта)
;_time - время перемещения объекта.
;_tip – режим выполнения скрипта. Возможные значения:“LAND”, “AIR”,”AUTO”(в кавычках)
_target = _this select 0
_moveto = _this select 1
_time = _this select 2
_tip = _this select 3
;создание, установка эффектов для камеры и мгновенное перемещение камеры в ;координаты перемещаемого объекта
_cam = "camera" camcreate [0,0,0]
_cam cameraeffect ["terminate", "back"]
_cam camsetpos getpos _target
_cam camcommit 0
;плавное перемещение камеры в координаты объекта _moveto за время _time
_cam camsetpos getpos _moveto
_cam camcommit (_time)
;выбор одного из режимов работы скрипта и переход к соответствующему циклу
?(_tip == "LAND"): goto "loopLAND"
?(_tip == "Air"): goto "loopAIR"
?(_tip == "AUTO"): goto "loopAUTO"
?((_tip != "AUTO") AND (_tip != "AIR") AND (_tip != "LAND")): hint "Zadan nevernyi regim raboti scripta"; goto "exit"
;цикл, который исполняется, если _tip равняется “LAND”
#loopLAND
~0.01
_target setpos [getpos _cam select 0, getpos _cam select 1 ]
?(getdammage _target == 1): goto "exit"
goto "loopLAND"
;цикл, который исполняется, если _tip равняется “AIR”
#loopAIR
~0.01
_target setpos [getpos _cam select 0, getpos _cam select 1, getpos _cam select 2]
?(getdammage _target == 1): goto "exit"
goto "loopAIR"
;цикл, который исполняется, если _tip равняется “AUTO”
#loopAUTO
~0.01
;расчеты координаты z
?((getpos _cam select 2) < 0): _posZ = 0
?((getpos _cam select 2) >= 0): _posZ = getpos _cam select 2
_target setpos [getpos _cam select 0, getpos _cam select 1, _posz ]
?(getdammage _target == 1): goto "exit"
goto "loopAUTO"
#exit
exit
Определение заданного режима работы скрипта и последующий выбор нужного цикла происходит в этих строчках(строчки пронумерованы):
1 - ?(_tip == "LAND"): goto "loopLAND"
2 - ?(_tip == "Air"): goto "loopAIR"
3 - ?(_tip == "AUTO"): goto "loopAUTO"
4 - ?((_tip != "AUTO") AND (_tip != "AIR") AND (_tip != "LAND")): hint "Zadan nevernyi regim raboti scripta"; goto "exit"
Все эти строчки – условия. В каждом из них проверяется значение переменной _tip , а значение этой переменной передается в скрипт как аргумент. В первом условии проверяется значение переменной _tip на равенство строке “LAND” и в случае равенства происходит переход к циклу “loopLAND”. В этом цикле объект _target устанавливается в координаты камеры _cam без учета координаты Z. То есть объект двигается только по земле. Если значение проверяемой переменной не равно “LAND”, то скрипт переходит к следующей строчке, а следующая строчка – очередное условие, проверяющее значение переменной _tip на равенство строке “AIR”. В случае верности условия происходит переход к циклу “loopAIR”. В этом цикле при перемещении объекта учитывается координата Z камеры. Так как эта координата может быть отрицательной то объект может двигаться под землей. В третьем цикле, который выполняется в случае верности условия _tip == "AUTO", перед перемещением объекта в координаты камеры происходит расчет координаты Z объекта. Вот это вычисление:
?((getpos _cam select 2) < 0): _posZ = 0
?((getpos _cam select 2) >= 0): _posZ = getpos _cam select 2
Это два условия. В них проверяется, положительна или отрицательна координата Z камеры. Если положительна, то эта координата и используется при перемещении объекта _target. Если отрицательна (камера двигается под землей) то при перемещении объекта _target координата Z равна 0. Значение координаты Z используемой при перемещении объекта хранится в переменной _posZ.
Мы еще не рассмотрели последнюю строчку из определения режима работы скрипта. Вот эта строчка:
?((_tip != "AUTO") AND (_tip != "AIR") AND (_tip != "LAND")): hint "Zadan nevernyi regim raboti scripta"; goto "exit"
Это условие проверяется только в том случае, если переменная _tip не равна ни “LAND”, ни “AIR”, ни “AUTO”. То есть если в скрипт передано неверное значение режима скрипта. В этом случае выводится подсказка (hint) которая и сообщает пользователю о том, что режим задан неверно, а затем выполнение скрипта прекращается. Вместо условия можно было просто поставить предупреждение-подсказку hint, так как до этой строчки скрипт дойдет, только если все три выше написанные условия будут неверны. Если хотя бы одно из условий будет верным, то скрипт перейдет к нужному циклу, а эта строчка будет пропущена (также как и нижеследующие условия). Если не совсем понятно смотрим:
Примечание: зеленая стрелка, если условие верно, а красная – если неверно.
Попробуйте походить (мысленно!) по этим стрелкам, и Вы сможете дойти до строчки hint, только если будите ходить по красным стрелка, а красные стрелки – только в случае неверности условия.
Для проверки скрипта, найдите на одном из островов горку повыше и поставьте по обе стороны от горки двух солдат, как и в скрипте “Move.sqs”. Запустите скрипт так:
[Sold1, getpos Sold2, 20,”LAND”] exec “move.sqs”
Смотрим! Затем замените значение режима и посмотрите на результат. Ну как? Работает!?
Раз уж я начал говорить о камерах, то попробую рассказать все, что знаю о нестандартном их использовании. Итак, команда camcreate кроме основных своих функций – создания камеры – служит также и для… создания объектов! Да именно для создания объектов, любых, или почти любых. Как? А вот так:
ObjectName = “{Тип объекта}” camcreate [x,y,z]
Где NameOfObject – имя (переменная, значение которой - ссылка на объект) создаваемого объекта, с помощью этого имени можно будет управлять объектом (но об этом потом). {Тип объекта} – тип, или внутреигровое название типа объекта. То есть, например, мы хотим создать обычного солдата СССР. Так вот название типа простого русского солдата – SoldierEB. И следовательно чтобы создать такого солдата нужно использовать (в On(De)Activation, в скриптах и т.д.) такую строчку:
_Sold = “SoldierEB” camcreate getpos Barrak
Теперь локальная (может быть и глобальная) переменная _Sold служит для управления созданным объектом. НО, созданный объект СТАТИЧНЫЙ! То есть, хоть это и солдат, ему можно давать команды только как для статических объектов (дома, ящики)! Это такие команды как setpos, setdir, switchmove и другие. Скажем так, у такого юнита нет интеллекта (искусственного): он не будет по Вам стрелять, не будет ходить, не будет исполнять приказы, НО может умереть! Именно с помощью такого использования этой команды создают взрывы в произвольном месте! Например, для артобстрела. Просто нужно создать в нужном месте снаряд от танка (например, Shell73) или другой боеприпас! Но учтите, что снаряды и пули, созданные в воздухе падают вниз и при контакте с землей взрываются (снаряды)! Ракеты же не падают, а летят вперед! Некоторые типы объектов Вы найдете в списка команд на английском.
Очень важной и интересной является команда nearestobject . Важная, потому что, используя эту команду, можно сделать скрипты универсальными и “автономными”. Интересная, потому что открывает новые приемы в написании скриптов. Как можно понять из названия (nearestObject – “ближайший объект”) эта команда служит для определения ближайшего объекта к данному объекту. Существует несколько способов использования этой команды:
1. nearestObject [ObjectName, “{тип объекта}"]
2. nearestObject [[x,y,z], “{тип объекта}"]
В первом случае команда возвращает ссылку на объект определенного типа находящейся ближе всего к объекту ObjectName. Например:
_Lamp1 = nearestObject [player, "StreetLamp"] – локальная переменная _lamp1 принимает значение ближайшего к игроку объекта типа “ StreetLamp” (фонарный столб)
Во втором случае команда возвращает ссылку на объект определенного типа находящейся ближе всего к точке с координатами [x,y,z]. Например:
_Lamp2 = nearestObject [[x,y,z], "StreetLamp"] – локальная переменная _lamp2 принимает значение ближайшего к точке с координатами [x,y,z] объекта типа “ StreetLamp” (фонарный столб).
В третьем случае команда возвращает ссылку на объект находящейся ближе всего к точке с координатами [x,y,z]. В данном случае тип объекта не требуется. Например:
_Lamp3 = nearestObject [x,y,z] (или _Lamp3 = nearestObject getpos SomeObj1) – локальная переменная _lamp3 принимает значение ближайшего к точке с координатами [x,y,z] объекта любого типа.
Рассмотрим работу команды на примере одного скрипта. Этот скрипт немного улучшает AI (искусственный интеллект) снайперов. Может быть, Вы замечали, что если снайпер замечен и по нему открыт огонь, то он все равно лежит на месте и пытается отстреливаться, даже если ему противостоит целая армия солдат. Настоящий снайпер после обнаружения срочно покидает позицию и отходит. Этот скрипт предназначен для улучшения действий снайпера при обнаружении. Теперь, когда снайпер замечен, то сначала он создает дымовую завесу, а потом в зависимости от переданных аргументов либо двигается по вейпоинтам, либо двигается в определенную точку. Смотрим скрипт:
“SniperAI.sqs”
;В качестве аргументов мы получаем юнит (_sniper), способ отхода или режим ;работы скрипта (_mode), координаты точки отхода (_posback), тип поведения при ;отходе (_behaviour), положение юнита при отходе (_unitpos), боевой режим юнита ;при отходе (_combatmode)
_sniper = _this select 0
_mode = _this select 1
_posback = _this select 2
_behaviour = _this select 3
_unitpos = _this select 4
_combatmode = _this select 5
SniperAIletmove = false
; проверка : снайпер человек иди нет
_checkarray = [_sniper]
?("Man" counttype _checkarray == 0): hint "SniperAI: Переданный юнит не человек"; exit
;нахождение первоначального значения здоровья и ближайшего к снайперу ;кратера
_oldcrater = nearestobject [_sniper,"crater"]
_olddammage = getdammage _sniper
;проверка правильности заданного режима работы скрипта
?((_mode != "POS") AND (_mode != "WP")): hint "SniperAI script: Неверно задан тип отхода (2ой аргумент)"; exit
;начало цикла
#loop
;проверка условия
?((((_sniper distance nearestobject [_sniper,"crater"]) < 20) AND (nearestobject [_sniper,"crater"] != _oldcrater)) OR ((getdammage _sniper) > _olddammage)): goto "loop2"
?(getdammage _sniper == 1): exit
;hint "Поиск"
~1
goto "loop"
#loop2
;hint "Есть кратер"
;проверка мертв ли снайпер
?(getdammage _sniper == 1): exit
;проигрывание анимации
_sniper switchmove "BinocLyingToLying"
~2
;нахождение координат точки находящейся в 0.8 метрах впереди от снайпера
_cam = "camera" camcreate [0,0,0]
_cam cameraeffect ["terminate", "back"]
_cam camsettarget _sniper
_cam camsetrelpos [0,0.8,0]
_cam camcommit 0
;создание рюкзака в найденных координатах
_somesmokething = "PipeBomb" camcreate [(getpos _cam select 0), (getpos _cam select 1),0]
;создание дымовой шашки в координатах рюкзака
_somesmoke = "SmokeShell" camcreate getpos _somesmokething
;уничтожение используемой для получения координат камеры
camdestroy _cam
;выбор режима работы скрипта
?(_mode == "WP"): goto "exitWP"
?(_mode == "POS"): _sniper setunitpos _unitpos
;установка поведения, режима боя и т.д. для снайпера
_sniper setcombatmode _combatmode
_sniper setbehaviour _behaviour
_sniper domove _posback
;hint "Снайпер отходит"
goto "exitPOS"
#exitWP
SniperAIletmove = true
#exitPOS
exit
Сначала я расскажу о новых для Вас командах.
“{тип}” counttype {массив}
Эта команда определяет, сколько юнитов, указанных в заданном массиве, принадлежат к заданному типу. О разные типах юнитов Вы узнаете из списка команд на английском. Например:
_soldiersnum = “soldier” counttype MyArray1 – получение количества юнитов (элементов) типа “soldier” (солдаты) в массиве MyArray1 и сохранение значения в локальную переменную _soldiersnum.
UnitName SetUnitPos {положение}
Эта команда заставляет юнита принять определенное положение. В дальнейшем юнит будет передвигаться только в заданном положении. Доступные положения: “UP” – стоя , “DOWN” – лежа, “AUTO” - позволить юниту самому выбрать наилучшее положение. Например:
Unit1 setunitpos “UP” – заставляет юнит передвигаться стоя.
Примечание: При использовании положения “UP” юнит переходит в боевое положение. То есть если юниту было задано поведение “SAFE”(оружие за спиной) и если юнита заставить передвигаться стоя, то он возьмет оружие в руки.
UnitName SetCombatMode {режим боя}
Эта команда устанавливает для юнита определенный режим боя. Доступные режимы боя: “BLUE” - никогда не стрелять, “GREEN” - не стрелять до приказа, “WHITE” - не стрелять до приказа, но атаковать врага в поле видимости, “YELLOW” - открыть огонь (с того места, где находится юнит), “RED” - открыть огонь, но оставаться рядом с командиром. Например:
Unit1 SetCombatMode "GREEN" – устанавливает для юнита Unit1 режим боя “GREEN”.
UnitName domove [x,y]
Эта команда заставляет юнит двигаться в точку с координатами [x,y]. Пример:
Unit1 domove [3415,1355] - заставляет юнит Unit1 двигаться в точку с координатами [3415,1355]
Unit2 domove getpos Unit3 - заставляет юнит Unit2 двигаться в точку с координатами юнита Unit3
Похоже это все неизвестные Вам команды из этого скрипта. Приступим к рассмотрению кода. В качестве аргументов мы получаем юнит (_sniper), способ отхода или режим работы скрипта (_mode), координаты точки отхода (_posback), тип поведения при отходе (_behaviour), положение юнита при отходе (_unitpos), боевой режим юнита при отходе (_combatmode). Доступные способы работы скрипта: “WP” и “POS”. “WP” – отход по заранее установленным в редакторе вейпоинтам. Для правильной работы этого режима нужно в редакторе для снайпера поставить один вейпоинт (максимально близко к самому снайперу) с условием выполнения (Condition) : SniperAIletmove == true. А затем расставлять вейпоинты для отхода. Причем в них (в поле OnActivation или в свойствах самого вейпоинта) можно использовать команды для установки режима боя, положения (setunitpos) юнита и т.д. Например, в поле видимости врага передвигаться лежа и не стреляя (в свойствах вейпоинта: режим боя – не стрелять, этот режим нужно обязательно ставить иначе снайпер так и не прекратит огонь и не начнет отходить ), а потом стоя и с боевым режимом боя. Второй режим – “POS” – отход в координаты _posback. Причем юнит будет отходить в соответствии с режимами, переданными в скрипт в качестве аргументов: _behaviour, _unitpos, _combatmode. Режим отхода по вейпоинтам более предпочтителен, так как он более “гибкий” и имеет гораздо больше возможностей чем режим “POS”. Режим
“POS” был добавлен в скрипт только лишь для еще одной иллюстрации возможности работы скрипта в разных режимах.
Сразу же после получения аргументов следует проверка переданного в скрипт юнита на тип “Man” – то есть скрипт проверяет этот юнит человек или нет. Если нет, то выводится сообщение об ошибке и выполнение скрипта прекращается. Как происходит проверка? Мы знаем, что для проверки юнита на принадлежность к какому-либо типу используют команду counttype. Однако эта команда работает только с массивами, а у нас есть всего лишь один юнит. Все просто! Мы просто создаем массив, в котором содержится только один нужный нам юнит, и проверяем его тип командой counttype. В нашем случае если юнит принадлежит к типу “MAN” то команда counttype возвращает значение 1 - количество юнитов в массиве этого типа, если не принадлежит то значение 0. Возвращаемое значение и проверяется в условии: если 1 то скрипт выполняется дальше, если 0 – скрипт прекращает выполнение. Такой способ определения типа используется чаще всего для объектов полученных командой nearestobject или командой List (об этой команде читай в списке всех команд).
Далее в скрипте происходит присвоение переменным _oldcrater и _olddammage значения ближайшего к снайперу кратера и значения здоровья снайпера соответственно. Кратер в игре – след от пули на земле или другом объекте (кратеры иногда не прорисовываются, но все же существуют как объекты). Для чего эти переменные? Представим, что снайпер занимает позицию и рядом с этой позицией уже есть кратер. Скрипт найдет этот кратер и заставит снайпера отступать. Значение здоровья понадобится для проверки ранен ли снайпер или нет (снайпер может занять позицию уже раненым). Проверка здоровья необходима, потому что при попадании пули в человека не создается объект кратер и, следовательно, при ранении снайпер не будет отступать.
После присвоения значений переменным в скрипте идет проверка режима работы скрипта. Если заданный режим не предусмотрен, то выводится сообщение об ошибке.
Теперь рассмотрим цикл в скрипте. Вот первое проверяемое в скрипте условие:
?((((_sniper distance nearestobject [_sniper,"crater"]) < 20) AND
(nearestobject [_sniper,"crater"] != _oldcrater)) OR
((getdammage _sniper) > _olddammage)): goto "loop2"
Довольно сложное для понимания условие, неправда ли? Для того чтобы Вам было легче понять, я отметил скобок разными цветами. То есть для какого-то выражения открывающая и закрывающая скобка одного цвета. Что же проверяется в условии? В условии проверяется расстояние от снайпера до ближайшего к нему кратера (_sniper distance nearestobject [_sniper,"crater"]). Однако одновременно этот кратер не должен быть равен кратеру _oldcrater (…. AND (nearestobject [_sniper, "crater"] != _oldcrater)). Если эти условия не верны одновременно, то проверяется, ранен снайпер или нет (…. OR ((getdammage _sniper) > _olddammage)). Если одно из этих условий верно, то происходит переход на метку “loop2”. Также в цикле проверяется, мертв ли снайпер и если снайпер мертв, то выполнение скрипта заканчивается.
При верности первого условия происходит переход к метке “loop2”. После этой метки происходит проверка: жив ли снайпер. Затем проигрывается анимация: кажется, что снайпер, что-то кладет на землю. И как раз в том месте, (примерно) куда он что-то кладет, появляется рюкзак (муляж – для вида) и в том же месте дымовая завеса. Для вычисления координат появления рюкзака и дымовой завесы используется камера. После этого выбирается способ отхода (режим работы скрипта) и в зависимости от него выполняются различные действия. При отходе по вейпоинтам глобальная переменная SniperAIletmove принимает значение True, а именно значение этой переменной должно является условием в первом вейпоинте отхода! При отходе в заданную точку (режим “POS”) снайперу устанавливается тип поведения, режим передвижения и т.д. Затем команда domove заставляет его передвигаться в точку отхода.
Скрипт работает так: при запуске начинается поиск нового кратера (кратеры появятся, когда в снайпера будут стрелять), при нахождении такого кратера или при ранении снайпера начинается отход снайпера. Перед отходом ставится дымовая завеса, затрудняющая прицельную стрельбу. Отход происходит либо по вейпоинтам, либо просто в установленную точку.
Теперь о том, как использовать этот скрипт. Создаем карту на Колгуеве. В квадрате Ff52 есть база; вот туда и ставим несколько пулеметчиков (одного из них называем hevygunnner1) и разворачиваем их на северо-запад – на склон горы. Недалеко от горы ставим снайпера с именем sniper1. Затем расставляем ему вейпоинтов до самого склона; в вейпоинтах можно (даже нужно) прописывать команды. Команда типа Sniper1 setunitpos “UP” приписанная в OnActivation вейпоинта начнет выполняться только со следующего вейпоинта. Итак, понаставили вейпоинтов до самого склона, теперь в поле OnActivation последнего вейпоинта прописываем запуск скрипта: [sniper1,"WP",getpos gl1,"CARELESS","down","BLUE"] exec "SniperAI.sqs"; sniper1 reveal hevygunnner1; sniper1 dofire hevygunnner1. Команда reaveal заставляет один юнит узнать о другом. Нужно обязательно указать все аргументы скрипта (gl1 – Game Logic с именем Gl1) даже ненужные в данном режиме работы скрипта! Конечно, можно сделать так, чтобы ненужные аргументы не нужно было вводить. Но этим Вы займетесь сами, если захотите. Подсказка: нужно сначала определить режим работы скрипта(2-й аргумент), а затем уже получать остальные только нужные в зависимости от режима работы аргументы. К примеру, создаем метку, после которой идут аргументы необходимые для работы в режиме “POS”. В начале скрипта определяем, выбран ли этот режим работы, и если выбран, переходим к этой метке. В конце получения нужных аргументов ставим команду goto чтобы “перепрыгнуть” получение ненужных аргументов. Все это должно выглядеть примерно так я объяснил. Вернемся к вейпоинтам. Рядом с последним вейпоинтом ставим еще один с условием (Condition) SniperAIletmove == True (или просто SniperAIletmove). После этого вейпоинта идут вейпоинты отхода. Вот и все!
Конечно, этот скрипт не сильно “прибавит мозгов” снайперу- компьютеру, однако если его доработать.… Например, выбрать главную цель и, определяя какое оружие имеют рядом стоящие (опять nearestobject) бойцы, уничтожать их по-очереди в соответствии с типом их оружия (на дистанции M21 опаснее, чем M60; для определения оружия – команда hasWeapon (см. список команд)). Собственно имея хорошую фантазию и терпенье в ОФП с помощью скриптов можно сделать ВСЕ или почти все!
Кажется, я рассказал обо всех основных приемах создания скриптов. Для начала хватит. Если же Вы хотите стать мастером в создании скриптов, то Вам поможет только терпенье и любопытство. Удачи.
Sanek
PS:Если Вы хотите разместить это учебник на своем сайте, пожалуйста, черкните мне об этом на мыло. Напишите в письме о сайте (название и ссылка) и о Ваших впечатлениях об учебнике (понравился или нет и тд). Для меня это очень важно! =)
PSS:Огромное спасибо Martini за помощь!
PSSS:Нашли ошибки? Неточности? Опечатки? По всем вопросам пишите - Sanek . Или ICQ# 77449483
Sanek
Наша Армия [CCCP]
Flashpoint on Xaos
|