Четверг, 21.11.2024, 20:42


Главная
Регистрация
Вход
Welcome to Home Приветствую Вас Гость | RSS  
Меню сайта

Категории раздела
Мои статьи [25]

Мини-чат

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Главная » Статьи » Мои статьи

Осваиваем 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. Я думаю теперь Вы сможете писать более качественные скрипты для Ваших карт, а процесс написания станет куда более простым и понятным. Еще раз рекомендую внимательно изучить прилагающуюся карту пример, без нее вы вряд ли усвоите вышеизложенный материал.

Категория: Мои статьи | Добавил: Enemy1PK (02.04.2017)
Просмотров: 422 | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Вход на сайт

Поиск

Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • База знаний uCoz

  • Copyright MyCorp © 2024uCoz