Мальчики и девочки, дамы и господа, сэры и сэрихи, ЗДРАВСТВУЙТЕ.
Бывают времена, когда мне нечего делать на работе, а вы меня совсем не радуете новыми постами, поэтому, я скучаю.
Поэтому, по совету товарищей я решил... нет, не купить автомобиль "Москвич", я решил выкладывать уроки по скриптописанию и созданию миссий в Арме. Писать сюда я буду только тот материал, который интересен мне и который мне не стыдно выложить. Но это же форум - любой ваш вклад приветствуется. Глядишь, совместными усилиями наберём материал, и сделаем методичку. А если совместных усилий не будет, то будут хотя бы несколько хороших статей.
Сегодня я представляю вам материал от
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
, может с низенькой тумбочки моего небольшого опыта в этом деле я не могу судить адекватно, но мне кажется, его блог очень полезен и крайне рекомендован к прочтению. Материалы хорошие, стиль изложения - понятный. Кроме материалов КК, также приводятся материалы с вики БИСов.
Перед началом работы рекомендую посетить вот эти две ссылочки: Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Введение
Скрипты - важная часть создания миссий. Они позволяют делать чудные видеовставки (катсцены), создавать эффекты и настраивать по своей задумке практически любой аспект создаваемой миссии. Вот вам несколько различных примеров, что можно сделать скриптами: симуляция артиллерийского огня, облако ядовитого газа, а также денежная система для покупки экипировки.
Алгоритм
Ваша первая задача при написании скриптов - определить, какие шаги надо предпринять, чтобы решить какую-либо проблему. Последовательность таких шагов - это и будет алгоритм.
Например, если ваше намерение - транспортировать оружие от Петровиче до Липан, вы (как командир) отдадите вот такие приказы:
- Командир: Рядовой Хонка, рядовой Куба, ко мне!
- Командир: Рядовой Куба, сесть в Урал на место водителя!
- Командир: Рядовой Хонка, сесть в Урал!
- Командир: Рядовой Куба, езжайте в Петровиче и загрузите Урал!
- Рядовой Куба: Я не знаю, где Петровиче, сэр!
И у нас проблема. Всё, что знает Куба - это как доехать в Липаны, а Хонка знает дорогу только до Петровиче. Наши солдаты - новобранцы и они реально тупые. Они понимают только простые приказы, и-то в количестве не больше одного за раз. Сесть и вылезть из машины они могут, и вести её могут, и даже загрузить и разгрузить оружие - тоже смогут. Но командиру нужно составить правильную последовательность приказов, и предвидеть, какие препятствия исполнению этих приказов могут возникнуть.
- Пункт назначения - Петровиче
- По машинам!
- Если водитель не знает, как доехать в пункт назначения - поменяться местами водителю и пассажиру!
- Вести машину в пункт назначения
- Выйти из машины
- Загрузить машину
- Пункт назначения Липаны
- По машинам!
- Если водитель не знает, как доехать в пункт назначения - поменяться местами водителю и пассажиру!
- Вести машину в пункт назначения
- Выйти из машины
- Разгрузить машину
Интерпретатор
Когда вы составили алгоритм, вам нужно что-то, что может его исполнить. У нас есть компьютерная игра - наша несравненная Армочка, которая может это сделать; у командиров есть солдаты.
Язык программирования
Это способ - как записать наш алгоритм так, чтобы наша несравненная Армочка БИСовна его поняла и могла исполнить. Скрипты Армочки - это последовательность "приказов", описывающих, как это сделать.
Исходный код
Исходный код - это алгоритм, написанный на каком-либо языке программирования.
Синтаксис
Синтаксис - это набор команд и параметров, причём, иногда хватает лишь одной лишь команды, как в случае команды - exit, но чаще синтаксис в синтаксисе присутствует один и более параметров. У каждой команды есть своя страничка с руководством на вики [color=rgb(128,128,128);][читайте раздел "ArmA Scripting Tutorials: Список Cкриптовых Команд Arma 2 / Arma 3"][/color]. У каждой команды есть пример применения, указаны необходимые параметры и рассказано, как каждая команда работает [color=rgb(128,128,128);][ах, если бы - не пришлось бы это всё писать, кабы было оно так][/color].
if (PrivateHonka == TheMostCleverSoldierInTheWorld) then { IAmChineseGodOfHumour = true; };//Если рядовой Хонка самый умный солдат в мире, то я - китайский бог юмора
Стоит заметить, что в Армочке есть две скриптовых грамоты - SQS и SQF [color=rgb(128,128,128);][на вторую ждите скорой статьи, а от первой БИСы советуют отказываться в пользу SQF везде, где только можно][/color]. И хотя большинство функций у них одинаковы, всё-таки существуют некоторые различия в написании выражений, ходе программы и управляющих структурах.
Как работает интерпретатор
Несравненный движок несравненной Армочки читает ваш код из скриптовых файлов и переводит команды в соответствующие им действия, и на выходе вы получаете желаемый эффект/результат в игре.
Начнём же!
Каждый скрипт состоит из набора команд [color=rgb(128,128,128);font-family:helvetica, arial, sans-serif;][читайте раздел "ArmA Scripting Tutorials: Список Cкриптовых Команд Arma 2 / Arma 3"][/color] и выражений, управляющих ходом программы (в Армочке они исполняются как команды, но на данном этапе это неважно [color=rgb(128,128,128);font-family:helvetica, arial, sans-serif;][ждите статью по управляющим структурам, где это будет объяснено подробнее][/color]). Самой используемой командой при первых ваших шагах в скриптописании может стать команда titleText. Она может написать любой текст, какой вы захотите, на экране игрока.
titleText ["Good morning, captain", "PLAIN DOWN"];
Перед вами самый обычный способ вызова команд. Сразу за именем команды следуют параметры (но это всё-таки зависит от команды, и надо сверяться с руководством). Каждая команда может иметь не более одного параметра перед своим именем и/или один - после имени (большее количество аргументов может быть передано с помощью массивов, поскольку один массив воспринимается как один параметр). Параметры могут быть самых различных типов [color=rgb(128,128,128);][ждите материал по типам данных][/color]. В нашем примере параметр - это массив [color=rgb(128,128,128);][читайте урок по массивам в трёх частях, или хотя бы его первую часть][/color] - набор других типов данных. Он может содержать от 0 до 4096 (?) значений. Первое значение - это строка представляющая собой текст, который будет выведен на экран, а второе значение, в данном случае, говорит в каком месте экрана текст будет показан (см. руководство к команде
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
). Также, данная команда допускает третье значение в массиве - число, которое укажет, как долго текст будет появляться на экране , если значение не введено, будет использоваться значение по умолчанию (1).titleText ["Good morning, captain", "BLACK IN", 5];
Скрипты, которые называются функциями, нахотядся в .sqf-файлах в папке миссии, те же, что хранятся в .sqs-файлах, называются скриптами.
теперь вы можете попробовать исполнить наш "скриптик". Для этого создайте в редакторе миссию, сохраните её под каким-нибудь названием, например testingmission, откройте текстовый редактор (блокнот сойдёт), напишите:
titleText ["Good morning, captain", "PLAIN DOWN"];
и сохраните файл как hello.sqf в папку с только что созданной миссией. В редакторе, в своей миссии добавьте солдатика, и напишите в его строке инициализации:
nul = [] execVM "hello.sqf"
Теперь, когда вы запустите эту миссию, вы должны будете увидеть результат работы скрипта. Отлично, солдат!
Настоятельно рекомендую ознакомиться с разделом "ArmA Scripting Tutorials: Начинаем",- поскольку там представлен более удобный и быстрый способ знакомства со скриптами. Но эта статья тоже должна быть полезна - тут первое упоминание того, как же добавлять скрипты в миссии.
Ведите себя хорошо
ArmA Scripting Tutorials: Начинаем.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Ещё во времена альфы, а конкретнее, во время версии 0.54 была добавлена такая штука...
Added: improved public debug console
Добавлено: отладочная консоль для широкого круга пользователей
И она сильно упрощает тестирование и отладку скриптов. Добыть её можно, нажав в редакторе ESC во время предпросмотра (preview) миссии. Консоль позволяет осуществлять взаимодействие "на лету" с переменными и объектами в миссии.
На видео показано как создать и запустить простой скрипт, выводящий "hello world", как подсказку. Подсказки очень полезны, когда вы хотите быстро посмотреть значение переменной. Команда hint требует строкового аргумента, вот почему вам следует придерживаться нижепритведённого формата, при выведении на экран подсказок, и этот формат позволит вам выводить любые переменные, определённые, неопределенные, строковые, не строковые, нулевыи или вообще любые - hint format ["%1",вашаПеременная];
_somevariable = 123; hint format ["%1", _somevariable];// показывает "123" _somevariable = objNull; hint format ["%1", _somevariable];//показывает "<NULL-object>" _somevariable = compile "a = b"; hint format ["%1", _somevariable];//показывает "{a = b}" _somevariable = call {}; hint format ["%1", _somevariable];//показывает "<null>" _somevariable = nil; hint format ["%1", _somevariable];//показывает "any"
Подсказка висит на экране несколько секунд, потом плавно сходит на нет. Кроме того, любая последовательная команда hint[color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;] [/color]затрёт подсказку, которая была до этого. Поэтому, если вы желаете вести лог нескольких выводов/значений переменных, вместо команды hint[color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;] и[/color]спользуйте команду diag_log - diag_log forman ["%1" ,ваша переменная]; diag_log будет делать записи в .rpt-файл, каждый вызов diag_log создаст новую строку. Этот файл можно найти в C:Users[ИмяВасНенаглядного]AppDataLocalArma 3 Alphaarma3_[ШтампСамыхПоследнихВремениИДаты].rpt.
_somevariable = call {}; diag_log format ["%1", _somevariable]; _somevariable = nil; diag_log format ["%1", _somevariable]; //Записи в .rpt файле //"<null>" //"any"
Очень важно использовать format при вызовах как diag_log так и hint для отладочных целей, поскольку это позволяет вам видеть такие значения как any и <null>. <null> означает, что переменная имеет нулевое значение, поскольку call в нашем примере возвращает чуть меннее, чем ничего, в качестве результата. any означает, что переменная пока неопределена, и может иметь любое значение, поскольку мы сбросили её, присвоив ей значение nil. Также .rpt-файл позволяет узнать, были ли в скриптах какие-нибудь ошибки. Если в скрипте случается ошибка, он может быть отвергнут, так что убедитесь, что в ваших скриптах ошибочек нет. А сейчас немного веселья.
//заставим игрока плавать в воздухе player playMove "AbswPercMrunSnonWnonDf"; //сделаем игрока невидимым player hideObject true; //Пущай человечек проходит сквозь твёрдые объекты null = [] spawn { while {true} do { { player disableCollisionWith _x; } forEach ((position player) nearObjects 100); sleep 1; }; }; //в сортиры с этим чудным скриптом не пускает : )
Кстати, как уже показал в приведённом выше примере. если вы собираетесь использовать команду sleep со скриптом в отладочной консоли, то убидетесь, что не забыли инкапсулировать свой код в структуру навроде null=[]spawn{...ваш код...}; Это нужно делать, потому что скрипт в отладочной консоли вызывается командой call и вы не можете приостанавливать скрипт командами навроде sleep с call. Мы ещё вернёмся к этому вопросу в следующих уроках.
Ведите себя хорошо
ArmA: Скриптописание. Синтаксис SQF
Автор: Community, с полным списком авторов можно ознакомиться здесь:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Синтаксис SQF
Синтаксис SQF впервые был представлен в OFP:Resistance и является общим стандартным синтаксисом, начиная с Armed Assault. Альтернативой ему является синтаксис SQS (устаревший ещё во временя Armed Assault, поэтому, уроков по нему именно я переводить/добывать/делать не собираюсь).
Правила
Основные правила:
- Фигурные скобки {} группируют код в блок
- Выражения (кстати, тоже блоки) заканчиваются знаком точки с запятой ;
Что такое блок, и что такое выражение, вы можете узнать под спойлером:
Блоки
Блок (Block) - это эдакий кода кусок, объединённый в одну группу. Блок начинается и заканчивается фигурными скобками: {кода кусок}. Блоки могут быть независимыми и вызываться командой call, или же принадлежать управляющей структуре.
Выражения
Выражение (Statement) - это просто кода кусок. Выражение является командой для интерпретатора скриптов, и говорит ему (интерпретатору) что-нибудь делать.
Виды выражений:
Выражения могут быть:
- присваиванием значения
- управляющей структурой
- скриптовой командой
Присваивание значения
Такое выражение просто присваивает значение переменной.
_variable = 15;
Управляющая структура
Подобное выражение представляет собой любую управляющую структуру, включая входящие в неё блоки.
if (_value > _limit) then { hint "oh no"; };
Команда
Любая скриптовая команда вместе с аргументами.
player sideChat "hello";
Последнее правило (то, что про выражения) говорит игровому движку где одно выражение заканчивается, и где начинается следующее.
Пример:
STATEMENT 1; STATEMENT 2; BLOCK { STATEMENT 3; STATEMENT 4; };
Если синтаксис SQS основан на строках, то синтаксис SQF основан на структурированных выражениях. Окончание строки не имеет никакого особого значения - он принимается эквивалентным пробелу или табуляции, и, таким образом, он не требуется, даже если и находится в конце выражения.
Комментарии
Комментарий - это любой текста кусок, который игнорируется игровым движком. В синтаксисе SQF вы можете написать комментарий с помощью команды comment.
comment "Это комментарий";
Если файл загружается одной из команд: preprocessFile, execVB или spawn то в нём могут быть Си-подобные комментарии (не работает с командой loadFile):
Строчный комментарий начинается с двух слэшей //, за которыми следует, собственно, комментарий.
Блочный комментарий начинается с /*, а заканчивается на */. Любой текст между ними считается комментарием.
// Это строчный комментарий /*а это ОЧНЕ длинный блочный комментарий */
На этом введение в синтаксис заканчивается, ждите статей по управляющим структурам, функциям и переменным. Особенно хорошего урока ожидайте по переменным - это будет перевод материала от КК, а это не так скучно, как БИСовское.
ArmA Scripting Tutorials: Основы.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Быть ; или не быть ; - вот в чём вопрос! Ответ прост - если вы в смятении, поставить ; лучше вам. Давайте объясню. Точка с запятой используется в SQF-скриптах, чтобы сказать движку (несравненной Армочки), где заканчивается одна команда, и где начинается другая. Если вы пропустите ; там, где оно требуется, скрипт остановится и в .rpt-файл будет записано сообщение об ошибке, которое и скажет вам ,что вы пропустили точку с запятой. Вы можете опустить точку с запятой непосредственно перед закрывающейся фигурной скобкой }, поскольку она тоже указывает на конец блока кода. Также можно опускать точку с запятой в самой последней строчке кода, по той же самой причине: конец файла - также является и концом кода. Но никто не помрёт (даже ваш скрипт), если вы поместите точку с запятой в этих местах, даже не смотря на то, что вы не обязаны. Но будьте осторожны при передаче аргументов в функции: лишняя точка с запятой может всё испортить. То же справедливо и для скобок (). Скобки используются для того, чтобы объяснить движку (несравненной Армочки) в каком порядке ему придётся обрабатывать выражение. Первыми выполняются те выражения, которые находятся в самой глубине иерархии, в самых внутренних скобках, затем родительские скобки, затем прародительские, и так далее (((1)2)3). Например, вам нужно выбрать массив внутри массива (ПРИМ: Используя выражение select, вы можете опустить скобки в некоторых простых случаях: _eight = _array select 3 select 4 select 0; работает просто прекрасно) Такого же результата можно добиться, используя промежуточные переменные: Используйте скобки когда вам надо логически разделить команды Армочки (несравненной) или же рано или поздно вы столкнётесь с непредсказуемыми результатами. Сравните вот эти два выражения: Не смотря на то, что первое выражение не выдаст ошибки, результатом будут несколько рандомных чисел, но никак не то, чего хочется. А хочется получить список объектов на расстоянии не более 50 метров от игрока и второе выражение делает как раз это, да здравствуют скобки! А, да, конечно же неплохо бы в этом же посте объяснить как вызывать функции. Но сначала её придётся создать, это очень просто, поскольку эта функция - ничего иное, как переменная, с присвоенным ей кодом. Переменная может быть приватной и публичной. Чтобы вызвать фукнцию, вы можете использовать команду call (вы узнаете побольше о командах чуть позже). [color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;font-size:14px;]Функции могут и выдавать результаты и принимать аргументы. Чтобы переслать аргументы в функцию, используя команду [/color]call[color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;font-size:14px;], поставьте перед ней переменную, содержащую аргументы.[/color] Обратите внимание на использование скобок во втором примере, без них просто не будет работать. Ну вот и всё по самым-пресамым основам, надеюсь, что это полезно. Ведите себя хорошо
a = b;
c = d;
// всё чётко
a = b
c = d;
// нихрена не всё чётко. ошибка
a = {c = d;};
// ништяк
a = {c = d};
// и так ништяк.
while {true} do {hint "ok"; breakOut ""}
// Кашу маслом не испортить
while {true;} do {hint "ok"; breakOut "";};
// Машу пальцем тоже. В обоих случаях всё нормально и работать будет
a = {"ok"}; hint format ["%1", call a;];
//ошибк
аa = {"ok"}; hint format ["%1", call a];
//так гораздо лучше
_array = [1,2,3,[4,5,6,7,[8,9]]];
_eight = ((_array select 3) select 4) select 0;
_array1 = _array select 3;
_array2 = _array1 select 4;
_eight = _array2 select 0;
//НЕ НАДО ТАК ДЕЛАТЬ:
{diag_log format ["%1",_x]} forEach position player nearObjects 50;
//А ВОТ ТАК ПРАВИЛЬНО
{diag_log format ["%1",_x]} forEach ((position player) nearObjects 50);
my_function = {
a = 1;
b = 2;
c = a + b;
};
call my_function; //теперь c равняется трём
// либо
my_function = {
private ["_a","_b","_c"];
_a = _this select 0;
_b = _this select 1;
_c = _a + _b;
_c
};
// либо
my_function = {
(_this select 0) + (_this select 1)
};
_result = [1,2] call my_function;
//_result = 3
ArmA Scripting Tutorials: Список Cкриптовых Команд Arma 2 / Arma 3.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Несмотря на то, что я взял эту статью у КК и указал его, как автора, должен заметить, что список на его сайте [color=#808080;][пока не умею размечать таблицы на этом форуме - вам придётся ходить по ссылочкам][/color] весьма и весьма современен и удобен, это не единственное место, где вы можете найти эти команды. Более того, все, кто занимается скриптописанием прекрасно знают эти места, но для новичков эта информация может оказаться полезной
Вот вам ещё ссылок:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Чтобы найти интересующую команду в данных списках, можно нажать Ctrl+F [color=#808080;][Да, знаю, что те, кто с этим имеет дело, знают эту команду, но вы не поверите, сколько мне встретилось человеков за последние 2 года, кто этого не умел[/color]], кроме того, в отладочной консоли в Армочке можно набрать команду и нажать F1.
ArmA Scripting Tutorials: Как включить "ленивую" проверку условий.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Перевод свободный, поэтому, посещение
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
лишь привествуется.
Стоит запомнить, что движок армы в своей повседневной работе слегка долбанутый - он очень любит создавать работу сам себе, и если вы не скажете ему остановиться и передохнуть, он будет продолжать и продолжать делать что-нибудь. Сегодня в фокусе нашего внимания - выражения с условными операторами, возвращающими нам значения true или false. Давайте взглянем на простой пример:
if (a && b && c) then { //делайте что-нибудь };
Что мы имеем: если a, b и c - все имеют значение true, то всё выражение в скобках () должно быть оценено как true и наш код в фигурных скобках {} будет исполнен. В общем, КАЖДАЯ переменная должна иметь значение true, чтобы всё выражение было расценено как true. Выражения оцениваются движком слева направо и в идеале, если a принимает значение false, нам не нужно дальше оценивать выражение. Ну что тут скажешь, движок Армочки думает иначе, и продолжает проверять всё, до тех пор, пока не встретит закрывающуюся скобку ).
Как это влияет на написание кода? Это значит, что для того, чтобы ускорить работу скриптов, вам лучше каскадировать несколько операторов if. Приведённый выше код может быть оптимизирован как:
if (a) then { if (b) then { if (c) then { //делайте что-нибудь }; }; };
В этом случае, если a принимает значение false, то движок игнорирует код фигурных скобках {} и переходит к выполнению других задач.
К нашему счастью, в BETA 93632 в движок Армочки была введена "ленивая" оценка условий.
New: Lazy evaluation variants of scripting functions and / or.
Чтобы включить это чудо, нужно использовать другой синтаксис. Обычный режим оценки условий включается при вот таком синтаксисе:
- boolean [and/or] boolean [and/or] boolean … [and/or] boolean
Сравните это с синтаксисом "ленивой" оценки условий:
- boolean [and/or] code [and/or] code … [and/or] code
Начинается оценка с переменной булевого типа, а продолжается с оценкой кода. Вы можете использовать булевый тип данных только однажды и только в самом начале выражения. Вам необходимо осуществить оценку кода и приведению к значениям true или false, так же как и переменные булевого типа. Итак, чтобы включить "ленивую" оценку условий нам нужно переписать наш пример как:
if (a && {b} && {c}) then { //делайте что-нибудь };
Не так уж и плохо, хотя, для единообразия, лучше бы оно и начиналось с кода - было бы безобразно, зато единообразно, а не как сейчас. Кстати, это работает даже с операторами switch, while и waitUntil.
private ["_result","_step","_c"]; _result = "is false"; a = { _step = "1"; true; }; b = { _step = "2"; false; }; _c = { _step = "3"; true; }; scopeName "main"; while {call a && b && _c} do { _result = "is true"; breakTo "main"; }; hintSilent format ["expression %1, quit at step %2", _result, _step];
Исполнение этого кода выведет: Expression is false, quit at step 2. Ну вот он и закончился, мой урок по "ленивой" оценке условий в Армочке.
Есть смысл применять тогда и только тогда, когда события обязаны выполняться в определённом порядке, и хоть сколь-нибудь ощутимый выигрыш можно получить при применении метода if () then { if () then { if () then {}}}; Этот метод даёт выигрыш, если вещи идут в нужном вам порядке, и ухудшает производительность, если этот порядок нарушается, применение же БИСовской оптимизации не даёт почти никакого выигрыша, если вещи идут в нужном вам порядке, и ухудшает производительность, если этот порядок может быть нарушен.
Ведите себя хорошо,
КК
ArmA Scripting Tutorials: Переменные (Часть Номер Раз).
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
В несравненной Армочке есть 5 разных типов переменных: приватные, специальные приватные, публичные локальные, специальные публичные локальные и публичные глобальные. У каждого типа переменных своя область действия, и об этом надо знать и помнить.
Приватные переменные
Движок (несравненной Армочки) автоматически воспринимает любую переменную, чьё имя начинается с подчёркивания как приватную. Для имён переменных вы можете использовать стандартную алфавитно-цифровую нотацию: от A до Z, от a до z, от 0 до 9 и _ - все эти символы можно использовать для того, чтобы дать имя переменной. Таким образом, _hello, _HELLO_WORLD2000, _____hello, _123, ____123_______hello - все они имеют правильные, с точки зрения движка, имена и будут восприниматься как приватные. Приватные переменные создаются в тех местах, где они их определят, в рамках тех областей действия, в которых происходит это определение, и удаляются как только ход программы выходит за рамки их области действия. Таким образом, любая приватная переменная, определённая в какой-либо области действия, будет неопределена за рамками этой области.
//_i в этом месте ещё не определена и имеет значение nil if true then { //начало области действия _i = 1; //определяем _i // в этом месте _i равна 1 и определена }; // конец области действия //_i имеет значение nil, поскольку мы за рамками области действия
Если у вас есть какая-то дочерняя область действия, находящаяся в родительской области действия, а в этой родительской области ваша приватная переменная определена, то она будет точно так же определена и в дочерней области, и в дочерней области вашей дочерней области, а если и в ней есть дочерняя область - то и в ней тоже, и т.д. Поэтому, если намерены изменять значение приватной переменной в дочерней области действия, и вам нужно, чтобы эти изменения отразились в родительской области - сперва убедитесь, что вы определили эту приватную переменную в родительской области.
_i = 1; if true then { //в этом месте _i определена и равна 1 if true then { //в этом месте _i определена и равна 1 if true then { //в этом месте _i определена и равна 1 }; }; };
Но как быть, если вам необходимо, чтобы значение приватной переменной, которую вы используете в дочерней области действия, существовало только в той самой области? Тогда вам нужно использовать команду private. Приватные переменные вы можете определять по-одной, либо же скопом - в едином массиве[color=#808080;] [рекомендую к прочтению хотя бы первую часть урока по массивам. Урок представлен ниже в этом же посте][/color].
private "_a"; private "_b"; private "_c"; //это то же самое, что и private ["_a","_b","_c"];
Использование команды private гарантирует, что если даже в родительской области действия уже определена переменная с таким же именем, то её (родительской переменной) значение не будет изменяться в дочерней области действия никоим образом и переменная, которую вы непосредственно объявили приватной будет неопределена в дочерней области действия, пока вы не определите её.
hint format ["%1", _i]; //"any" _i = 1; hint format ["%1", _i]; //"1" if true then { hint format ["%1", _i]; //"1" private "_i"; hint format ["%1", _i]; //"any" _i = 2; hint format ["%1", _i]; //"2" }; hint format ["%1", _i]; //"1"
Специальные приватные переменные
Обратите внимание, что чуть ниже представлен отдельный урок про специальным приватным переменным, также здесь приведу ссылку на статью автора, взгляните на список специальных приватных переменных (
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
). При выполнении определённых команд движок будет автоматически создавать специальные переменные, которые существуют только в области действия этой команды, и последующие присваивания значений такой переменной тоже зависит от самой команды. Поскольку движок сначала использует команду private на эти переменные, то они существуют только в рамках действия этой команды.// описана только первая итерация { { hint str _x; //_x = 1 } forEach _x; //_x = [1,2,3] } forEach [[1,2,3],[4,5,6],[7,8,9]];
На вышеприведённом примере видно что _x в родительском цикле - это не тот же самый _x, что в дочернем цикле. Это позволяет нам безопасно создавать вложенные циклы. Если же вам нужно, чтобы родительский _x менялся в результате действия дочернего цикла, вы можете присвоить _x другой переменной и передать её в дочерний цикл
// описана только первая итерация { _x2 = _x; { hint str _x; // "1" hint str _x2; // [1,2,3] } forEach _x; } forEach [[1,2,3],[4,5,6],[7,8,9]];
Больше мне нечего добавить о специальных приватных переменных, за исключением того, что не нужно создавать свои переменные с такими именами, которые используются для специальных приватных переменных. Также, в заключение этого урока, хочется сказать, что никогда не помешает проявить дополнительную осторожность и непосредственно объявить переменные в вашем скрипте, как приватные (за исключением случаев, когда у вас есть веские причины этого не делать), особенно если ваша функция вызывается командой call. Для этого есть неплохой инструмент вот в этой статье: (http://arma3.ru/foru...-oblegchaiusch/), называется этот инструмент "ArmA 2 / ArmA 3 Simple Private Variable Extractor".
_function = { _var = _var + 1; }; _var = 10; call _function; hint str _var; // "11" _var = 100; //определение _var где-то за пределами требуемой нам области действия function = { private "_var"; _var = _this select 0; _var = _var + 1; _var }; hint str ([10] call function); //результат будет 11, а не 101
На этом мы заканчиваем первую часть, смотрите, что изменится в части 2.
Ведите себя хорошо
ArmA Scripting Tutorials: Переменные (Часть Номер Два).
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
В первой части я рассказал о приватных переменных. Эта часть будет о двух типах публичных переменных, публичных локальных и специальных публичных локальных. Локальными они называются, потому что эти переменные доступны только на машине, которая выполняет этот скрипт, публичными - потому что будучи однажды определены, эти переменные могут быть изменены из любого места, при условии, конечно, что это "любое место" на этой машине.
Публичные локальные переменные
Чтобы дать имя публичной переменной вы можете использовать стандартый алфавитно-цифровой набор символов, как и в случае с приватными переменными. Но, имя публичной переменной не может начинаться с подчёркивания (очевидно же, что в этом случае, она будет считаться приватной), и имя не может начинаться с цифры. 123variable - является недопустимым именем, а variable123 - хорошее, годное имя для переменной. Также избегайте названия своих публичных переменных теми же именами, которыми названы команды Армочки или существующие функции БИСовской библиотеки. Не смотря, что во второй Армочке можно было такое творить, типа как в примере прямо под этим абзацем, в Армочке Третьей это уже невозможно по причинам безопасности и движок выдаст ошибку (да ещё и жаловаться будет!).
false = true; call = "It's true!"; if (false) then {hint call}; // "It's true!
Если вы просто напишете myvar = "somevar"; переменная myvar будет по умолчанию проинициализирована в пространстве имён (namespace) миссии и будет существовать до самого конца миссии, и пока вы не вернётесь в лобби. Для уничтожения публичной переменной во время сессии, вам нужно присвоить ей значение nil, вот так: myvar = nil;
При создании публичной функции, вы, по сути, создаёте публичную локальную переменную и присваиваете ей код. Поскольку доступ к ней можно получить из любого скрипта, который будет запущен на этой же машине, такие переменные - неплохой выбор, когда вы хотите разбить код на модули. Таким образом, для дополнения какой-либо функциональности, вместо создания нового скриптового файла вы можете просто создать ещё одну функцию.
Вернёмся к пространству имён. Если приватные переменные существовали только в рамках определённой области действия, в которой они были определены, для публичных переменных такими рамками является пространство имён. Вы можете играться с четырьмя такими пространствами, воспринимайте их, как разделы кода.
missionNamespace
Пространство имён миссии - это основной раздел, также являющийся разделом "по умолчанию". Это пространство имён существует с момента загрузки миссии до момента её окончания. Все переменные, определённые в этом пространстве имён, прекратят существовать, как только вы вернётесь в лобби. Если вам нужно, чтобы ваши переменные существовали подольше, вам следует воспользоваться другими пространствами имён.
uiNamespace и parsing Namespace
Это два разных пространства имён с весьма похожими рамками действия. Публичные локальные переменные, которые создаются в этих пространствах будут существовать с того момента, когда вы их определяете до момента, когда вы выключаете Армочку.
profileNamespace
Это пространство имён будет хранить ваши переменные настолько долго, насколько вы пользуетесь одним профилем в игре. Если вы создаёте другой профиль, или переключаетесь на другой заранее созданный профиль, то публичные локальные переменные, которые вы создали в старом профиле, очевидно, не будут существовать в новом. Сохранение переменных в этом пространстве имён происходит автоматически, но вы можете принудительно сохранить их с помощью команды saveProfileNamespace.
В итоге, чтобы создать публичную переменную в пространстве имён миссии, вам можно просто написать что-то вроде myvar = "somevar"; потому что это пространство по умолчанию. Это значит, что по умолчанию, любое подобное действие будет восприниматься движком так же, как еслы бы вы писали:
with missionNamespace do { myvar = "somevar"; };//это то же самое, что просто написать: myvar = "somevar";
Чтобы оперировать с другими пространствами имён, вы можете использовать команду with и проделать такой же трюк.
with uiNamespace do {}; with parsingNamespace do {}; with profileNamespace do {};
Обратите внимание на то, что ваши публичные переменные из одного пространства не будут доступны в других пространствах. Даже если вы объявите переменную с тем же именем, но в другом пространстве имён, они не будут влиять друг на дружку. Это похоже на поведение приватных переменных в области их действия. Но со специальными публичными локальными переменными всё обстоит иначе (о них мы поговорим чуть позже).
myvar = 123; with uiNamespace do { hint str (isNil "myvar"); // true myvar = 456; }; with profileNamespace do { hint str (isNil "myvar"); // true myvar = 789; }; with uiNamespace do { hint str myvar; // 456 }; with profileNamespace do { hint str myvar; //789 }; hint str myvar; //123
Также вы можете установить значение переменной, используя команду setVariable, а прочтитать - командой getVariable. При использовании данных команд, вам необходимо прикрепить их к чему-нибудь, например к пространству имён.
missionNamespace setVariable ["tro","lolol"]; //это то же самое, что и with missionNamespace do { tro = "lolol"; }; //и то же самое, что tro = "lolol";
Это значит, что при желании считать значения публичных переменных динамически, в зависимости от их имени, вместо проделывания такого трюка:
var1 = 1; var2 = 2; for "_i" from 1 to 2 do { private "_myvar"; // обратите внимание на то, как необходимо предопределять _myvar call compile format ["_myvar = var%1;", _i]; diag_log _myvar; };
вам достаточно проделать такой:
var1 = 1; var2 = 2; for "_i" from 1 to 2 do { diag_log (missionNamespace getVariable (format ["var%1;", _i])); };
Разумеется, вы можете проделать такой трюк и с другими пространствами имён. Есть несколько вещей, касающихся setVariable и getVariable, о которых надо упомянуть. У команды setVariable есть третий аргумент, который указывает, должна ли переменная быть глобальной или же нет. Я расскажу об этом позже, но на данный момент публичные переменные в пространстве имён могут быть только локальными, поэтому, если вы попробуете сделать их глобальными, выпадет ошибка. У команды getVariable есть альтернативный синтакстис, в котором вы можете назначить значение по умолчанию, которое будет использоваться, если переменная, к которой вы обращаетесь, ещё не определена.
_myrank = profileNamespace getVariable ["MY_RANK", "Private"];
Этот кусок кода установит _myrank в значение "Private", если
with profileNamespace do { hint str (isNil "MY_RANK"); //true };
Кроме пространства имён вы можете прикреплять публичные переменные к объектам, технике, инструментам управления, группам, задачам, месторасположению, членам группы и местам. Тут есть свои ограничения, о которых будет вестись речь в следующей части. Чтобы разопределить локальную публичную переменную командой setVariable, используйте значение nil в качестве устанавливаемого значения.
profileNamespace setVariable ["MY_RANK", nil];
Специальные локальные публичные переменные
Таким переменным движком заранее присваиваются специфичные предопределённые значения, и они существуют во всех пространствах имён, но они всё-таки локальны, и существуют только на машине, на которой они запущены. Например, time на одной машине может отличаться от time на другой машине. А переменная player будет различной на каждом отдельном клиенте, а на выделенном сервере она просто нулевая. Всячески избегайте назначения таких имён вашим переменным. Для упрощения, такие переменные подсвечиваются в автоподсветке синтаксиса от КК (можно взять тут).
true false time enemy friendly sideLogic sideUnknown sideFriendly sideEnemy playerSide east west civilian resistance independent opfor blufor nil objNull grpNull netOnjNull locationNull taskNull controlNull displayNull teamMemberNull player
На этом завершим вторую часть. Кстати, сейчас будет полезно прочтитать урок "ArmA Scripting Tutorials: Чувствительность к Регистру", по крайней мере, ту часть, что касается переменных. Больше о переменных вы узнаете в третьей части урока.
Ведите себя хорошо
ArmA Scripting Tutorials: Переменные (Часть Номер Три).
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Прежде, чем продолжить разговор о локальных публичных переменных, начатый во второй части, я бы хотел рассказать о типах переменных или о типах данных, если вам так больше нравится. Пока переменная неопределена, у неё нет типа данных. Когда вы присваиваете данные переменной, она автоматически воспринимается, как имеющая тот же тип, что и данные, которые вы ей присваиваете. Если вам необходимо проверить, правильного ли типа данные вы хотите использовать, перед тем, как обрабатывать их, вы можете проверить их с помощью команды typeName. Эта команда вернёт строку с названием типа данных.
_var = 0; hint typeName _var; //SCALAR _var = ""; hint typeName _var; //STRING _var = true; hint typeName _var; //BOOL _var = []; hint typeName _var; //ARRAY _var = {}; hint typeName _var; //CODE _var = objNull; hint typeName _var; //OBJECT _var = grpNull; hint typeName _var; //GROUP _var = controlNull; hint typeName _var; //CONTROL _var = teammemberNull; hint typeName _var; //TEAM_MEMBER _var = displayNull; hint typeName _var; //DISPLAY _var = taskNull; hint typeName _var; //TASK _var = locationNull; hint typeName _var; //LOCATION _var = opfor; hint typeName _var; //SIDE _var = parseText ""; hint typeName _var; //TEXT _var = configFile; hint typeName _var; //CONFIG _var = missionNamespace; hint typeName _var; //NAMESPACE
Если переменная неопределена, то typeName не вернёт никакого значения, но и ошибку тоже не выдаст. Фактически, вы даже сравнить результат команды typeName ни с чем не сможете, потому что это долбанное ничто!
_var = nil; hint typeName _var //ничего if (typeName _var == "") then { diag_log "true"; } else { diag_log "false"; }; diag_log "neither"; //.rpt //"neither"
Кстати, в Армочке Третьей есть новая команда, которая на данный момент не документирована, и, возможно, не поддерживается. В любом случае, это зарезервированная переменная, которая возвращает значение false, поэтому, я думаю, что всё-таки расскажу о ней.
_var = netobjNull; hint typeName _var; //BOOL
Глобальные публичные переменные
Когда вам нужно, чтобы существовала переменная, которая бы присутствовала на всех присоединившихся компьютерах, вы можете просто определить её в файле init.sqf, а когда миссия загрузится, каждая машина исполнит этот файл и, таким образом, на каждой из них будет присутствовать эта переменная. Но как же быть, если нужно обмениваться данными такой переменной в течение миссии? Вот тут-то вам и понадобится специальная команда, которая будет вещать значение вашей переменной в сеть на все подключённые машины - publicVariable.
//компьютер номер раз myvar = "somevar"; //компьютер номер два hint format ["%1", myvar]; // "any" //компьютер номер три hint format ["%1", myvar]; // "any //компьютер номер раз myvar = "somevar"; publicVariable "myvar"; //компьютер номер два hint format ["%1", myvar]; // "somevar" //компьютер номер три hint format ["%1", myvar]; // "somevar"
Прекрасными особенностями такого типа обмена данными являются скорость и надёжность, также вы можете прикрепить обработчик событий публичных переменных addPublicVariableEventHandler к переменной, и она будет "выстреливать" максимально быстро, как только обнаружит, что значение наблюдаемой переменной изменилось. Хреновой же особенностью является сама возможность передавать данные с одного компьютера на другой - это открывает возможности эксплойтам, хакерству и прочим излишествам нехорошим. В то время, когда КК писал эту статью, разработчики Армочки Третьей как раз работали над улучшением безопасности.
Есть ещё парочка очень полезных команд для работы с публичными переменными: publicVariableClient и publicVariableServer. Разница между ними в том, что вы можете заставить клиент общаться только с сервером, а сервер - с указанным клиентом. Это может серьёзно уменьшить сетевой трафик, поскольку вместо передачи данных всем компьютерам в сети, вы можете передавать данные только выбранным клиентам.
Ещё один способ глобально обмениваться публичными переменными состоит в их прикреплении к объектам, которые имеются на всех машинах, к боевой технике, например. Правда, нет обработчика событий, который бы отслеживал изменения таких переменных, поэтому этот способ больше подходит для хранения вместе с объектами каких-нибудь дополнительных данных, и вычитыванию таких данных по запросу. Чтобы прикрепить переменную к объекту, можете воспользоваться командой setVariable. Это делает переменную публичной, но локальной на машине, на которой происходит такое прикрепление.
//компьютер номер раз myobj = (typeOf player) createVehicle (position player); myobj setVariable ["lastCreated", time]; hint str (myobj getVariable ["lastCreated", 0]); // время с начала миссии publicVariable myobj; //компьютер номер два hint str (myobj getVariable ["lastCreated", 0]);//"0"
Чтобы сделать переменную lastCreated глобальной, вам нужно установить 3-й параметр команды setVariable в значение true.
//компьютер номер раз myobj = (typeOf player) createVehicle (position player); myobj setVariable ["lastCreated", time, true]; hint str (myobj getVariable ["lastCreated", 0]); // время с начала миссии publicVariable myobj; //компьютер номер два hint str (myobj getVariable ["lastCreated", 0]);// время с начала миссии
Вы должны быть осторожны, объявляя глобальными переменные, прикреплённые к объектам, поскольку это то же самое, что и применение к переменным команды publicVariable. Если вы храните вместе с объектом часто изменяющиеся данные, то сделать такие переменные глобальными - откровенно отстойная идея, поскольку каждое изменение переменной будет транслироваться в сеть, увеличивая трафик.
Я должен успеть вставить ко-какое предупреждение, пока вы не убились об стену, борясь с вопросом: "Почему эта хня не работает с этим объектом?!?". Дома, например, как и другие картографические объекты (и я имею в виду те объекты, которые были добавлены при создании карты, а не миссии) - подгружаемые. Воспринимайте это как... ну... карты в Армочке огромны, поэтому, даже если вы можете загрузить всю карту сразу, это заберёт слишком много вычислительной мощи вашего процессора. Это означает, что если вы прикрепите переменную к объекту на карте и попытаетесь сделать её (переменную) глобальной, на других компьютерах этого предмета может ещё не существовать. А когда, позже, он появится на тех компьютерах, в них может и не оказаться той переменной, которую вы прикрепили, поэтому, стоит за помнить этот нюанс.
Ну и на этом пока всё о переменных. В будущих уроках я намерен рассказать о взаимодействии сервера с клиентом.
Ведите себя хорошо.
ArmA Scripting Tutorials: Специальные Приватные Переменные.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Что такое специальные переменные? Эти переменные обладают особой значимостью для движка. Приватные же специальные переменные ограничены в пределах функции, к которой они приписаны, и могут уже содержать какие-либо данные, когда к ним идёт обращение. [color=#808080;][Значение этой переменной может быть изменена только самой функцией, которой она вызывается - в этом весь смысл приватности][/color].
Типичными приватными специальными переменными, которые вы наверняка уже знаете являются _this, которая используется для передачи параметров в скрипты, _x, используемая в циклах forEach и счётных циклах и _forEachIndex тоже используемая в циклах forEach. Другие специальные частные переменные менее известны, но несмотря на это они всё-таки специальные.
Как показывает практика, лучше, чтобы имена специальных переменных не конфликтовали не совпадали с именами приватных переменных, созданных кодером. Вот почему специальные переменные имеют свою подсветку в
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
) приведён список всех специальных переменных, которые КК смог найти. Если вам известны другие частные переменные, используемые в Армочке, и они похожи на специальные, пожалуйста, поделитесь этой инфой.
Ведите себя хорошо
P.S. И если кто может сказать, как разметить таблицу для удобного представления данных на этом форуме - напишите об этом, пожалуйста (можно в ПМ).
ArmA Scripting Tutorials: Массивы (Часть Номер Раз).
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Армочка полна массивов, и так уж случилось, что тов. КК тоже их любит. Неважно, вызываете вы скрипт или функцию, добавляете предметы в инвентарь или считываете положение объекта, рано или поздно вам придётся иметь дело с массивами. Очень много важных команд в Армочке возвращает результаты в виде массива, и наступит время, когда вам нужно будет считывать массивы значений из классов конфигураций.
Узнать массив можно по квадратным скобкам. Чтобы определить массив, назначьте переменной пустой массив [] или заполните его значениями через запятую. Значениями массива могут быть числа, строки, булевы значения, объекты, другие переменные, да всё не перечислить. И даже всё вышеперечисленное может быть содержимым массива.
_array = []; //пустой массив _array = [1,2,3,4,5,6,7,8,9]; //массив чисел _array = ["Bob","Peter","John"]; //массив строк _array = [player,_var,true,var,56,"ok",_this]; //смешанный массив
Массив может не содержать никаких значений (пустой массив), может содержать 1 элемент, или множество элементов, даже массивы внутри массивов. Чтобы узнать сколько элементов содержится в массиве, можно сосчитать их командой count.
_array = []; _result = count _array; //_result = 0 _array = ["hello"]; _result = count _array; //_result = 1 _array = [1,2,3]; _result = count _array; //_result = 3
Команда count может считать элементы только в одномерном массиве. Чтобы исследовать многоразмерный массив, вам нужно проделать несколько итераций - для каждого измерения отдельно.
_array = [[1,2,3],[5,6,7]]; //В массиве содержится ещё два массива: [1,2,3] и [5,6,7]
Чтобы получить доступ к элементу массива вам нужно использовать команду select и индекс элемента, к которому вы хотите получить доступ. Самому первому элементу в массиве (слева направо) соответствует индекс 0, второму соответстует 1, третьему - 2 и т.д. Таким образом, чтобы получить доступ к 15-му элементу, необходимо использовать команду select 14. Чтобы получить доступ к последнему элементу массива, нужно сначала посчитать количество элементов в массиве и вычесть 1 из результата.
_array = ["orange","blue","red","green"]; _result = _array select 1; //_result = "blue" _result = _array select ((count _array) - 1); //_result = "green", последний элемент
Чтобы получить доступ к элементу многомерного массива, необходимо отслеживать иерархию массива. Чтобы выбрать 3-й элемент 1-го массива, внутри 2-го массива (внутри общего массива _array):
_array = [[[1,2],[3,4]],[[5,6,7],[8,9]]]; _result = ((_array select 1) select 0) select 2; //_result = 7
[color=#808080;][сначала выполняются команды в скобках, потом те, что за скобками][/color]
А теперь снова вернёмся к команде count. У этой команды есть альтернативный синтаксис, который позволяет выполнять код на каждый отдельный элемент массива. Если выполненный код возвращает true, элемент считается, если же нет - не считается. Например, нам нужно узнать, как много раз имя John встречается в следующем массиве:
_array = ["John","Samantha","Billy","Archer","John","John","Peter","Casey"]; _result = {_x == "John"} count _array; //_result = 3
Пытливый читатель (обожаю этот оборот, как и "не имеет аналогов") может заметить, что мы используем в коде _x. В таком формате команда count будет просматривать каждый элемент массива, и вызывать код {} в каждой итерации. _x является специальной переменной, которая приписывается к тому элементу массива, с которым сейчас работает команда count. Поэтому, если значение элемента, с которым мы сейчас работаем есть John, то он считается. Да, и ещё, будьте бдительны: когда вы работаете со смешанным массивом, убедитесь, что производите сравнение с правильным типом данных.
//ЛАЖА _array = ["John",23,"Samantha",25,"Billy",67,"Archer",45,"John",23,"John",32]; _result = {_x == "John"} count _array; //ОШИБКА скрипта
Показанный выше массив выдаст ошибку, как только доберётся до второго элемента в массиве, потому что код в функции count ожидает типа данных string, а вместо этого получает число. Чтобы недопустить этого, вы можете проверить тип данных элемента перед выполнением сравнения. Также в приведённом ниже примере используется "ленивая" проверка условия, потому что обычная выдаст ошибку.
//ВСЁ ЧЁТКО _array = ["John",23,"Samantha",25,"Billy",67,"Archer",45,"John",23,"John",32]; _result = {typeName _x == "STRING" && {_x == "John"}} count _array; //нет ОШИБКИ
Если нужно просто узнать, есть ли элемент с определённым значением в массиве, используйте команду in. Она возвращает true если такой элемент есть, и false - если его нет. Чтобы проверить, присутствует ли такой элемент, и если есть, то узнать его индекс, пользуйтесь командой find. Команда find вернёт значение -1, если совпадений не найдено, в обратном случае, она вернёт индекс первого совпадающего элемента.
_array = [1,"two",3,"four",1,"two",3,"four"]; _result = "four" in _array; //_result = true; _result = 4 in _array; //_result = false; _result = _array find "three"; //_result = -1 _result = _array find 3; //_result = 2
Стоит отметить, что обе команды find и in чувствительны к регистру, и обе же они не работают со вложенными массивами.
_array = ["one","two","three"]; _result = "TWO" in _array; //_result is false _result = _array find "TWO"; //_result is -1 _array = [[1,2,3],[4,5,6]]; _result = [1,2,3] in _array; //_result is false _result = _array find [1,2,3]; //_result is -1
На этом мы завершаем первую часть урока, посвященного массивам. Во второй части будет рассказано как манипулировать массивами и изменять их.
Ведите себя хорошо
ArmA Scripting Tutorials: Массивы (Часть Номер Два).
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
В первой части объяснялось, по большей части, то, как получить информацию из архивов. В этой части мы собираемся посмотреть как их изменять и как манипулировать ими. Но сначала позвольте рассказать о ещё двух командах, связанных с массивами, они вам понадобятся когда будете работать с записями в конфигах классов, getArray и isArray. getArray возвращает выходные данные правильного типа (массив), но не конвертирует их. Например, если запись в конфиге имеют строковый тип (string), навроде "sometextstring", getArray выдаст [], а не ["sometextstring"]. isArray просто говорит, является ли запись конфига массивом или нет.
class MakarovSD: Makarov { descriptionshort = "Silenced semi-automatic pistol <br/>Caliber: 9x18mm"; displayname = "Makarov SD"; magazines[] = {"8Rnd_9x18_MakarovSD", "8Rnd_9x18_Makarov"}; ... }; _result = isArray (configFile >> "cfgWeapons" >> "MakarovSD" >> "magazines"); //_result = true _result = getArray (configFile >> "cfgWeapons" >> "MakarovSD" >> "magazines"); //_result = ["8Rnd_9x18_MakarovSD","8Rnd_9x18_Makarov"] _result = getText (configFile >> "cfgWeapons" >> "MakarovSD" >> "magazines"); //_result = "" _result = isArray (configFile >> "cfgWeapons" >> "MakarovSD" >> "displayname"); //_result = false _result = getArray (configFile >> "cfgWeapons" >> "MakarovSD" >> "displayname"); //_result = [] _result = getText (configFile >> "cfgWeapons" >> "MakarovSD" >> "displayname"); //_result = "MakarovSD"
Чтобы поменять значение, записанное в элемент массива, пользуйтесь командой set. Эту команду надо запускать с аргументами [index,newvalue] [color=#808080;]([индекс,новое значение])[/color]. set дополнительно изменит размер массива, если вы записываете новое значение, которое выходит за длину массива. Кроме того, вы можете напрямую изменить размер массива командой resize. Команда resize либо "обрежет", либо "растянет" массив, в зависимости от значения введённого вами аргумента.
_array = [1,2,3,4,6,6,7,8,9]; _array set [4,5]; //значение _array теперь будет [1,2,3,5,5,6,7,8,9] _array set [count _array,10]; //теперь _array становится [1,2,3,4,5,6,7,8,9,10] _array set [12,13]; //_array теперь [1,2,3,4,5,6,7,8,9,10,<null>,<null>,13] _array resize 3; //_array теперь [1,2,3] _array resize 5; //_array теперь [1,2,3,<null>,<null>] _array resize 0; //_array теперь []
Вы можете убрать определение с элемента массива, присваивая ему значение nil. Эта команда не будет ни стрирать элемент, ни изменять длину массива.
_array = [1,2,3,4,5]; _array set [2,nil]; //_array примет значение [1,2,any,4,5]
В Армочке нет способа убрать элемент из массива, при этом одновременно изменив его длину. Но есть несколько "костылей", основанных на сложении и вычитании массивов в Армочке. Вы можете складывать и вычитать массивы друг из друга. Сложение массивов приведёт к тому, что добавит значения из второго массива в первый, соответственно, изменив его длину.
_array = [4,5,6]; _array = _array + [7,8,9]; //_array = [4,5,7,8,9] _array = [1,2,3] + _array; //_array = [1,2,3,4,5,6,7,8,9]
Вычитание же удалит ВСЕ элементы второго массива из 1-го, изменяя длину первого массива.
_array = [1,1,2,2,3,3,4,4,5,5]; _array = _array - [1]; //_array принимает значение [2,2,3,3,4,4,5,5] _array = _array - [4,2,5]; //теперь наш _array = [3,3]
Если элементом массива является массив, он не может вычитаться из дугого массива.
_array = [[1,2],[3,4]]; _array = _array - [1,2]; //_array по прежнему равен [[1,2],[3,4]]
Таким образом, чтобы убрать элемент из массива, надо сначала изменить его значение, на что-нибудь, не имеющее аналогов в данном массиве (теперь всегда буду переводить так слово "уникальный"), а потом вычесть это значение. Кстати, присваивание и вычетание нулевых объектов, таких как objNull, locationNull, grpNull, taskNull, teamMemberNull, controlNull, displayNull - тоже работает.
_array = [1,2,3,4,5]; _array = set [1,"deletethis"]; //_array принимает значение [1,"deletethis",3,4,5] _array = _array - ["deletethis"]; // теперь _array равен [1,3,4,5]_ array set [3,objNull]; //_array равен [1,3,4,<Null-object>] _array = _array - [objNull]; //_array равен [1,2,3];
Если вам хочется добавить элемент в массив, и при этом вам необходимо, чтобы он не имел аналогов, то для этого есть неплохой трюк. Конечно, всегда можно сначала проверить, имеется ли такой элемент в массиве или нет, но можно предположить, что он есть, вычесть его, и добавить заново. После столь хитрожопого действа, новый добавленный вами элемент точно не будет иметь аналогов.
_array = [1,2,3,4,5]; _array = (_array - [6]) + [6]; //_array равен [1,2,3,4,5,6] _array = (_array - [6]) + [6]; //_array по-прежнему [1,2,3,4,5,6]
На этом мы завершаем вторую часть массивного урока. Дополнительные инструменты по работе с массивами вы сможете найти в третьей части марлезонского балета нашего урока
ArmA Scripting Tutorials: Массивы (Часть Номер Три).
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Итак, во второй части мы научились манипулировать с массивами. Время перейти к кое-чему посложнее, навроде копирования массивов. Видите ли, если вы присваиваете значения одного массива другому, навроде _array2=_array1, вы не создаёте независимый массив _array2, но создаёте ссылку на _array1. Если после этого вы меняете каким-либо образом _array2, те же изменения произойдут и в _array1. И если бы на этом всё заканчивалось, но нет же! Если вы передадите _array1 в какую-либо функцию, как параметр, а внутри функции присвоите его значения массиву _array2, то изменения в _array2 будут и в этом случае влиять на _array1.
_array1 = [1,2,3]; _array2 = _array1; _array2 set [2,0]; //_array1 равен [1,2,0] a = { private ["_array2"]; _array2 = _this; _array2 set [2,10]; }; _array1 call a; //_array1 = [1,2,10]
Во избежание сего конфуза вы можете добавить или вычесть пустой массив из оригинального массива, затем присвоить результат вашему новому создаваемому массиву, или исопользовать оператор +, который создаёт копию массива, а не создаёт ссылки на оригинальный массив. Хорошенько запомните это, поскольку это спасёт вас от вырывания волос на собственной голове (или спине - кто уж откуда вырывает) при отлаживании скриптов.
_array1 = [1,2,3]; _array2 = [] + _array1; //_array2 = [1,2,3] _array2 set [2,0]; //_array1 как и был - [1,2,3], _array2 теперь будет [1,2,0] _array2 = _array1 + []; //_array2 = [1,2,3] _array2 set [2,10] //_array1 как и был - [1,2,3], _array2 теперь будет [1,2,10] _array2 = _array1 - []; //_array2 = [1,2,3] _array2 set [2,20]; //_array1 как и был - [1,2,3], _array2 теперь будет [1,2,20] _array2 = + _array1; //_array2 = [1,2,3] _array2 set [2,30]; //_array1 как и был - [1,2,3], _array2 теперь будет [1,2,30]
Чтобы изменить значение элемента в массиве, глубоко вложенном в другие массивы, просто аккуратно выбирайте элементы, следуя иерархии массивов, а когда дойдёте до нужного элемента, просто установите его значение, как обычно.
_array = [[1,2,3],[4,5,6],[7,8,[9,10]]]; ((_array select 2) select 2) set [0,10]; //_array = [[1,2,3],[4,5,6],[7,8,[10,10]]]
Чтобы поочерёдно пройтись по одному измерению массива, просто пользуйтесь командой forEach. Она именно это и делает - проходит по каждому элементу последовательно, похоже на команду count, которую мы рассматривали в первой части. Специальная переменная [font="'courier new', courier, monospace;"]_x[/font] будет содержать значение текущего элемента массива, а специальная переменная [font="'courier new', courier, monospace;"]_forEachIndex[/font] - его (текущего элемента) индекс, так что, если вам он когда-нибудь понадобится - вы знаете, где его искать.
_array = ["John","Paul","George","Ringo","John","Paul","George","Ringo"]; _lastGeorgeIndex = -1; { if (_x == "George") then { _lastGeorgeIndex = _forEachIndex; }; } forEach _array; //_lastGeorgeIndex = 6
Ну а теперь к интересненькому. Забудьте об использовании функций, сделанных сторонними производителями, навроде BIS_fnc_selectRandom, чтобы случайно выбирать 1 элемент из массива. Будьте умнее - полностью контролируйте ваши скрипты. Вся эта функция может быть сделана однострочным выражением.
private ["_colours","_randomColour"]; _colours = ["green","yellow","purple","white"]; _randomColour = _colours select (floor (random (count _colours))); //чётко, да?
Кстати, в Армочке нет как такового манипулирования текстом, нет регулярных выражений, а сравнение строк нечувствительно к регистру ("hEllO" == "heLLo" выдаст true в качестве результата, чтобы получить зависимое от регистра сравнение, надо воспользоваться оператором switch/case). Но есть команда toArray. Она преобразует любую строку в массив ASCII значений, то есть массив чисел. После этого вы можете совершить операции с массивом и toString'нуть обратно, когда закончите. Вот пример из практики: когда хочется привести в порядок имена, и заменить символы открывающихся и закрывающихся угловых скобок <> значениями < и > соответственно, чтобы они не мешались со структурированным текстом.
private ["_array","_temp","_sanitised"]; _array = toArray "<<<Killzone_Kid>>>"; _temp = []; { switch _x do { case 60 : { _temp = _temp + toArray "<"; }; case 62 : { _temp = _temp + toArray ">"; }; default { _temp = _temp + [_x]; }; }; } forEach _array;_sanitised = toString _temp; //_sanitised = <<<Killzone_Kid>>>
Ну на этом всё, завершаем с массивами. КК даже не думал, что эта третья часть будет написана, когда начинал. И КК надеется, что вы нашли для себя что-нибудь полезное в этом уроке из трёх частей.
Ведите себя хорошо
ArmA Scripting Tutorials: Чувствительность к Регистру.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Необходимо рассказать немного о РеГИстРоВОй чуВсТвиТЕЛЬнОсТи в Армочке, поскольку иногда она может вызвать замешательство и даже привести к ошибкам в коде. Давайте взглянем на некоторые примеры:
_variable = "_var"; hint _VArIAblE; //покажет "_var" variable = "var"; hint VARIaBLE; //покажет "var" with missionNamespace do { hint variaBLE; //покажет "var" }; hint (missionNamespace getVariable "VArIAbLe"); //покажет "var" missionNamespace setVariable ["VARIABLE","VAR"]; hint variable; //показывает "VAR" _funCTION = { hint "func"; }; null = [] spawn _functioN; //показывает "func" funCtioN = { hint "func"; }; call FUNCtiON; //показывает "func"
Имена переменных и команды Армочки являются нечувствительными к регистру. Любая из команд HINT, HiNt и hint сработает как надо. Записи конфигов, кстати, тоже.
_result = getText (configfile >> "cfgWeapons" >> "arifle_MXC_F" >> "displayname"); //_result = "MXC 6.5 mm" _result = GEtTeXt (configfile >> "cfgWEAPONs" >> "aRIFLE_mxc_f" >> "displayNAME"); //_result = "MXC 6.5 mm"
Но всё-таки, для читабельности и понятности кода, следует всегда соблюдать регистр. Вообще, это правило хорошего тона - вне зависимости, есть требования к этому или нет - всегда и везде лучше придерживаться регистра. Дополнительной причиной этому служит то, что несмотря на нечувствительность к регистру сравнения строк, таких как == или !=, вам неожиданно, по мере разработки, может понадобиться чувствительность к регистру.
a = "HELLO"; b = "hello"; _result = if (a==b) then {"true"} else {"false"}; //_result = "true" _result = if (a!=b) then [{"true"},{"false"}]; //_result = "false"
если же вам понадобится чувствительное к регистру сравнение, вы можете воспользоваться сравнением элементов массива, используя in или даже find, а оба они чувствительны к регистру.
a = "HELLO"; b = "hello"; _result = if (a in [b]) then {"true"} else {"false"}; //_result = "false" _result = if !(a in [b]) then [{"true"},{"false"}]; //_result = "true" _result = if (([b] find a) >=0) then {"true"} else {"false"}; //result = "false" _result = if (([b] find a) < 0) then [{"true"},{"false"}]; //result = "true"
Или же, вы можете полностью заменить in/find похожей консрукцией switch/case и достигнуть того же результата.
a = "HELLO"; b = "hello"; _result = switch a do {case b : {"true"}; default {"false"}; //_result = "false" b = "HELLO"; _result = switch a do {case b : {"true"}; default {"false"}; //_result = "true"
А в случае, если вы используете регистрочувствительные сравнения и вам небходимо переключить всё в один регистр, вам стоит запомнить пару команд: си toLower.
a = "HeLlO"; a = toUpper a; //теперь a = "HELLO" a = toLower a; // теперь a = "hello" _array = ["MARK","STEVE","HENRY"]; _firstName = "Steve"; _result = _firstName in _array; //_result = false _result = (toUpper _firstName) in _array; //_result = true
Если что-то упустил - не стесняйтесь, дополняйте, давайте знать
Ведите себя хорошо
ArmA Scripting Tutorials: Циклы.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Цикл - это кода кусок, который повторяется, и повторяется, и повторяется, и повторяется, и повторяется, и ... Когда-нибудь такая хитрая штука понадобится и вам, и есть не так уж много путей, как организовать сие. Сейчас приведу пример. Допустим, вам необходимо добавить 3 одинаковых вещи в инвентарь. Вы можете просто добавить вещь, добавить вещь и добавить вещь - всего три раза. Но если вам придётся делать это 20 раз, что может быть лучше, чем написать простенький код и зациклить его на 20 повторений? Быстро и изящно.
Циклы for. Эти циклы позволяют выполнить кода кусок, заключённый в фигурные скобки {} указанное количество раз. Синтаксис позволяет установить счётчик двумя способами. Хотя они очень похожи, всё-таки разница между ними есть, и от того, какой способ вы выберете, будет зависеть производительность, а разница такая нехеровая - один способ работает в 2,5 раза быстрее, чем другой.
for "_i" from 5 to 93 step 7 do {hint str _i}; //последнее значение _i равно 96
Это быстрый вариант цикла. Этот синтаксис читается примерно так: Создай приватную переменную _i, присвой ей значение 5, проверь, не является ли эта переменная большей или равной 93, и если является, то заверши цикл, если же нет, то выведи на экран командой hint значение _i на экран, а затем прибавь к ней 7, и снова проверь, не является ли теперь переменная большей или равной 93, и если является, то заверши цикл, если же нет, то выведи на экран командой hint значение _i на экран, а затем прибавь к ней 7... и так далее, пока условие проверки не будет соответствовать тому, что от неё ожидается (что она будет большей или равной 93), и тогда цикл завершается. Если вы опустите step, то _i будет увеличиваться с шагом 1 по умолчанию. Вы можете заметить, что результат "выскакивает" за предел 93, который мы установили (значение _i при завершении цикла будет равно 96 - можете проверить на логарифмической линейке). Так происходит, поскольку при данном синтаксисе проверка условия выглядит как если _i >=93, то завершай цикл. Также, значение to проверяется только один раз - в самом начале цикла, поэтому, вы можете использовать, например, ceil(random 10), чтобы задать случайное количество итераций, скомпилировано это будет только один раз. Ещё одна характерная особенность этой конструкции - то, что _i является частной переменной, и область её действия ограничена лишь этим циклом, это означает, что если у вас где-то за пределами цикла есть переменная _i, то изменения в нашей _i ту, внешнюю, _i не испортят. Бесконечный цикл можно создать, если задать step равным 0.
for "_i" from 0 to 1 step 0 do {};//будет зацикленно навечно
Если вам случалось кодить на других языках, то вам, возможно, удобнее другой вариант циклов for. Медленный.
for [{_i=5},{_i<=93},{_i=_i+7}] do {hint str _i}; // последнее значение _i равно 89
Такой синтаксис читается несколько иначе: Присвой переменной _i значение 5, проверь, чтобы оно было меньше или равно 93, если это не так, то заверши цикл, если всё так, то хинтани значение _i на экран, и увеличь _i на 7, снова проверь, чтобы оно было меньше или равно 93, если это не так, то заверши цикл, если всё так, то хинтани значение _i на экран, и увеличь _i на 7 ... и продолжай, пока проверяемое условие не станет ложным, и тогда завершай цикл. В противоположность первому условию, _i никогда не будет больше установленного порога. Так получается, потому что здесь условие проверки несколько иное - пока _i<=93 повторяй. В довесок к этому, переменная _i не будет приватной, если вы сами её такой не сделаете, непосредственно указав это требование.
_i = 101; for [{private "_i"; _i=5},{_i<=93},{_i=_i+7}] do {}; hint str _i; //на экран хинтанётся значение 101
В отличие от первого варианта цикла for, этот цикл проверяет условие завершения каждую итерацию, и я предполагаю, что именно поэтому он медленнее. Но зато вы можете динамически изменять количество итераций, которое он совершит.
k = 3; a = {k = k + 0.5; k}; for [{_i=0},{i<(call a)},{_i=_i+1}] do { diag_log format ["_i:%1/k:%2", _i, k]; }; //.rpt файл //"_i:0/k:3.5" //"_i:1/k:4" //"_i:2/k:4.5" //"_i:3/k:5" //"_i:4/k:5.5" //"_i:5/k:6" //"_i:6/k:6.5"
Такой цикл тоже можно сделать бесконечным как намеренно, так и случайно. Всё, что вам надо сделать - обеспечить, чтобы результат проверки всегда выдавал результат true.
for [{_i=0},{true},{_i=_i+1}] do {}; //будет выполняться бесконечно
Вы можете сказать: "воу-воу, палехчи! чё мы тут паримся, забубеним while и всё будет ништяк!" Что же, не совсем так, ну, по крайней мере, не всегда. Давайте взглянем на циклы while.
_i=5; while {_i<=93} do {hint str _i; _i=_i+7};// последнее значение _i = 89 _i=5; while {_i<=93} do {_i=_i+7}; hint str _i;// последнее значение _i = 96
В результате очевидно, что мы можем достичь с циклом while тех же результатов, что и с циклом for. Но также очень просто ошибиться и сделать порог, который будет догонять наша переменная, и никогда не догонит, поэтому движком несравненной Армочки количество выполняемых while-циклов ограниченно 10000 итераций, после чего происходит выход из цикла. Поместите следующий кода кусок в поле init юнита в редакторе и нажмите превью.
i=0; while {true} do {i=i+1}; hint str i;//в результате исполнения цикла на экране хинтанётся 10000
Такое ограничение установлено, потому что когда скрипт вызывается движком, он должен ждать от for до возвращения, прежде чем сможет делать что-то ещё. И нехорошести могут случиться, когда вы вызываете командой call бесконечный while-цикл из обработчика событий, FSM-скрипта или строки инициализации. Если ваш скрипт выполняется с помощью команды spawn или execVM, вы можете сделать бесконечный while-цикл. А у вышеупомянутых циклов for нет таких ограничений. Кстати, файл init.sqf не вызывается, поэтому, в нём цикл while может крутиться вечно. Объединим вышесказанное:
spawn {while {true} do {}} //бесконечно бесконечен call {while {true} do {}} // 10000 если вызван из FSM или обработчика событий, или поля init call {while {true} do {}} // бесконечен, если вызван из init.sqf или execVM spawn {call {while {true} do {}}} //бесконечно бесконечен
Большинство человеков используют циклы while true, чтобы проверять разные вещи, а особо он популярен когда человеки делятся своими кодами, поскольку это самый простой способ подключить кусок кода к уже существующему коду. Это отстой: мало кто хочет, чтобы полчища циклов срали ему в процессор, и ухудшали производительность его пекарни. Не делайте так, научитесь использовать обработчики событий (event handler). У несравненной Армочки есть множество разнообразных обработчиков событий, которые запустят ваш код, когда сработает установленное для его выполнения условие.
Допустим, вам нужно узнать, когда игрок садится в машину или выходит из неё.
//хорошо _veh addEventHandler ["GetIn",{_this call isIn}]; _veh addEventHandler ["GetOut",{_this call isOut}]; //не очень хорошо lastVehicle = objNull; lastSeat = ""; null = [] spawn { private ["_veh","_roles","_seat"]; while {true} do { _veh = vehicle player; if (_veh != player) then { _roles = assignedVehicleRole player; _seat = if (count _roles > 0) then [{_roles select 0},{""}]; if (_veh != lastVehicle || _seat != lastSeat) then { lastVehicle = _veh; lastSeat = _seat; [_veh, _seat, player] call isIn; }; } else { if (!isNull lastVehicle) then { [lastVehicle, lastSeat, player] call isOut; }; }; sleep 0.1; }; };
Если вы хотите повысить скорость отклика в примере с while, вам нужно уменьшить значение sleep, что приведёт к большему количеству повторений, и пустому расходованию ресурсов процессора. А обработчик событий, просто врубает ваш скрипт так быстро, как только возможно, каждый раз.
Цикл forEach. В уроке про массивы мы его касались, поэтому его получится объяснить быстрее. Этот цикл проходит по массиву, перебирая его элементы по-одному, и завершается, когда в данном массиве не остаётся непройденных элементов. Если вы попытаетесь добавить или удалить элементы из исходного массива операторами + или - в то время, как работает цикл, это не повлияет на количество итераций, не смотря на то, что длина исходного массива изменилась.
_array = [1,2,3]; diag_log format ["_array:%1", _array]; { _array = _array + [0]; diag_log format ["_x:%1/_forEachIndex:%2/_array:%3", _x, _forEachIndex, _array]; } forEach _array; diag_log format ["_array:%1", _array]; //.rpt файл //"_array:[1,2,3]" //"_x:1/_forEachIndex:0/_array:[1,2,3,0]" //"_x:2/_forEachIndex:1/_array:[1,2,3,0,0]" //"_x:3/_forEachIndex:2/_array:[1,2,3,0,0,0]" //"_array:[1,2,3,0,0,0]"
И всё же, следует быть крайне осторожным с командой set, поскольку , поскольку если она изменит исходный массив во время работы цикла, то может случиться и так, что цикл станет бесконечным. В следующем примере используется команда min, чтобы ограничить длину массива и остановить цикл, не дав ему убежать в бесконечность.
_array = [1,2,3]; diag_log format ["_array:%1", _array]; { _array set [(count _array) min 5, 0]; diag_log format ["_x:%1/_forEachIndex:%2/_array:%3", _x, _forEachIndex, _array]; } forEach _array; diag_log format ["_array:%1", _array]; //.rpt file //"_array:[1,2,3]" //"_x:1/_forEachIndex:0/_array:[1,2,3,0]" //"_x:2/_forEachIndex:1/_array:[1,2,3,0,0]" //"_x:3/_forEachIndex:2/_array:[1,2,3,0,0,0]" //"_x:0/_forEachIndex:3/_array:[1,2,3,0,0,0]" //"_x:0/_forEachIndex:4/_array:[1,2,3,0,0,0]" //"_x:0/_forEachIndex:5/_array:[1,2,3,0,0,0]" //"_array:[1,2,3,0,0,0]"
А есть ещё 3 дополнительных цикла forEach, специально сделанных для работы с группами: forEachMember, forEachTeamMember и forEachMemberAgent. Во всех этих случаях переменная _x содержит значение текущего элемента, а _forEachIndex - его индекс.
Баааалин, чуть не забыл про цикл onEachFrame. Это бесконечный цикл, который будет выполнять код в фигурных скобках {вот этот вот} каждый кадр. Полезно, если вам нужно выводить на экран что-то, связанное с вашим FPS. Например, есть у вас один скрипт GUI (графическое пользовательское междумордие), сделанный на while-true, и если поставить его молотить с параметром sleep равным 0.01, то он будет отрисовывать HUD (намордный дисплей) 100 раз в секунду, ну и какой в этом смысл, даже если у вас 60 FPS, не говоря уж о более приземлённых значениях. В этом случае команда onEachFrame подходит как нельзя лучше.
onEachFrame {hint str diag_fps}; onEachFrame {hint str diag_tickTime};
Эта команда зацикленно запускает код в фигурных скобках {}. Движок может запустить только одну инстанцию onEachFrame, поэтому каждый последующий вызов этой команды заменит собой предыдущий вызов. Чтобы отменить исполнение команды onEachFrame, нужно запустить её снова с пустым кодом. Она не допускает использования других способов останова скрипта, навроде sleep или waitUntil. И ещё, если вам надо передать какой-либо параметр в код внутри этого цикла, вы будете вынуждены использовать глобальную переменную.
_i = "123"; onEachFrame { hint str (isNil "_i"); // хинт будет постоянно выводить "true" }; cutText [str _i, "PLAIN DOWN"]; //скрипт продолжает выполняться и начинает выводить "123" onEachFrame {hint "1"; sleep 1; hint "2"); //.rpt засерается ошибками i = 0; onEachFrame { i = i + 1; hint str i; //хинт начинает считать }; i = 0; //старое значение хинта сбрасывается и, хинт начинает считать заново onEachFrame {}; // отменяет цикл
Ждите ещё интересный материалов по циклам - их есть у меня
Ведите себя хорошо.
ArmA Scripting Tutorials: Циклы V2.
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Мне хотелось бы вернуться к уроку по циклам и особое внимание уделить циклам, которые могут запускаться каждый кадр. Почему именно на них? Да потому что если вы привязываете код к FPSу, то вы получаете наилучшую возможную скорость отклика вкупе с наиболее плавным отображением. Сейчас объясню о чём я - если вам нужно двигать что-то на экране, обновлять это что-то каждый фрейм - это всё, что вам нужно, поскольку это именно то, что вы видите. Нет никакого смысла обновлять такие вещи между фреймами, даже если ресурсы позволяют. То же самое с откликом/реакцией - вы видите реакцию/отклик только если у вас обновляется кадр.
Вот несколько фактов. Обычный цикл for может быть выполнен около 170 000 раз в секунду, когда вызывается напрямую обработчиком событий или FSM. Если вы spawn'ите или execVM'ите этот же цикл, у вас получится порядка 25 000 раз в секунду. Чтобы не отставать от фреймрейта вам надо выполнить всего 60 итераций (40/30/25/15 - зависит от фреймрейта) в секунду.
В прошлом уроке я уже касался цикла onEachFrame. И хотя эта команда хороша для быстрого исполнения грязной работёнки, у неё есть большой минус - вы не можете запустить несколько циклов onEachFrame одновременно, этих циклов не может быть больше одного. А ещё его нельзя приостановить командами навроде sleep.
Другим способом создания циклов, привязанных к фреймрейту, является установка обработчиков событий. В Армочке Третьей вы можете использовать "миссионный" (лучше я буду называть его миссионерским) обработчик событий "Draw3D". Команда addMissionEvenHandler["Draw3D",{}] будет вызывать код каждый фрейм. И вот этим способом вы можете создать более, чем один цикл, и это ещё и будет работать. Как подсказывает имя команды, такой тип циклов предполагался скорее для отрисовки HUD-ов (намордных дисплеев), чем для чего-то ещё. Но поскольку это обработчик событий - он не допускает использования команд останова скриптов. [color=#808080;][Пример использоания этой команды вы увидите позже в уроке "ArmA Scripting Tutorials: boundingBox, boundingBoxReal, composeText, formatText, visiblePosition, visiblePositionASL", - да, в этом названии ни одного слова по-русски, только запятые. Ожидайте перевода][/color]
Двигаемся дальше всё ближе к FSM. [Пора, пожалуй, объяснить что такое FSM - это автомат состояний, дословно "машина конечных состояний" - finite state machine]. Условия, которые связаны с текущим состоянием FSM проверяются каждый фрейм. Кроме того, вам доступна секция предварительных условий, и если вы туда поместите какой-нибудь код, то он всегда будет выполняться до самой проверки условий. Таким образом, этот код будет исполняться каждый фрейм. Если вам необходимо несколько таких циклов, вы можете создать дополнительные условия и связать их с тем же состоянием автомата. У автомата есть один офигительный плюс - у него приоритет повыше, чем у других скриптов. Ну и традиционно, он не любит остановы скриптов.
И, наконец, нежданчик в виде... waitUntil. По умолчанию, эта команда будет исполнять свой код каждый фрейм, пока код не вернёт значение "true". Вы можете заspawn'ить, несколько циклов waitUntil, если вам надо. Чтобы выйти из этого цикла, вам нужно сделать так, чтобы самое последнее выражение в коде waitUntil возвращало "true".
_null = [] spawn { _time = diag_tickTime + 1; _i = 0; waitUntil { _i = _i + 1; diag_tickTime >= _time }; hint format [ "Code executed %1 times per second", _i ]; }; //взгляните, сколько раз в секунду будет выполняться ваш код
Если этого недостаточно, вы можете добавить останов скрипта к этой конструкции, в случае, если вам хочется ещё сильнее замедлить цикл, но всё-таки оставить скорость цикла сравнимой с фреймрейтом. А ещё я откопал интересную аномалию, пока проделывал этот трюк. Значение sleep >0.0005 заставляет waitUntil терять фрейм каждый раз, что тем более особенно, если учесть, насколько это малая задержка.
_null = [] spawn { _time = diag_tickTime +1; _i = 0; waitUntil { sleep 0.00051; _i = _i + 1; diag_tickTime >= _time }; hint format [ "Code executer %1 times per second", _i ]; };//сравните, сколько раз в секунду исполняется этот код.
Я подозреваю, что эта штука делает что-то с планировщиком движка, что также привело меня к заключению, что использование цикла waitUntil требует меньшей вычислительной мощности процессора, чем цикл while с задержкой, созданной командой sleep. Поскольку waitUntil проверяется во время отрисовки кадра, то после того, как проверка произошла, она не будет запланирована на проверку до тех пор, пока не нужно будет отрисовывать следующий кадр. Проверка же условия команды while может, напротив, быть проверена множество раз за время отрисовки кадра.
Ведите себя хорошо
ArmA Scripting Tutorials: Создание безопасных функций обратного вызова (коллбэков)
Автор: KillzoneKid
Оригинал:
Пожалуйста Войдите или Зарегистрируйтесь чтобы увидеть скрытое содержание
Бывает так, что вам нужно, чтобы определённый кусок кода был выполнен на всех компьютерах, вне зависимости от причины. Как создатель миссии или мода, вы отвечаете за всё, что позволительно и непозволительно в вашем творении. Одна вещь, которую вам, наверняка, не захочется допустить - это передача кода по сети, и разрешение на выполнение этого кода на всех подключенных машинах. По большей части, потому что это любезное приглашение разного рода хаков и эксплойтов в ваш код. А ещё потому что передача отрезков кода по сети - отстой для пропускной способности канала. Чего вам следует хотеть - так это создавать кастомные функции для каждого клиента, блокировать их, и иметь возможность вызывать их с набором параметров из любого места - это намного лучше, чем пересылать голый код.
Создать функцию у каждого клиента - просто. Когда миссия загружается, загружается init.sqf, и каждая машина выполнит этот файл один раз, даже выделенный сервер. Поэтому, определение функции в init.sqf или в любом другом файле, который вызывается из init.sqf - ровно тот трюк, который сработает. Вызывать такую функцию откуда угодно - тоже просто, вам просто придётся использовать комбинацию из publicVariable и ответственного за переменную обработчика событий. И всё это, в итоге, сводится к созданию фреймворка обратных вызовов, т.е. основной функции, которая будет совершать вызовы на всех клиентах для нас.
Мы будем передавать название нашей кастомной функции в виде строки в нашу функцию обратного вызова. Эту строку нужно как-нибудь преобразовать в реальную переменную, содержащую код кастомной функции. Но что бы вы не делали - не используйте call compile format для этой цели. Ща покажу почему. Вот, в примере ниже мы сконструировали нашу плохую функцию, создающую фреймворк обратного вызова, и определили её на каждой пекарне.
our_badfunction = { private ["_fnc","_params"]; _fnc = _this select 0; _params = _this select 1; call compile format ["%2 call fnc_%1", _fnc, _params]; };
Мы даже попытались сделать её безопасной, используя префикс, чтобы никакие другие функции не могли быть вызваны, кроме тех, которые начинаются на "fnc_". А теперь давайте определим кастомную функцию, которую мы хотим вызвать на каждой машине.
fnc_lolfunk = { private "_params"; _params = _this select 0; hint str _params; };
Чтобы заставить всю эту херобазу взлететь, нам нужен обработчик событий, который будет вызывать наш хреновенький фреймворк обратных вызовов, который, в свою очередь вызовет нашу кастомную функцию.
our_publicvariable = []; "our_publicvariable" addPublicVariableEventHandler { (_this select 1) call our_badfunction; };
Теперь, чтобы обработчик событий "выстрелил", прежде всего нам нужно определить и транслировать нашу публичную переменную с другой машины.
our_publicvariable = ["lolfunc", ["LMAO!"]]; publicVariable "our_publicvariable";
И наконец, мы выполняем call compile format, и получаем вот такой вот результат:
call {["LMAO!"] call fnc_lolfunc};
Вроде бы похоже на то, что всё работает. Неплохо, до тех пор пока хакер не придёт и не будет транслировать его собственную публичную переменную, навроде этой:
our_publicvariable = ["lolfunc;...злонамеренный код...", ["LMAO!"]]; publicVariable "our_publicvariable";
И вот теперь после call compile format, мы уже получаем вот такой вот результат:
call {["LMAO!"] call fnc_lolfunc;...злонамеренный код...};
Это именно то, что я и хотел продемонстрировать, сначала наша хреновенькая функция выполнить намеренный вызов, а затем вся радостная и счастливая притащит злонамеренный код, инжектированный хакером. Поэтому, сначала - о главном, мы никогда не используем комбо call compile format, вместо этого мы используем пространства имён (namespace) [смотри вторую часть урока по переменным]
_params call missionNamespace getVariable [format ["%1", _fnc], fallback_function];
В эту конструкцию просто не получится инжектировать код. Ну... на самом деле можно, конечно, но это не сработает, поскольку ничего не компилируется, поэтому и бесполезно. И, как сверху бантик, мы можем присобачить резервную функцию (fallback) которая будет выполнена в случае, если передаваемая функция - не существующая переменная, и эта резервная функция даст нам знать, что кто-то пытается сделать кое-что нечестное. Кроме того, мы создаём условия, когда мы гарантированно не можем вызвать нашу фреймворковскую функцию из самой этой функции, создавая бесконечный цикл. После всех этих умозаключений мы получаем:
KK_fnc_CB_ERROR = { diag_log (format [ "CB_ERROR! Call to non-existent function '%1'(Passed params: %2)", _this select 0, _this ]) }; KK_fnc_CB = { private ["_fnc","_code"]; _fnc = _this select 0; if (_fnc != "KK_fnc_CB") then { _code = missionNameSpace getVariable [ format ["%1", _fnc], KK_fnc_CB_ERROR ]; if ((_this select 2) == "call") then { _this call _code; } else { null = _this spawn _code; } } };
Как и раньше, нам нужна кастомная функция и обработчик событий.
fnc_lolfunc = { hint (_this select 1); }; KK_pv_CB = []; "KK_pv_CB" addPublicVariableEventHandler { (_this select 1) call KK_fnc_CB; };
И, чтобы транслировать это в сеть:
KK_pv_CB = ["fnc_lolfunc", "LMAO!", "spawn"]; publicVariable "KK_pv_CB";
Помните, однако, что обработчик событий публичных переменных не сработает на той машине, с которой вызвана publicVariable, поэтому, на том компьютере, с которого вы вызываете publicVariable, кастомную функцию придётся запускать напрямую.
После того, как всё оттестировано, отлажено и работает, как задумывалось, самое время заблокировать код, чтобы никто не мог переписать ваш фреймворк обратных вызовов. Для этого мы собираемся изспользовать функцию compileFinal (особенность Армочки Третьей на данный момент). Эта команда компилирует переданную строку в код и финализирует его. После этого невозможно изменить переменную, содержащую код. Мы можем конвертировать наши функции в строки вручную, либо же воспользоваться Конвертером кода в compileFinal, который сделал КК. Конечный результат будет выглядеть вот так:
KK_fnc_CB_ERROR = compileFinal " diag_log (format [ ""CB_ERROR! Call to non-existent function '%1' (Passed params: %2)"", _this select 0, _this ]) "; KK_fnc_CB = compileFinal " private [""_fnc"",""_code""]; _fnc = _this select 0; if (_fnc != ""KK_fnc_CB"") then { _code = missionNamespace getVariable [ format [""%1"", _fnc], KK_fnc_CB_ERROR ]; if ((_this select 2) == ""call"") then { _this call _code; } else { null = _this spawn _code; } } ";
И неплохо бы предотвратить нашу кастомную функцию от неавторизированной перезаписи.
fnc_lolfunc = compileFinal " hint (_this select 1); ";
Ждите продолжения и дополнения этой статьи!
2013 09 03 Добавлены материалы по самым началам скриптописаний и SQF - синтаксису.. Изменён порядок изложения статей.
2013 09 04 Добавлен материал по циклам
2013 09 05 Добавлены первая и вторая части по работе с переменными. Изменён порядок изложения статей
2013 09 06 Закончен урок по переменным
2013 10 01 Обновлена информация по "ленивой" проверке, и изменен перевод БИСовского туториала - приведён к рабочему варианту, в отличие от того, что в бис вики.
Edited by Avi, 07 May 2014 - 07:47.