Этот мануал первым делом предназначен для начинающих jass-ов, тех из них, кто стремится понять принцип работы этого необычного языка и узнать многие его тайны. В конце концов, именно знание принципов работы отличает хорошего программиста от плохого, верно? В целом, несмотря на слово "теория" в заголовке, я собираюсь быстро пробежаться по основным местам, отмечая все хоть немного полезные на практике вещи. Для лучшего понимания текста также рекомендуется иметь хоть базовое представление о работе компьютера как машины.
[#=types]
Типы переменных.
Начнём с того, что всего зарезервировано 6 типов переменных, остальные же являются потомками или иначе дочерними типами. Любой родительский тип может «содержать» в себе любого из своих потомков, хотя для хранения данных рекомендуется все же использовать специально выделенный под это дело тип. Непонятно? Тогда давайте рассмотрим каждый из этих 6 видов переменных подробней, возможно ситуация прояснится. integer, real, boolean, string – самые примитивные из типов, отвечают за обозначение целочисленных, дробных, логических и строковых переменных соответственно. Совсем не имеют потомков, однако это не очень огорчает. Примеры: integer x = 1; real y = 1.0; Подробно рассматривать тип real я не буду, он используется только для расчётов. А вот integer для нас более интересен, ведь многие игровые объекты являются целочисленным типом, хотя они представлены в несколько иной форме. Форме? Да, числа 'A000', 0x31 или 34 все являются целыми числами, просто они записаны в 256-ой, 16-ой и 10-ой системе счисления соответственно. Поэтому мы можем использовать с любой из этих форм любые операции, будь то сложение, вычитание или запись в переменную. Всё вроде? Ах да, integer может принимать значения от -2147483648 до 2147483647, что соответствует 4 байтам.
Использование выражения 1.0 вместо 1 для типа real не случайно, довольно давно, благодаря ADOLF’у и Van Damm’у было установлено, что использование точки уменьшает работу машины минимум на 1 операцию. Конечно это капля в море, тем не менее, использование точек есть показатель хорошего тона. Также некоторые нули можно опускать при желании, например число 0.0 представимо в виде "0." или ".0".
boolean z = true; string c = “word”; Ничего необычного, boolean - переменная логического типа, читайте, принимает значение "да" и "нет" (true или false). Несложно догадаться, что string - строка, любой набор символов в скобках "...". Важно отметить, что "" != null, то есть пустая строка тоже строка, а не пустое значение.
Тип boolean может принимать только два значения, однако некоторые функции, возвращающие этот вид переменных, могут на самом деле вернуть несколько неожиданное число. Дело в том, что boolean равно false, если возвращаемое значение 0 и true во всех остальных случаях. И некоторые функции подло используют это, поэтому при использовании записи вида «if IsUnitType(u, UNIT_TYPE_HERO) then» вы можете никогда не добраться до тела условия, даже если юнит u действительно герой. Поэтому в данном случае лучше добавить == true к условию, дабы избежать неточности.
Обнулять все эти типы бессмысленно. Почему? На самом деле сложный вопрос, косвенно связанный со способном выделения памяти под нужды игры. Переменные просто перезаписываются при необходимости и всё.
handle - самый интересный тип, дескриптор/ссылка, отвечает за положение объекта в памяти. Занимает 4 байта, что позволяет сравнивать его с integer. Имеет более 9000 дочерних типов (конкретно 74, если я ничего не забыл), однако я отмечу только несколько: boolexpr, widget, trigger, timer, group, texttag. Наиболее сложный из них - widget - собирательный тип всех объектов, имеющих здоровье. Иначе говоря дочерними типами widget являются unit, destructable и item. Что это значит? А то, что наряду с обыденным выражением: [code=jass]local unit u = CreateUnit(...)[/code] также допустимо
[code=jass]local widget u = CreateUnit(...)[/code] или вообще
[code=jass]local handle u = CreateUnit(...)[/code]
При этом ни одна из этих записей не вызовет ошибки и корректно сработает. Почему? Просто handle считается родителем widget, а widget родителем unit.
Остальные типы работают по такой же системе. Их смысл понятен из их названия, исключая разве что boolexpr. Что это? Просто ссылка/указатель на другую функцию, которую вы собираетесь использовать как фильтр (если конечно собираетесь). Допустимо писать так: local boolexpr b = Condition(function MyFunc), где MyFunc обязательно возвращает boolean. Например:
[code=jass]local boolexpr f = Condition(function MyFunc) call GroupEnumUnitsInRange(group, x, y, r, f)[/code]
Здесь мы просто собираем юнитов в группу, но согласно условию MyFunc. Иначе говоря, Warcraft перебирает всех юнитов на расстоянии r от точки (x; y), для каждого запускает функцию f и, если она вернёт true, то группа увеличивается на одного юнита. Также можно писать так:
[code=jass]call GroupEnumUnitsInRange(group, x, y, r, Condition(function MyFunc))[/code]
Правда потом не получится удалить это условие, что не есть хорошо.
Многие из этих типов нуждаются в удалении и обнулении, об этом будет сказано дальше. Ах да, в 1.24 ввели новый тип agent который находится где-то посередине между handle и всем остальным.
Также появилась интересная, но мало популярная функция SaveAgentHandle(...). Она позволяет сохранять любой тип, дочерний от handle без указания самого типа. Иначе говоря, вместо SaveUnitHandle(...), SaveEffectHandle(...) и, скажем, SaveButtonHandle(...) можно использовать SaveAgentHandle(...) во всех 3 случаях... довольно удобно?
code - ссылка в чистом виде, самый необычный тип и этим все сказано. Очевидно, служит для хранения некоторого кода, абсолютно любой функции, например local code c = function MyFunc. Разумеется MyFunc должна существовать и быть выше точки вызова, иначе при сохранении вылетит ошибка. Не правда ли тип code очень похож на boolexpr? Оба хранят в себе функции, правда boolexpr требует, чтобы хранимая функция возвращала boolean. Тем не менее он (code) является старшим/базовым типом и с типом handle (=> и с boolexpr) не связан. Добавьте к этому невозможность создание массива code, компилятор просто не даст это сделать. Возможно code был выделен из handle хитрыми разработчиками, чтобы мы не могли так или иначе использовать хэш и организовать массив...
Добрые Blizzard не оставили нас совсем у разбитого корыта, так функция TriggerAddActionспособна принимать в себя code и возвращать triggeraction - дочерний тип от handle. Тоже самое с уже упоминавшейся функцией Condition, которая принимает code, но возвращает conditionfunc (иначе boolexpr). Это можно использовать при необходимости передать много кода... куда нужно.
Вы не можете удалять code, это даже несколько абсурдно, удалить некоторую функцию/код по ходу игры. Однако её можно обнулить... хотя после нескольких тестов я начинаю сомневаться в необходимости таких действий. Похоже code это просто замаскированное целое число, отвечающее за номер такой-то функции.
[#=arrays]
Массивы.
Строго говоря, массив - набор однотипных переменных, расположенных в памяти непосредственно друг за другом, доступ к которым осуществляется по индексу. Иначе это можно представить как некоторое количество банок с неким содержимым, причём все банки пронумерованы. Нумерация массива всегда начинается с 0, максимальный элемент - 8191. Получается мы имеет 8192 банки в которые можно положить любой из доступный Warcraft 3 типов, кроме code.
Blizzard шлёт нам привет из страны не выученных уроков, их компилятор допускает весьма забавную вещь, согласно которой, вы можете хладнокровно сохранить определённое значение в элемент массива с... отрицательным индексом. Всё верно, запись вида set MyArray[-1000] = 3 не вызовет никаких нареканий со стороны редактора. Разумеется, на самом деле значение никуда не сохранится и даже страшно подумать, что там происходит на самом деле.
Напрямую можно задать только одномерный массив, остальное вам придется делать самим через параллельные массивы и некоторую integer переменную. Выделение памяти идёт динамично, однако сразу по 1000 (примерно же) элементов.
[#=optimization]
Оптимизация.
Ок'эй, наверно уже все наслышаны, что бездумное использование языка Jass (и через GUI тем более), приводит к так называемым "утечкам" памяти. Речь идёт не о вытекании памяти из вашего компьютера, так что можно не бояться промочить носки. В общем смысле под "утечкой" подразумевают отсутствие своевременного освобождения памяти тем, или иным объектом. Доступной памяти становится меньше, следовательно, у ещё свободных ячеек начинает скапливаться очередь. В очереди принято ждать, верно? Это ожидание причина лагов, которые можно получить уже через небольшой промежуток времени. При достаточном угле изгиба рук можно добиться даже полного вылета из игры (но нужно действительно постараться).
Что же делать?! Первым делом не стоит паниковать, если ваш код выполняется только 1-2 раза за 2 часа, то ничего страшного не произойдет, даже если он (код) страшен как атомная война. Но если промежуток между вызовами вашего триггера плавно приближается к 0.0, то уже через n секунд вы сможете на здоровье любоваться прекрасными лагами даже с 4 ГБ оперативки.
Также перед оптимизацией очень важно остановится и подумать (вообще хорошая черта программиста). Увидев не обнуленные переменные типа handle (timer, unit...) не стоит сразу изрыгать потоки ненависти, вспомним что вообще такое переменная. А ведь по сути это просто ссылка на ячейку памяти с определённым объектом, будь-то integer или unit. Вы можете лёгким движение руки (пальцев) установить значение переменной на null. Вот только это никак не повлияет на сам объект. Он удаляется только специальными функциями (их 100500, не буду их перечислять). Тогда зачем вообще нужно обнуления?!
Хитрость в том, что, даже получив, приказ удалится, объект не будет этого делать пока есть ссылки (переменные) на него. Непонятно? Тогда вот пример: допустим, есть unit rabbit, он утратил свою полезность, и мы хотим жестоко удалить его с помощью функции call RemoveUnit(rabbit). Кролик послушно исчезнет с нашего экрана. Однако исчезнет ли он из памяти? Сделаем так, пусть в бесконечном цикле будет вызываться некая функция. Суть функции - создать новую переменную, создать нового кролика и сразу удалить, переменную конечно не обнулим. Цикл у нас без пауз, работает очень быстро, через несколько минут наблюдаем приличные лаги. А ведь мы удаляли всех кроликов! И экран чистый, никого не видно. Добавляем обнуление после функции и внезапно всё работает безотказно! Вывод? Функции удаления объектов удалят его и без обнуления переменной, если ссылка уже отсутствует. Как это можно использовать? Ну если вы создаете того же кролика в цикле:
[code=jass]local unit rabbit loop exitwhen (i > 100000) set rabbit = CreateUnit(...) call RemoveUnit(rabbit) set rabbit = null // <------------------------------- А вот это можно не писать! Ссылка всё-равно исчезнет. set i = i + 1 endloop set rabbit = null // <------------------------------- Но тогда напишите здесь, последняя переменная останется. [/code]
Я думаю (надеюсь) никто, никогда и не писал эту строку... но здесь я даю только теорию, показываю почему это так.
Вы знаете смысл функции UnitApplyTimedLife(...)? Многие очень ценят её, ведь она не оставляет "утечек" и при этом работает с отсрочкой. Идеально для дамми! А ведь на самом деле она только убивает юнита. Кто же тогда удаляет объект? Вы можете удивиться, но оказывается Warcraft 3 всегда удаляет всех неактивных юнитов сам! Всё что нужно сделать, это убрать все ссылки на объект (обнулить переменные), а затем прихлопнуть нашего кролика. Как только расслоятся его кости (вроде 88 секунд в константах), он покинет мир игры навсегда.
Особую головную боль при оптимизации доставляют динамические триггеры... Конечно, существует не очень (совсем не) сложная функция DestroyTrigger(t), однако на практике оказывается мало удалить сам триггер, как и кролик он просто исчезнет (перестанет работать), однако даже после обнуления переменных никуда не денется. В чём причина? В начинке. Точнее в условиях и действиях, ведь они являются объектами и при этом ссылками на триггер. Хотите удалить триггер? Удалите все его действия и условия с помощью функций TriggerClearAction(...) и TriggerClearCondition(...).
Все ещё хуже, чем вы думаете. Писать функцию TriggerClearAction(...) (и возможно TriggerClearCondition) доверили каким-то индусам... Как результат она тупо не работает. Совсем. Делать нечего, придется использовать TriggerRemoveAction(..), которая требует переменную triggeraction для удаления. А это значит её тоже придётся сохранять... Печально.
Вообще старайтесь избегать динамических триггеров, если их и создавать, то пусть навсегда. Удаление других объектов не должно быть затруднительным и специально я останавливаться на них не буду.
Но зато приторможу здесь! Что вы знаете о texttag'ах? Удалять их всегда проблематично, создавать для этого таймер просто лень. Не стоит боятся, Warcraft с радостью удалит ваш текст за вас, просто уничтожьте все ссылки (переменные же) и убейте через функцию временной жизни: SetTextTagLifespan(...).
[#=misc]
Повседневные хитрости.
1) Функция call GroupEnumUnitsInRange(...) и ей подобные сами очищают группу перед началом своей работы. Поэтому можно не использовать call GroupClear(...), если вы используете одну глобальную группу, а не плодите локальные.
2) Вы знаете, как расположены юниты в группе? Оказывается, это определятся их игровым номером, иначе говоря, первым становится тот юнит, функция GetHandleId(...) для которого вернёт меньшее значение и так далее по возрастанию. Обычно на практике это означает, что FirstOfGroup(...) вернет самого "старшего" юнита.
3) Невозможно мгновенно повернуть юнита... максимальная скорость обеспечивается функцией call SetUnitFacingTimed() с параметром времени на 0.01. Это соответствует скорости поворота 3.0 в настройках этого юнита.
4) Игра не будет инициализировать ваши переменные за вас, старайтесь избегать вот таких конструкций:
[code=jass]local integer i set i = i + 1[/code]
Получается, что вы прибавляете "пустоту" к 1... как результат, данный поток немедленно завершится, ни одно действие после set i = i + 1 не выполнится никогда, по крайней мере, пока i остается неопределённым. Однако ошибка не вылезет!
5) Использование return в любой форме немедленно заканчивает выполнения функции. Даже если ваша функция ничего не возвращает, вы можете так и написать - return, это всё равно завершит работу данного участка кода.
6) Если вам нужно создать эффект, а удалять его лень, можете использовать запись вида:
Однако в этом случае проиграется только анимации birth и death, вам придется сохранять эффект и удалять потом, как положено, если вы хотите насладиться анимацией stand.
7) В чём отличие SetUnitX/SetUnitY и SetUnitPosition(...)? На самом деле SetUnitPosition(...) дополнительно проверяет точку на проходимость, как результат работает немного медленней, что сильно заметно при обработке большого числа юнитов. Также использование SetUnitX/SetUnitY может привести к вылету без отчета, если перемещаемый юнит выйдет за границы карты.
8) Записи if (IsUnitEnemy(...) == true) then... и if IsUnitEnemy(...) then... имеют одинаковый смысл, однако хорошим тоном считается использовать второй вариант. Будьте внимательны при использовании IsUnitType(...)!
9) Что быстрее глобалки или хэш? Теоретически глобалки, но только по причине необходимости вызова, зачастую даже двух, функции для нормальной работы с хэш-таблицей: call SaveInteger(HASH, GetHandleId(...), 0, 5), в то время как для операций над глобалками вызывать никаких функций не надо. Вызов функции требует время большее, чем загрузка переменной из памяти... но на практике заметить (и похоже даже измерить средствами Warcraft) разницу во времени нельзя. Поэтому выбор между хэшом и глобальными переменными - дело вкуса.
Использование структур и, например, единого таймера сводит необходимость хэша на 0.0, хотя вы и можете сохранять в хэш структуру, но зачастую это превращается в лишнюю трату времени и ресурсов. Впрочем, сохранять значения в хэш можно и с помощью строки, что открывает довольно широкие возможности для использования его в роли базы данных.
10) При поиске ошибок в коде полезно использовать функцию BJDebugMsg("..."), а также неплохой идеей будет скачать счетчик утечек и вставить в свою карту на время теста. Ведь "утечки" могут сказываться только через полчаса и даже более часа игры.
Вроде всё, больше сейчас ничего не вспомню, да и лимит подходит к концу.
Всем спасибо. Надеюсь понятно изложил, а то изначально было подробнее, но вмешался лимит сообщения. Впрочем статья в основном интересна для тру-кодеров, желающих докопаться до сути происходящего в Warcraft.
Хочу научиться с нуля писать скрипты на Джасе ө штудирую статейки с тупым выражением лица - потому что нифига не понимаю(( скиньп лиз FAQ где будет описан простой пример, в котром будет описанно как с нуля делать простенький тригер на джасе (юнит умер - тип юнита горный король - создасть спецэфект - тип этого) я слышал там какойто особый едитор нужен для скриптов? объясните плиз! зарание признателен)
работа с ним быстрее на порядок чем работа с глобалками
Пруфлинк? В Warcraft нельзя организовать длительный поток, плодить потоки тоже нельзя. По крайней мере "в лоб". Значит не проверить. У вас есть дизассемблированный код функций работы с хэшом? Скорость любого хэша, в основном, зависит от выбранного алгоритма. Сборка функции (любой) тоже занимает некоторое время, но оно -> 0.0. Время обращения к глобальной переменной -> 0.0. Как сравнить тут что-то? На счет удобнее, пожалуй, согласен, но я использую структуры, а хэш подрабатывает базой данных.
Цитата (Quasar)
Ajaccio, можеш обьяснить как автокаст сделать????
Идеальный вариант - глобальная система отлова урона. Способность изготавливаем на основе ледяных стрел (любые характеристики, главное - баф), далее создаем событие "юнит получил урон". По событию, если урон > 0.0 и юнит имеет баф ледяных стрел, то удаляем его (баф, если нужно) и делаем свои дела. Возможно стоит создать тему в "вопросах"?
брат я картостроитель с әх годовым стажем)) gui знаю на раз-два) но вот все пытался обойти Джас - раньше получалось - ведь требования к картам были не такие ка сейчас - но теперь придется изучить ведь игроки требуют такого что бы всех вставило по полной а Gui уже исчерпал свой ресурс да и понял уже что в джасе функций гоооооооооооооооооооораздо больше)) так что ГОТОВ К ОБУЧЕНИЮ И ПОСТОРАЮСЬ НЕ ТУПИТЬ! кинье чтонибуть для начало добрые люди!)
http://avsn.3dn.ru - мой сайт Вечно докапываясь до мозилы, пытаясь найти во вкладке инструменты игровые константы(говорит уже о фанатизме или пора завязывать) Ищу учителя по Jass.
Также появилась интересная, но мало популярная функция SaveAgentHandle(...). Она позволяет сохранять любой тип, дочерний от handle без указания самого типа.
Не рекомендуется использовать, по то причине что читабельность кода падает.
Цитата (Ajaccio)
code
Вот если бы code мог быть массивом...
Цитата (Ajaccio)
Как только lol расслоятся его кости (вроде 88 секунд в константах), он покинет мир игры навсегда.
Время настраивается, рекомендую ставить значение ~25.
Цитата (Ajaccio)
Невозможно мгновенно повернуть юнита... максимальная скорость обеспечивается функцией call SetUnitFacingTimed() с параметром времени на 0.01. Это соответствует скорости поворота 3.0 в настройках этого юнита.
В РО можно поставить любое значение.
Цитата (Ajaccio)
Получается, что вы прибавляете "пустоту" к 1... как результат, данный поток немедленно завершится, ни одно действие после set i = i + 1 не выполнится никогда, по крайней мере, пока i остается неопределённым. Однако ошибка не вылезет!
Гуишникам боятся нечего, гуи производит инициализацию самостоятельно.
Цитата (Ajaccio)
Если вам нужно создать эффект, а удалять его лень, можете использовать запись вида:
А лучше сделайте функцию отдельную, в которой будет эта конструкция, дабы не писать много букаф.
Цитата (Ajaccio)
В чём отличие SetUnitX/SetUnitY и SetUnitPosition(...)? На самом деле SetUnitPosition(...) дополнительно проверяет точку на проходимость, как результат работает немного медленней, что сильно заметно при обработке большого числа юнитов. Также использование SetUnitX/SetUnitY может привести к вылету без отчета, если перемещаемый юнит выйдет за границы карты.
Что бы юнит не улетел за границы карты(это кстати вызовет крит эррор), используем эту конструкцию:
[code=jass]function CoordProjectionX takes real x, real d, real a returns real local real newx = x + d * Cos(a * .0174532) if newx < GetRectMinX(bj_mapInitialPlayableArea) then newx = GetRectMinX(bj_mapInitialPlayableArea) endif if newx > GetRectMaxX(bj_mapInitialPlayableArea) then newx = GetRectMaxX(bj_mapInitialPlayableArea) endif return newx endfunction
function CoordProjectionY takes real y, real d, real a returns real local real newy = y + d * Sin(a * .0174532) if newy < GetRectMinY(bj_mapInitialPlayableArea) then newy = GetRectMinY(bj_mapInitialPlayableArea) endif if newy > GetRectMaxY(bj_mapInitialPlayableArea) then newy = GetRectMaxY(bj_mapInitialPlayableArea) endif return newy endfunction[/code]
Цитата (Ajaccio)
Что быстрее глобалки или хэш?
Большая таблица ХТ серьезно снижает скорость работы. Настоятельно рекомендую в целях оптимизации, а так же защиты нужных данных от удаления, использовать сразу много ХТ. Спрашивается зачем? Во первых при поиске данных в хт, провека идет всех ячеек, т.е. чем больше табл. тем больше проверок, это не круто. Во вторых у вас может быть тонна систем которые вешают данные на юнит. И очищая таблицу, которая привязана к юниту, вы можете удалить нужные вам данные. В некоторых случаях это может привести даже к фаталам. Глобалки быстрее массива, так же отмечу что локалки не всегда быстрее глобалок, в прочем это больше зависит от платформы, как дело обстоит на вар, не известно. Не стоит ими злоупотреблять.
Цитата (Ajaccio)
Использование структур и, например, единого таймера сводит необходимость хэша на 0.0, хотя вы и можете сохранять в хэш структуру, но зачастую это превращается в лишнюю трату времени и ресурсов. Впрочем, сохранять значения в хэш можно и с помощью строки, что открывает довольно широкие возможности для использования его в роли базы данных.
Можно просто написать 1 раз функцию, и наслаждатся, а так же аттачить структуры черех хт.
О нас думают плохо лишь те, кто хуже нас, а те кто лучше нас... Им просто не до нас. My Project: Nindogatari MAL
Ajaccio, Ну внеси поправки, потом мне текст скинь в личку, я модеров пну что б изменили=) Или можешь попробовать сразу Дракона попросить в личку, он няшка, поправит
О нас думают плохо лишь те, кто хуже нас, а те кто лучше нас... Им просто не до нас. My Project: Nindogatari MAL
Все очень просто. Когда создаются объекты, им даются ID handle'ов по порядку. Т. е. если между вызовами функции было много утечек, второе число будет больше первого. Правда, есть один существенный недостаток: если удалить какой-нибудь "старый" объект, его ID будет считаться свободным, и ID таймера счетчика утечек будет маленьким. Так что функцию надо вызывать чаще, чем раз в минуту.
То есть количество утечек определяется не самим отображаемым числом, а разностью между числами?
Добавлено (31 Январь 2012, 13:14:42) --------------------------------------------- И ещё: какая зависимость между этой разностью и количеством утечек? Т.е.,к примеру, если было число 1049068, потом несколько раз выполнилась некая функция ( скажем, 10 раз), и получилось 1050994, то эта функция утечная?