WarCraft 3: Осваиваем JASS - исправления и дополнения

» Раздел: Триггеры и объекты

Советуем ознакомиться со всей базой статей по триггерам редактора (и не только).

Пару дней назад из чувства ностальгии я перечитал статью "осваиваем JASS", статью, по которой я и сам когда-то осваивал его. Ну то, что она писалась давно, и содержала некоторые неточности я знал, но что их будет так много... а после этого я удивляюсь почему многие кодеры допускают совершенно нелепые ошибки.
Я понимаю, что сия статья писалась в далеком 2005 году (ну по крайней мере была опубликована) и конечно тогда информации было намного меньше, и в общем данная статья дала большой толчок к развитию все моддинга warcraft’a, но при всем том думаю стоит описать основные ошибки, которые в ней есть (а их там на метр квадратный хоть отбавляй). Приступим...
Я предполагаю, что вы прочли эту статью и поняли как минимум 90% из описанного там материала и теперь готовы к принятию более сложных вещей, в противном случае идите и перечитайте ее снова.
Для удобства я буду приводить цитаты из оригинальной статьи, а потом объяснять, где ошибка и как ее избежать.

1. Что есть jass и для чего он нужен

Более того, я не рекомендую писать все триггеры сценария исключительно на jass. Для многих задач редактор триггеров подойдет лучше – ведь это действительно очень удобная штука.
В этом разделе это единственное с чем я не согласен. Удобство редактора триггеров как и "сложность" написания JASS кода - вещи весьма сомнительные. На самом деле если вы хорошо освоите JASS (практика, практика и снова практика) то написание кода станет куда удобнее выбора GUI подменюшек. Да и еще, я до сих пор не понимаю как на триггерах пишутся сложные арифметические выражения, к примеру (a*b+c*2)/x*x+(y-8)/r. а на JASS это просто набирается и будет выглядеть точно также как и тут ;)

2. Локальные переменные

Начну с того, что Sergey дает в принципе неправильное объяснение области применения локальных переменных как таковых.
Локальные переменные созданы вовсе не для использования вместе с wait'ами (более того эта конструкция весьма порочна, в идеале я вообще не рекомендую использовать wait (и конструкции, основанные на нем), а использовать именно таймер). Реально локальные переменные - это временные переменные в прямом смысле этого слова. Память под них выделяется в момент входа в функцию, и освобождается при выходе из нее, также это значит то, что для каждой функции будет создан свой набор локальных переменных.
Локальные переменные самые быстрые (обращение к ним как правило незначительно быстрее чем к глобальным переменным, и намного быстрее чем к кешу). Так же важное свойство локальных переменных - что их область видимости ограничена одной функцией, т.е. мы не должны беспокоиться о том, что имя двух переменных из разных функций совпадут, и что при пересечении потоков (если вы не поняли о чем я то ничего страшного) может быть получены/записаны не те данные и т.д.
Где применять локальные переменные? Если в рамках одной функции мы либо должны производить какие либо вычисления а потом обращаться к их результату достаточно часто - результат стоит поместить в переменную. Если мы должны обращаться к какому либо объекту - его стоит занести в переменную. К примеру в функции, которая вызываться как действие триггера локальная переменная может быть использована так:
// неправильно
function myFunc takes nothing returns nothing
 call UnitAddAbility(GetTriggerUnit(), 'A000')
 call UnitAddAbility(GetTriggerUnit(), 'A001')
 call UnitAddAbility(GetTriggerUnit(), 'A002')
 call UnitAddAbility(GetTriggerUnit(), 'A003')
 call UnitAddAbility(GetTriggerUnit(), 'A004')
 call UnitAddAbility(GetTriggerUnit(), 'A005')
 if GetWidgetLife(GetTriggerUnit())>100.then
  call SetWidgetLife(GetTriggerUnit(), 100.)
 endif
endfunction

// правильно
function myFunc takes nothing returns nothing
 local unit u=GetTriggerUnit()
 call UnitAddAbility(u, 'A000')
 call UnitAddAbility(u, 'A001')
 call UnitAddAbility(u, 'A002')
 call UnitAddAbility(u, 'A003')
 call UnitAddAbility(u, 'A004')
 call UnitAddAbility(u, 'A005')
 if GetWidgetLife(u)>100.then
  call SetWidgetLife(u, 100.)
 endif
 set u=null
endfunction
Есть способ исправить эту проблему: для каждого запуска файербола помещать значения не в переменные, а в ячейку массива. Для каждого запуска сохранять значения в свои ячейки, каким-то образом отслеживать, что пришел момент создать спецэффект для такого-то юнита из массива или удалить такой-то спецэффект из другого массива. Это не очень удобный и достаточно громоздкий способ. В итоге, простая по сути задача – становится очень тяжелой.
Именно так реализованы struct в vJass. реально этот метод более чем удобен, не имеет побочных эффектов (какие имеет wait) и оправдывает себя на 100%. Я ниже подробно опишу его.
Ты умеешь создавать их при помощи редактора переменных.
Конечно когда писалась эта статья JassHelper'а еще не было, или он был еще в зачаточном состоянии. В любом случае я советую установить его и объявлять переменные в нем непосредственно в коде - это намного удобнее, тем более что можно отказать от уродливого префикса udg_). Делается это так:
globals
 //<тип переменной> <имя> = <значение>  // просто переменная
 //<тип переменной> array <имя>         // массив

 integer MyInt=12   // целочисленная
 unit array Heroes  // массив юнитов
endglobals
Скачать NewGenWE, в состав которого входит JassHelper можна тут.
Также автор статьи использует понятие триггер (переменные можно использовать во всех триггерах) - это так но это не совсем точное определение, правильно было бы сказать что их область видимости - все функции.
Более того, для большей понятности читателю Sergey переносит потом данные из локальной переменной в глобальную. Понятно что так делать не стоит)

3. Применение локальных переменных

Локальные переменные очень хорошо решают проблему хранения данных при отсроченных действиях, как мы разобрали в прошлом примере.
Нет, нет и еще раз нет. Если использовать таймеры - вы напишете примерно столько же кода, но обезопасите себя от тупых багов, более того с точки зрения быстродействия это также ужасно... Понятно что в статье рассматриваются примеры "для новичков".
Более того, я не считаю что стоит делать сложные заклинания, навороченные системы и т.д. на триггерах (хм, это конечно условно, но если вы будете делать это вы должны быть уверенны что в результате у вас не получится лагающая нерабочая дрянь)
Локальные переменные выступают как хранилища на время пауз в триггере, глобальные переменные нужны для каких-то мгновенных действий
И опять же с точностью до наоборот - глобальные переменные нужны в т.ч. для отсроченных действий (к примеру какие либо счетчики, или главные герой, который будет использоваться на протяжении всей игры, или позиции точек появления мобов) а локальные переменные для действий в одном потоке (ну в одной функции без прерываний потоков (wait)).
Итак, Читатель, ты уже достаточно узнал, чтобы создать свой собственный jass код. Правда, есть определенные тонкости который тебе нужно узнать.
Честно говоря это звучит как некоторая издевка. На самом деле изложенный в первых разделах материал - это даже не вершина айсберга, впрочем не стоит унывать, идем дальше)

4. Условия, циклы в jass

Сразу скажу что автор упустил то, что конструкция if куда более гибкая на JASS:
if i==0then
 // если равно 1
elseif i==10 and (b==2 of c>3)then
 //
 // если i равно 10 и при этом
 // или b равно 2 или c больше 3
 //
elseif i==1then
 // если равно 1
elseif i==2then
 // если равно 2
elseif i==3then
 //
 // --->
 //
else
 //
 // если не удовлетворено ни одно условие,
 // ставить необязательно
endif
Итак, эта конструкция может быть почти любой, это стоит учитывать.
О циклах добавлю, что в директиве exitwhen boolean, значение может быть получено из любого выражения, к примеру

local integer i=0
loop
 // --->
 set i=i+1
 exitwhen i==12 or GetRandomInt(0, 99)==0 or (u!=null and y>2 and x<=2.12)
endloop
В этом цикле при исполнении директивы exitwhen сначала будет проверенно значение переменной i, если она равна 12 произойдет выход из цикла, если же она не равна 12 будет вызвана функция GerRandomInt (получить любое целочисленное число от 0 до 99), если это число - 0 произойдет выход из цикла, если же это число не будет равно 0 то будет проверенна переменная u, если ее значение не равно null, и значение y больше двух и значение x меньше или равно 2.12 то произойдет выход из цикла, иначе произойдет прыжок к метке loop.
Вообще это очень важно правило при вычислении значений переменных типа bollean, к примеру if A() and B()then, если A вернет false то B вызвана не будет, и алогично if A() or B()then, тут если A вернет true то B вызвана не будет.
Теоретически также цикл может содержать несколько exitwhen, но на практике это применяется редко.

5. Функции в jass

AdjustPlayerStateBJ - это встроенная функция с тремя параметрами. Список всех таких встроенных функций имеется в MPQ архивах. Так что получается у нас, что все триггеры устроены так, что одни функции ссылаются на другие, те на третьи и т.д.
Остается уточнить что в конечно случае все функции, которые так или иначе воздействуют на игровой процесс "вшиты" в движок, определяются как native и их список находится в MPQ архиве в файле scripts\common.j
Также существуют паразитические BJ функции, о них советую почитать в стать Jon'a "оптимизация".

6. Устройство триггера с точки зрения jass

Исключение Map Initialization, но это отдельный разговор.
Там где раньше была галочка "изначально включен" появляется галочка "продолжение инициализации карты", именно она и запустит триггер после инициализации.
Рекомендую извлечь с помощью MPQ архиватора из любой карты war3map.j и поизучать его. Это что то вроде домашнего задания ;)

7. Динамическое создание триггера

Вообще пример с массивами не так уж плох. Но на что я хочу тут обратить внимание... Негоже искать юнита в массиве перебором всех его элементов. Для этого используются разные damage detection системы, в общем принцип работы у них такой же, но на триггер "аттачиться" ссылка на юнита, но это уже другая тема)

8. События с малым периодом

Ну первое что я хочу заметить - удобнее использовать место триггеров таймеры, хотя каких либо серьезных отличий этот вариант не имеет.
Что интересно - идея с "параллельными" массивами просто замечательна, лично я так и поступаю при движение снарядов (правда использую другой алгоритм выделения свободной ячейки, об этом как и было обещано написано ниже).
Цикл в действие триггера/функции повешенной на таймер полностью себя оправдывает как с точки зрения удобства, так и с точки зрения быстродействия. Это уже потом пойдут разные системы с кешем, которые в статье преподносятся как шаг вперед (фактически это куда менее эффективно и является шагом назад).

9. Полярные координаты (ликбез)

Если у вас есть две точки A и B, координаты которых нам известны. Как вычислить координаты третьей точки C, находящейся на заданном расстоянии R от точки A в направлении к точке B?
Если у нас заранее не посчитан угол между точками - то вектором.
Сейчас поясню - вектор из точки a в точку b равен (b.x-a.x; b.y-a.y). Длина вектора равна корню квадратному из (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y). Теперь сделав просто деление можно определить координаты искомой точки:
//local real ax
//local real ay

//local real bx
//local real by

//local real vx
//local real vy

//local real r

set vx=bx-ax
set vy=by-ay

set r=(vx*vx+vy*vy)/range // range = расстояние, причем в данной формуле оно не должно быть возведено в квадрат
                      // поскольку мы ищем именно соотношение данных велечин

set vx=vx/r
set vy=vy/r
Будет ли это работать быстрее? Не знаю, в принципе если считать это единожды оно так на так и выйдет, но! Во первых вектор посчитан изначально (хотя можно и с помощью Sin(real)/Cos(real) посчитать его единожды), а при работе в 3D векторы более чем оправдывают свое использование.
Да и еще, помните, что функции Cos и Sin принимают угол в радианах, это значит то что развернутый угол равен 3.1415... (пи), а прямой 1.57...

10. Оптимизация: утечки памяти

Умершие юниты должны быть удалены действием Remove Unit, чтобы не занимать место в памяти
Это не так - умерший юнит после разложения благополучно удаляется. Более того его handle будет удален если на него нету никаких ссылок (он не находится в переменных).
Кстати это и есть объяснение почему надо обнулять локальные переменные - после помещения юнита в локальную переменную и выхода из функции его handle становится "висячим" и не может быть использован повторно. Более того насколько я догадываюсь это таки небольшая утечка (но не сравнима с не удаленным объектом).
В большинстве случаев можно считать координаты без точек вообще (в принципе иногда можно использовать точки для оптимизации, к примеру если нам параллельно надо вычислить высоту поверхности но опять же это не намного быстрее) - поэтому мой совет - не используйте точки! все аналоги функций есть и на координатах (кроме GetLocationZ(location) & GetSpellTargetLoc()).
Во первых вам не надо выделять время на создание/перемещение/удаление/обнуление. Во вторых точки двумерны и делать 3d движение с ними неудобно. В третьих работая с координатами вы получаете быстродействие. Что бы сместить точку нам надо вызывать функцию, а смещение координат делается арифметическим действием, что явно быстрее.
 function AngleBetweenPoints takes location locA, location locB returns real
    return bj_RADTODEG * Atan2(GetLocationY(locB) - GetLocationY(locA), GetLocationX(locB) - GetLocationX(locA))
endfunction

// --->
set p = GetUnitLoc(u)
set a = AngleBetweenPoints(p, p2)
call MoveLocation(p, GetLocationX(p) + 50 * CosBJ(a), GetLocationY(p) + 50 * SinBJ(a))
// <---
Тут используется совершенно ненужная функция, которая вычисляет угол, а реально же она сначала делает вектор, потом вызывает Atan2, и потом с помощью Sin & Cos мы получаем искомые координаты.
Вот как можно это сделать.
// если производить вычисления
// исходя из соотношения длины векторов

local real x2=1234. // это координаты второй точки
local real y2=5678. // 

local real x=GetUnitX(u)
local real y=GetUnitY(u)
local real r

set x2=x2-x
set y2=y2-y

set r=SquareRoot(x2*x2+y2*y2)/50.

set x=x+x2/r
set y=y+y2/r

// использую вариант с углом

local real x2=1234. // это координаты второй точки
local real y2=5678. // 

local real x=GetUnitX(u)
local real y=GetUnitY(u)
local real r=Atan2(y2-y, x2-x)

set x=x+50.*Cos(r)
set y=y+50.*Sin(r)
Отлично! весь код линейный, использовано меньше памяти (все типы - скалярные), и самое главное код может быть оптимизирован дальше, к примеру если мы заранее знаем стартовые X & Y.
Второй вариант ефективней, если мы заранее знаем угол, первый если мы делаем движение в 3D.
call RemoveLocation(p)
set p=null !!!
Необходимость в этом в случае с координатами отпадает по понятным причинам.
Читатель, если удалить все основные утечки, то для 99,99% сценариев больше ничего не нужно оптимизировать.
Что же, это тоже неправда. Лаги могут быть не только от засорения памяти, ну и от слишком "долгого кода". К примеру попробуйте каждые .01 секунды создавать полсотни точек и удалять их и вы поймете о чем я. Дело в том что каждая инструкция в коде требует время на исполнение, поэтому когда вы пишете код, особенно в случае с функциями, вызываемыми на малых периодах вы также должны использовать максимально быстрые инструкции и не совершать лишних действий.
Но, как оказалось, существуют и другие виды утечек. Например, куда девается локальная переменная после того, как триггер кончил исполнение? На самом деле они продолжают сидеть в памяти.
Насколько я понимаю это не так. Но опять же тут не уточнено, речь скорее идет именно о висячих указателях, которые не могут быть освобождены. Локальные же переменные, исходя из здравого смысла хранятся в локальном стеке для JASS, как и адрес возврата. т.е. поскольку вершина стека всегда гуляет, утечка памяти нам не грозит, эта память будет использована повторно, а данные благополучно переписаны, хотя это и только мое предположение.
set i = 0
set r = 0
set s = ""
В этих трех строках сделано 4 ошибки)
  1. Тип integer - скалярный - это значит что он не является ссылкой на какой либо обьект. обнулять его не надо, его реальное значение соответствует тем байтам, что хранятся по адресу переменной в стеке, и он будет перезаписан при выходе из функции и повторном использовании локальных данных
  2. Тоже касается и real
  3. Строки обнулять не надо, хоть они и являются ссылками. строки не удаляются, и не имеет "счетчика ссылок"
  4. Если мне нужна пустая строка я напишу set s=null , в этом примере же переменной присваивается ссылку на полноценную строку, у которой есть как либо обозначенное окончание.
Еще хочу сказать - обнулять не надо boolean, а также handle, которые не требуют этого (player обнулять смешно просто).

11. RETURN BUG (RB)

Вообще говоря разработчики war3 вряд ли хотели, чтобы картостроители могли работать с памятью, но они допустили при разработке один важный баг, которым научились пользоваться спецы по jass. Баг не позволяет менять содержимое ячейки памяти - но зато он дает возможность для любого игрового объекта найти номер ячейки памяти, в которую он записан, а также по номеру ячейки найти ссылку на объект.
Это не так, доступа к области памяти, в которой хранятся объекты доступа с помощью RB добиться нельзя. на факте RB - это обход типобезопасности в JASS.
Все переменные в JASS - dword, поэтому такая фичя не приводит ни к каким осложнениям. Хорошо, сейчас объясню)
Переменная - это место в памяти компьютера, куда можно что либо записать. Переменные можно условно разделить на скалярные типы (real, integer, boolean в JASS) и ссылочные - это все остальные. В первом случае информация о непосредственно самом объекте храниться в самой переменной (к примеру если мы помещаем в переменную i единицу, то в памяти, там где числиться наша переменная биты принимают значение 1). Во втором же случае если мы присваиваем переменной типа unit значение какого либо юнита, то в переменную, точнее по ее адресу запишется дескриптор юнита (handle), и по этому дескриптору движок найдет юнита. Точнее сказать записывается "ссылка на ссылку", т.к. объекты не могут быть расположены так близко и дескриптор не являеться указателем, т.е. не указывает на конкретную область памяти, в которой записанны данные, относящиеся к указанному объекту.
RB дает возможности обойти это. т.е. благодаря ему мы можем узнать этот дескриптор и записать его именно как число, и обратно же мы можем любое число использовать как дескриптор. Вообще это бред ;) (ну к примеру в нормальном языке программирования это просто нонсенс использовать отдельную функцию для возможности обращаться к переменной так, как ты считаешь нужным, во много благодаря тому что понятие переменной, как и понятие функции там всего лишь макросредства, надеюсь все поняли что я о своем горячо любимом ASM).
function H2I takes handle h returns integer
 return h
 return 0
endfunction
Что делает эта функция? Мы сообщаем в нее переменную типа handle которая к примеру имеет значение 0x12345678 (я записываю числа в 16ричной нотации, если вы не знаете что это прочитайте мою статью "тип integer"). Теперь она нам возвращает это же значение, но уже типа integer, результат кода set i=H2I(h) будет таким, что в переменной i окажется... 0x12345678
Мы можем хешировать (сжать) нашу переменную типа integer с помощью элементарной арифметики и использовать как индекс к массиву, и как следствие создавать свои хеш - таблицы (использую массивы или gamecache). Идем дальше, хотя лично я не всегда рекомендую так поступать, почему - объясню потом.
function I2H takes integer i returns handle
 return i
 return null
endfunction

//

set h=I2H(h)
Теперь мы передаем число 0x12345678 и получаем переменную h типа handle со значением 0x12345678. и теперь мы можем передать это в любую native функцию для совершения реальных действий. вот и вся суть.
Скажем, на твоей карте несколько сотен регионов, в которых должны появиться одни и те же монстры и отправиться в патруль в следующую точку. Проще всего делать это через массивы с циклами.
Стоп! Это - измена! Юниты создаются либо по координатам (что самое лучшее, т.е. два параметра типа real, либо надо передать объект типа location, что не так удобно). Как многие создавали юнитов раньше - это было сделано за счет создания точки посреди региона и в ней создание юнита. но это бессмысленно. это не нужно ни вам ни компьютеру! Не делайте так!
А я сделал предположение, что регионы, которые мы создаем в редакторе, занимают соседние ячейки памяти. Т.е. если создали мы первый регион - он попал в ячейку X, тогда следующий регион попадет в ячейку X+1 и т.д. Т.е. если мы знаем номер первого региона и остальные регионы создавали в определенном порядке, мы можем при помощи RB найти все остальные регионы и занести их в массив на автомате.
Это - тоже не правильно, сейчас объясню. к примеру мы создаем 10 регионов. Пусть их handle будут равны x+0; x+1; x+2; ... ; x+9. теперь мы удаляем первые 5, тогда движок считает что первые 5 handle свободны и в них можно помещать снова ссылки. При повторном создание объектов их handle будут равны x+0; x+1; ... ; x+4; x+10; x+11; ... ; x+14, и при попытке обратится к 10 регионом, от x+0 до x+9... Надеюсь ошибка понятна. Also к памяти как я уже указывал выше хендлы не имеют никакого отношения.
Дело в том, что все ячейки памяти имеют сквозную нумерацию. Т.е. благодаря RB мы можем сопоставить всем объектам их номера (точнее номера ячеек, которые они занимают в памяти). И все эти номера будут уникальны. Совпадений не будет.
И это не так) Если вы будете удалять триггер в его же потоке, потом использовать TriggerSleepAction(real) и создавать новые обьекты - это более чем возможно.

12. Тип Handle

Насчет типа handle я советую всего навсего открыть common.j и посмотреть какие типы от чего наследуются. Хотя по сути это все иллюзия, на факте же все типы это dword (т.е. просто 32 бита данных), значение может иметь что помещено в этот dword и как мы к нему обращаемся.

13. Система Super Custom Value (SCV) или RB+cache

Sergey в дополнении к статье описывает порочность этой системы и предлагает крайне уродливое решение основанное на локальной переменной и wait. Очень надеюсь что всерьез это решение никто не рассматривает.
Реально вы можете относительно спокойно "аттачить" именно на handle объектов переведенные в integer какие либо данные, если вы точно уверены что эти данные не будут изменены (т.е. данный объект не будет уничтожен а по его ссылке разместиться ссылка на новый).
Но, опять же куда более элегантное решение - использование параллельных массивов. Это удобнее, быстрее для движка игры, и небагоопастно.
Тут стоит сделать небольшое лирическое отступления и рассказать, что массивы в Jass не совсем являются таковыми, вы и сами наверное заподозрили это узнав что они могут иметь только фиксированный (и достаточно большой размер - 0x2000). Реально память под данные вJass массиве выделяется динамически. В итоге мы получаем достаточно мощный инструмент, имея возможность эфективно использовать память, единственный минус - то, что при обращении к n елементу под массив будет выделенная новая память, и будет произведен перенос всех ранее записанных данных, но это легко лечиться обычным помещением чего либо в элемент массива который предположительно будем считать максимальным размером массива. Теперь можно продолжить...
Есть очень удобное макросредство в JassHelper, имя ему - struct. Хотя лично я пишу все сам ручками - это копирование примерно 15 строк кода и изменение нескольких символов, с другой стороны я извлекаю из этого некоторую выгоду, но к нашему рассказу это отношения не имеет.
struct spell
 unit caster
 unit target
 integer lvl
endstruct

// --->
function Spl takes nothing returns nothing
 local timer t=GetExpiredTimer()
 local spell s=*attach get*(t)
 //
 // action with s.caster and s.target
 //
 call s.destroy()
 call DestroyTimer(t)
 set t=null
endfunction

function SpellAction takes nothing returns nothing
 local spell s=spell.create()
 local timer t=CreateTimer()

 set s.caster=GetSpellAbilityUnit()
 set s.target=GetSpellTargetUnit()
 set s.level=GetUnitAbilityLevel(s.caster, 'A000')

 call *attach*(t, integer(s))
 call TimerStart(t, 2., false, function Spl)
 set t=null
endfunction
// <---
Первое, строка integer(s) это чисто для видимости, иначе JASSHelper выдаст ошибку, реально же это и есть integer.
Теперь, как работает этот код? Очень просто, Вам стоит сохранить карту с ним и посмотреть содержание war3map.j
У нас есть три массива (реально) и есть функции, которые выдают свободный индекс или помечают использованный как свободный. s.create() дает нам такой индекс. Далее мы использую его заполняем массивы. Потом мы аттачим этот индекс к таймеру (можно и через кеш, можно через аналогичные системы, к примеру XAT). Запускаем таймер, когда таймер истечет будет вызвана функция, которую мы указали в параметрах его запуска. В ней мы снова извлекаем (с помощью RB конечно) из таймера индекс к нашим массивам, и делаем действия с данными занесенными в них ранее.
Я скажу больше, иногда достаточно одного таймера. Этот пример есть в карте, внизу статьи, постарайтесь разобраться в нем сами.

14. Да здравствует SCV!

Рассмотрим один из наших старых примеров – полет юнита снаряда. Можно ли улучшить его при помощи SCV? Раньше нам приходилось использовать массивы, чтобы сохранить информацию, что такой-то юнит - снаряд летит к такой-то цели и имеет такой-то уровень заклинания.
Да, и именно так и надо поступать - это быстрее, удобнее и легче! смысл использовать вариант с кешем, который хуже во всех отношениях?! не усложняйте себе и другим жизнь... и внимательно смотрите код в карте - примере.

15. Послесловие

Отлов вреда, получаемого юнитом (или damage detection systems) - тема отдельная, и требующая долгого описания, там есть много своих нюансов, поэтому описывать ее я не буду. Пришло время и мне заканчивать.
Отлично, вот Вы и прочитали мое скромное дополнение к исторической статье о Jass. Я думаю теперь Вы сможете писать более качественные скрипты для Ваших карт, а процесс написания станет куда более простым и понятным. Еще раз рекомендую внимательно изучить прилагающуюся карту пример, без нее вы вряд ли усвоите вышеизложенный материал.

Просмотров: 29 392

» Лучшие комментарии


Garret #1 - 8 лет назад -6
пафос и несуществующие ошибки детектед, такое впечатление, что человеку нечем было заняться и он решил найти какие только можно ошибки. пропитано неким средним между "злостью" и "фу, нуб!"
вот так превращают новичков в задротов
J #2 - 8 лет назад -6
Garret +1 )
ScorpioT1000 #3 - 8 лет назад 13
Garret, я понимаю если бы это сказал человек, знающий хоть малую часть того что знает Адольф.. ТТ
Manhant #4 - 8 лет назад -1
+1 Garret так и есть).
ADOLF #5 - 8 лет назад 7
2гарет - пример несуществующих ошибок в студию
алсо писать унылый код, не задроство, а указывать как можно секономить свое время и силы и сделать код качественне - задроство...
J #6 - 8 лет назад -2
ну по поводу пафоса я согласен с гаретом, также согласен с "пропитано неким средним между "злостью" и "фу, нуб!"" =)
ну т.е. я как бы не против дополнения статьи сергея, но написано как так... ну это необъяснимо, это сложно назвать обучающей статьей, вообщем склоняюсь ко мнению что адольф просто неможет(неумеет) писать статьи на эту тему)
ADOLF #7 - 8 лет назад 6
Ж, ну в твоем случае это скорее всязанно с личным отношением. насчет материала - я изложил все факты в доходчивом виде.
хмм, форма с сылками на текст статьи может внешне напоминать то, как часто по цитатам критикует троли чужой креатив, однако я преводил цитаты только для того, что бы было все яснее (т.е. что к чему относиться и т.д.)
Onua Mistika #8 - 8 лет назад 0
А какой прогой писать Жасс И как го в карту вставлять?
ADOLF #9 - 8 лет назад 1
[url]http://xgm.ru/articles.php?name=jass_introduction[/url] читать отсюда
J #10 - 8 лет назад -2
какое личное отношение?
Garret #11 - 8 лет назад 0
"Стоп! Это - измена! Юниты создаются либо по координатам (что самое лучшее, т.е. два параметра типа real, либо надо передать объект типа location, что не так удобно). Как многие создавали юнитов раньше - это было сделано за счет создания точки посреди региона и в ней создание юнита. но это бессмысленно. это не нужно ни вам ни компьютеру! Не делайте так!"
Это должно относиться к тексту, который находится выше, но связи я не нашёл.....=(
"Это - тоже не правильно"
Наверно, у нас с ADOLF'ом разное восприятие, но я понял этот кусок статьи Сергея так, как описано у Адольфа.
2ScorpioT1000
Почему ты так свободно утверждаешь, что я не знаю даже малую часть от знаний Адольфа? Может, я постоянно создаю кучу тем в Академии? Вроде нет. Наработок не выкладываю - это тоже не говорит о знаниях. Интересно, откуда ты почерпнул информацию обо мне?
З.Ы. Я просто выразил впечатление, которое было после прочтения статьи.
ADOLF #12 - 8 лет назад 0
"Это должно относиться к тексту, который находится выше, но связи я не нашёл.....=("
хмм, ну это относиться к цитате прямо перед этим предложением, там явно указанно, что есть много регионов, в которых должны создаваться юниты. как по мне это очень неудобно и неправильно, поэтому я указал на ошибку
ну восприятие у всех разное, это понятно, впрочем тут можно увидеть полезную инфу для себя, можно указать на те моменты, которые могут быть улучшенны/переписанны, а можно устроить срач и перейти на личности, и это уже зависит не от восприятия, а человека...
и всетаки я был бы очень благодарен, если бы кто то указал на конкретные места или смысловые ошибки=)
J #13 - 8 лет назад 0
"Первое, строка integer(s) это чисто для видимости, иначе JASSHelper выдаст ошибку, реально же это и есть integer."
невыдаст
Garret #14 - 8 лет назад 0
2ADOLF
Может, просто сделать что-то вроде ремейка статьи Сергея? Т..е соединить эти 2 статьи?
Mihahail #15 - 8 лет назад -2
хех, скучный кодинг, лучше быть художником...©
Onua Mistika #16 - 8 лет назад -1
Блин, так какой прогой его пистать? В статье не нашол. Т_Т
ADOLF #17 - 8 лет назад 0
2J - хм, возможно несовпадение версий, т.е. наш боливийский друг чето намутил. но имхо лишним она не будет
2гаррет - я думал об этом, ну тут либо писать с 0, что долго, да и возможно ненужно, либо такой "патч" в виде дополнительной статьи. статья сергея являеться вводной в жасс, и свое дело делает, я же просто хочу что бы она не прививала начинающим кодерам "плохие привычки" (юудь то локалки с вейтами, обнуление интегеров, кешь где надо и ненадо и т.д.) (как вариант можно было бы пробовать резать статью сергея, но я, и наверное сам сергей были бы против)
2товарищу который не может найти где писать код - открыть редактор тригеров, зайти в кастом код или перевести любой триг в жасс и писать там
arko59 #18 - 8 лет назад 0
я считаю что статья хороша. лично я много уяснил и она мне не мало помогла в изучении джасс. одно НО - "пропитано неким средним между "злостью" и "фу, нуб!""
ADOLF #19 - 8 лет назад 0
"пропитано неким средним между "злостью" и "фу, нуб!"
хмм... перечитал - что то все таки есть=/
уф, хорошо, постараюсь учесть), в след раз если что буду добрее;)
FunkieFoO #20 - 8 лет назад 1
1)ADOLF - Респект брат, я в Жассе новичег, но всю работу проделаную ADOLF'ом я понимаю и приветствую, а окрикивать: "Да ты, мол Алексея хотел унизить до ранга ламера" - это подлое дело. Одна система диалогов чего стоит?! Лучше детки читайте, вникайте и учитесь кодировать, и не будте задротами! =)
2)Прошу впредь не отзыватся так о ADOLF'е, пусть кто нить из вас попробует понятней и умней изъяснить то что ADOLF изъяснил в своей статье. Если такая статья появится я возьму слова обратно... )
  1. Недавно купил СД диск "Warcraft MASTER", кто то что то об этом слышал? В него вошли как наработки Vexorian'a так и ADOLF'а. Значит их стоит поставить в один ряд МЕГА-Жассеров. =)
P.S На этом СД, ссылка на этот сайт указана не как xgm.ru, а как xgn.ru =)
bee #21 - 8 лет назад -3
статья для тех кому нечего делать (в комп надоело играть или книги все кончились) ^^ здесь нет нечего нового (полезного) что можно было использовать.
BioAleks #22 - 8 лет назад 0
Руллез статья
JamesBlack #23 - 8 лет назад 2
Статья очень хорошая и полезная. Спасибо, ADOLF. Насчет "пропитанности" чем-бы там ни было - вранье. Он просто указал ОШИБКИ. Можно конечно сказать так "ну он конечно хорошо все написал, но лучше немножко по-другому сделать....", но ИМХО это бред. Четко и коротко: тут неправильно потому-то, тут нерационально потому-то, вот так-то и так-то - лучше. Ведь он не просто писал "вот это некрасиво!", "вот это мне не нравится!". Везде указаны четкие причины, почему чтото не так.
Gam_Over #24 - 8 лет назад 1
узнал мало нового но все же узнал поэтому статья мне кажется довольно полезна, а в каком стиле написана статья это дело автора а не пользователей. Это мое мнение
MaiorPain #25 - 7 лет назад 0
А куда нажать то что бы появилась палочка которой писать jass? или куда ваще нажать что бы начать его писать?
ау
Дед Банзай #26 - 7 лет назад 1
Автор, работай над орфографией, в статье куча ошибок!
JamesBlack #27 - 7 лет назад 1
» А куда нажать то что бы появилась палочка которой писать jass? или куда ваще нажать что бы начать его писать?
ау
facepalm
открываешь редактор триггеров, выделяешь нужный триггер, "Правка"->"Конвертировать в текст"
fobius #28 - 7 лет назад -3
мне статья ваще непонятна, я ненаю полностью джасс, вот создаю мапу, захотел очень научиться джассу, чтбы мапа была более продвинутой и без багов, а тут ваще ерезь какая то.
Некрон71 #29 - 7 лет назад 1
fobius, Статься понятная, читай внимательнее, перечитывай по нескольку раз, и занимайся практикой, да и ведь это статья исправляет ошибки в статье Сергея, так что прочти для ознакомления и ее
BandRes #30 - 7 лет назад 1
ADOLF, молодец, как ни крути - ошибки нашел настоящие. Да, у Sergey'я статья получилась понятнее и приветливее, но это - ВОС-ПРИ-Я-ТИ-Е, за это не судят непрофессионала (сомневаюсь, что ADOLF писатель/журналист или т.п. :)
Wortecs #31 - 7 лет назад -6
Более того, я не считаю что стоит делать сложные заклинания, навороченные системы и т.д. на триггерах - Гениальная фраза а на чем их писать? В ТХТ формате или в Каталоге?
Sergant1000 #32 - 5 лет назад 0
отличная и познавательная статья
Это сообщение удалено
Dota_2015 #34 - 1 год назад 0
я так как понял, использовать переменную вместо GetTriggerUnit() и подобных локальных bj_CreateUnit???
Dota_2015 #36 - 1 год назад 0
nvc123, ясно, спасибо теперь буду знать, а как насчет GetTriggerUnit????
nvc123 #37 - 1 год назад 0
Dota_2015, переменные надо использовать там где они нужны
твой вопрос изначально не имеет смысла
советую прочитать предыдущие статьи для понимания что и где нужно использовать
Steal nerves #38 - 7 месяцев назад 0
что такое damage detection systems?
LainMikoroso #39 - 7 месяцев назад 0
Steal nerves:
что такое damage detection systems?
Системы отслеживания урона
Singularity #40 - 2 месяца назад 1
Наконец, нашёл, на какую исправленную и дополненную статью ссылался автор оригинала. Прочитал и удивился тому, насколько глубоко автор заглянул в JASS и его структуру.
Конструктивно. Заметно, что автор рецензии разбирается в предмете (если я не ошибаюсь, автор разработал интерпретатор для диалекта cJASS (AdicHelper), а это уже говорит об опыте и компетенции). Не упущу случая сделать несколько существенных добавлений к рецензии.
» Всё, сказанное под этим спойлером, является субъективным мнением комментатора
  1. Яркий пример нарушения этических норм критики. Не стоит унижать автора, особенно автора того труда, по которому Вы сами обучались программированию на языке JASS. Может, Вы и не хотели, чтобы так читалось, но введение будто бы написано немножко хамоватым тоном. Следом идёт ровно такое же потенциальное унижение читателей. Если изложить суть введения вкратце, то у меня получается следующее: "Как-то учился JASS по статье одного автора, подсознательно понимая, что он где-то неправ, но теперь я с уверенностью и авторитетом заявляю: автор - подлинное недалёкое нубьё. А те, кто даже в его изложении ничего не понял - нубьё похуже автора, недостойное читать мои солнцеликие изречения, проливающие свет истины на ситуацию."
    У меня нет претензий к автору, однако, слушается это (как по мне) несколько резко.
  2. Подпишусь под каждым словом. С другой стороны, если нужно быстро "набросать" какую-то рутинную простенькую неарифметическую функцию для того, чтобы посмотреть, как движок будет себя с ней вести (скажем, перемещение всех войск в области в другую точку), редактор триггеров (GUI) для этого отлично подойдёт.
  3. У меня здесь не критика, а вопрос. Исходя из приведённой трактовки локальных переменных ("Память под них выделяется в момент входа в функцию, и освобождается при выходе из нее"), странно как-то получается. Если всё действительно так, как Вы пишете, зачем обнулять локальные переменные в конце функции, ведь при выходе из функции память всё равно будет освобождена? Насчёт использования таймеров - подписываюсь - они намного гибче GUI-шных Wait и PolledWait. Да и не в пример быстрее. И поддерживают значения меньше сотой доли секунды.
    Думаю, что vJASS (как и ZINC, как и JESP, как и cJASS) - отдельная тема для рассмотрения, которая достойна отдельной статьи. Здесь под прицелом статья о классическом JASS, том самом JASS "для самых маленьких" - без диалектов, без модификаций, NewGen-паков и прочих наворотов. Поэтому "уродливые udg_" (расшифровка, надо полагать - Uniquely Defined Global - уникально объявленная глобальная переменная) - это требование синтаксиса, а не порок.
    А так, в общем и целом... только вопрос выше и ремарка, упрощающая восприятие всего этого раздела: если хранить используемые объекты в локальных переменных, ими становится гораздо легче управлять - создавать, изменять, уничтожать и вытирать ссылки на них.
  4. Мне и самому было интересно, откуда у автора ненормальная зависимость от Wait-ов. Новичкам стоит сразу рассказать, что использовать Wait - плохо, что в арсенале JASS есть такая вещь, как таймеры, что с её помощью можно решить всё гораздо эффективнее. Жирный плюс этому разделу.
  5. Про конструкцию с использованием elseif очень верное замечание. Эта конструкция является одной из очень удобных вещей в JASS. Сюда же стоит отнести и множественный exitwhen с несколькими точками выхода из цикла. Это позволяет проворачивать в WarCraft III более интересные алгоритмы. Официант, ещё одну печеньку автору рецензии.
    Поправьте ошибку, пожалуйста: не bollean, а boolean.
  6. Меня огорчило наличие BJ в примере автора рассматриваемой статьи без разъяснения того, что такое Native и что такое BJ. Замечание существенное.
    Кажется, рецензию на статью Jon'a (это ведь нынешний J?) я написал немного раньше, о её недостатках можно узнать там же, но ссылка сделана правильно. Плюс.
  7. О-о-о, на таких "извлечениях" файла war3map.j из карты какое-то время назад учился программировать и я сам. Крайне интересное чтиво, особенно для тех, кто мало понимает, что изначально "сидит" в карте. Ещё меньше людей знают то, что этот код (та самая заветная функция init или main, каждый зовёт её по-своему) можно изменить только в самом war3map.j при необходимости (и то делать это советую осторожно - неправильная редакция сломает всю карту). Спасибо за ностальгию по моим собственным первым шагам картодела.
  8. DDS, стековая организация, игровой кэш (до 1.24), хеш-таблицы (после 1.24), сортировка массивов... Способов вагон и маленькая тележка. Перебор массива неэффективен, да, но зато это самое простое и понятное для новичка средство поиска нужного объекта. Уже позже ему откроется мир, полный удивительного, но пока что ему просто интересно, как сделать это попроще. Хеш-таблица - тоже вариант объяснения, но требуется как минимум понимание принципа работы двумерных массивов.
  9. Полностью согласен. Да и кроме того, событие триггера, вроде бы, менее поддаётся антиутечному контролю, чем таймер.
  10. Интересная идея с векторами. Она точно будет работать быстрее, чем формула с вызовом Cos и Sin, видел такое решение ещё очень давно в некоторых способностях, сделанных нашими вьетнамскми коллегами-скриптерами. Они утверждали, что есть преимущество в быстродействии. Незначительное, но есть.
  11. Думаю, лучше, чем в уже упомянутой статье Jon'a, нельзя сказать об очистке памяти и сборке мусора.
  12. С 11 по 15 главы чтение уже не имеет смысла, так как Return Bug был убран в обновлении 1.24. Но всё, что там написано - справедливо.
Итог - плюс. Несколько рекомендаций - уделите немного внимания грамматике, а также сглаживанию углов во вступлении. Получится замечательная, образцовая критика.
Всегда ваш,
Singularity, 16.06.2017
Goreblaze #41 - 3 дня назад (отредактировано ) 0
Год назад я уже читал эту статью. Её понимание приходит только тогда, когда приходится самому глубоко вникать в то, что на самом деле происходит во время исполнения скрипта на языке Jass.
То есть, когда нужно максимально оптимизировать то, что итак неплохо работало.
Поэтому думаю, что нелестно о ней отзывающиеся не испытывали мучительных сомнений, доходя до подобных выводов самостоятельно. Лично же я слагал структуры русского мата, не понимая, что же лучше использовать взамен устаревших или медленных систем.
Сомнения у меня ещё остались, но некоторые свежие идеи начали обретать более чёткую форму.
В пример одной такой бредовой идеи:
globals
    handle array H
endglobals

...

function NoMoreLongCJ takes nothing returns nothing
	set H[0]=ConvertUnitState(0)
	set H[1]=ConvertUnitState(1)
	set H[2]=ConvertUnitState(2)
	...
endfunction

...

function ManaLeak takes nothing returns nothing
	call SetUnitState(GetTriggerUnit(),H[2],0)
endfunction
Это незначительно сокращает объём кода, который пишется в war3map.j, поэтому я до сих пор сомневаюсь в полезности замены каждой константы.
Как мне известно, замена UNIT_STATE_LIFE на ConvertUnitState(0) исключает обращение к архивам игры, но скорость обращения к переменной в массиве вместо вызова функции конвертации будет не намного выше.
Думаю, что если заменить ConvertUnitState(0) на H[0], то сократится разве что несколько байтиков в файле скрипта (ну, и вызов функции, пусть и нативной - это всё-таки вызов функции). Поэтому и возникают сомнения в необходимости перенесения 100+ строк из Common.j в основной код карты.
А вот статьи, которая подтвердила бы мои догадки я не могу найти уже долгое время.