Главная » Статьи » Создание карт » JASS / AI скрипты учебники

Осваиваем Jass: Глава 6, Глава 7, Глава 8.

10-12-2016» 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, который значительно расширяет возможности создания сценариев.

» 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. И тогда мы сможем этот пример значительно улучшить.

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

К статье прикреплен файл: Magic_Shield.w3x

К статье прикреплен файл: Holy_Bolt.w3x

Категория: JASS / AI скрипты учебники | Добавил: imDarkCount (10-12-2016 в 19:31:18)
Просмотров: 1435 | Рейтинг: 5.0/1
Всего комментариев: 0
avatar
Рейтинг@Mail.ru
Яндекс.Метрика

Copyright © 2010-2017
Вакансии :: Контакты
Мобильная версия сайта