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

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

Ответ
 
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Владу
Влад, извини, у меня с аськой были какие-то проблемыю Хорошо, что в прошлый раз успел рассказать все, что хотел. Короче, если ты не против, можем продолжить в этой теме.

Отредактировано vlad_C0M, 15.07.2005 в 00:58.
Старый 30.05.2005, 21:13
vlad_C0M
Босс DarkSkyTeam
offline
Опыт: 10,459
Активность:
Ок! :) Все будет прочитано и запомнено.

Повторим значит чего я узнал из Jass :
:
udg_(название) - значение обьекта\ глобальной переменной. Собственно обьект сам должен быть "поставлен" на карте.
local unit u - (создание локальной переменной u , типа юнит) , каждая переменная локализована только на данном триггере. Сколько раз триггер запущен, столько раз создана переменная, а по завершению уничтожена.
При изменении названия глобальной переменной, все строки из джасс использующие сию придется переписывать, вар едит не распознает джасс строки как функции. *(искл. проверка на наличие ошибок, читает функции)
:
Вроде понял как пользоваться локальными переменными в виде подстрок custom scripts.
Старый 31.05.2005, 05:47
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Все правильно.
Влад, ты уже понял достаточно, чтобы мог проводить первыек эксперименты с jass. Пока ты не знаешь, как выглядят триггерные команды в jass. Но вообще говоря этого и знать не обязательно (хотя фанаты jass знают). Например, хочешь ты узнать, как выглядит команда
wait game time (...) в jass. Создаешь новй триггер, в которй помещаешь едиснтвенную команду wait game time и переводишь триггер в jass. Смотришь, в какую строку превартилась команда. Затем можешь скопировать ее и использовать в другом триггере.
А еще, в примере, который мы рассматривали, создавались 2 локальных переменных. Одна типа unit вторая - спецэффект.
local unit u
local effect e
Откуда я узнал, что слово effect означает в джасс спецэффект? Можно узнать по разному, я предлагаю метод намеренной ошибки.
Создай в редакторе глобальную переменную sp типа спецэффект. Затем в каком-нибудь триггере сделай ошибку, связанную с jass. К примеру, вставь
cs set i=1
в триггер, где переменная i не объявлена. Затем включи и выключи триггер. Тебе будет выдана ошибка, но не только она. Вглядись внимательно в окно с информацией об ошибке. В нем записан весь jass код, который только есть в сценарии. Пролистай этот код вверх. Вверху есть раздел, в котором объявляются все глобальные переменные, какие есть в сценарии. Там ты найдешь строчку, в которой будте название переменной sp и рядом с ней тип переменной effect.
Так что если захочешь создать локальную переменную типа спецэффект, в триггер нужно вставить строчку
local effect <имя переменной>
Таким методом ты можешь определить название любого другого типа.
Многие навзания типов в триггерах и jass совпадают:
integer
real
boolean
string
Но не все. Тип Регион называется в jass не region, а rect (если я не ошибаюсь).
Влад, можешь потренироваться в создании локальных переменных. Уже того, что ты сейчас знаешь по jass, мне с лихвой хватило для разработки МОМ. Хотя конечно же у него больше возможностей. Поразмышляй, какие возможности открывают тебе локальные переменные, особенно при создании триггерных заклинаний (для остроченных действий).
Влад, триггеры в твоем проекте на высоком уровне, но без jass ты иногда делал громоздкие и неудобные конструкции, связывался с массивами и пр. Во многих местах можно селать гораздо проще. Подумай над этим.
Влад, еще раз предупреждаю - когда работаешь с jass, нужно быть очень осторожным. Ошибки могут приводить к весьма печальным последствиям. К примеру, цепное отключение ссылок на триггер с ошибкой. Довольно часто бывает так, что редактор вылетает из-за ошбки в jass без всякого сохранения карты. Это очень обидно, поэтому перед тем как запускать карту - всегда сохраняй ее.

Отредактировано Sergey, 31.05.2005 в 15:54.
Старый 31.05.2005, 15:43
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Влад, массивы мы уже рассмотрели, перейдем к условиям и циклам в 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 не равно 10, то оно будет выглядеть
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 больше
  1. Если не стала, производится какое-то действие и затем переменная 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 в них можно записывать все что угодно.
Старый 01.06.2005, 18:11
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Переходим к изучению функций в 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 <Фнукция> (<параметры>)
Но понятное дело, значение функции, которое оно возвращает, не будет никуда записано.
  1. Команда return представляет определенный интерес сама по себе. Если ты проверишь, во что превратится команда skip remaining actions в jass - она превратится в return. Т.е. это команда, которая прерывает исполнение функции.
  2. Допускается запуск одной функции из другой. К примеру, в функцию summa можно вставить строчку
call property(1,1000)
Но может возникнуть ошибка. Обращаться можно только к функии, которая записана выше данной (т.е. создана раньше). Т.е. если функция property будет ниже чем summa - то обращаться к property из summa нельзя.
Кстати, код в специальном месте для триггерных функций расположен ВЫШЕ чем код всех игровых триггеров. Поэтому к функциям записанным здесь можно обращаться из любого триггера.
  1. Если внимательно приглядеться, то кроме функций, определенных пользователем (т.е. тобой) существуют еще и встроенные функции. К примеру, глянь команду
call AdjustPlayerStateBJ( gold, Player(n-1), PLAYER_STATE_RESOURCE_GOLD )
Слово call тебе ни о чем не говорит? :). AdjustPlayerStateBJ - это встроенная функция с тремя параметрами. Список всех таких встроенных функций имеется где-то в MPQ архивах. Так что получается у нас, что все триггеры устроены так, что одни функции ссылаются на другие, те на третьи и т.д. :).
На этом о функциях пока все.
Старый 02.06.2005, 08:39
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Влад, напиши, все ли до сих пор понятно? Получается ли самому делать примеры на jass?
Старый 03.06.2005, 07:57
Sergey
Старейший
offline
Опыт: 44,363
Активность:
УСТРОЙСТВО ТРИГГЕРА с точки зрения 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, который значительно расширяет возможности создания сценариев.
Старый 03.06.2005, 15:15
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Можно. Я и думаю над этим. Неплохая кстати статья выйдет - что-то Осваиваем jass. Но я надеюсь, что Влад тоже подключится. Лучше всего статьи писать когда сам осваиваешь.
Старый 03.06.2005, 15:43
vlad_C0M
Босс DarkSkyTeam
offline
Опыт: 10,459
Активность:
Sergey : Сдав алгебру :) Я снова в деле! У меня правда трабл. Я вот сделал пример. Он дейсвительно не работает. Не могу найти причину. Что-то все же я , делаю неправильно :(

vlad_C0M добавил:
Я все прочитал, теоритически все понял. Даже то , что при проверке на if ставится "==" а не "=" :)! Собтсвенно большинство фунцкий, мне понятно как никому больше , только из-за тех нагромождений , что я делал :)!
Старый 06.06.2005, 12:42
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Хорошо, что все понял. Фактически, теперь мы от теории перейдем к практике и рассмотрим несколько примеров. Но для начала про огнеметную задачу. Ты сделал в ней несколько ошибок.
  1. Итак, при запуске заклинания, почему-то ничего не происходит. Как проверить, где ошибка? Можно добавлять в код триггера строки типа
call DisplayTextToForce( GetPlayersAll(), "1" )
Т.е. выдать текстовую строку.
Одну такую функцию я поместил в начало триггера, другую в цикл. Выяснил, что при испольщзовании спела не выдается никаких сообщений. Значит в чем ошибка? В условии триггера или в событии. С событием все в порядке, а вот условие, как оказалось, было ориентировано на проверку другого спела. Поэтому ничего не работало.
  1. Какой вид цикла был использован? For loop integer A. Это совершенно недопустимо для такого триггера. Я пояснил выше, что переменные для данного вида цикла являются глобальными. Т.е. если ты несколько раз исполльзуешь заклинание, то триггер запустится несколько раз, и какждый раз он будет задействовать одни и те же глобальные переменные. Произойдет конфликт и оба заклинания подействуют не так, как надо.
Очевидно, необходимо было использовать для цикла какую-то локальную переменную.
3а. Огнемет получился любопытным. Дейсттвие с большим периодом. Маленький период с помощью действия wait или wait game time создать нельзя. Наименьший - 0.1 доля секунды, а для плавного движения этого мало.
Мы еще рассмотрим, как делать нормальные действия с малым периодом. А в нашем случае - согнеметом, вполне можно было сделать, чтобы огонь долетал до всех точек мгновенно. Т.е. обойтись без wait game time
3б. Тем более, что ты не созранил угол обстрела. Посмотри сам, к какому результату это приводит. Пока огонь летит из точки в точку, если герой повернется, то и огонь сменит направление движения!
  1. Явно ошибка с повреждениями от огня. Ежесекундно повреждения наносятся юниту-целм, даже если огонь до нее не долетел и вообще не долетит. При этом, тем юнитам, которые стоят на пути огня, никакого повреждения не будет.
Ну ладно, для первой jass наработки сойдет. Отправляю "исправленный" вариант, хотя ничего особого я там не исправлял. Если хочешь - доделай до конца.
Еще высылаю свой вариант огнемета, который я когда-то разработал на первых порах, пока изучал jass. Посмотри, как он устроен.
Скоро напишу продолжение.
Старый 06.06.2005, 17:10
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Влад, давай рассмотрим пример, который покажет некоторые возможности jass, недоступные в редакторе триггеров.
Вот например, есть у тебя система, которая добавляет эффекты стана, кровотечения и пр. Правда определяешь ты, что удар был нанесен - необычным способом. Множество вложенных if then else с паузами и повторяющимися действиями. Почему ты вынужден был делать так? Да просто потому, что близзы схалтурили. Определить момент удара можно лишь при событии Unit takes damage, которое можно создать лишь для конкретного юнита. Жуть как неудобно. А теперь попробуем решить эту задачу исходя из того, что мы узнали о jass.
Если мы можем динамически создавать новые триггеры прямо по ходу игры, почему бы не сделать так, что когда один юнит атакует другой, мы СОЗДАДИМ триггер, который будет отлавливать, когда повреждения будут нанесены. Делается это достаточно просто:
делаем триггер с событием unit is attacked. В триггере имеется локальная переменная t типа триггер. Используем эту переменную, чтобы создать в игре новый триггер событием unit takes damage где в качестве проверяемого юнита выступает атакованный.
В качестве действия для нового триггера, нужно указать какую-нибудь функцию. Я использовал функцию "AttackedUnitDamage", которая записано в специальном месте для пользовательских функций. Действия у функции могут быть самые разные. Можно прописать действия в виде когда прямо внутри функции "AttackedUnitDamage", но я для удобства сделал по-другому: в функции прописано запустить действие триггера DamageAction - нормального триггера, без всякого jass.
А действия такие:
  • выдать тип юнита triggering unit - юнита, получившего повреждения.
  • выдать число полученных повреждений.
Ньюансы:
  1. Созданный триггер в любом случае будет уничтожен через 0.7 секунд (а то мало ли, может быть атака была отменена).
  2. Первое действие функции AttackedUnitDamage - это отключить текущий триггер (т.е. триггер, установивший факт повреждения). Чтобы он не выполнился повторно. Ведь сколько раз юнит атаковали, столько триггеров-детекторов повреждения будет создано. Так что нет необходимости дублировать действия.
Влад, посмотри пример. Не знаю, какое он произведет впечатление, но не спеши с выводами. Дело в том, что система в целом довольно корявая. Например, повреждение юниту может быть нанесено при помощи заклинания. А если в это время юнит был атакован, то вполне возможен случай, когда повреждения от заклинания будут ошибочно приняты за удар от другого юнита. А триггер то отключится... Или скажем юнит атакован несколькими другими юнитами. Создается несколько триггеров-детекторов, которые при ударе все сработают одновременно и затем отключатся (а следовательно, пропустят повреждения, которые последуют в следующие 0.01-0.7 секунд.
Естественно, этот пример можно улучшить. И мы это обязательно сделаем в скором времени. Если я не ошибаюсь, такая наработка позволит заметно упростить триггеры твоего проекта.
Примечание:
  • на основе этого эффекта я создавал триггерные заклинания типа магическая броня (поглащающая n повреждений юнита), а также заклинание пассивной magic shield - который потребляет ману юнита при повреждениях (если маны больше 0).
Старый 07.06.2005, 18:35
vlad_C0M
Босс DarkSkyTeam
offline
Опыт: 10,459
Активность:
Серега! Все супер! :) Разбираюсь потихоньку! Уже сейчас начал разбирать твои примеры. :) Ошибки понял! =)
Старый 08.06.2005, 17:47
Sergey
Старейший
offline
Опыт: 44,363
Активность:
СОБЫТИЯ С МАЛЫМ ПЕРИОДОМ
Влад, предлагаю рассмотреть еще один пример. Напрямую он конечно не связан с 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. Вообще говоря, эти действия можно было делать и при помощи обычных триггеров, но при этом возник бы очень неприятный эффект - утечка памяти. Что это такое, чем вызвано и как с этим бороться - напишу в следующий раз.
Старый 09.06.2005, 09:01
Sergey
Старейший
offline
Опыт: 44,363
Активность:
temp
ОПТИМИЗАЦИЯ: УТЕЧКИ ПАМЯТИ
Влад, некоторое представления на эту тему ты уже имеешь. Например, известный факт, что если не удалять созданные спецэффекты, то игра через некоторое время начнет сильно тормозить. Поэтому, даже если спецэффект мгновенного действия и через некоторое время уже не виден, его все равно нужно удалять. Почему так происходит? Потому что каждый спецэффект - это игровой объект. Когда мы создаем новый спецэффект, он попадает в память. Если его не удалять, то он останется в памяти до конца игры.
Аналогичная история с юнитами в ТД, Дотах или Аеонах. Умершие юниты должны быть удалены действием 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. Т.е. это и есть полярные координаты.
Но в чем преимущество со стандартной функцией полярных координат? Преимущество в том, что МЫ НЕ СОЗДАЕМ НОВУЮ ТОЧКУ, чтобы вычислить полянрыне координаты. Вместо этого мы ПЕРЕМЕЩАЕМ СУЩЕСТВУЮЩУЮ ТОЧКУ.
Есть конечно еще один ньюанс. Обрати внимание на команду:
set p = GetUnitLoc(u).
Присваиваем точке p положение юнита u. Но я уже говорил, что переменная со ссылко на объект и сам объект - это разные вещи. Так вот, до этого действия, переменная p была пустой. Точка с положением юнита не существовала. После действия, переменная p начала ссылаться на какую-то точку. Какой вывод? Игра создала объект типа точка и сделала на него сслыку в переменной p. Т.е. даже когда ты используешь элементарную функцию - определит положение юнита, в игре создается объект. И он тоже засоряет память - и это тоже станет заметно в триггерах с малым периодом.
Чтобы окончательно избавиться от утечки, необходимо применить действие для удаления объекта типа точка из памяти. В триггерах ты такой команды не найдешь. В jass эта команда выглядит следующим образом:
call RemoveLocation(p)
Можешь посмотреть, как это сделано в примере, который я выслал в прошлый раз.
Ну, вроде с утечкой из-за точек разобрались. Но, увы, есть и другие виды утечек. Группы юнитов. Вообще-то есть переменные типа unit group. Это из той же серии. Переменная есть ссылка. А на что ссылается переменная uniy 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 и разберем работу наработки Димона - системе SCV.
Старый 09.06.2005, 09:10
Sergey
Старейший
offline
Опыт: 44,363
Активность:
return bug

RETURN BUG
Прежде всего, нужно рассказать немного о том, что собой представляет компьютерная память. Влад, может быть ты и так это знаешь, но на всякий обрисую картину. Все без исключения игровые объекты - юниты, строки, числа - все это находится в компьютерной памяти. Память пердставляет собой просто последовательность идущих подряд ячеек, в каждую из которых записано определенное число. При помощи чисел можно закодировать все - и юниты и строки. Разные объекты занимают разное количество ячеек. Ведь чтобы записать объект юнит, нужно записать целую кучу информации о всех его параметрах. Это гораздо больше информации, чем то, которое несет какое-то значение типа 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)? Ну например, для наших функций. Зачем нам может понадобиться узнавать номера для регионов? Я этому сходу нашел следующее применение. В редакторе waorldedit, к сожалению, невозможно простым и быстрым способом занести созданные регионы в массив. А ведь иногда бывает нужно. Скажем, на твоей карте несколько сотен регионов, в которых должны появиться одни и те же монстры и отправиться в патруль в следующую точку. Проще всего делать это через массивы с циклами. Но заносить вручную сотни регионов в массив - тоже приятного мало.
А я сделал предположение, что регионы, которые мы создаем в редакторе, занимают соседние ячейки памяти. Т.е. если создали мы первый регион - он попал в ячейку X, тогда следующий регион попадет в ячейку X+1 и т.д. Т.е. если мы знаем номер первого региона и остальные регионы создавали в определенном порядке, мы можем при помощи RB найти все остальные регионы и занести их в массив на автомате.
Итак, сначала при помощи функции FindHandle мы находим номер первого региона, затем при помощи функции FindRegion в цикле заносим все остальные регионы в массив. Элегантное решение нудной задачи :).
Но конечно RB имеет гораздо более ценное применение. Дело в том, что все ячейки памяти имеют сквозную нумерацию. Т.е. благодаря RB мы можем сопоставить всем объектам их номера. И все эти номера будут уникальны. Совпадений не будет. Эту нумерацию можно использовать, чтобы... Впрочем, это тема другой части :).

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

Старый 09.06.2005, 09:15
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Система 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 ты поднимешься на следующую ступень мастерства.
Старый 09.06.2005, 09:17
vlad_C0M
Босс DarkSkyTeam
offline
Опыт: 10,459
Активность:
ААА! Я В ШОКЕ! :) КУРЮ СТАТЬИ. Делаю выводы! Думаю джасс это НЕЧТО БОЛЬШЕЕ ЧЕМ Я ПРЕДСТАВЛЯЛ! ... ОГРОМНОЕ СПАСИБ ЗА СТАТЬИ..... ПЕРЕВАРИВАЮ...... ДУМАЮ СДЕЛАЮ МИНИ ПРОВЕРКУ САМ СЕБЕ, СДЕЛАЮ МИНИ-КАРТУ с проверкой всех твоих систем. Чтобы убедится правильно ли я их понял!

vlad_C0M добавил:
ПОЛЬЗУЯСЬ почти всеми познаниями которые ты описал.... ДаЮ ОГНЕМЕТ :)!!!!! Свой! Фирменный!
Моя ВТОРАЯ наработка на джасс!
Старый 09.06.2005, 22:29
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Отлично, пример посмотрел. Действительно рабочий и красивый огнемет. Но нет предела совершенству :). Можно переделать его на триггер с малым периодом, а также удалить утечки. А утечек пока полно - и полярыне координаты, и группы юнитов.
И с повреждениями можно сделать покруче. А так - отлично.
Влад, изучай все постепенно, не особо торопись. Фактически, когда разберешься с SCV, считай, что теория осталась позади. Дальше будут только задачи и их решения.
Старый 09.06.2005, 23:53
vlad_C0M
Босс DarkSkyTeam
offline
Опыт: 10,459
Активность:
Sergey : Неа! С юнит группами я поработал. Я удаляю каждую юнит группу по завершению! =) Там после каждого call - pick unit group - стоит destroylastcreatedgroup ;;;
Осталась утечка связанная с полярными координатами :)! Сегодня буду тренироватся.. Учится пользоватся полярными координатами. ... Эээх .. Неужели мне придется столько заклинаний переписывать :)! У меня полным полно полярных радиусов в заклинаниях
Старый 10.06.2005, 09:14
Sergey
Старейший
offline
Опыт: 44,363
Активность:
Да здравствует SCV!

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

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

Т.е. если юнит находится под постоянной атакой, для него будет создан триггер, отлавливающий повреждения. Этот триггер будет работать до тех пор, пока пауза между атаками на юнит не станет больше 2-х секунд. Так что будет некоторая экономия: юнит атакуется - для него создается триггер. Не атакуется - триггер уничтожается.

Как это реализовать? Во-первых, мы можем при помощи SCV сопоставить атакуемому юниту триггер, который создается, чтобы отлавливать повреждения. Что касается определения, прошло ли 2 секунды между атаками на юнит, мы сделаем это при помомщи таймеров. Вместе с триггером определения повреждений, для каждого юнита будет создаваться таймер на 2 секунды. Если юнит атакован повторно до того, как таймер истек - то ставим таймер опять на 2 секунды. Если таймер истекает - значит юнит не был атакован в течении 2 секунд - тогда мы уничтожаем и триггер и таймер. Кстати, таймер мы сопоставляем юниту также как и триггер.

Влад, если ты внимательно смотрел SCV, там не было функций для работы с триггерами и таймерами. Но в том то и достоинство системы 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

Вот и все. Теперь мы сможем сопоставлять триггеры и таймеры. С решением задачи рекомендую ознакомиться в сценарии Test2a.

Прокомментирую некоторые моменты. Как происходит проверка, существует ли триггер-детектор для юнита? Если триггерсоздан, он должен быть записан по определенному адресу (cv "damtr" для юнита). Проверяем, что записано по этому адресу. Если записано null - т.е. триггера там нет, то значит триггер нужно создавать. А вместе с ним и таймер. Если же триггер уже создан, то нужно просто продлить таймер.

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

Влад, как насчет новых методов в проекте? К примеру, ты пишешь все боевые параметры героев в массивы. А ведь при помощи SCV можно писать непосредственно в cv для героя. Это будет значительно экономнее, чем массив. Ведь в массиве 8000 записей, из которых ты используешь не более 12-ти. Да и к тому же в карте сложно ориентироваться, когда в ней десятки массивов с переменными.

Вспомним еще одну задачу, с которой были сложности. Это задача по созданию юнита-снаряда, летящего из одной точки в другую. Для того, чтобы организовать такое движение, мы использовали триггер с событием Time periodic. Неудобство было в том, что приходилось помещать юниты-снаряды в массивы. Мы ведь не могли сопоставить триггеру нужный юнит. А теперь можем.

При помощи SCV эта задача решается гораздо проще. При каждом применении заклинания, мы СОЗДАЕМ новый триггер с событием Time periodic и СОПОСТАВЛЯЕМ этому триггеру юнит снаряд. Что это нам дает? При каждом запуске периодического триггера, мы помещаем в локальную переменную сопоставленный юнит снаряд и двигаем этот снаряд дальше. Когда снаряд достигает цели, мы просто уничтожаем триггер, предварительно стерев из кеша указательн на юнит, сопоставленный этому триггеру.

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

Можно глянуть здесь
http://forum.wc3.ru/showthread.php?t=10999
12 пост.

Отредактировано Sergey, 10.06.2005 в 10:10.
Старый 10.06.2005, 10:01
Ответ

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

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

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

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



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