Язык JASS - Программирование в Варкрафте.
| |
Maroder | Date: Суббота, 28.10.2006, 18:10:07 | Message # 1 |
Админ №1
Group: Админы
Posts: 141
Reputation: 3
Status: Offline
| Вступление. Основы JASS. В этой статье я расскажу о языке JASS (или скрипте) в Warcraft'e 3. Для прочтения статьи необходимо знать хотя бы основы программирования на любом языке (понятие 'переменная' пояснять не буду!). Также необходимое знание триггеров в Варкрафте. Что такое JASS? JASS - это специально разработанный Blizzard язык для Варкрафта. JASS - программа есть в каждой карте Варкрафта, функции AI также написаны на этом языке. Скрипт (для War3) - программа, написанная на языке JASS. Главный скрипт запускается сразу после выбора карты в игре или открытии ее в редакторе. За то время пока карта "грузится" W3 или WE читают и выполняют скрипт - создают юнитов / погодные эффекты на карте, ассоциируют звуки, устанавливают музыку. Когда этот процесс заканчивается, редактор / игра начинают выполнять часть скрипта, связанную с самим игровым процессом. Почему я говорю - часть скрипта, а не триггеры? Дело в том, что триггер - все - лишь переменная в памяти определенного типа, с которой связаны одна или несколько функций. Обычно, когда говорят слово "скрипт", имеют в виду не главный скрипт карты, а какую-то конкретную функцию или их набор. В этом плане такое определение аналогично понятию "текстовый триггер", который представляет из себя несколько функций, выполняющих конкретную задачу. Скрипт недоступен для редактирования из WorldEdit - редактор позволяет только косвенно вносить изменения, например, при создании триггеров, переменных или юнитов. Единственный способ вручную редактировать скрипт - распаковать карту при помощи WinMPQ и отредактировать. Единственная возможность в редакторе - перевод триггера в Custom Text из меню Edit. В этом случае можно редактировать только функции одного триггера. Итак, зачем вообще это все нужно? Если можно обойтись редактированием карты стандартными средствами? В этом случае вы получите стандартные возможности, не более того. Используя JASS, можно просто реализовать идеи, которые в редакторе принципиально недоступны. Даже если можно придумать замену, используя редактор триггеров, реализация получается очень сложной. Зная JASS, можно часть триггеров сделать обычными, а некоторые - текстовыми. Так очень многие и делают. Полностью редактировать скрипт имеет смысл только при глубоком знании JASS и только после того, как карта полностью отредактирована. WorldEdit (далее WE), скорее всего, не откроет карту с отредактированным вручную скриптом. Далее будут приведены и рассмотрены примеры - что можно достаточно просто сделать на JASS. Попробуйте сделать такое на обычных триггерах - даже если это возможно, то реализовать будет очень и очень сложно. Главный скрипт хранится в пользовательской карте в виде отдельного файла с именем MapName.j, где MapName - имя карты. Для тех, кто не знает - w3m - это MPQ архив и состоит он из нескольких запакованных файлов. В качестве дополнения к скрипту идет отдельный файл, который помогает World Editor'у раскладывать скрипт на триггеры для редактирования в окне Trigger Editor. Если файл поврежден или его нет в архиве MPQ, редактор не дает открыть карту. На этом основаны многие защиты карт от постороннего вмешательства. Хе-хе, однако для пользователя, знающего скрипт, изучение файла *.J может дать очень очень много полезной информации Кроме того, в главном MPQ архиве игры хранятся библиотеки JASS и алгоритмы AI. Их можно посмотреть, распаковав архив WinMPQ. Функции из этих библиотек используются во всех картах. Сейчас я опишу структуру j - файла любой карты (в библиотеках немного другая структура): Код: Code | // ----------------------- Начало файла --------------------------------- // Определения типов - рассматривать не буду, это не нужно // т.к. используется только в библиотеках. globals // Здесь находятся определения всех глобальных переменных, // т.е. переменных, видных из любой функции в скрипте. endglobals // Здесь находятся определения всех функций, кроме main() // и config() function main takes nothing returns nothing // Функция, инициализирующая все остальные (кроме config()) endfunction function config takes nothing returns nothing // Функция, устанавливающая описание карты, заставку и // другую информацию, которая видна до запуска карты. endfunction // ------------------------ Конец файла --------------------------------- | А теперь немного пояснений. Возьмите любую карту в редактое, зайдите в меню - File - Export script и сохраните скрипт в файл с расширением *.j. Это и будет тот самый файл - он послужит хорошим наглядным примером к этой статье. Обратно засунуть его без редактора MPQ будет невозможно. По первое время это не понадобится. В этом примере приведены самые важные функции, без остальных можно теоретически обойтись , но это только теоретически. Перечислю все по - порядку: Globals / Endglobals - между этими ключевыми словами находятся определения всех глобальных переменных в игре. В дальнейшем они будут доступны откуда угодно. Только не думайте, что здесь находятся переменные, которые определяет пользователь в редакторе переменных! Да, они тут есть, но составляют далеко не большую часть. Здесь находятся определения всех юнитов, регионов, звуков. Да, это все - игровые переменные и их возможно в дальнейшем менять. Эта возможность очень ограничена у пользователей обычного редактора! Если вы создали скрипт при помощи редактора (как бывает в 99.999% случаев), то в этом разделе переменные будут инициализироваться, т.е. будут определятся их имена, тип и резервирование минимального количества памяти для данного типа. Установление их начальных значений будет производится позже. Программе без разницы, как будут называться переменные (конечно, они должны состоять только из разрешенных символов), но принята определенная классификация по названиям (редактор WE - World Editor сам классифицирует переменные) - так легче по названию понять, что из себя переменная представляет. Существуют два класса: Global Generated - Сгенерированная глобальная переменная. Название такой переменной начинается с gg_, например, gg_trg_MyTrig. Обычно эти переменные создаются редактором и к ним относятся: Юниты - gg_unit_ Регионы - gg_rect_ Звуки - gg_snd_ Триггеры - gg_trig_ Пример: gg_trig_MyTrig _ по названию ясно, что это триггер. User-Defined Global - Глобальная переменная, созданная пользователем (те переменные, что создаются в редакторе переменных). Эти переменные начинаются с udg_ независимо от типа (Пример: udg_MyVar). Т.е. переменная, созданная вами в редакторе и названная MyVar, во всех скриптах / текстовых триггерах будет называться не MyVar, а udg_MyVar!!! Крайне желательно придерживаться такой классификации (да и поначалу по-другому не выйдет, так как редактировать сам файл скрипта вручную следует только в необходимых случаях, в основном можно обойтись использованием текстовых триггеров). Подробнее о переменных, их типах, создании и присвоении я расскажу далее. Определения функций - здесь находятся все прочие функции - пользовательские, созданные редактором - этот раздел обычно самый большой (если триггеров на карте много). Про функции и их синтаксис расскажу далее. Функция main() - ничего не берет, ничего не возвращает - эта функция служит для инициализации всех остальных и запускается в двух случаях - когда пользователь запускает карту из игры или открывает ее в редакторе. Функция config() - судя по названию понятно, что она делает. Предназначена для установления на карте описания, автора, групп игроков и т.д. - то есть всей информации, которая используется до запуска карты. Синтаксис JASS. Теперь я расскажу о синтаксисе языка JASS - сразу предупреждаю, что опишу далеко не все ключевые слова (слова, используемы только в библиотеках вроде handle рассматривать не буду) - только то, что может пригодится в даже самой сложной карте. Если вы не собираетесь писать новую библиотеку функций, то это вам не понадобится. Первое правило, на которое надо обратить внимание, особенно если вы занимались раньше программированием. Правило очень простое: A != a - то есть, строчные и заглавные буквы различаются! Если ваша функция называется myfunc, а вы вызовете ее как MyFunc, то интерпретатор выдаст ошибку. Будьте внимательнее! Второе - нет форвардинга, т.е. если вы определили функцию или переменную после ее вызова (по строчкам - вызов на 100 строке, определение начинается на 120), то интерпретатор выдаст ошибку. Теперь понятно, почему main стоит в самом конце программы? Код программы состоит из строк. Слова разделяются пробелом. Если пробелов несколько, они считаются как один. В отличие, скажем, от Delphi или Cpp, символы, указывающие конец строки (';'), не нужны. Вместо них служит признак переноса строки (0D0Ah) - когда вы нажимаете enter и переходите на новую строку, эти два символа появляются в конце старой строки и не видны пользователю, но указывают программе, что надо перенести строку. Простой пример: Код: Code | set MyVarX = 26 set MyVarY = 26 // Это два выражения разделены переносом строки. К сожалению, из этого следует, что перенос строки // не может служить пробелом, поэтому пример ниже приведет к ошибке: // Неправильно - интерпретатор посчитает это как две строки с ошибками. set MyVarX = 26 * MyVarY + MyVarZ // Правильно set MyVarX = 26 * MyVarY + MyVarZ | Если между двумя строками стоит несколько переносов (пустых строк), то они воспринимаются интерпретатором как одна. Да, для тех, кто не знает - интерпретатор (в отличие от компилятора, который переводит программный код в машинный) непосредственно выполняет программу. War3 - типичный интерпретатор (в отличие от Cpp, который компилирует исходный код в исполняемый файл). Двойной слэш // означает комментарий - все слова до конца строки после него игнорируются. Очень удобно использовать для временного 'отключения' ненужных строк или комментирования кода. Теперь перечислю основные ключевые слова с кратким комментарием: Объявление функций. function - объявляет функцию, после этого слова идет имя функции. Также используется как значение переменных типа boolexpr (см. далее - типы переменных) endfunction - обозначает конец функции. takes - используется в строке объявления, после этого слова идет список входных аргументов. returns - используется в строке объявления, после этого слова идет тип возвращаемого значения. nothing - aka null, используется в строке объявления после takes и / или returns и указывает что нет входных / выходных аргументов - смотря где стоит. return - используется в теле функции, после этого слова стоит значение, которое должна вернуть функция. Если функция ничего не возвращает, то после ничего и не стоит. call - вызов функции, после этого слова идет ее имя. Синтаксис: Код: Code | function FuncName takes XaType, Xa ... XbType Xb returns YType // Это тело функции, в нем может быть строка return Y - возвращает значение Y. endfunction | FuncName - имя функции. Редактор использует определенную классификацию (наподобие переменных - см. пред. статью. Кого заинтересует - расскажу подробно). XaType - тип первого входного аргумента (см. раздел 'типы переменных') Xa - название первого входного аргумента XbType - аналогично для последнего аргумента Xb - название последнего аргумента YType - тип возвращаемого значения (оно всего одно). Функция может брать как и несколько входных аргументов, так и один или вообще ничего не брать. Ни одно из входных, ни выходное значения не могут быть массивом!!! Это очень серьезный недостаток JASS. Ниже приведены примеры для одного аргумента, для трех и для функции без аргументов: Код: Code | function MyFunction takes integer Number returns boolean // Тело функции, у нее входной аргумент - Number, его // тип - integer (целое число), // функция возвращает булевское значение. // Остальные примеры - аналогично. endfunction | Code | function MyBigFunction takes boolean Number, region MyUnitReg, real UnitLife returns unit // Тело функции, входных аргументов много, а возвращает // переменную типа unit (юнит в игре). endfunction function MyVoidFunction takes nothing returns boolean // Тело функции. endfunction | Конечно, если функция ничего не возвращает, то после returns ставится nothing. Не забудьте - все ключевые слова пишутся с маленькой буквы! Не забывайте, в каком регистре написаны имена переменных - потом это приведет к ошибкам. Вызов функции из тела другой функции - не забудьте определить функцию ДО того, как она будет вызвана. Код: Code | call FunctionName(Xa,...,Xz) | Xa - Xz - все аргументы, их число и тип должны строго совпадать с определением функции FunctionName. Пишутся через запятую. Если аргументов нет, то пустые скобки пишутся обязательно! Если функция вызывается из выражения или как аргумент, то сall писать не надо. Пример: Код: Code | call FunctionA(FunctionB(X,Y),Y) | Здесь функция FunctionB со своими аргументами X и Y используется как входной аргумент FunctionA. Разумеется, тип первого аргумента FunctionA и тип возвращаемого аргумента FunctionB должны быть совместимы (в JASS 99% случаев под совместимостью понимается равенство типов - это вам не Delphi) Условия. if - оператор условия, после него идет выражение, которое должно возвращать булевское значение (правду или ложь). then - используется в строке с if, после этого слова на следующей строке идет список действий, которые выполняется, если условие выполнено. elseif - после этого слова на следующей строке идут действия, которые выполняются когда условие не выполнено. endif - обозначает конец оператора if и ставится обязательно! Синтаксис: Код: Code | if Condition then // Действия, если условие выполнено. elseif // Действия, если условие не выполнено. endif if Condition then // Действия, если условие выполнено. Используется, // если действия elseif не нужны. endif | Condition - выражение или функция, возвращающие булево значение, а также переменная булевого типа. В выражениях можно использовать любые операторы / переменные / функции, но главное условие - тип возвращаемого значение - бул! Конечно, внутри выражения типы должны быть cовместимы. Пример (см. ниже, если что непонятно): Код: Code | if (UserEnteredNumber >= 26) and HeroIsAlive then set Number = 0 call MyFunctionA() else set Number = UserEnteredNumber call MyFunctionB() endif | В этом примере когда выражение (UserEnteredNumber >= 26) and HeroIsAlive выполнено, т.е. равно 'true', запускается ветка set Number = 0 и call MyFunctionA(). Если нет, то - set Number = UserEnteredNumber. Да, в между then / esle / endif можно вставить НЕСКОЛЬКО строк! Если помните, в редакторе - только одно действие, в результате чего часто приходилось копировать действия с if и по нескольку раз выполнять одну и ту же проверку. Здесь этого маразма нет, не знаю, зачем он был сделан в редакторе. Вроде в TFT исправили. И вообще, код, написанный от руки обычно раз в несколько раз меньше генерированного редактором и выполняется гораздо быстрее. Операторы сравнения Они возвращают булевское значение (см. раздел 'типы переменных' - смотрите этот материал далее) > и < - больше и меньше - используется для сравнения любых чисел == - равно. Ну путать с одним знаком = - он используется не для сравнения, а для приравнивания! Можно сравнивать две строки. <= и >= - что это такое, догадаться нетрудно. Надо помнить - местами знаки 'равно' и 'больше / меньше' менять нельзя, то есть оператор => будет ошибкой! != - не равно - условие выполнено, если выражения справа и слева не равны. Можно сравнивать две строки. Синтаксис: Код: Типы операндов A и B должны быть совместимы, то есть нельзя сравнивать число со строкой, а юнит с регионом Но можно, например, перевести число в строку и затем сравнить. Пример: Код: Это выражение будет правдой (true), если А больше, чем B или равно ему и ложью (false), если наоборот - B больше, чем A. Все эти операторы возвращают булевый тип переменной! Операторы для булевых переменных. Те, кто не знает, что такое 'булева переменная' - смотрите раздел 'типы переменных'. not - инверсия (отрицание) - верно, когда операнд - ложь и наоборот. and - логическое 'и' - верно, когда все операнды (A и верны. or - логическое 'или' - верно, когда хотя бы один операнд верен. Оператора исключающего или (xor) я в Варкрафте не обнаружил - кому надо пишите сами: (A xor то же самое, что и ((not A) and or ((not and A). Синтаксис: Код: Code | A operator B Примеры: Код: not A A and B not (A or B) | Операторы для булевых типов всегда возвращают булевый тип! Работа с переменными. globals - используется в начала файла, после этого слова идут определения всех глобальных переменных. Такие переменные доступны в любой точке программы. Все переменные, которые создаются пользователем в редакторе переменных - глобальные. endglobals - указывает окончание раздела глобальных переменных. local - используется внутри тела функции, после этого слова идет определение локальной переменной, то есть переменной, которая доступна только в той функции, где она была определена. В редакторе триггеров World Edit с такими переменными работать нельзя. А зря - на самом деле одна из самых полезных вещей в скрипте. set - после этого ключевого слова идет установление значения переменной или ее создание для некоторых типов переменных (см. далее) constant - после этого слова идет определение константы array - обозначает, что переменная - массив. Ставится сразу за определением типа переменной. Массив - набор из нескольких переменных одного типа (в математике - понятие 'вектор'). Пример массива из строк: string array MyStrings null - нулл, он и есть нулл - обозначает дырку от бублика. Если переменной присвоено это значение, то она существует, но ровным счетом ничего не содержит. При попытке прочитать такую переменную игра радостно вылетает в OS с еггогом 'memory couldn't be read' Типичный пример - обращение к динамическому массиву set IntA = MyIntegerArray [-1] - вертолет гарантирован! Синтаксис: Код: Code | // В начале файла идут globals, их нельзя писать // в другом месте! globals VarType gg_Var = null VarNumType gg_NumVar constant ConstType ConstName // Приравнивание к null в начале требуют все переменные // (хе-хе, кто не знает - скорее всего, такие переменные в // самом представлении Варкрафта описываются хорошо // известным любому программисту типом class), кроме // числовых и строк. Классификацию имен (что такое gg_) // смотрите в предыдущей статье. // Аналогично и для констант. World Edit их определение // вообще не использует в карте. endglobals | Использование локальных переменных внутри тела функции и установление значений, а также обращение к массиву: Код: Code | function MyFunc takes integer Number returns boolean local VarType VarName = InitValue // VarType - тип вашей переменной, VarName - ее имя. // При желании можно назначить ей начальное значение - // написать 'равно' и значение. Очень удобно, что значение // может возвращать функция / переменная / выражение // этого типа, например: // local integer PNum = GetPlayerNumber(VarPlayer) + 1 // Внимание! Определения локальных переменых должны // идти в начале функции! Иначе будет ошибка! // Тут тело функции. set Var = NewValue // Var - переменная, которую приравниваем значению // NewValue - оно может быть также переменной / // функцией / выражением. Если надо написать сложное // выражение, используйте скобки () для расстановки // приоритетов. set MyArray[ArrayIndex] = NewValue set Var = MyArray[ArrayIndex] // Обращаемся к элементу массива MyArray с номером // ArrayIndex. Разумеется, ArrayIndex может // быть как числом, так и переменной / функцией или // выражением. // ВНИМАНИЕ! - перед чтением элемента, убедитесь, что // его значение уже установлено. Читайте предупреждение // в начале этого параграфа - см. null endfunction | Математические / строковые операторы. + - сложить, можно использовать со строковым типом string для склейки строк. - - вычитание. / - деление (зависит от типа - целое число или real) * - умножение. "" - между этими символами заключается строка. Тут есть много тонкостей (см. след. статью) За примером далеко ходить не надо: Код: Code | set A = F + 26 set MyStr = "Привет всем!" + " Короче, вы уже все проиграли." | Циклы. В языках програмирования существует несколько типов циклов, скрипт WE использует самый универсальный - loop с выходом по условию. Редактор триггеров предлагает пользователю только циклы for, да и то для двух переменных. В редакторе скриптов доступно гораздо большее. Итак, список ключевых слов: loop - начало цикла, после этого слова на следующей строке начинается тело цикла. exitwhen - после этого слова ставится условие, при выполнении которого програма выходит из цикла. endloop - это слово обозначает конец цикла. Синтаксис: Код: Code | loop // Действия до проверки условия. exitwhen Condition // Действия после проверки условия. endloop | Condition - выражение / переменная или функция, возвращающая булевское значение. За каждый проход это условие проверяется и если значение равно true, программа выходит из цикла. В качестве условия необязательна должна идти проверка числа выполненных шагов - условие может быть любым, главное - чтобы возвращало boolean. Пример цикла с самым простым условием приведен ниже: Код: Code | local integer LoopIndex = 1 loop exitwhen LoopIndex > 10 set MyString = MyString + MyStringArray[LoopIndex] set LoopIndex = LoopIndex + 1 endloop | В этом простом примере мы копируем все строки из строкового массива MyStringArray (размер массива равен 10 - дальше я расскажу как определять размеры массива в программе) и добавляем их к переменной MyString. Проверка условия выполняется в начале цикла. Если условие выхода - выполнение определенного числа циклов (контролируется переменной), то не забудьте прибавлять к этой переменной хотя бы единицу, иначе цикл будет бесконечным. Необязательно exitwhen ставить первой строкой - это зависит от того, как вы используете цикл. Конечно, можно использовать вложенные циклы - одна комбинация loop / endloop в другой. Расстановка приоритетов. В этом плане JASS не отличается от других языков. У скобок "(" и ")" - наивысший приоритет. Главное - не забывать их закрывать и не запутаться. Типы переменных (но не все!) А теперь расскажу о типах переменных, встречающихся в JASS. О том, как переменные объявляются или как им присваивается значение, написано в предыдущей статье. Я перечислю, опять же, не все типы переменных, так как некоторые используются только в библиотеках функций для определения остальных типов. Я пока не знаю случаев, когда в карте потребовалось использование переменных таких типов (например, handle и extended), так же как и всего раздела types (см. первую часть). Вот большинство типов переменных: * - этот тип более или менее часто используется. ! - редкий тип, переменные такого типа работают внутри специфических функций. Новичкам лучше не обращать на это внимания. real (*) - Число с плавающей запятой (десятичная дробь). integer (*) - Целое число. А почему нельзя использовать real без десятичной части? А потому что real обрабатывается мат. процессором, а integer - процессором, не надо мат. процессор напрягать. Есть еще причины. На основе integer построено 70% типов - все перечисляемые типы (которые принимают несколько заранее заданных значений, например, race) array (*) - Массив (см. пред. статью). Максимальный размер массива - 1024 элемента. Элементом может быть любой тип, кроме массива. string (*) - Строка. Состоит из символов. Подробности далее. event (*) - Ссылка на зарегистрированное событие (см. далее) player (*) - Ссылка на одного игрока widget (!) - Объект в игре, у которого есть жизни. Следующие типы являются производными от этого: unit (*) - Ссылка на юнита. destructable (*) - А это ссылка на разрушаемый доодад. item (*) - Ссылка на вещь force (*) - Сила в игре - состоит из игроков group (*) - Группа trigger (*) - Переменная 'триггер', с ним ассоциировано несколько функций. ВСЕ триггеры в игре - переменные, так же как и юниты, разрушаемые доодады, регионы и т.д. triggercondition (*) - Является ссылкой на функцию, возвращающую переменную типа boolexpr, служит для проверки условий в триггере. triggeraction (*) - Ссылка на функцию "действия" триггера. timer (*) - Таймер, он и есть таймер. location (*) - Регион с нулевыми размерами (точка) region (!) - Регион, используется в регистрируемых событиях. rect (*) - Обычный игровой регион прямоугольной формы. boolexpr (!) - Сложный для новичка тип. Ассоциирован с функцией, которая возвращает boolean. Используется для отбора объектов, для которых данная функция вернет true из множества других. Типичный пример использования - все условия триггеров в качестве аргумента берут boolexpr. sound (*) - Тип звуковой переменной. conditionfunc (!) - Функция для внутреннего использования - обозначает аргумент условия триггера (см. тип. boolexpr). filterfunc (!) - функция-фильтр. Выбирает из множества объекты с подходящим условием. Типичный пример - используется во внутренних функциях, аналогичных действию триггера 'Every unit matching condition'. race (*) - Раса, может быть ШЕСТИ типов - человек, орк, андед, эльф, демон., или что-то другое (вот только что же? ) alliancetype (!) - Тип заключаемого союза. playergameresult (*) - Статистика игры unitstate (*) - Статус юнита (спрятанный и т.д.) eventid (!) - ID номер события - в картах не используется. Используются производные типы: gameevent (*), playerevent (*), playerunitevent (*), unitevent (*), widgetevent (*), dialogevent (*) - Все эти типы хорошо известны - посмотрите в событиях любого триггера список событий и сразу все поймете. Правда все это дело используется как аргумент для функции регистрации события. Редко используется в собственных функциях. unittype (!) - Тип юнита. Ничем не отличается от integer!!! Подробности см. далее. Это важно! Редкий тип т.к. в таком виде используется только в библиотеках. В скриптах пишут - integer. gamespeed (!) - Тип переменной, определяющей скорость игры. gamedifficulty (!) - То же самое для сложности. gametype (!) - Тип игры - meelee / ffa и т.д. mapflag (!) - Тип переменной, которая отвечает за видимость карты по умолчанию - т.е. открыта ли карта изначально и т.д. mapvisibility (!), mapsetting (!) - Комментировать не буду - все по одному принципу. playerslotstate (!) - Тип слота (пустой / человек / компьютер) camerafield (!) - Поле зрения камеры. Напрямую редко используется. camerasetup (!) - Используется еще реже и содержит все настройки камеры. playercolor (!) - Цвет игрока (перечисляемый) placement (!) - Показывает распределение стартовых точек (random и т.д.) effect (*) - Ссылка на спецэффект в игре. effecttype (!) - Тип спецэффекта. weathereffect (*) - Тип погодных эффектов в регионе. fogstate (!) - aka Visibility Modifier fogmodifier (*) - Тип выходных переменных всех функций, работающих с туманом войны. dialog (*) - Тип переменной, содержащей диалог. button (*) - Кнопка в диалоге. quest (*) - Квест, ну что же еще. questitem (*) - Задаче квеста. timerdialog (!) - Оболочка таймера. leaderboard (*) - Доска лидерства. gamecache (*) - Тип переменной, ссылающийся на игровой кэш. Переменные, кторые указаны как 'ссылки' являются всего лишь указателями на объект. Например, когда мы используем в качестве вохдного аргумента функции тип переменной unit, то такая переменная указывает на конкретного юнита в игре. Работая с этой переменной, мы работаем с этим юнитом. С остальными типами - наоборот - когда мы указываем integer в качестве входного аргумента функции, то передаем в функцию копию этого числа - если в функции поменять это значение, на оригинале оно никак не отразится. Из этой кучи вам далеко не всегда прийдется все использовать, перечислю максимально используемые типы и расскажу о них подробно. Про остальные (в том числе встроенные или редко используемые - сами читайте в common.j - встроенный в главный MPQ файл и содержащий определения многих типов и все перечисляемые значения. Файл прилагается. Если нужно узнать, какие значения может принимать переменная типа playerslotstate - смотрите там. Там также описаны несколько типов, о которых я не рассказал). Но для начала скажу, что большинство переменных (кроме числовых и строковых) представляют в Варкрафте ни что иное, как классы (IMHO!). Если вы объявили переменную такого типа, это еще не значит, что с ней можно работать - ее еще нужно создать специальным методом Create(). У каждого типа есть своя соответствующая функция, которая и создает данный тип. Integer - тут рассказывать вроде нечего, единственное, что стоит отметить - тип unittype (тип юнита) - integer. Более того, unittype внутри карты работать не будет - используется в библиотеках blizzard. Да, еще integer используется как номер элемента в массиве. В общем, один из самых распостраненных типов. Числа могут быть записаны в нескольких системах счисления: - Десятичной: Вы просто пишите число - 0, 26, 100 и т.д. - Восьмеричной: Перед числом пишется дополнительный ноль: 07, 0153, 072. Учтите, что число 079 редактор распознает как неверное (в восьмеричной системе цифры от 0 до 7) - Шеснадцатеричной: Перед числом пишется 0x : 0x12, 0xffff, 0x5a26c и т.д. Такое число не может состоять более чем из восьми знаков после 'x'. - 256-ричной: Число берется в одинарные кавычки: 'Etoe', 'A000'. Число может состоять макс. из четырех символов (без скобок). В такой форме в Варкрафте записаны все типы юнитов, ID магий и т.д. Real - все математические функции (корень и т.д.) работают с этим типом. Если вам нужны рассчеты на карте, используйте этот тип, затем переведите его в integer (см. в след статье). Если integer можно использовать вместо real, то наоборот - нельзя! String - строка. Обычно содержит сообщения, но может служить и для других целей - например, в ней может храниться путь к спецэффекту. Также используется для обращения к переменным типа real для отслеживания события изменения переменной. Строки можно склеивать при помощи оператора сложения: Код: Code | set NewString = OldString + "Привет всем!" | Если вы хотите добавить текст внутри скрипты, он должен быть заключен в двойные кавычки - не путать с одинарными! Если текст не заключен в кавычки, то программа использует текстовую переменную с таким именем.
Администратор сайта и форума
Post edited by Avatar - Четверг, 16.11.2006, 22:58:52 |
|
| |
Maroder | Date: Суббота, 28.10.2006, 18:10:29 | Message # 2 |
Админ №1
Group: Админы
Posts: 141
Reputation: 3
Status: Offline
| Как устанавливать цвет букв? Есть два контрольных слова для этих целей. Когда программа находит их в строке, она автоматически меняет цвет последующего текста пока не встретит новое контрольное слово. Вот эти слова: Code | |сaarrggbb - устанавливает для всех символов после непрозрачность aa и цвет, задаваемый rrggbb. aa, rr, gg, bb записываются в шестнадцатеричном виде (от 00 до FF) Пример: |c00FF0000 - устанавливает непрозрачный красный цвет |сFF0000FF - устанавливает полностью прозрачный синий (но он не будет виден). |r - сброс до белого цвета по умолчанию. | Code | Пример: This |c0000FF00is|r example - слово 'is' будет зеленым. | Как использовать strings? Когда вы пишите название карты или меняете имя юнита или используете текст в триггере (не комментарий), он сохраняется отдельно в виде строк. Вы можете импортировать в карту / экспортировать из карты строки (File - Import / Export Strings). У каждой строки есть свой номер. Если вы хотите использовать ее текст в скрипте, напишите так: "TRIGSTR_xxx" - в двойных кавычках обязательно! xxx - номер строки, его смотрите из экспортированного файла. Я предпочитаю их не использовать, а весь текст храню или в константах, или в глобальных переменных, или же просто пишу внутри триггера в двойных кавычках. Хотя строки - более удобны - вы можете запросто сделать их полную поддержку, а затем легко переводить вашу карту на любые языки. Разумеется, можно перевести строку в число и наоборот, но с оговорками (переведите в число строку "NOOB" ). Для этого есть специальные функции, о которых речь пойдет далее. Эти три простых типа не требовали присвоения null при объявлении в globals, более того, вам не нужно запускать какую-то функцию для их создания. Далее пойдет разговор о типе 'триггер', из чего он состоит, с какими функциями связан и как работает. Добавляю файл common.j для тех, кто хочет узнать о тех типах, о которых я не рассказал или об возможных значениях перечисляемых типов. Как уже было сказано - триггер все-го лишь переменная определенного типа в памяти, с которой обычно ассоциировано несколько функций. Для наглядности я не буду писать возможные варианты, в качестве примера сделайте простой триггер с одним событием, одним условием и действием. Code | Событие - например, periodic time event - каждые 5 секунд. | Условие - boolean: самый примитив true = true (верно всегда). Зачем такое условие, если оно всегда верно? В данном случае только для того, чтобы посмотреть как оно работает. Code | Action - DoNothing() - ничего не делать. | Переводим триггер в текст. У меня под рукой сейчас нет редактора, буду писать по памяти, кое-какие неважные детали могут отличаться. Редактор разбил триггер на несколько функций. Определение триггера как переменной находится в globals, а редактор этот раздел даже под страхом смерти не покажет. Кому интересно, экспортируйте скрипт и посмотрите - в начале будет что-то типа (Пусть триггер называется MyTrig): Code | trigger gg_trg_MyTrig = null | Это строка резервирует название триггера, но сам он пока не существует. Если залезть и посмотреть функцию main - там есть сызовы нескольких функций, одна из которых устанавливает предварительные значения переменных, другая - инициализирует звуки и т.д. Надо найти, кажется вызов call InitCustomTriggers. Идем туда. В этой функции стоят вызовы инициализации всех триггеров. Для нашего триггера этот вызов будет примерно таким: Code | call InitTrig_MyTrig() | Аналогично и для остальных. Теперь идем обратно в редактор и смотрим наш текстовый триггер MyTrig. В самом его конце и находится та самая функция InitTrig_MyTrig takes nothing returns nothing. Она отвечает за создание триггера в памяти. Триггер создается вызовом специальной функции: Code | set gg_trg_MyTrig = CreateTrigger() | После этой строки триггер начинает существовать. Ниже идут строки: Code | call TriggerRegisterTimerEventPeriodic(gg_trg_MyTrig, 5) | Эта строчка очень важна! Она вызывает регистрацию события триггера. После этой строки триггер будет реагировать на события. Если событие другое, то вместо TriggerRegisterTimerEventPeriodic будет стоять другая функция с другими аргументами (кроме имени триггера). Если несколько событий, то строки идут последовательно. Когда выполняется хотя бы одно событие, срабатывает триггер. А кто мешает нам вместо 5 поставить глобальную переменную типа real? Конечно, никто! Но управлять периодом из игры пока мы не можем - дело в том, что при вызове регистрации события, функция регистрации записывает в память ТЕКУЩЕЕ значение переменной! Если нам надо динамически менять событие, то надо после изменения переменной уничтожить и вновь создать триггер. Смотрите пример по динамическому изменению периода события (в следующих статьях). Смотрим дальше: Code | call TriggerAddCondition(gg_trg_MyTrig, condition (Trig_MyTrig_Conditions)) | Как видно из названия, эта строка добавляет условие в триггер. Условие одно - редактор переводит все условия в одно через функцию люгического 'и'. Вот здесь и используется тип boolexpr. аргументом функции condition как раз и является переменная типа boolexpr. В предыдущей статье я сказал, что с переменой такого типа ассоциирована функция, возвращающая boolean. В данном случае - Trig_MyTrig_Conditions. Пишется без скобок и аргументов!!! Аргумент - сама функция, а не бул. значение, что она возвращает. Вы не можете паписать Condition(true) - в этом случае вместо true должна быть функция, возвращающая true! Теперь смотрим функцию Trig_MyTrig_Conditions - она идет в начале текстового триггера. У нее нет входных аргументов (и к сожалению, НЕ может быть), а выходное значение - boolean. Когда происходит событие, зарегистрированное для данного триггера, вызывается эта функция и проверяет условия. Если оно верно, выполняется действие. Возвращается значение слове return (см. пред. статью). Да, редактор генерирует такой бред, что у любого програмиста волосы дыбом встанут. Пример: Code | Код: function Condition takes nothing returns boolean if (not(GetTriggeringUnit() != 'etoe') == true) then return true else return false endif endfunction | Этот бред элементарно записывается в виде: Код: Code | function Condition takes nothing returns boolean return GetTriggeringUnit() == 'etoe' endfunction | Есть разница? И чем сложнее условие, тем больше бредит редактор.Смотрите главу "оптимизация", там рассказано как можно избежать ненужной писанины в коде. Далее добавляется действие - оно всегда одно! А уже в нем может быть много функций. Код: Code | call TriggerAddAction(gg_trg_MyTrig, function Trig_MyTrig_Actions) | Второй аргумент - функция, смотрим ее. Тут собраны все действия. Внутри могут быть также проверки дополнительных условий. Разумеетс, без добавления условий / действий / регистрации событий можно обойтись! Более того, можно создавать один триггер в другом, уничтожать, вызывать все функции. Надо только помнить об отсутствии форвардинга - call может вызвать только функцию, расположенную ДО места вызова. В редакторе есть еще одно неудобство - вы сделали два текстовых триггера, один перед другим. Если вы из последующего триггера попытаетесь вызвать функцию действий предыдущего (что правильно для JASS), редактор выдаст ошибку. Он для текстовых триггеров видит только функции, расположенные внутри одного триггера, да еще и без форвардинга! Что же делать в таком случае? Поместить два триггера в один! Кто этому мешает? НО ФУНКЦИЮ ИНИЦИАЛИЗАЦИИ ПРЕДЫДУЩЕГО НЕОБХОДИМО ОСТАВИТЬ ПУСТУЮ. Иначе редактор вместе со старым триггером сотрет и объявление переменной старого триггера в globals! Если вы оставляете пустую функцию инициализации, то и объявление триггера сохраняется! А уже из второго триггера можно создать первый без особых проблем. Если вы правите весь скрипт вручную, от этого маразма можно смело избавляться - главное, оставьте объявление в globals. Это основные моменты, касающиеся текстовых триггеров. Далее осталось описать некоторые функции и можно писать примеры . Немного об оптимизации кода. Оптимизация - упрощение и уменьшение объема программного кода с целью экономии ресурсов (в том числе временных). В предыдущих статьях об этом уже заходила. Редактор очень неграмотно генерирует скрипт с точки зрения оптимизации и удобства чтения. Чем сложнее триггер, тем больше будет код, созданный редактором. В одном триггере редактор может ассоциировать десятки ненужных функций, причем если триггеры похожи, то все равно для каждого из них будет создан этот огромный список. В этом можно будет легко убедится, конвертировав обычный триггер, желательно большой и сложный, в текстовый. Кроме обычных функций инициализации, условия и действия там, скорее всего, будет несколько десятков других с такими названиями (пусть триггер называется MyTrig): Trig_MyTrig_Function_xxxxxxxx, где xxxxxxxx - набор из чисел. Эти числа генерируются по специальной схеме - ее объяснять не буду, она достаточно простая. Обычно такие функции возвращают boolean и служат для проверки каких-то условий. В 90% случаев они нужны только самому редактору для упрощения построения триггерных схем. Исключение составляют некоторые функции, которые, например, используются в структурах 'Pick Every Unit in Group and do Action...' - такие функции ничего не возвращают. Больше всего получается сократить размер условия - сначала смотрим функцию Trig_MyTrig_Conditions - что из нее вызывается, идем туда и переносим содержимое тех функций в условие, после чего сами функции удаляем. Так можно избавиться от многих лишних функций, сократить объем кода и увеличить скорость выполнения программы. Еще одна из "ошибок" редактора - неграмотное использование синтаксиса операторов и функций. Пример (еще очень короткий) неграмотного условия: Код: Code | function Trig_MyTrig_function_00000010 takes nothing returns boolean if not (GetTriggerUnit() == 'nslf') then return true else return false endfunction function Trig_MyTrig_Conditions takes nothing returns boolean if not (Trig_MyTrig_function_00000010() == true) then return true else return false endif endfunction | Это типичный пример условия, генерированного редактором. Функция Trig_MyTrig_function_00000010 является лишней, да и операторы if и return использованы неграмотно. Вот как можно все упростить: Сначала упростим первую функцию: она возвращает boolean, тогда зачем нужен if? Конечно, не нужен! Можно записать возвращаемое значение так: Код: Code | return GetTriggerUnit() != 'nslf' | Можете проверить, эта одна строчка делает ТО ЖЕ самое, что и 4 строки в оригинальной функции. Когда GetTriggerUnit() равно 'nslf' - выражение будет равно true, мы его и вернем. Наоборот тоже верно. Теперь смотрим функцию условия - ситуация аналогична, вместо Trig_MyTrig_function_00000010() подставляем (GetTriggerUnit() != 'nslf') в скобках, а то можно напутать приоритеты вычисления! Теперь первую функцию можно смело удалить. Оптимизируем дальше - строку Код: Code | if not ((GetTriggerUnit() != 'nslf') == true) then можно упростить до такой строки: Код: if GetTriggerUnit() == 'nslf' then | А затем вообще избавиться от if: Код: Code | function Trig_MyTrig_Conditions takes nothing returns boolean return GetTriggerUnit() == 'nslf' endfunction | Условие сократилось до одной строки! Вот так. Так что нет ничего удивительного в выражении - мы присваиваем VarBool true, если A равно B и наоборот. Код: В теле функции действия ошибок не меньше - редактор не использует локальные переменные. Так десятки обращений к элементу массива можно осуществить одной локальной переменной integer, которой присвоено необходимое значение. Хотел привести пример реального скрипта из своей карты, но не решился - неоптимизированный код занимал ровно 499 (!) строк и использовал для внутренних расчетов 2 глобальные переменные, а написанный 'от руки' - 67 строк и для расчетов использовал только локальные переменные. Кроме того, 'ручной' код гораздо легче читать и выполняется он намного быстрее. В кажом приведенных примерах вы увидете, какие именно способы оптимизации могут быть применены. Хотя это немного усложняет понимание примеров новичкам. Зачем так много триггеров? В этой статье речь пойдет о возможности перекрестного использования кода в скриптах - т.е. как использовать в одном триггере внутренние функции другого и создавать несколько триггеров в одном. Сначала расскажу, как редактор воспринимает скрипт: Скрипт разделяется на разделы (каждый триггер в редакторе записывается в отдельный раздел, когда триггер переводится в текст, он тоже занимает один раздел), которые все мапперы воспринимают как 'триггеры'. Когда говорят 'я сделал новый триггер', надо это воспринимать 'я сделал новый раздел'. Триггеры как было сказано ранее - это обычные переменные, а разделы можно представить как контейнеры, к которых хранятся функции триггера. Редактор разделяет все переменные на два класcа: gg_ и udg_ (см. первую статью), причем в окне редактирования переменных можно создавать / изменять / удалять только udg_ переменные (переменные, созданные пользователем). Реализовать идею совмещения можно несколькими способами, которые принципиально ничем не отличаются. Первый способ такой - создаем в редакторе переменных несколько переменных типа 'триггер'. Создаем новый триггер, переводим в текст и в его функции инициализации добавляем инициализацию для пустых триггеров-переменных. Функции условия / действия этих триггеров пишем здесь же. В результате получается карта, состоящего из ОДНОГО раздела, в котором собраны много триггеров. Пример: Мы создали в редакторе переменные InitTrig и WorkTrig типа trugger. Затем в редакторе создаем триггер (конечно, правильнее сказать, раздел) с названием BagTrig, затем переводим его в текст. В результате у нас есть один пустой текстовый триггер с названием BagTrig. Теперь пишем в него функции инициализации двух триггеров-переменных: Код: Code | // Здесь описываются рабочие функции триггеров одна за другой Можно писать в любом порядке, // главное - чтобы вызов функции был после ее определения. function Trig_InitTrig_Condition takes nothing returns boolean // // ............................ // endfunction .... function Trig_WorkTrig_Actions takes nothing returns nothing // // ............................ // endfunction function Trig_BagTrig_Init takes nothing returns nothing // Сюда пишем инициализацию остальных триггеров. Т.к. переменную сделали в редакторе, то она будет udg_ call CreateTrigger(udg_InitTrig) call CreateTrigger(udg_WorkTrig) // Здесь пишем функции регистрации событий, условий и // действий для этих триггеров. Все функции мы описали // до этого. endfunction | Вообще несколько триггеров в одном разделе лучше писать так: 1. Общие функции. 2. Функции - условия. 3. Функции действия. 4. Функции динамической инициализации (если надо). 5. Статическая инициализация триггера (-ов). В результате мы сделали три триггера в одном разделе! Причем все предыдущие функции видны их последующих. Это главное преимущество - можно сделать кучу триггеров, используя общие функции - и удобно и объем кода гораздо меньше. Второй способ - все аналогично, но используем gg_ триггеры: создаем несколько триггеров, затем переносим их код в один. Если стереть пустые триггеры, редактор сотрет их определение из Globals. Поэтому оставляем только функцию инициализации, но пустую - редактор в этом случае оставит определение. Типичный пример такого использования был уже выложен (событие с переменным периодом) - там использовано два пустых триггера как раз для этих целей. Заключение. Итак, в этой статье я потытался немного рассказать про JASS. Программисты уже отметили, что сам язык очень простой - но это, наоборот, плохо т.к. многие вещи на простых языках очень сложно реализовать. Однако возможностей JASS хватит почти для всех возможных карт и модов Варкрафта. Я умышленно не приводил описания функций - на это ушло бы много времени, да и кому, это в принципе, надо? Если нужна какая-то конкретная функция, есть два способа ее получить: Сделать новый триггер с этой функцией, перевести его в текст и скопировать имя функции куда надо. Однако далеко не все функции доступны редактору. Поискать определение функции в Blizzard.j (для функций, содержащих в названии аббревиатуру BJ, например RAbsBJ()) или в Common.j (Базовые функции и определения типов). В этих двух файлах содержатся ВСЕ функции JASS. Эти файлы находятся в главном архиве MPQ и достать их оттуда можно при помощи программы WinMPQ. Редактировать их не надо, так как это в 90% приведет к неработоспособности игры. Ошибки интерпретатора JASS описывать тоже не буду - т.к. информация об ошибках, выдаваемая интерпретатором, неверна. Допустим, функция неправильно названа, а он говорит, что не хватает "endif". Для проверки кода включайте / выключайте триггер - при включении он полностью проверяется. Рекомендуется часто сохранять - при сложном алгоритме на таких проверках у меня иногда вылетал редактор. Да, строки, которые временно не нужны "отключаются" при помощи комментариев - пишем // перед такой строкой. Говорю новичкам, программисты это давно знают. В JASS можно по очереди отключать сомнительные строки и таким образом находить ошибку.
Администратор сайта и форума
Post edited by Avatar - Четверг, 16.11.2006, 21:54:51 |
|
| |
Vedun | Date: Среда, 15.11.2006, 21:15:09 | Message # 3 |
Уровень 2
Group: Пользователи
Posts: 24
Reputation: 1
Status: Offline
| Джассеров, млин, развелось, плагиатных. =) Гм, а почему нет ничего по полярным координатам? Где утечки в памяти? Это обязательная часть арзвёрнутых статей (эт я про утечки). Собсна можно ещё добавить про работу с памятью, в частности РетурнБаг, работа с кешем, создание собственных функций (имеется в виду не мелкие функции с назначением убить героя), изменение стандартных функций и т.д. Можно привести ещё много примеров того, что должен знать новичок о JASS. А тут нужно не одну статью писать. Этим я как раз и занимаюсь... Так что эта статья рассказывает лишь об основах ДЖАССА. Но в общем не плохо.
|
|
| |
Avatar | Date: Четверг, 16.11.2006, 13:56:22 | Message # 4 |
Уровень 3
Group: Модераторы
Posts: 57
Reputation: 2
Status: Offline
| кстати о рб. скоро выложу наработку основонаю на РБ+кеш выводящая инфо героя. думаю спи* у димона метод работы с тракеблями.
|
|
| |
Vedun | Date: Четверг, 16.11.2006, 15:45:13 | Message # 5 |
Уровень 2
Group: Пользователи
Posts: 24
Reputation: 1
Status: Offline
| Насчёт РБ. Я делаю небольшое пособие для начинающих (чё то вроде карты примера), я там это использовал для самого элементарного: вместо переменных с убийствами и смертями (опять же, есть номер юнита в памяти... =). Хотел всё это сделать в виде стандартных тригов, да вот WinMPQ полетел...
|
|
| |
Avatar | Date: Четверг, 16.11.2006, 22:14:52 | Message # 6 |
Уровень 3
Group: Модераторы
Posts: 57
Reputation: 2
Status: Offline
| Maroder, немного поправил вид для изящества. как смотрицо?
|
|
| |
Smile | Date: Пятница, 17.11.2006, 14:24:27 | Message # 7 |
Group: Удаленные
| Так уже гораздо лучше....
|
|
| |
Maroder | Date: Суббота, 18.11.2006, 17:56:10 | Message # 8 |
Админ №1
Group: Админы
Posts: 141
Reputation: 3
Status: Offline
| Smile, Прикольно. Надо мне было сразу сделать.
Администратор сайта и форума
|
|
| |
|