XGM Forum
Сайт - Статьи - Проекты - Ресурсы - Блоги

Форуме в режиме ТОЛЬКО ЧТЕНИЕ. Вы можете задать вопросы в Q/A на сайте, либо создать свой проект или ресурс.
Вернуться   XGM Forum > Warcraft> Академия: форум для вопросов> Желтая пресса: обучающие статьи
Ник
Пароль
Войти через VK в один клик
Сайт использует только имя.

 
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Осваиваем jass
0. Вступление
Этот цикл статей посвящен изучению языка jass (текстовых триггеров). Хотя jass уже написано достаточное число статей, но многие картостроители все равно не хотят его изучать. И я их вполне понимаю – для тех, кто не занимается программированием в тех статьях, которые имеются, разобраться довольно сложно. Не хватает некоторых базовых знаний, не понятно, с чего начинать и т.д.
Любой учитель знает, что излагать предмет надо поэтапно и в определенной последовательности. Я выбрал последовательность в том порядке, в котором сам когда-то начал осваивать язык jass. Так что триггерщики, которые до сих пор не решаются приступить к изучению jass, смогут проделать тот же путь.
Эти статьи я написал для одного своего друга триггерщика. И друг вполне оправдал мои ожидания - не прошло и месяца, как он стал вполне квалифицированным jass-ером.

Для удобства, я буду обращаться в статьях к читающему – «Читатель». Надеюсь, что читатель уже неплохо овладел триггерами, знает, что такое переменные, массивы и циклы.

Содержание:
0. Вступление
1. Что есть jass и для чего он нужен
2. Локальные переменные
3. Применение локальных переменных
4. Условия, циклы в jass
5. Функции на jass
6. Устройство триггера с точки зрения jass
7. Динамическое создание триггера
8. События с малым периодом
9. Полярные координаты (ликбез)
10. Оптимизация: утечки памяти
11. RETURN BUG (RB)
12. Тип Handle
13. Система Super Custom Value (SCV) или RB+cache
14. Да здравствует SCV!
15. Послесловие

Отредактировано Sergey, 17.06.2005 в 18:00.
Старый 17.06.2005, 17:43
Sergey
Старейший
offline
Опыт: 44,363
Активность:
1. Что есть jass и для чего он нужен
Итак, Читатель, мы приступаем к освоению jass. Прежде всего, я хочу, чтобы ты понимал, что jass не является чем-то сверхъестественным и необычным. Многое, из того, что нам предстоит изучить уже будет знакомо по триггерам. И неудивительно – ведь каждое триггерное действие имеет свой аналог на jass.

Дело в том, что blizzard создали свой миниязык программирования jass. Они хотеи упростить работу по программированию сценариев и их правил. Но даже такой упрощенный язык слишком сложен для многих картостроителей. Поэтому был создан редактор Trigger editor – где вместо того, чтобы набирать команды вручную, можно создавать триггеры путем выбора команд из списка. В редакторе мы работаем с триггерами, но когда игра запускает определенный сценарий, она читает не триггеры, а КОД, который создается путем перевода всех триггеров игры на язык jass.

По сути триггеры – это надстройка для языка jass упрощающая ввод команд. Но при любом упрощении происходит потеря функциональности. Т.е. возможности программирования игры триггерами в чистом виде заметно меньше, чем при программировании при помощи jass.

Итак, jass дает картостроителю новые возможности. Но стоят ли они того, чтобы их изучать? Это уже каждый картостроитель решает для себя. Jass может помочь в следующих случаях:
1. Упростить создание сложных триггеров, которые позволят полностью или почти полностью поменять правила оригинальной игры.
2. Создавать триггерные заклинания, у которых нет аналогов в оригинальной игре.
3. Оптимизировать карту, сделать, чтобы сложные триггеры не тормозили игру.
4. Вставить на карту некоторые команды на jass, которые не имеют аналогов для обычных триггеров.
5. Создавать собственные AI, более гибкие, чем в редакторе AI.

У некоторых бытует мнение, что на jass в игре можно сделать все. Это конечно же не так. И более того, я не рекомендую писать все триггеры сценария исключительно на jass. Для многих задач редактор триггеров подойдет больше – ведь это действительно очень удобная штука.
Старый 17.06.2005, 17:44
Sergey
Старейший
offline
Опыт: 44,363
Активность:
2. Локальные переменные
Локальные переменные – это воистину первый шаг в освоении jass. И этот шаг очень важный. Умея работать с локальными переменными вы уже сможете заметно упростить себе решение многих триггерных задач.

Читатель, прежде чем начнем изучение, я советую тебе скачать файл sample locs.w3x, который приложен к данной статье. Скачай, затем открой в редакторе и запусти на исполнение. Суть примера в том, что если стрельнуть заклинанием файербол, над головой жертвы появляется спецэффект «восклицательный знак», который через несколько секунд исчезает. Действие сделано на триггерах с применение локальных переменных.

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

А можно ли сделать этот эффект при помощи обычных триггеров? Можно конечно. Для одного юнита, это сделать относительно просто. Например, запустил юнит файербол, помещаем цель заклинания в переменную u типа юнит. Ждем время, пока файербол долетит (которое равно расстояние до цели делить на скорость полета). Затем создаем спецэффект на юните u, который записываем в переменную se типа спецэффект. Через несколько секунд уничтожаем спецэффект se.

Все просто, но... Насколько такой триггер будет универсален? Вот предположим ситуация, несколько юнитов имеют заклинание файербол, и друг за другом применяют его, так что два триггерных действия (создание спецэффекта) выполняются одновременно. Тогда у нас произойдет триггерный конфликт. Ведь в одни и те же переменные u и se будут писаться параметры для разных файерболов. В итоге у нас могут появиться 2 спецэффекта над одним и тем же юнитом и один из этих спецэффектов останется навсегда. Все это произойдет из-за того, что без переменных вообще обойтись нельзя, а для разных запусков нельзя использовать одни и те же переменные.
Есть способ исправить эту проблему: для каждого запуска файербола помещать значения не в переменные, а в массивы переменных. Для каждого запуска сохранять значения в свои ячейки массива, каким-то образом отслеживать, что пришел момент создать спецэффект для такого-то юнита из массива или удалить такой-то спецэффект из другого массива. Это способ долгий и нудный и не универсальный. И в итоге, простая по сути задача – становится очень тяжелой.

В то же время, в том примере sample locs, эта задача решена очень легко. Чтобы узнать как – рассмотрим, что же такое локальные переменные.

Читатель, ты уже знаком с переменными в редакторе. Ты умеешь создавать их при помощи редактора переменных. Так вот, все переменные, которые создаются в редакторе переменных, будем отныне называть глобальными переменными. Глобальные переменные можно использовать во всех триггерах игры. Оказывается, что кроме глобальных переменных, существует еще один вид – локальные переменные. Локальные переменные – это переменные, которые работают только внутри определенного триггера. Локальные переменные создаются при запуске триггера и уничтожаются после того, как выполнение триггера закончено. Если триггер запущен на исполнение несколько раз, то при каждом запуске создается свой набор локальных переменных, никак не связанный с другими наборами.

В каждом триггере можно определить набор локальных переменных. Для этого нужно применить команду из jass. В редакторе есть возможность вставить в триггер команду из jass – так называемый Custom Script (в дальнейшем cs). Читатель, давай посмотрим, как это сделано в примере – см триггер Cast fireball method 1.
В самом начале триггера идут команды
Код:
cs:   local unit u
cs:   local effect e

Это объявление того, что при запуске этого триггера будут созданы 2 локальные переменные: u типа юнит и e типа спецэффект. Создавать локальные переменные можно всех тех же типов, что и глобальные и в любом количестве. Можно даже создавать массивы локальных переменных.
Далее идет обычная триггерная команда
Код:
Set unit = (Target unit of ability being cast)

В глобальную переменной unit помещается юнит - цель нашего заклинания.

Дальше идет еще одна jass команда:
Код:
cs:   set u = udg_unit


Что это значит? Дело в том, что в jass есть такое правило: перед глобальными переменными ставится приставка udg. udg_unit - это наша глобальная переменная unit. Что касается локальных переменных, то их имена пишутся непосредственно без всяких приставок. Что же означает наша команда, записанная выше? u – локальная переменная, udg_unit – глобальная, set – это оператор присвоения.
Ответ таков: мы в локальную переменную u поместили то, что было записано в глобальную переменную unit (а в ней у нас был юнит-цель заклинания).
Оставим пока вопрос зачем, просто посмотрим, что будет дальше. А дальше идет команда
Код:
Wait ((Distance between (Position of (Casting unit)) and (Position of (Target unit of ability being cast))) / 1000.00) game-time seconds
.

- ждать время равное расстояние между кастонувшим юнитом и юнитом целью делить на 1000. 1000 – это скорость снаряда файербола. Т.е. ждать время полета. Далее идет команда на jass:
Код:
cs:   set udg_unit = u

Догадаетесь, что она означает? :). В глобальную переменную unit помещаем то, что записано в локальной переменной.
Далее проделана аналогичная схема с созданием спецэффекта. Созданный спецэффект помещается в глобальную переменную se, затем в локальную переменную e помещается что, что записано в se. Затем ждем период 3 игровых секунды и делаем обратное: записываем в se то, что записано в e. И уничтожаем спецэффект.
Итого, весь триггер напоминает тот, который мы создали бы, чтобы реализовать появление спецэффекта для одного юнита. Разница лишь в нескольких jass вставках. Но без этих вставок триггер НЕ УНИВЕРСАЛЕН, а со вставками – УНИВЕРСАЛЕН. Почему?

Давай вспомним про локальные переменные, которые мы создали. При каждом запуске триггера «Cast fireball method 1» будет создаваться набор из двух локальных переменных u и e. Причем для каждого запуска свой набор – не зависящий от других наборов. Запустим триггер 1000 раз – будет создано 1000 локальных переменных u типа юнит и e типа спецэффект.
В локальную переменную u мы поместили юнит-цель заклинания (сначада в глобальную unit, затем в локальную u). Через несколько секунд мы не можем гарантировать, что значение глобальной переменной unit не изменится. Ведь другой юнит может запустить файербол по другому юниту и значение unit будет перезаписано. НО ЗНАЧЕНИЕ ЛОКАЛЬНОЙ ПЕРЕМЕННОЙ ДЛЯ ДАННОГО ЗАПУСКА НЕ ИЗМЕНИТСЯ. Ведь при следующем запуске триггер будет создан новый набор локальных переменных, а старые наборы не будут затронуты.

Итак, при помощи локальных переменных мы можем сохранить юнит цель для каждого запуска заклинания файербол. А через некоторое время, равное времени полета файербола, мы должны создать на юните спецэффект. Мы делаем нужную паузу и затем помещаем в глобальную переменную unit ссылку на юнит из переменной u. И создаем спецэффект над юнитом из переменной unit.
Таким приемом мы можем гарантировать, что сколько бы файерболов не было выпущено, спецэффект будет создаваться над юнитом-целью и только над ним. Никаких сбоев не будет. Точно такой же прием с удалением спецэффекта через 3 секунды после создания. Все эти три секунды ссылка на спецэффект будет храниться в локальной переменной e. А затем мы перебросим ее значение в глобальную переменную se и удалим спецэффект.

Итак, как показывает пример, локальные переменные очень удобны для реализации УНИВЕРСАЛЬНЫХ отсроченных действий. Это свойство локальных переменных делает их незаменимыми при создании триггерных спелов. Причем добавить локальные переменные в триггер, как вы убедились, совсем не сложно.

Этот пример я специально сделал наиболее простым. Все jass команды, которые в нем используются – это создать локальную переменную и присвоить значение переменной. Мы все равно используем глобальные переменные unit и se. Мы используем их как посредники – для переброски в них значений из локальных переменных и наоборот. Вообще говоря в нашем примере можно обойтись и без глобальных переменных – одними локальными. Но проблема в том, что использование локальных переменных не предусмотрено в редакторе. Так что чтобы использовать эти переменные необходимо записывать команды на jass.

Теперь, Читатель, используй команду Правка->Конвертировать в текст, чтобы перевести весь триггер в Cast fireball method 1 в jass. Не вдаваясь пока в устройство триггеров, обрати внимание на фрагмент, в который превратились наши триггерные действия. Каждая строчка триггерных команд превратилась в какую-то строчку jass-кода. Что касается строчек из custom script, они не изменились, т.к. они уже были написаны на jass. Теперь посмотри на триггер Cast fireball method 2. Похоже? Да, это почти то же самое, только во втором примере я уже не использую глобальные переменные.

Обрати внимание на то, что в jass-код можно вставлять комментарии
// любой текст
Если нужно отключить какую-то строчку кода, не обязательно ее стирать. Можно превратить ее в комментарий. Второй пример на самом деле не работает, т.к. я превратил в комментарий строчку, которая отвечает за события триггера.
Еще обрати внимание, что названия спецэффектов в jass-коде записываются немного иначе - вместо одного \, записывается два:
"Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"

Любой триггер можно конвертировать в jass-код, но обратную процедуру выполнить невозможно. Хотя есть такое действие – отменить последнюю команду редактора: ctrl-z. Если что, можно посмотреть, как выглядит код триггера, а затем вернуть триггер обратно.

Наконец, посмотри на третий триггер Cast fireball method 3. Он делает то же самое, что и первые два. Но этот третий пример является как бы смесью первых двух. Как и во втором примере, здесь не используются глобальные переменные, но все команды, в которых используются локальные – пришлось переписать в виде cs.

Итак, первый шаг в мире jass уже сделан.
Прикрепленные файлы
Тип файла: w3x sample locs.w3x (21.5 Кбайт, 282 просмотров )

Отредактировано Sergey, 18.06.2005 в 10:24.
Старый 17.06.2005, 17:49
Sergey
Старейший
offline
Опыт: 44,363
Активность:
3. Применение локальных переменных
Закрепим то, что узнали. Чтобы создать локальную переменную, нужно вставить команду:
Код:
local <тип переменной>  <имя переменной>

Типы переменных – это строки. В некоторых случаях они совпадают с названием переменных в Редакторе переменных. Например unit, integer, real, string. Но иногда не совпадают как в случае с effect, который означает спецэффект.
Если вы не знаете, как называется такой-то тип переменных в jass, как это узнать? Можно использовать такой способ: создаете глобальную переменную нужного типа. Затем используете команду редактора Файл->Экспорт кода – сохраняете код сценария в файл. Затем смотрите содержание этого файла при помощи блокнота. Находите пункт
//* Global Variables
- он будет в самом верху.
Там перечислены все глобальные переменные в сценарии и рядом записан их тип.
Можно еще использовать такой способ: сделать какую-то ошибку в jass-коде, после чего игра отключить и подключить триггер с ошибкой (disable/enable). Игра выдаст ошибку и в окне с ошибкой будет виден код сценария. Так что там же можно найти раздел Globals.

Команды по созданию локальных переменных всегда должны располагаться в самом верху триггерных действий (за исключением только комментариев), иначе будет выдана ошибка.

Можно создавать массивы локальных переменных при помощи команды
Код:
local <тип переменной>  array <имя переменной>

Например, массив юнитов:
Код:
local unit  array u

Обращение к элементам этого массива такое же как в триггерах:
Код:
Set u[1] = …

- записываем в первый элемент массива такое-то значение. И т.п.

При создании переменных, можно сразу же записывать в них какое-то значение.
Код:
local integer i =1

создаст переменную i и присвоит ей значение 1.

Локальные переменные очень хорошо решают проблему хранения данных при отсроченных действиях, как мы разобрали в прошлом примере. Существует способ решения задач при помощи локальных переменных: способ движения от частного к общему. Алгоритм такой:
1. Создай обычный не универсальный триггер, который решает задачу для одного запуска.
2. Добавь локальные переменные и запиши в них все, что должно сохраниться во время паузы.
3. Помести данные обратно в глобальные переменные и делай нужные действия.
Локальные переменные выступают как хранилища на время пауз в триггере, глобальные переменные нужны для каких-то мгновенных действий. См. пример sample locs. Мы не можем угадать, что будет храниться в переменной unit в какой-то момент времени. Ее значение будет постоянно меняться в зависимости от игровых событий. Мы не можем помещать в эту переменную данные ДЛЯ СОХРАНЕНИЯ, но можем использовать ее для мгновенных действий. В нашем случае работает только одно триггерное заклинание, но мы могли бы использовать ту же самую переменную unit для сотни точно таких же заклинаний.

Если хотите обойтись одними локальными переменными, то нужно либо весь триггер переводить в jass, либо переводить в cs те строки, где имеются ссылки на эти переменные.

Чтобы посмотреть, как выглядит та или иная команда в jass, можно использовать такой прием: создаем новый пустой триггер, создаем внутри него нужную команду и переводим триггер в текст. Затем этот текст можно будет вставить в cs один в один. Так что нет необходимости запоминать все команды на jass.

Итак, Читатель, ты уже достаточно узнал, чтобы создать свой собственный jass код. Правда, есть определенные тонкости который тебе нужно узнать. Во-первых, если в jass допущена ошибка, то при попытке сохранить карту или запустить ее будут выданы ошибки. При этом триггер тут же отключится и ты не сможешь его включить, пока не исправишь ошибки.
А теперь представь, что на данный триггер ссылается еще один. Что произойдет? Триггер отключился из-за ошибки и все триггерные команды, которые ссылались на него тоже отключатся.
Еще одна ситуация. Допустим, имеется триггер на jass или с cs, в котором идет ссылка на глобальную переменную unit. Затем, мы берем и меняем название глобальной переменной на unit2. Во всех нормальных триггерных действиях название старой переменной на новую произойдет автоматически. Но не в jass-коде! Там все названия останутся старыми. Т.е. нам нужно вручную менять везде udg_unit на udg_unit2, иначе будет выдана ошибка.

Поэтому при создании jass кода надо всегда соблюдать осторожность. Тем более, что ошибки в jass не всегда бывают безобидными. Некоторые из них приводят к тому, что редактор вылетает без сохранения карты. Так что когда работаете с jass-кодом – ЧАЩЕ СОХРАНЯЙТЕСЬ!

Итак, Читатель, если есть время и желание, поработай над реализацией какой-нибудь из задач на jass. К примеру:
1. Заклинание разговор: когда применяешь его на юнит, на две секунды над ним появляется фраза плавающего текста «Привет».
2. Заклинание banish (триггерный аналог): на 20 секунд юниту-цели дается способность ethereal (дух).

Отредактировано Sergey, 18.06.2005 в 10:25.
Старый 17.06.2005, 17:50
Sergey
Старейший
offline
Опыт: 44,363
Активность:
4. Условия, циклы в jass
Рассмотрим такой пример: имеется фрагмент триггерного действия
Код:
For each (Integer i) from 1 to 10, do (Actions)
    Цикл
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            Условие
                i равно 1
            Действие
                Set s = (s + 2)
            Иначе
                Set s = (s + 1)
.

Цикл по i от 1 до 10 и условие внутри цикла. Во что превратится это действие, когда мы переведем его в jass? Создай в редакторе такой триггер и проверь.
Действие превратится в следующий фрагмент
Код:
set udg_i = 1
    loop
        exitwhen udg_i > 10
        if ( Trig_____________________________________001_Func001Func001C() ) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif
        set udg_i = udg_i + 1
    endloop
.

Думаю, что пока не очень понятно, что здесь за что отвечает. Начнем с оператора if. Очевидно, он превратился в строки:
Код:
if ( Trig_____________________________________001_Func001Func001C() ) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif
.

Все что ниже первой строки - понятно, но почему вместо нормального условия в первой строке стоит "( Trig_____________________________________001_Func001Func001C() )"? Дело в том, что редактор триггеров довольно глупо переводит условия из триггеров или триггерных действий. После такого перевода часто приходится исправлять и оптимизировать код. В нашем случае, редактор создал специальную функцию с именем Trig_____________________________________001_Func001Func001C() для того, чтобы проверить нужное нам условие, что i=1. Эту функцию ты можешь увидеть вверху триггера:
Код:
function Trig_____________________________________001_Func001Func001C takes nothing returns boolean
    if ( not ( udg_i == 1 ) ) then
        return false
    endif
    return true
endfunction
.

Пока не будем вдаваться в то, что это за функция и что она делает. Самое главное - эта функция возвращает значение true (истина) если i=1, или ложь, если i не равно 1. Возникает вопрос: что же, при каждом применении оператора if нам придется создавать какую-то функцию? Ничего подобного - можно обойтись и без нее! Стираем эту ненужную функцию, а в строчку вносим изменения:
Код:
if (udg_i == 1) then

И все. остальное оставляем неизменным. У нас получится фрагмент кода:
Код:
set udg_i = 1
    loop
        exitwhen udg_i > 10
        if (udg_i == 1) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif
        set udg_i = udg_i + 1
    endloop

Теперь ты знаешь, что такое оптимизация :).
Но остается открытым вопрос: что же такое мы вставили в условие оператора if. Это просто проверка, равна ли переменная i единице. В jass есть специальные значки для проверки условий равенства или неравенства:
== (два знака равно) переводится как равно
!= переводится как не равно
< меньше
> больше
<= меньше или равно
>= больше или равно.

Т.е. если мы хотим записать условие i[.b] не равно 10, то оно будет выглядеть
[b]i!=10

Теперь ты можешь сам разобраться с условным оператором в jass:
Код:
if (udg_i == 1) then
            set udg_s = ( udg_s + 2 )
        else
            set udg_s = ( udg_s + 1 )
        endif

Переводится как "Если i=1 то делать то-то иначе делать то-то".

Хорошо, с условным оператором разобрались. А как насчет циклов?
К оператору цикла относятся следующие строки:
Код:
set udg_i = 1
    loop
        exitwhen udg_i > 10
        ...
        set udg_i = udg_i + 1
    endloop

... - это могут быть любые действия, которые происходят внутри цикла
Итак, перед началом цикла переменной i присваивается значение 1 - это начальное значение для нашего цикла.
loop - ключевое слово, означающее начало цикла
endloop - конец цикла
Т.е. действия между loop и endloop будут повторяться. Но сколько раз они должны повторяться? Вообще говоря 10. Но в jass все циклы устроены более универсально, чем в триггерах. Тут циклы повторяются не ОПРЕДЕЛЕННОЕ ЧИСЛО РАЗ, а ДО ТЕХ ПОР, ПОКА НЕ БУДЕТ ВЫПОЛНЕНО ТАКОЕ-ТО УСЛОВИЕ. За проверку этого условия отвечает строка:
exitwhen <УСЛОВИЕ> - переводится как выйти из цикла, когда выполнено УСЛОВИЕ.
exitwhen udg_i > 10 - переводится как выйти из цикла, когда переменная i станет больше 10.
Мы могли бы к примеру написать условие
exitwhen udg_i == 10- выйти из цикла, когда iстанет равно 10. Тогда в цикле будет выполнено на одно действие меньше.

Итак, вся наша структура
Код:
set udg_i = 1
    loop
        exitwhen udg_i > 10
        ...
        set udg_i = udg_i + 1
    endloop

имеет следующий смысл. Переменная i приравнивается к 1. На каждом витке цикла проверяется, не стала ли переменная i больше 10. Если не стала, производится какое-то действие и затем переменная i увеличивается на 1. И так до тех пор, пока не будет выполнено условие окончания цикла.

Итоги:
1. Условный оператор при переводе триггера в jass не очень удобен, т.к. его приходится оптимизировать.
2. Оператор цикла в jass более универсальный, т.к. действие производится не фиксированное число раз, а до тех пор, пока не выполнится условие. Кроме того, переменную цикла в триггерах можно увеличивать только на 1, а в jass - ее можно изменять произвольным образом.

Читатель, про циклы можно сказать еще следующее. Если ты попробуешь перевести в текст действие
Код:
For each (Integer A) from 1 to 10, do (Actions)
   ...

(т.е. воспользуешься одним из циклов с Integer A или Integer B, то на выходе получишь:
Код:
set bj_forLoopAIndex = 1
    set bj_forLoopAIndexEnd = 10
    loop
        exitwhen bj_forLoopAIndex > bj_forLoopAIndexEnd
        ...
        set bj_forLoopAIndex = bj_forLoopAIndex + 1
    endloop

Что такое bj_forLoopAIndex и bj_forLoopAIndexEnd? Оказывается это специальные глобальные переменные, которые используются для проверки условий окончания такого вида цикла. Обычные переменные типа integer. Проверь сам - действие в цикле будет выполнено ровно 10 раз.
Отсюда вывод: хочешь ты того или нет, но редактор всегда вставляет в твой сценарий 4 специальных глобальных переменных:
set bj_forLoopAIndex
set bj_forLoopAIndexEnd
set bj_forLoopBIndex
set bj_forLoopBIndexEnd

В принципе они предназначены для циклов, а на самом деле при помощи jass в них можно записывать все что угодно.

Отредактировано Sergey, 18.06.2005 в 10:38.
Старый 17.06.2005, 17:53
Sergey
Старейший
offline
Опыт: 44,363
Активность:
5. Функции в jass
Переходим к изучению функций в jass.

Что такое функция? Функция, это фрагмент кода, в который можно передавать параметры, который может возвращать один параметр и производить определенные действия. Не очень понятно, но вспомни в предыдущем сообщении, как для определения выполняется ли условие i=1 создавалась специальная функция.

Кроме того, ты наверное замечал, что при переводе триггера в jass, в итоге создаются несколько функций. Обычно, функция для действий триггера, для условия, а также функция для присоединения события. Но об этом я еще напишу позже.

Самое главное, что функции можно использовать, чтобы сделать код более удобным и коротким. Синтаксис функции выглядит следующим образом:
Код:
function <ИМЯ ФУНКЦИИ> takes <ПЕРЕЧЕНЬ ПАРАМЕТРОВ, которые функция БЕРЕТ> returns <тип параметра, который функция ВОЗВРАЩАЕТ>
...
<ПЕРЕЧЕНЬ ДЕЙСТВИЙ ФУНКЦИИ>
...
endfunction
.

Все это может быть выглядит страшно, но мы разберем на примерах. Самый простой вид функций - та которая ничего не берет и ничего не возвращает. К примеру, создадим функцию с именем property, которая при каждом ее запуске дает игроку1 1000 золотых.

Такая функция будет выглядеть следующим образом:
Код:
function propery takes nothing returns nothing
    call AdjustPlayerStateBJ( 1000, Player(0), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Несколько замечаний. Во-первых действие добавления денег взято путем перевода такого действия из триггеров в jass. Я сказал, что деньги даются игроку1, а в коде написано Player(0) - это потому что в jass игроки начинают нумероваться с нуля. Т.е. 0 - номер первого игрока, 1 - второго и т.д. PLAYER_STATE_RESOURCE_GOLD - кодовое слово, которое означает, что прибавляется именно золото, а не скажем лес.
Вместо перечня параметров, которые берутся и вместо типа параметра, который возвращается функцией стоит слово nothing - на английском означает ничего. Т.е. функция ничего не берет и ничего не возвращает. Она просто делает действие - добавляет деньги игроку1.

Для того, чтобы вызвать эту функцию на исполнение, достаточно написать команду
Код:
call property()

() - это скобки, в которых указывается список параметров для функции, но в нашем случае он пуст.
Ты можешь вставить эту команду в триггеры (в виде custom script) или в jass. Когда триггер запущен и очередь дойдет до этой команды, будет запущена функция и выполнены все ее действия. И при каждом запуске игрок1 будет получать 1000 золота.
Конечно, функция состоящая из одного действия не имеет смысла, но действий может быть и больше. Если в триггерах или коде имеются часто повторяющиеся фрагменты, то имеет смысл создать функцию и заменять фрагмент на вызов функции.

Пока что я не рассказал, а куда нужно вставлять текст функции. Это нельзя делать куда попало. Нельзя вставлять функцию внутрь другой функции. Функцию можно вставить в пустое пространство между другими функциями в триггере или в специально отведенное место (второй вариант предпочтительнее, позже расскажу почему).

Путь к этому специальному месту: открой редактор триггеров. Слева в окне найди дерево триггеров (список папок и самих триггеров). Самая высокая позиция этого дерева - иконка карты. Щелкни на нее. Справа откроется окно "Нестандартный код". Вот в него и нужно вставлять функции.

Вставь в это окно текст функции property. Затем сделай триггер с событием Map Initialization и действием: cs call property()
Запусти сценарий и проверь, что функция действительно работает.

Итак, первая и самая простая функция сделана. Но функции очень удобны тем, что они могут принимать определенные параметры, которые влияют на действие функции. К примеру, модернизируем функцию property, чтобы она давала 1000 золота не первому игроку, а игроку, которого мы укажем в параметре. Т.е. в функцию мы будем передавать параметр номер игрока (типа integer). В итоге, функция будет выглядеть так
Код:
function propery takes integer n returns nothing
    call AdjustPlayerStateBJ( 1000, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Смотри, в первой строке вместо takes nothing теперь стоит takes integer n. Это означает, что функция имеет 1 параметр типа integer. Чтобы запустить функцию с параметром, нужно будет вставить строку
Код:
call property(<какое-то число>)

И это самое число будет передано в функцию при запуске и записано в локальную переменную n. Вот такой фокус. Мы можем вводить номер игрока, которому мы хотим дать 1000 золота и этот номер будет передан в функцию. А для того, чтобы дать 1000 золота игроку с этим номером, мы переделали вторю строку:
Код:
call AdjustPlayerStateBJ( 1000, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )

Т.е. дать 1000 золоту игроку с индексом n-1. Я нарочно поставил не n, а n-1, т.к. мы привыкли нумеровать игроков с 1, а в jass нумерация идет с 0.
Итак, если у нас имеется указанная функция, то чтобы дать игроку1 1000 золота, мы можем набрать команду
Код:
call property(1)

Еще несколько слов о параметрах. Во-первых, параметров может быть любое число и они могут быть любого типа. Если параметров более одного, то они идут перечислением через запятую. Например, вот модернизированная функция, в которую мы в качестве параметров передаем не только номер игрока, но и количество золота.
Код:
function propery takes integer n, integer gold returns nothing
    call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Но сколько параметров у функции, столько должно передаваться и при ее вызове. Т.е. для вызова нужно использовать строку
Код:
call property(1,1000)

Во-вторых, параметры, как я и говорил, передаются в локальные переменные. Но в любой функции могут быть и другие локальные переменные. Просто нужно объявить их в начале функции
Код:
function propery takes integer n, integer gold returns nothing
    local real r
    call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
endfunction

Ну и еще одно замечание. В большинстве языков программирования имеется разделение понятий процедура и функция. Процедура - фактически тоже самое что и функция, но она ничего не возвращает в качестве параметра. Все примеры, рассмотренные нами выше - брали или не брали параметры, но все равно ничего не возвращали. Т.е. грамотнее было бы назвать их процедурами.

И переходим к последнему - самому общему варианту, когда функция что-то возвращает. Раньше мы везде писали returns nothing, но если мы хотим, чтобы функция что-то вернула, нужно указать какой-нибудь тип. Скажем returns integer (возвратить параметр типа integer). Например, если мы хотим создать функцию, которая будет возвращать нам сумму чисел от 1 до n, где n - параметр, передаваемый в функцию. Функция выглядит так:
Код:
function summa takes integer n returns integer
local integer i
local integer s
    set i = 1
    set s = 0
    loop
        exitwhen i > n
        set s = s + i
        set i = i + 1
    endloop
return s
endfunction

Попытайся разобраться с действием этой функции. Внутри есть цикл, который нужен для нахождения суммы 1+2+...+n. Далее есть ключевое слово return - это одновременно команда прекратить выполнение функции, и способ заставить функцию вернуть значение.
return s означает, что функция вернет значение из переменной s, т.е. искомую сумму.

Как же обратиться к такой функции для ее вызова? Функции, возвращающие определенное значение, вызываются по-особому. Их можно использовать в каких-то выражениях или равенствах. К примеру, если у тебя есть глобальная переменная i, ты можешь вызвать функцию summa следующим образом:
Код:
cs set udg_i = summa(10)


И тогда РЕЗУЛЬТАТ ФУНКЦИИ, то что она возвращает - сумма, будет помещен в переменную i. Или можно сделать так:
Код:
cs set udg_i = summa(9+1)+2

Тогда в переменную i будет помещена сумма чисел от 1 до 10 плюс еще 2 единицы.

В этом и состоит смысл функций, с возвращаемым значением.
Примечания:
1. Тип данных, возвращаемых функцией должен совпадать с переменной, куда мы пишем это значение. integer-integer или real-real.
2. Вообще говоря, даже если функция возвращает значение, ее можно запустить методом
Код:
call <Функция> (<параметры>)

Но понятное дело, значение функции, которое оно возвращает, не будет никуда записано.
3. Команда return представляет определенный интерес сама по себе. Если ты проверишь, во что превратится команда skip remaining actions в jass - она превратится в return. Т.е. это команда, которая прерывает исполнение функции.
4. Допускается запуск одной функции из другой. К примеру, в функцию summa можно вставить строчку
Код:
call property(1,1000)

Но может возникнуть ошибка. Обращаться можно только к функции, которая записана выше данной (т.е. создана раньше). Т.е. если функция property будет ниже чем summa - то обращаться к property из summa нельзя.

Кстати, код в специальном месте для триггерных функций расположен ВЫШЕ чем код всех игровых триггеров. Поэтому к функциям записанным здесь можно обращаться из любого триггера.
5. Если внимательно приглядеться, то кроме функций, определенных пользователем (т.е. тобой) существуют еще и встроенные функции. К примеру, глянь команду
call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
Слово call тебе ни о чем не говорит? :). AdjustPlayerStateBJ - это встроенная функция с тремя параметрами. Список всех таких встроенных функций имеется в MPQ архивах. Так что получается у нас, что все триггеры устроены так, что одни функции ссылаются на другие, те на третьи и т.д. :).

На этом о функциях пока все.

В качестве примера по тому, что мы пока прошли, предлагаю изучить сценарий AR, в котором реализован достаточно простой огнемет на jass с использованием массивов, циклов и функций.
Прикрепленные файлы
Тип файла: w3m AR.w3m (18.5 Кбайт, 122 просмотров )

Отредактировано Sergey, 18.06.2005 в 10:47.
Старый 17.06.2005, 17:58
Sergey
Старейший
offline
Опыт: 44,363
Активность:
6. Устройство триггера с точки зрения jass
Теперь, когда ты уже изучил функции, остановимся подробнее на устройстве триггера. Я уже говорил, что при переводе триггера в текст, он преобразуется в несколько функций. Но что же такое триггер? Просто несколько jass функций? Не совсем так. Правильнее сказать триггер, все его события, условия, действия СОЗДАЮТСЯ при помощи jass-функций. Функции сами по себе, а триггер как бы объединяет их в единую структуру.

Давай рассмотрим этот процесс. Возьмем какой-нибудь триггер:
Код:
Триггер sample
События
    Every 5.00 seconds of game time
Условия
    ((Triggering unit) is Здание равно Да
    (Ability being cast) равно «Гальванизация»
Действия
    Wait 2.00 game-time seconds
    Play (no unit)'s stand animation

Вообще говоря, бессмысленный триггер, но важно не это. Во что он превратится, когда мы переведем его в jass? В следующий код:
Код:
function Trig_sample_Conditions takes nothing returns boolean
    if ( not ( IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) == true ) ) then
        return false
    endif
    if ( not ( GetSpellAbilityId() == 'AUan' ) ) then
        return false
    endif
    return true
endfunction

function Trig_sample_Actions takes nothing returns nothing
    call PolledWait( 2 )
    call SetUnitAnimation( null, "stand" )
endfunction

//===========================================================================
function InitTrig_sample takes nothing returns nothing
    set gg_trg_sample = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_sample, 5.00 )
    call TriggerAddCondition( gg_trg_sample, Condition( function Trig_sample_Conditions ) )
    call TriggerAddAction( gg_trg_sample, function Trig_sample_Actions )
endfunction
.

Первая функция - это то во что превратились условия триггера. Можешь проверить, что эта функция возвращает значение ИСТИНА, если условия исходного триггера будут выполняться, и ложь в противном случае. Вторая функция - действия триггера. Об этом и так можно догадаться, если глянуть на названия триггеров "Trig_sample" - т.е. триггер с названием sample "_Conditions" - условия, "_Actions" - действия.
Что касается третьей функции, то у нее свое особое назначение. Посмотри на название "InitTrig_sample". Приставка Init - напоминает слово Initialization, т.е. это что-то связанное с загрузкой карты. Эта функция запускается при инициализации карты. Ее назначение - собрать наш триггер воедино, объединив события, условия и действия. Сейчас посмотрим на строки:
Код:
set gg_trg_sample = CreateTrigger(  )

Что-то к чему-то прировняли... Вообще говоря gg_trg_sample - это разновидность глобальной переменной типа ТРИГГЕР, которая будет отвечать за хранение нашего триггера в памяти компьютера во время игры. Такие переменные автоматически создаются, когда ты создаешь в редакторе новый триггер.
В начале игры все такие переменные пустые. Действие set gg_trg_sample = CreateTrigger( ) приводит к тому, что в игре создается НОВЫЙ ТРИГГЕР - настоящий триггер, который до этого не существовал. В нем пока нет ни условий, ни событий, ни действий. При этом переменная gg_trg_sample будет ссылаться на этот триггер.
Далее идет строка
Код:
call TriggerRegisterTimerEventPeriodic( gg_trg_sample, 5.00 )

Эта команда приводит к тому, что к нашему пустому триггеру добавляется событие "Every 5.00 seconds of game time". Т.е. у нашего триггера уже есть событие. Для добавления любого события есть своя команда, которую ты можешь посмотреть при переводе триггера в текст. Исключение Map Initialization, но это отдельный разговор.
Далее
Код:
call TriggerAddCondition( gg_trg_sample, Condition( function Trig_sample_Conditions ) ) 
.

Это специальная команда, которая добавляет нашему триггеру условие. Заметь, в качестве аргументов мы указываем триггер и функцию, в которой записано условие триггера.
Далее
Код:
call TriggerAddAction( gg_trg_sample, function Trig_sample_Actions )

По аналогии с предыдущей командой - эта добавляет в триггер действия.

Вот когда при загрузке карты будет автоматически выполнена функция InitTrig_sample, только тогда объект триггер будет загружен в память компьютера и начнет работать. Вот такая механика.

Частенько структура триггера может быть сложнее, чем в описанном примере. Например, применение условного оператора (в триггерном виде), действий типа Pick every unit and do <action> приводит к тому, что в триггере создаются новые функции - события и действия. Бывает, что триггер так загромождается ими, что при переводе в jass трудно разобраться, что к чему. Иногда код можно оптимизировать и сделать более простым.

Еще такой интересный вопрос: если мы создаем триггеры при загрузке карты, не можем ли мы воспользоваться теми же командами, чтобы создавать триггер ПРЯМО ВО ВРЕМЯ игры. Ответ положительный - можем. И иногда это бывает очень удобно.

Читатель, если ты прочитал все что было до этого и разобрался в этом, то считай, что ты уже не начинающий, а средний jass-ер. Хотя еще не хватает практики собственной работы. И теперь мы сможем приступить к изучению продвинутого jass, который значительно расширяет возможности создания сценариев.

Отредактировано Sergey, 18.06.2005 в 10:53.
Старый 17.06.2005, 18:01
Sergey
Старейший
offline
Опыт: 44,363
Активность:
7. Динамическое создание триггера
Читатель, давай рассмотрим пример, который покажет некоторые возможности jass, недоступные в редакторе триггеров.

Вот например, известно, что определить, когда юнит получает повреждения можно лишь при событии Unit takes damage, которое можно создать лишь для конкретного юнита. Жуть как неудобно. А если возникает задача по ходу игры узнать, когда ударили юнит и сколько повреждений нанесли? Попробуем решить эту задачу исходя из того, что мы узнали о jass. Предлагаю скачать пример, который я выслал в данном сообщении и посмотреть его устройство.

Пример называется Magic shield. В нем реализовано заклинание волшебной брони для создания защиту, которая поглощает определенное число повреждений юнита, а затем исчезает. Причем юнитов с броней может быть сколько угодно. Как это реализовать?

Если мы можем динамически создавать новые триггеры прямо по ходу игры, почему бы не сделать так, что когда юнит применяет заклинание защиты, мы СОЗДАДИМ триггер, который будет отлавливать, повреждения. Делается это достаточно просто:
делаем триггер Magic Shield, который сработает при произнесении заклинания брони. Для юнита создается триггер с событием unit takes damage где в качестве проверяемого юнита выступает кастер. В качестве действия для нового триггера, нужно указать какую-нибудь функцию. Я использовал функцию "Adv_Trig_Actions", которая записана в специальном месте для пользовательских функций. Эта функция определяет действия, которые произойдут, когда юнит получит повреждения.

Правда имеются с триггером и некоторые сложности. Во-первых, где-то надо хранить информацию о том, что у такого-то юнита имеется такой-то запас брони. Локальные переменные тут не годятся, т.к. информацию мы сохраняем в одном триггере, а действие реализовано в другом. Поэтому в качестве хранилища информации пришлось использовать массивы. При применении заклинания, в массив MS_units заносится кастер, в массив MS_power заносится количество повреждений, которые может поглотить броня, в массив MS_trigs заносится триггер, созданный для отлавливания повреждений кастера. В переменной MS_num – общее число юнитов с данной защитой. Т.е. к примеру, произнес юнит заклинание брони. Мы проверяем. Не ли такого уже в массиве. Если нет – увеличиваем MS_num на 1, а затем заносим данные об этом новом юните в элементы массивов MS_units[MS_num], MS_power[MS_num] , MS_trigs[MS_num] . Если же юнит уже находится внутри массива под номером N, это значит он уже применил ранее заклинание щита. Тогда нам не нужно заново создавать для него триггер, отлавливающий повреждения. Мы просто обновим уровень защиты в переменной MS_power[N] .

В этом смыл действий основного триггера, который происходит во время применения заклинания защиты. А как насчет дополнительного триггера, создаваемого по ходу игры?

Прежде всего, когда мы отловили дополнительным триггером нанесенные повреждения юниту, нужно найти номер этого юнита в массиве. Скажем, его номер N. Далее, мы сопоставляем полученные юнитом повреждения и уровень защиты юнита MS_power[N] . Если защиты больше – мы просто уменьшаем уровень защиты на количество повреждений, а затем восстанавливаем юниту потерянную жизнь. Если же уровень защиты меньше количества повреждений, то мы должны все таки восстановить юниту число повреждений, равное остатку защиты, после чего удалить триггер MS_trigs[N] .
Чтобы удалить данные из N-того элемента массива, мы просто заменяем значение N-того элемента на значения последнего элемента с номером MS_num. После чего уменьшаем MS_num на 1 – ведь элементов стало меньше.

Кстати, при каждом ударе по юниту с защитой, также появляется спецэффект.

Обрати внимание на действие
Код:
call DestroyTrigger(<ссылка на триггер>)

У него нет аналога для обычных триггеров. Это действие уничтожает выбранный триггер, убирая его из памяти компьютера прямо во время игры.

Вообще-то пример получился не очень простым. Для работы заклинания требуются 3 массива и еще одна переменная. При каждом запуске или ударе по юниту с броней происходит цикл, в котором проверяется, есть ли такой-то юнит в массиве... Неудобно. Но что делать – как напрямую сопоставить юниту какие-то значения? Вообще-то сопоставить можно. Есть custom value, но для нашего примера его явно недостаточно.

На самом деле есть на jass есть замечательный прием, который позволяет сопоставить любому игровому объекту какие-то значения. Но для того, чтобы узнать как, нам придется углубиться в jass. И тогда мы сможем этот пример значительно улучшить.
Прикрепленные файлы
Тип файла: w3x Magic Shield.w3x (20.1 Кбайт, 129 просмотров )

Отредактировано Sergey, 18.06.2005 в 12:51.
Старый 17.06.2005, 18:02
Sergey
Старейший
offline
Опыт: 44,363
Активность:
8. События с малым периодом
Читатель, предлагаю рассмотреть еще один пример. Напрямую он конечно не связан с jass, но зато расширит твои познания в триггерных спелах.
Итак, иногда в триггерах возникает задача делать действия в нужной последовательности, но в течении очень малого периода времени. Проблема в том, что в вар3 действия типа wait работают крайне коряво. Минимальный период для этого действия 0.1 доля секунды. Иногда этого бывает недостаточно. К тому же действие wait ужасно не стыкуется с командами цикла.
Если ты сделаешь цикл такого типа:
Код:
Цикл от 1 до 100
   (какое-нибудь действие)
   wait game time (0.1)
конец цикла


По идее, цикл должен завершиться через 100*0.1=10 секунд, а на самом деле пройдет гораздо больше времени. Можешь проверить сам. Поэтому циклы + wait-ы оказываются непригодными для организации действий на малых периодах. А ведь эти действия ой как полезны.
Ну, раз wait-ы нам помочь не могут, остается надеяться на другой метод - использование события Time periodic, которое, к счастью, позволяет генерировать запуск триггера с периодом до 0.01 секунды. К сожалению, это событие не связано с каким-то конкретным объектом. Поэтому, если нам нужно организовать какой-нибудь триггерный спел, работающий для множества объектов, придется переходить к массивам (примерно как с триггерной защитой).

Итак, рассмотрим такую задачу: заклинание паладина Благодать срабатывает мгновенно. К юниту-цели не летит никакой снаряд и в настройках редактора объектов невозможно сделать так, чтобы заклинание работало как снаряд. Но мы попробуем сделать это.

Итак, при запуске заклинания-пустышки, должен создаваться юнит-снаряд, который начинает движение к цели. Снаряд будет двигаться не сам по себе - мы это будем делать при помощи триггеров. Причем скорость снаряда у нас будет такой, какую мы захотим сделать, не ограничиваясь пределами в игровых константах.
Идея состоит в том, чтобы при каждом использовании заклинания, мы будем заносить в массивы u и u2 юнит-снаряд и юнит-цель, а массивы u_level - уровень заклинания. Допустим, у нас в данный момент уже имеется n юнитов-снарядов, летящих к своей цели, тогда все данные про новый снаряд мы будем заносить под номером n+1. Т.е. u[n+1] , u2[n+1], u_level[n+1]. Далее, при запуске у каждого снаряда, переменную num увеличиваем на 1 (а когда снаряд долетит - будем уменьшать). Т.е. переменной num будет храниться общее число юнитов-снарядов в любой момент времени.

Далее, у нас будет триггер с событием
Every 0.05 seconds of game time
Мы не случайно взяли период 0.05. Именно такой период нужен, чтобы организовать плавное движение юнита. Ведь триггер выполнится 20 раз в секунду - как раз такова частота обновления информации человеческого глаза. Меньший период уже не требуется.
Действие этого триггера: делаем цикл от 1 до num по всем юнитам-снарядам. Для каждого юнита-снаряда определяем направление движения (угол между юнитом снарядом и целью), определяем расстояние до цели. Если расстояние до цели больше определенного числа, то производим перемещение юнита-снаряда в сторону юнита цели (при помощи полярных координат).
Если же расстояние до цели стало меньше какого-то значения, то нужно во-первых, уничтожить юнит-снаряд,во вторых, произвести нужные действия с целью (добавить жизнь - своим, отнять жизнь у умертвий).

Можно было сделать так: дать юниту снаряду заклинание Благодать (настоящее) и когда он долетит, заставить применить его на цели, а затем уже удалить снаряд. Но поскольку действия с целью вполне можно совершать при помощи триггеров, я и сделал их триггерно (чего там - добавить 200*уровень спела жизней своим или отнять 100*уровень спела жизней чужим).

Высылаю пример и предлагаю посмотреть, как он устроен.

Примечание: действия по передвижению юнита-снаряда сделаны в виде функции на jass. Вообще говоря, эти действия можно было делать и при помощи обычных триггеров, но при этом возник бы очень неприятный эффект - утечка памяти. Что это такое, чем вызвано и как с этим бороться - напишу в следующий раз.

Примечание2: я не останавливаюсь подробно на примерах и предлагаю исследовать их самостоятельно. Предполагается, что Читатель уже знает и умеет использовать массивы, циклы и т.п. Во всяком случае, рассмотреть их применение можно в статьях по триггерам. Тем не менее, остановимся подробнее на так называемых полярных координатах. Они вызывают вопрос у многих картостроителей.
Прикрепленные файлы
Тип файла: w3x Holy bolt.w3x (20.4 Кбайт, 96 просмотров )

Отредактировано Sergey, 18.06.2005 в 11:06.
Старый 17.06.2005, 18:03
Sergey
Старейший
offline
Опыт: 44,363
Активность:
9. Полярные координаты (ликбез)
Все достаточно просто. Если у вас есть две точки A и B, координаты которых нам известны. Как вычислить координаты третьей точки C, находящейся на заданном расстоянии R от точки A в направлении к точке B? Чтобы было понятнее, нарисуйте себе на бумаге точки A, B, выберите какой-то отрезок R длинна которого меньше AB. Точка C – находится на пересечении отрезка AB и окружности, проведенной из точки A радиуса R. Теперь должно быть понятнее.

Итак, зачем нам может понадобиться искать точку C? Как в примере, рассмотренном выше. Юнит-цедь движется из произвольной точки A в точку B. Каждые 0.05 секунды мы должны вычислить следующее положение юнита и переместить его на какое-то расстояние в направлении точки B. Для того, чтобы вычислять позицию точки C используются полярные координаты.

Итак, что такое обычные координаты ты знаешь. Они задаются двумя координатами X и Y. Но есть еще один способ записать координаты точки. Нарисуй координатные оси, выбери произвольную точку A. Соедини точку A и начало координат O. Пуская длинна AO=r, а угол, который образует AO с началом координат – равен a. Тогда полярные координаты точки называется пара чисел (r, a). Т.е. полярные координаты задаются расстоянием точки до начала координат и углом. Это просто еще один способ задать координаты точки. Можно через (X,Y) можно через (r, a).

В war3 есть встроенные функции для вычисления полярных координат. Например, можно записать такое действие
Код:
Set p = Point with polar [offset ((Center of (Playable map area)) offset by 256.00 towards 50.00 degrees)] 
.

p – переменная типа точка. После выполнения действия, в точке p будет точка, полученная из точки ЦЕНТР КАРТЫ (Center of (Playable map area)), путем перемещения последней на расстояние 256 под углом 50 градусов. Представили?

Полярные координаты очень удобны, если требуется организовать движение по кругу или движение по произвольной прямой. Например действие цикла
Код:
For each (i) from 1 to 10, do (Actions)
    Цикл Действия
        Set p = ((Center of (Playable map area)) offset by i*100 towards 50.00 degrees)
        <создать юнит в точке p>
.

Приведет к тому, что на расстоянии 100, 200, 300... -1000 от центра карты под углом 50 будет создано 10 юнитов.
Если же мы сделаем так:
Код:
For each (i) from 1 to 10, do (Actions)
    Цикл Действия
        Set p = ((Center of (Playable map area)) offset by 1000 towards 36*i degrees)
        <создать юнит в точке p>
.

То будет создано 10 юнитов, расположенных на окружности радиуса 1000. Один будет под углом 36, второй 2*36... последний под углом 10*36=360=0 градусов.

Вот что такое полярные координаты точки.

Впрочем, в игре функции для вычисления полярных координат устроены не самым лучшим образом, но об этом будет сказано ниже.

Пересылаю также сценарий paint, в котором демонстрируется, как можно при помощи полярных координат рисовать отрезки и окружности.
Прикрепленные файлы
Тип файла: w3x paint.w3x (16.8 Кбайт, 87 просмотров )

Отредактировано Sergey, 18.06.2005 в 13:10.
Старый 17.06.2005, 18:06
Sergey
Старейший
offline
Опыт: 44,363
Активность:
10. Оптимизация: утечки памяти
Читатель, некоторое представления на эту тему ты уже имеешь. Например, известный факт, что если не удалять созданные спецэффекты, то игра через некоторое время начнет сильно тормозить. Поэтому, даже если спецэффект мгновенного действия и через некоторое время уже не виден, его все равно нужно удалять. Почему так происходит? Потому что каждый спецэффект - это игровой объект. Когда мы создаем новый спецэффект, он попадает в память. Если его не удалять, то он останется в памяти до конца игры.
Аналогичная история с юнитами в ТД, Дотах или Аеонах. Умершие юниты должны быть удалены действием Remove Unit, чтобы не занимать место в памяти.

Оказывается, что такая же ситуация наблюдается и с остальными игровыми объектами. Предметы, декорации, регионы, точки, группы юнитов, плавающий текст, модификаторы видимости - все они с тем же успехом способны засорить память. Да, конечно объект типа точка занимает в памяти гораздо меньше места, чем юнит - в точку нужно записать только координаты X и Y, а в юнит - все его параметры. Но вообще говоря, утечки в памяти склонны к накоплению. И постепенно игра становится не менее тормознутой.

Показателен в этом смысле мой сценарий Air War - там где летает вертолет, управляемый стрелками. Передвижение вертолета реализовано примерно тем же методом, который рассматривался в прошлом сообщении, т.е. события с малым периодом. Но когда я выпустил первую версию, я понятия не имел о том, что нужно оптимизировать код и удалить утечки памяти.

Результат не заставил себя ждать. Через 2 минуты работы сценария, начиналось такое торможение, что дальнейшая игра не имела смысла. Я конечно догадывался об утечках памяти, пытался сделать все проще и эффективнее. Удалось увеличить период игры с 2-х до 5 минут. Да и то, при условии, что играет 1 человек, а ведь чем больше - тем хуже. Разгадку утечки помог найти Какодемон.

Дело было в том, что для вычисления координат юнита я использовал стандартные операции worldeit с полярными координатами. А сделаны они, как оказалось, очень коряво. Каждый раз, когда ты используешь функцию point with polar offset, в игре создается новый объект типа точка. Обычно, даже не один, а два объекта. А теперь представь, что у нас работает периодический триггер, запускаемый 20 раз в секунду. И при каждом запуске, создаются новые объекты, которые не будут удаляться до конца игры. 40 точек в секунду, все равно что 80 параметров типа real. А если функцию полярных координат применять не один, а несколько раз в периодическом триггере (к примеру, чтобы вычислить положение камеры), да еще и вычислять координаты для 4-ех игроков - то... Получаем торможение игры.


Вариант устранения утечки подсказал Какодемон. К примеру, если мы хотим вычислить полярные координаты точки, полученной из точки p заданной положения юнита u при угле a равном углу между точкой p и какой-то точкой p2. Для этого можно воспользоваться действиями:
Код:
set p = GetUnitLoc(u)
set a = AngleBetweenPoints(p, p2)
call MoveLocation(p, GetLocationX(p) + 50 * CosBJ(a), GetLocationY(p) + 50 * SinBJ(a))
.

Теперь давай проанализируем. Сначала мы переменной p присваиваем положение юнита u. Следующим действием мы ПЕРЕМЕЩАЕМ точку p в другое место. А что это за место? Вообще, в чем суть команды MoveLocation?
Если бы мы написали
Код:
call MoveLocation(p, 10, 15)

то координаты точки p стали бы (10,15)
Если бы мы написали
Код:
call MoveLocation(p, GetLocationX(p) + 50, GetLocationY(p) + 60)

то точка p сместилась бы на вектор (50,60) относительно старого положения.
А форма записи
Код:
call MoveLocation(p, GetLocationX(p) + r* CosBJ(a), GetLocationY(p) + r * SinBJ(a)) 
.

означает, что точка p сместится в точку, полученную пересечением окружности радиуса r и луча, проведенного из точки p под углом a. Т.е. это и есть по сути полярные координаты.
Если известно положение текущей точки (X,Y) и нужно найти координаты точки, полученной из текущей при смещении в направлении a на расстояние r – то ее координаты будут (X+r*cos(a), Y+r*sin(a)). Это все известные в математике факты.

Но в чем преимущество нового метода по сравнению со стандартной функцией вычисления полярных координат? Преимущество в том, что МЫ НЕ СОЗДАЕМ НОВУЮ ТОЧКУ, чтобы вычислить полярные координаты. Вместо этого мы ПЕРЕМЕЩАЕМ СУЩЕСТВУЮЩУЮ ТОЧКУ.

Есть конечно еще один нюанс. Обрати внимание на команду:
Код:
set p = GetUnitLoc(u)

Присваиваем точке p положение юнита u. Но переменная со ссылкой на объект и сам объект - это разные вещи. До этого действия, переменная p была пустой. Объект точка с положением юнита не существовал. После действия, переменная p начала ссылаться на какую-то точку. Какой вывод? Игра создала объект типа точка и сделала на него сслыку в переменной p. Т.е. даже когда ты используешь элементарную функцию - определит положение юнита, в игре создается объект. И он тоже засоряет память - и это тоже станет заметно в триггерах с малым периодом.
Чтобы окончательно избавиться от утечки, необходимо применить действие для удаления объекта типа точка из памяти. В триггерах ты такой команды не найдешь. В jass эта команда выглядит следующим образом:
call RemoveLocation(p)
Можешь посмотреть, как это сделано в примере, который я выслал в прошлый раз.

Ну, вроде с утечкой из-за точек разобрались. Но, увы, есть и другие виды утечек. Группы юнитов. Вообще-то есть переменные типа unit group. Это из той же серии. Переменная есть ссылка. А на что ссылается переменная unit group? На некий объект. Делаем выводы: объекты, на которые может ссылаться переменная unit group могут создаваться по ходу игры. И не только могут, но и создаются. И засоряют многострадальную память :).
Каждый раз, когда ты применяешь функцию pick every (unit in unit group) and do actions - ты в качестве unit group указываешь определенную функцию. Например, [units of type] или [units in range matching conditions]. Вот тут и скрывается зло. Именно из-за таких функций создаются объекты, которые будут торчать в памяти (кстати, во втором случае еще и точка может выплыть). И, как ты догадываешься, в триггерах с малым периодом - это означает торможение.
Так что если хочешь создавать, к примеру, огонь, который каждые 0.25 секунд наносит повреждения всем юнитам в такой-то области, нужно уметь бороться с утечками. А борьба тут довольно простая. После каждого такого действия, вставляем команду
Код:
call DestroyGroup(GetLastCreatedGroup())

- уничтожить группу (последняя из созданных групп).

Итак, подведем итог: в большинстве сценариев, утечки могут возникать из-за
- юнитов (создаем много и не удаляем)
- спецэффектов
- функция для работы с точками
- функций для работы с группами
Могут быть и другие варианты, но они встречаются реже.
Утечки особенно опасны, для триггеров с малым периодом.

Читатель, если удалить все основные утечки, то для 99,99% сценариев больше ничего не нужно оптимизировать. Но, как оказалось, существуют и другие виды утечек. Например, куда девается локальная переменная после того, как триггер кончил исполнение? Есть основания считать, что на самом деле они продолжают сидеть в памяти. И хотя занимают они очень мало, но на протяжении длинной игры, их может накопиться изрядно. И значит, после окончания триггера их предпочтительно обнулять.

Ты наверное замечал уже это в примере, который я присылал. В конце триггера идут строки типа
Код:
set i = 0
set r = 0
set s = ""

А как обнулять переменные со ссылками на юниты, точки и пр.? Есть способ. Для всех объектных переменных ноль это null.
Т.е. мы пишем
Код:
set p = null
set u = null

и т.д. И переменные обнуляются.

Есть еще одна интересная возможность увеличить ресурсы памяти для длинных сценариев. Для этого нужно в какой-нибудь триггер с событием Map Initialization добавить команду:
Код:
call DoNotSaveReplay()

- Эта команда заставит war3 не писать реплей игры. А значит, снизит ее загрузку.

Примечание. Мой друг картостроитель, прочитал про утечки и начал думать, что же с этим теперь делать? Глянул на свой проект и приуныл. Столько всего оптимизировать... Раньше для него не было проблемы утечек, а теперь пришлось думать, что с этим делать. А я думаю, может быть и не стоит так уж усердствовать? Во всяком случае не все сценарии требуют дотошно оптимизировать и удалять все утечки. В первую очередь, конечно, должны оптимизироваться сценарии для сетевой игры и сценарии, где есть триггеры с малым периодом. Остальные – надо смотреть по обстоятельствам. Если есть торможение, можно попробовать от него избавиться.
Вообщем, jass поможет вам решить проблемы, которые до его изучения не существовали. :)

Покончив с оптимизацией, мы наконец перейдем к одному из самых полезных аспектов jass.

Отредактировано Sergey, 18.06.2005 в 11:19.
Старый 17.06.2005, 18:07
Sergey
Старейший
offline
Опыт: 44,363
Активность:
11. RETURN BUG (RB)
Прежде всего, нужно рассказать немного о том, что собой представляет компьютерная память. Читатель, может быть ты и так это знаешь, но на всякий обрисую картину. Все без исключения игровые объекты - юниты, строки, числа - все это находится в компьютерной памяти. Память представляет собой просто последовательность идущих подряд ячеек, в каждую из которых записано определенное число. При помощи чисел можно закодировать все - и юниты и строки. Разные объекты занимают разное количество ячеек. Ведь чтобы записать объект юнит, нужно записать целую кучу информации о всех его параметрах. Это гораздо больше информации, чем то, которое несет какое-то значение типа integer. Вообще говоря разработчики war3 вряд ли хотели, чтобы картостроители могли работать с памятью, но они допустили при разработке один важный баг, которым научились пользоваться спецы по jass. Баг не позволяет менять содержимое ячейки памяти - но зато он дает возможность для любого игрового объекта найти номер ячейки памяти, в которую он записан, а также по номеру ячейки найти ссылку на объект.

К примеру, рассмотрим функцию
Код:
function FindHandle takes rect reg returns integer
    return reg
    return 0
endfunction

Что она по идее делает? В функцию передается параметр типа reg типа регион. Функция должна возвращать параметр типа integer. Вроде бы все нормально. Но почему-то присутствует два оператора return... Более того, первый из них говорит, что функция должна вернуть отнюдь не целочисленное значение, а наш регион reg.
Фишка в том, что war3 проверяет правильность функций по последнему оператору return. В этом последнем операторе записано:
Код:
return 0

Если бы не было этой записи, war3 выдал бы ошибку - т.к. наблюдается несоответствие типов. Должны вернуть integer, а возвращаем rect. Но у нас два оператора return. war3 смотрит - вроде все нормально, ошибок нет. 0 -ведь целочисленное число, значит его можно вернуть. И невдомек ему бедному, что хитрый jass-ер уже поставил до этого еще один return. И вот, возникает интересная задача - игра должна вернуть регион reg, как целочисленное число. По идее это бессмысленно, но тут и срабатывает return bug. Результатом работы функции будет число, равное адресу ячейки памяти, в которой содержится информация по региону reg.

Это первая часть бага. Есть и другая. Рассмотрим функцию:
Код:
function FindRegion takes integer i returns rect
    return i
    return null
endfunction

Все аналогично. Функция должна вернуть регион, а вместо этого мы передаем ей параметр типа integer. Результатом будет то, что игра вернет регион, записанный по адресу i. Если конечно по этому адресу действительно записан регион. Впрочем, если не записан, это не будет ошибкой. Ошибка может возникнуть, если попробовать обратиться к недоступному участку памяти. Но это произойдет, разве что если ты будешь экспериментировать с памятью war3.

Итак, у нас уже есть две функции, очень хорошо дополняющие одна другую. Создавать такие функции - очень просто. Так что для любого объекта в игре можно найти ячейку номер его яейки в памяти.

Какое может быть применение у return bug (RB)? Ну например, для наших функций. Зачем нам может понадобиться узнавать номера для регионов? Я этому сходу нашел следующее применение. В редакторе worldedit, к сожалению, невозможно простым и быстрым способом занести созданные регионы в массив. А ведь иногда бывает нужно. Скажем, на твоей карте несколько сотен регионов, в которых должны появиться одни и те же монстры и отправиться в патруль в следующую точку. Проще всего делать это через массивы с циклами. Но заносить вручную сотни регионов в массив - тоже приятного мало.
А я сделал предположение, что регионы, которые мы создаем в редакторе, занимают соседние ячейки памяти. Т.е. если создали мы первый регион - он попал в ячейку X, тогда следующий регион попадет в ячейку X+1 и т.д. Т.е. если мы знаем номер первого региона и остальные регионы создавали в определенном порядке, мы можем при помощи RB найти все остальные регионы и занести их в массив на автомате.
Итак, сначала при помощи функции FindHandle мы находим номер первого региона, затем при помощи функции FindRegion в цикле заносим все остальные регионы в массив. Элегантное решение нудной задачи :). Рекомендую посмотреть пример return bug, в котором реализованы все функции, описанные выше.

Но конечно RB имеет гораздо более ценное применение. Дело в том, что все ячейки памяти имеют сквозную нумерацию. Т.е. благодаря RB мы можем сопоставить всем объектам их номера (точнее номера ячеек, которые они занимают в памяти). И все эти номера будут уникальны. Совпадений не будет. Эту нумерацию можно использовать, чтобы... Впрочем, это тема другой части :).
Прикрепленные файлы
Тип файла: w3x return bug.w3x (17.4 Кбайт, 70 просмотров )

Отредактировано Sergey, 18.06.2005 в 12:19.
Старый 17.06.2005, 18:09
Sergey
Старейший
offline
Опыт: 44,363
Активность:
12. Тип Handle
Читатель, кроме всех тех типов, к которым ты привык в редакторе переменных, существует еще один тип – handle. Это очень специфический тип - что-то типа универсального указателя на объекты. Нас он интересует с позиции его применения. К примеру, если ты сделаешь функцию:
Код:
function <Имя> takes handle h returns <...>


в аргументе указан тип handle. Так вот, в эту функцию в качестве параметра можно передавать ЛЮБОЙ игровой объект. Хочешь, посылай в качестве аргумента юнит, хочешь - регион, хочешь - точку и т.д. В связи с тем, что мы уже описали return bug, мы можем использовать его, чтобы найти ячейку в памяти, на которую ссылается handle:
Код:
function H2I takes handle h returns integer
return h
return 0
endfunction

Это дает нам определенное преимущество. Вот в прошлой статье мы рассмотрели функцию:
Код:
function FindHandle takes rect reg returns integer
    return reg
    return 0
endfunction

Для региона находили номер. А как насчет других объектов? юнитов, точек, способностей и пр.? Для каждого создавать свою функцию? Можно конечно, но используя тип handle мы значительно упростим задачу. Функцию H2I, рассмотренная чуть выше, предназначена как раз для этой цели. H2I(O) - и есть номер ячейки в памяти, которую занимает объект O, каким бы ни был этот объект. Напишешь
Код:
set i = H2I(u)

- в i попадет номер для юнита u. И т.д.
Вот такой универсальный способ находить номер объекта. К сожалению, такого же универсального способа, чтобы по номеру определять что-за объект в нем записан нет.

Отредактировано Sergey, 18.06.2005 в 12:22.
Старый 17.06.2005, 18:10
Sergey
Старейший
offline
Опыт: 44,363
Активность:
13. Система Super Custom Value (SCV) или RB+cache
Читатель, мы вплотную подошли к системе SCV. Эта система, которая позволяет сопоставлять ЛЮБОМУ игровому объекту - либо другой объект, либо какое-то значение (или даже массив). Наподобие custom value, но значительно более универсальная. Значение этой системы трудно переоценить. Фактически, она позволяет упростить решение огромного множества задач, избавиться от глобальных переменных и создавать так называемые кеш-переменные прямо во время игры.

С чего тут начать. Пожалуй, с Кеша. Существует такая замечательная вещь, называемая кешь. Программисты называет такие структуры - ассоциативный массив. Кешь в war3 - это особый двумерный массив, в котором в качестве аргументов используются строки. Т.е. вводишь аргументами 2 строки, им сопоставляется значение. Можно сопоставить значение типа integer, типа real, типа string и типа boolean.
Как жаль, что в этот массив нельзя записать ссылку на юниты, предметы, способности и т.п. Стоп, а действительно ли нельзя? Или все таки можно?

Ссылку может и нельзя, но давай вспоминать, что мы узнали про RB. Каждому игровому объекту соответствует уникальный номер, число типа integer. Это число можно найти, и по этому числу можно найти объект. А ведь число типа integer может быть записано в кешь!

(*) Итак, если мы используем кешь не для переброски данных, а для хранения информации, то в качестве хранимой информации кешь способен записать указатели (номера) объектов.

Это первый важный вывод. А теперь подумаем, если мы в кешь можем сохранять объекты, можем ли мы при помощи кешь сопоставить какому-то игровому объекту какое-то значение? Игровой объект имеет свой уникальный номер. Номер есть число, но специальные функции позволяют перевести его в строку.

(**)Договоримся, если мы хотим сопоставить игровому объекту значение в кешь, то в качестве первого аргумента записи будем использовать уникальный номер этого объекта, переведенный в строку.

(***)Что касается второго аргумента кешь, то мы можем использовать его, чтобы дать нашему сопоставлению уникальное имя.

Сопоставь факты, отмеченные выше, и ты поймешь идею SCV.

Рассмотрим функцию вида:
Код:
function set_object_iparam takes handle h, string key, integer val returns nothing
   call StoreInteger(udg_cache, I2S(H2I(h)), key, val)
endfunction
.

Эта функция предназначена, чтобы сопоставлять любому объекту параметр типа integer. Аргументами выступает ссылка на объект handle h, строка key - имя сопоставления и переменная val типа integer - это число, которое мы сопоставляем объекту.
udg_cache - это переменная типа кешь - специальный кешь-файл создается в самом начале игры.
В функции единственное действие:
Код:
call StoreInteger(udg_cache, I2S(H2I(h)), key, val)

Это обычная команада занести значение в кешь.

Для записи в кешь, нудно передать 2 строки-аргумента. Первая строка:
Код:
I2S(H2I(h))


Разберемся подробнее. Здесь написана функция внутри функции. H2I(h) - мы уже рассмотрели выше. Она вернет номер для объекта, переданного через переменную h. Вторая функция I2S(...) - это обычная варкрафтовская функция перевода числа в строку. Итак, вся конструкция в целом приведет к тому, что первая строка - это переведенный в текст уникальный номер объекта.
Вторая строка key - это строка, которую заполняет сам пользователь, давая имя сопоставлению. Параметр для записи val.

Итак, если у тебя есть юнит u и ему нужно сопоставить число 10, то можно использовать команду:
Код:
call set_object_iparam(u, "int", 10)

имя сопоставления "int".

Отлично! Как делать запись мы выяснили. А можно ли эту запись прочитать обратно? Да! Во-первых, для удобства создадим вторую функцию:
Код:
function get_object_iparam takes handle h, string key returns integer
   return GetStoredInteger(udg_cache, I2S(H2I(h)), key)
endfunction

Она похожа по структуре на предыдущую, только аргументов на один меньше. Это потому, что функция нужна не для записи значения в кешь, а для чтения значения из кеша.
Код:
return GetStoredInteger(udg_cache, I2S(H2I(h)), key)


Т.е. наша функция вернет значение выражения GetStoredInteger(udg_cache, I2S(H2I(h)), key) . А что это за выражение? Стандартная функция для чтения из кеша. В качестве первой строки указывается уникальный номер объекта, переведенный в строку. Вторая строка - определена пользователем.

Итак, если мы хотим узнать, что записано в записи кеша "int" для юнита u, используем команду:
Код:
set i = get_object_iparam(u, "int")


Т.е. можно и записывать значения и читать их. Читатель, не замечаешь чего-то общего между нашими сопоставлениями и custom value? По сути, custom value - это тоже сопоставление, но менее универсальное, т.к. можно сопоставлять юнитам (и только юнитам) одно (и только одно) значение типа integer. А при помощи SCV можно сопоставить что угодно и чему угодно. Поэтому я называл эту систему Super Custom Value (SCV) , а сопоставления-записи - для краткости cv.

А как сопоставить юниту u - другой юнит u2? Очень просто.
Код:
call set_object_iparam(u, "int", H2I(u2))


Мы записали в параметр "int" уникальный номер u2.

Этот номер мы можем прочесть обратно. Проблема лишь в том, как при помощи этого номера получить ссылку обратно на u2. Для этого в SCV есть специальные функции.
Код:
function I2U takes integer i returns unit
return i
return null
endfunction

и
Код:
function get_object_uparam takes handle h, string key returns unit
   return I2U(GetStoredInteger(udg_cache, I2S(H2I(h)), key))
endfunction

Первая функция по уникальному номеру возвращает сам юнит, вторая сделана для простоты - она читает уникальный номер из записи в кеше и при помощи первой функции возвращает ссылку на этот юнит.
Так что, если нужно прочесть какой юнит записан в cv "int" для юнита u, используем команду
Код:
set u2 = get_object_uparam(u, "int")


Вот и все. Остальное все по аналогии. Есть и другие функции для сопоставления чисел real, строк, флагов boolean. Есть функции для нахождения не только юнитов по их номеру, но и других объектов - точек, регионов, спецэффектов и др.

Есть правда еще одна функция
Код:
function flush_object takes handle h returns nothing
   call FlushStoredMission(udg_cache, I2S(H2I(h)))
endfunction

- она позволяет быстро отчистить все записи кеша, относящиеся к какому-то объекту.
Скажем, собираешься ты удалить юнит u. Для того, чтобы cv этого объекта не занимали место в памяти, когда объекта уже нет, пишешь команду:
Код:
call flush_object(u)

Все эти функции в сумме вмещаются на 1-1.5 экрана. Переносить систему из сценария в сценарий - элементарно. Просто копируем код, создаем переменную cache и при событии Map Initizlization создаем кеш-файл.

Читатель, попробуй представить себе все возможные способы применения SCV. Вспомни примеры, которые мы рассмотрели ранее. Может быть есть способ что-то сделать проще, быстрее и надежнее ? ;)

Когда ДимонТ выпустил систему, я разработал по ней небольшой обучающий сценарий, который демонстрирует ее возможности, в том числе создание переменных и массивов cv. Я хочу, чтобы ты подробно изучил этот сценарий.

Освоив SCV ты поднимешься на следующую ступень мастерства.
Прикрепленные файлы
Тип файла: w3x SCV.w3x (26.7 Кбайт, 93 просмотров )

Отредактировано Sergey, 18.06.2005 в 12:30.
Старый 17.06.2005, 18:11
Sergey
Старейший
offline
Опыт: 44,363
Активность:
14. Да здравствует SCV!
Рассмотрим один из наших старых примеров – полет юнита снаряда. Можно ли улучшить его при помощи SCV? Раньше нам приходилось использовать массивы, чтобы сохранить информацию, что юнит-снаряд летит к такому-то другому юниту и уровень заклинания. Теперь мы можем сопоставить эти данные непосредственно юниту-снаряду при помощи SCV. Т.е. записать все необходимые данные в cv. А как нам сделать периодический цикл по всем юнитам снарядам, чтобы сдвигать их? О, тут у нас появляются новые интересные возможности. Мы можем для каждого юнита-снаряда создать отдельный триггер с периодом 0.05, отвечающий за его передвижения к цели.
Ну допустим, мы создали триггер с событием Периодическое 0.05. А как прописать, что этот триггер должен работать только для определенного юнита-снаряда? Очень просто, мы сопоставим триггеру (триггер ведь тоже игровой объект!) нужный нам юнит-снаряд. И при запуске триггера сможем определить, что нужно двигать такой-то юнит-снаряд.

В целом система организации движения юнита-снаряда становится довольно простой. На основе этого принципа я сделал несколько геройских заклинаний - предлагаю тебе ознакомиться с ними. К примеру, герой Лорд Хаоса. Заклинания Звездный конус, Групповой файербол и Сфера Хаоса сделаны таким способом. Это открывает широчайшие возможности по созданию триггерных заклинаний любой сложности.

Рекомендую глянуть примеры подобных наработок здесь:
- наработка Димона: герой Seal master
- моя наработка: герой Еретик
- моя наработка: герой Лорд Хаоса

Кстати, огромное достоинство системы SCV, что ее можно легко дополнить. Допустим, нам нужно чтобы объектам можно было сопоставлять триггеры и таймеры. К функциям SCV добавим новые:
Код:
function I2Tm takes integer i returns timer
    return i
    return null
endfunction

function I2Tr takes integer i returns trigger
    return i
    return null
endfunction

и еще две
Код:
function get_object_tmparam takes handle h, string key returns timer
   return I2Tm(GetStoredInteger(udg_cache, I2S(H2I(h)), key))
endfunction

function get_object_trparam takes handle h, string key returns trigger
   return I2Tr(GetStoredInteger(udg_cache, I2S(H2I(h)), key))
endfunction

Вот и все.

Автор системы SCV DimonT написал про нее отдельную статью, которую можно посмотреть на нашем сайте.

Отредактировано DimonT, 18.06.2005 в 17:40.
Старый 17.06.2005, 18:15
Sergey
Старейший
offline
Опыт: 44,363
Активность:
15. Послесловие
Читатель, ты уже очень многое узнал о jass. Конечно, в jass полно и других важных наработок. Есть команды, позволяющие выводить текст в любом месте экрана. Есть спец. команды, позволяющие производить общие действия для всех игроков на одном только компьютере (например, играть такой-то звук). Есть наработки Vexorian-а, который по сути сделал такую же систему, как SCV ДимонаТ, но раньше. Есть методы, позволяющие упростить работу с группами юнитов. Есть методы создания специальных объектов, которые определяют, что юзер щелкнул мышкой в такую-то точку (на основании этого построен инвентарь ДимонаТ на 80 слотов). Но всему этому обучать я уже не буду, т.к. во-первых, сам многого не знаю, во-вторых, в случае необходимости ты уже сможешь разобраться самостоятельно. Можешь проверить - статьи по jass, которые раньше казались сложными и непонятными, теперь станут доступными, а материал изложенный в них - будет уже знаком.

Владея приемами работы с jass, самое главное для триггерщика - умение правильно ставить и находить решение задач, умение алгоритмизировать задачу, умение мыслить творчески. Про это я уже писал отдельную статью. Дальше, все уже зависит от твоих мозгов и твоего опыта.

Вот, к примеру, как-то раз попросил меня один картостроитель разобраться с тем, как устроено заклинание passive mana shield известного буржуйского автора. Идея в том, что повреждения должны наноситься мане, а когда мана кончится – жизни юнита. Думаю, ты уже догадаешься, как это сделать. Единственный способ, пригодный для этого методами war3 - отлавливать повреждения, полученные юнитом при помощи триггера с событием unit takes damage. Т.е. для каждого юнита с пассивкой, нужно создать такой триггер - это задача решаемая. При нанесении повреждения юниту, возможны разные случаи:
- маны у юнита больше размера повреждения: в этом случаи мы восстанавливаем юниту жизнь и отнимаем ману.
- маны у юнита меньше размера повреждения: в этом случае из полученного юнитом повреждения мы должны восстановить число жизни, равное количеству маны, а потом обнуляем ману.

Вроде все хорошо. Все, да не все. Оказывается, что событие unit takes damage срабатывает на какую-то долю секунды раньше, чем юниту наносится повреждение. Из-за этого, если жизнь юнита полная, мы пытаемся добавить жизнь и отнять ману. Но жизнь полная и поэтому добавлять к ней что-то бесполезно. Получается, что жизнь юнита все равно страдает. Мелочь, а не красиво.
Можно конечно сделать паузу 0.1 секунды и уже после этой паузы восстанавливать жизнь юнита. Тогда все работает нормально, но все таки видно, как жизнь юнита колеблется. Опять же, некрасиво.

Картостроитель предложил глянуть работу спела у буржуйского автора - там было столько jass кода, в котором было так лень разбираться, что я на это забил. Стал думать. Есть ли возможность отловить момент, когда у юнита отнимутся жизни? В принципе есть. Существует такое событие, которое срабатывает, когда жизнь юнита станет меньше указанного значения. Этим можно воспользоваться! Просто если жизни у юнита почти полные и нельзя использовать метод добавить жизнь сразу. Вместо этого мы СОЗДАДИМ ТРИГГЕР с событием, которое сработает, когда жизнь текущего юнита станет меньше текущего значения. Это произойдет буквально через миг. И именно в этот момент мы добавим жизни юниту обратно. Проверил – все сработало. А триггерный код получился заметно короче, чем у буржуя.

- спел passive magic shield

Удачных тебе наработок, Читающий ;).

Отредактировано Sergey, 18.06.2005 в 12:50.
Старый 17.06.2005, 18:18
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Своим.
Ух, закончил статьи.
Знаю, что ссылки на другие сайты нужно заменить на ссылки на нашь сайт. Либо сам этим займусь, либокто-нибудь еще. Просто сейчас времени нет - надо уйти по делу. Короче просьба ничего не стирать.
Если что - делайте замечания в эту же тему. Ошибки исправлю и затем можно будет разместить на сайте.
Ух, класс. Наконец то и у нас будут статьи по jass. И еще какие статьи!!!! :)
Старый 17.06.2005, 18:27
Зевс
Адская Зверюга
offline
Опыт: 152,154
Активность:
Молодец. Всегда хотел с джасом разобраться. Пишешь хорошо. Пока три топа прочел. Еще раз респект.
Старый 17.06.2005, 19:29
tysch_tysch
Работаем
offline
Опыт: отключен
уфф, осилил...

Цитата:
это наша глобальная переменная unit


а ничего, что unit - зарезервированное слово?

а так ничё, зачот... статья действительно хороша, надо только код и прочее оформить...
ps может названия переменных выделять как-то, а то по тексту их иногда пропускаешь

ZlaYa1000 добавил:
димонт добавь теги
бар на форум( размершрифта+полужирный) - чтобы проще было с оформлением статей на форуме
тег примечаний на сайт - сам знаешь зачем
Старый 17.06.2005, 21:29
NETRAT

offline
Опыт: 83,712
Активность:
после прочтения удалить ибо флуд: не мог не высказацца - мегареспект!
Старый 17.06.2005, 22:44

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы можете скачивать файлы

BB-коды Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход



Часовой пояс GMT +3, время: 11:28.