Форум портала о WarCraft Форум портала о WarCraft
порно чат

Вернуться   Форум портала о WarCraft > Вселенная WarCraft > World Editor > Статьи
Регистрация Справка Пользователи Календарь Поиск Сообщения за день Все разделы прочитаны

Ответ
 
Опции темы Опции просмотра
Старый 24.07.2010, 22:36   #1
VAV
Pandora Directive
 
Аватар для VAV
 
Регистрация: 04.09.2002
Адрес: Area 404
Сообщений: 3,611
VAV как роза среди колючек VAV как роза среди колючек VAV как роза среди колючек
Отправить сообщение для VAV с помощью ICQ
По умолчанию Улучшенная версия огнемета...

Улучшенная версия огнемета...



Эту тестовую карту уже многие видели на "Башне Знаний", теперь я расскажу как устроен скрипт, управляющий огнеметом. Я уже рассказывал, как сделась простой огнемет - по крайней мере, как сделать повреждения от огня. В этот раз поставим перед собой такие задачи:

1. Сделать splash-повреждение. Пусть загораются монстры, стоящие в определенном радиусе от цели.
2. Сделать у каждой атаки random-повреждение, а не постоянное, как было в прошлой версии.
3. Сделать приличные спецэффекты как для самой атаки, так и после.

Перед изучением этой статьи обязательно ознакомьтесь с огнеметом, подробно описанным в одном из предыдущих примеров - многие очень важные детали я не буду повторять. Для начала скажу - в алгоритме старого огнемета есть один важный недостаток, который мы устраним в этом алгоритме. Итак, напомню принцип действия старого огнемета: при атаке на юнита мы проверяем, находится ли он в специальном массиве горящих юнитов. Если нет, то заносим в массив и поджигает. Если да, то ничего не делаем. Как только юнит сгорит или закончится время горения, мы вызывали функцию RemoveRecord(GetURef(InFire)), которая удаляла запись юнита из массива. Однако алгоритм этой функции был построет не очень правильно с точки зрения оптимизации и скорости работы. Как работала предыдущая функция удаления записи из массива? Вот пример:
xxxdxx000 - массив, x - ячейка с каким-то значением, 0 - пустая (а ней null), d - эту ячейку надо удалить. Удаление происходило за счет пошагового перемещения ячейки вправо к концу массива:
xxxdxx000 -> xxxxdx000 -> xxxxxd000 -> xxxxx0000. Это далеко не самый рацианальный вариант, а в нашем примере вообще абсолютно непригодный - если мы решили делать splash, то с массивом одновременно будет работать много триггеров из очереди и в результате этого очень высока вероятность появления ошибки. Мы сделаем все гораздо проще - не будеv использовать RemoveRecord вообще! Когда надо будет удалить запись из массива, мы ей просто присвоим null и все! Теперь рассмотрим этот пример:
xxxdxx000 -> xxx0xx000. В результате у нас получился массив, состоящий из двух частей. С каждым удалением пустых ячеек будет все больше - пока весь массив не станет пустым. Если в это время мы вызовем функцию GetURef(null) - она вернет номер первой пустой ячейки. ОДнако нам необходимо немного переписать эту функцию, чтобы она могла нормально работать с прерывающимся массивом (в котором встречаются значения null - старая функция выходила на них и тем самым мы автоматически не могли определить следующие после этого null ячейки). Итак, вот алгоритм с новой функцией. Добавлены две новые глобальные переменные:

Использованы глобальные переменные:

gg_trg_FireDmgSplash - Trigger - Этот скрипт.
udg_FD_Action (Trigger) - Триггер, отвечающий за горение конкретного юнита.
udg_FD_Smoke (Trigger) - Триггер, создающий дым и остатки юнита.
udg_FD_Units (Unit Array) - Массив, в котором храним горящих юнитов.

// ---------------------------- Начало скрипта -----------------------------

function GetURef takes unit URef returns integer
// Определение такое же, как и в прошлом примере.
local integer Size = 1
// Size - текущая ячейка массива. Мы начинаем счет не
// с нулевого элемента для сокращения алгоритма и
// чтобы сделать его безопасным. В нулевую ячейку массива
// можно писать, когда у нас все ячейки заполнены, но
// мы не нашли совпадения, функция вернет AtNull
// (см. в конце функции), который в этом случае будет
// равен нулю.
local integer AtNull = 0
// AtNull - здесь храним номер первого попавшегося null
// в массиве. Это надо на случай, если мы не нашли совпадения
// тогда мы запишем юнита не в конец массива, а в первый null.
loop
exitwhen (udg_FD_Units[Size] == URef) or (Size >= 500)
// Условие выхода другое - или совпадение ячейки с аргументом
// или когда номер ячейки достигнет значения,
// определяемого константой 500. Вместо этой
// константы лучше поставить что-то более реалистичное,
// например, 50 или 100 - этого вполне хватит.
if (udg_FD_Units[Size] == null) and (AtNull == 0) then
// Проверка на то, что элемент - null. Второе условие надо
// только для однократной записи - так как мы начинаем
// перебирать ячейки с единицы, то после первой записи
// AtNull != 0 и мы сюда уже ничего не запишем.
set AtNull = Size
// Запоминаем номер первого null.
endif
set Size = Size + 1
// Переходим к следующей ячейке - увеличиваем номер.
endloop
if udg_FD_Units[Size] == URef then
// Проверяем - если был выход по совпадению, значит
// возвращаем номер совпадения.
return Size
else
return AtNull
// Если совпадения не было и мы перебрали все разрешенные ячейки,
// то возвращаем номер первого null в массиве.
endif
endfunction

function CreateSmoke takes nothing returns nothing
// А это триггер, создающий дымящиеся останки после сгорания юнита.
// Они появляются не всегда, а с определенной вероятностью, о которой
// будет рассказано дальше. Этот триггер срабатывает на собитие, когда
// кто-то убил юнита (вообще любого юнита на карте, не обязательно огнеметчиком).
local unit URef = GetDyingUnit()
// В эту переменную заносим любого убитого юнита.
local effect Smoke
// В эту переменную запомним дым.
local effect Trash
// А в этой будет спеццэффект, отвечающий за скелет или пепел.
if udg_FD_Units[GetURef(URef)] == null then
// А теперь проверяем, есть ли убитый юнит в масиве горящих юнитов.
// Если в номере ячейке, который вернула функция GetURef находится
// null, это значит, что юнита или нет в массиве, или он был оттуда
// уже удален (например, если огонь на нем погас).
return
// Если юнита нет в массиве, то выходим
endif
if IsUnitType(URef, UNIT_TYPE_ANCIENT) then
// Это условие надо для того, чтобы после сгорания деревьев эльфов
// оставались не скететы, а труха. Обратите внимание на
// функцию I2S(GetRandomInt(0,1)) - она вернет 0 или 1 в виде текста
// и прибавит этот текст к названию модели. Дело в том, что у некоторых
// моделей в Варкрафте есть вариации - у таких моделей в конце файла
// есть номер, показывающий конкретную вариацию, например:
// Model0
// Model1
// Model2 и так далее.
// При помощи произвольного числа мы сделали так, что у нас будут
// в каждом случае появляться произвольные вариации каждой модели.
set Trash = AddSpecialEffectLocBJ(GetUnitLoc(URef)," Doodads\\LordaeronSummer\\Plants\\CornWheatScorche
d\\CornWheatScorched" + I2S(GetRandomInt(0,1)) + ".mdl")
// Создаем спецэффект с моделью CornWheatScorched (она лучше всего
// подходит для обгоревших деревяшек) - на случай, если юнит - дерево эльфов.
else
set Trash = AddSpecialEffectLocBJ(GetUnitLoc(URef)," Doodads\\Ashenvale\\Props\\ScorchedRemains\\Scorch
edRemains" + I2S(GetRandomInt(0,1) * 2) + ".mdl")
// Во всех остальных - скелеты в разных вариациях. GetRandomInt(0,1) * 2
// вернет только два значения - 0 или 2. Умножением на 2 мы исключили
// ненужную вариацию - под номером 1. Проверьте сами - единица никогда
// не выпадет.
endif
set Smoke = AddSpecialEffectLocBJ(GetUnitLoc(URef)," Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\Smok
eSmudge" + I2S(GetRandomInt(0,2)) + ".mdl")
// Создаем дым с вариацией на месте юнита и присваиваем этот спецэффект
// локальной переменной Smoke.
call TriggerSleepAction(GetRandomReal(30.00, 40.00))
// Ждем произвольное время от 30 до 40 секунд - за это время созданные
// остатки (скелет или труха) видны и дымятся.
call DestroyEffect(Smoke)
// Убираем дым.
call TriggerSleepAction(GetRandomReal(1, 5))
// Ждем от 1 до 5 секунд.
call DestroyEffect(Trash)
// Убираем остатки с поля боя.
endfunction

function FireConditions takes nothing returns boolean
// Проверка на то, что атакующий - огнеметчик.
// Эта функция - условия триггера, отвечающего за
// огнемет.
return GetUnitTypeId(GetAttacker()) == 'zcso'
// Возвращаем совпадение типа атакующего юнита с огнеметчиком.
endfunction

function Trig_FireDmg_Actions takes nothing returns nothing
// А вот и функция действия огнемета. Она отличается от
// аналогичной функции в старом огнемете!
local unit InFire = GetEnumUnit()
// Вот первое отличие - вместо атакуемого юнита мы берем
// GetEnumUnit() (аналог в редакторе триггеров - picked unit в
// конструкции pick every unit and do action ...). Такая замена
// нужна для splash-повреждения. Мы будем брать каждого юнита
// в определенном радиусе от места атаки, проверять, является ли
// он врагом огнеметчика и если является - то будем применять
// на него триггер, действием которого является эта функция.
// Этот триггер вызывается из действий другого через функцию
// ForGroupBJ, поэтому и нужно использовать GetEnumUnit().
local integer Index = GetURef(InFire)
// А эта переменная - номер горящего юнита в массиве.
local boolean CanSleep = UnitCanSleep(InFire)
// Переменная - может ли юнит спать. Пока он горит,
// мы будем отключать у юнита эту возможность.
local integer NumSteps = GetRandomInt(40,60)
// А сюда помещаем произвольное число от 40 до 60 - таким образом
// разные юниты будут гореть разное время. Это число - число шагов
// в цикле, где юниту наносятся повреждения от огня.
local effect UEffect = null
// Сюда помещаем спецэффект - огонь на горящем юните.
local effect EEffect = null
// А в эту локальную переменную - дым на юните.
local real Life
// Здесь мы будем хранить количество жизней, оставшихся у юнита.
if (udg_FD_Units[Index] != null) or (IsUnitEnemy(InFire, GetOwningPlayer(GetAttacker())) == false) then
// А теперь проверяем, если в ячейке, которую вернула функция поиска
// в массиве не null, то юнит там есть и это значит, что он уже горит.
// Поэтому выходим и повторные поджоги невозможны, пока огонь
// не погаснет. То же самое, если юнит - не враг огнеметчика.
return
else
set udg_FD_Units[Index] = InFire
// Если юнита в массиве нет, то записываем в пустую ячейку
// юнита. Таким образом мы отключаем все следующие за
// этим действием поджоги (см. пред. огнемет)
endif
call UnitAddSleep(InFire,false)
// Отключаем у крипов возможность спать.
set Index = 1
// Переменная Index нам уже не нужна, поэтому используем ее
// как номер шага в цикле и приравниваем ей для начала единицу.
set UEffect = AddSpecialEffectTargetUnitBJ("chest",InFire," Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFi
reDamage.mdl")
// Добавлям на юнита огонь и запоминаем его в локальную переменную.
set EEffect = AddSpecialEffectTargetUnitBJ("head",InFire," Doodads\\LordaeronSummer\\Props\\SmokeSmudge\\Smok
eSmudge1.mdl")
// Делаем то же самое с дымом.
loop
set Life = GetUnitState(InFire,UNIT_STATE_LIFE) - GetRandomReal(6,12)
// А теперь в цикле начинаем уменьшать жизни у юнита. Записываем в
// локальную переменную Life уже уменьшенное значение. Значение -
// произвольное от 6 до 12.
exitwhen (Index > NumSteps) or (Life <= 0)
// И выходим из цикла тогда, когда это значение будем меньше нуля
// (то есть юнит сгорел или его убили) или когда мы пройдем все шаги
// (то есть время вышло и огонь погас).
call SetUnitState(InFire,UNIT_STATE_LIFE,Life)
// Если не вышли на предыдущем шаге, то устанавливаем юниту
// уменьшенное значение Hit Points.
call TriggerSleepAction(0.1)
// Ждем 0.1 сек. - в результате полный цикл
// будет занимать от 4 до 6 секунд.
set Index = Index + 1
// прибавляем к номеру шага единицу.
endloop
if IsUnitAliveBJ(InFire) then
// А эта проверка нужна, чтобы избажать некоторых глюков -
// уберите полностью это условие и посмотрите, что получится
// Условие - юнит живой после выхода из цикла. Ведь его могли
// убить, пока крутился цикл.
call UnitAddSleep(InFire, CanSleep)
// Возвращаем возможность спать, если она была.
if Life <= 0 then
// А это условие нужно на случай, если мы вышли из
// цикла не по достижению максимального количества шагов,
// а по нехватке жизней у юнита. Life <= 0 не значит, что
// юнит умер, ведь это "виртуальное значение", которое мы
// самому юниту не присвоили т.к. вышли из цикла. Ведь присвоение
// этого значения идет после проверки этой переменной.
call AddSpecialEffectLocBJ(GetUnitLoc(InFire)," Objects\\Spawnmodels\\Human\\FragmentationShards\\
FragBoomSpawn.mdl")
// Добавляем на месте юнита маленький взрыв, он хоть и
// не очень заметен, но все равно красиво смотрится.
call ExplodeUnitBJ(InFire)
// Взрываем юнит.
endif
endif
// Окончаните условия IsUnitAliveBJ(InFire).
call DestroyEffect(UEffect)
call DestroyEffect(EEffect)
// После проверки мы убираем оба спецэффекта. Эти строки
// должны выполняться во всех случаях. Если их поместить
// внутрь предыдущего if'а, то убитые герои после возрождения
// все равно будут гореть, хоть и без уменьшения жизней!
call TriggerSleepAction(0.3)
// Ждем 0.3 секунды перед тем, как уничтожить запись в массиве.
// Если этой строки не будет, то у нас не успеет сработать
// триггер, который создает на месте убитого юнита дым.
// Чем больше это число, тем больше вероятность создания дыма.
// Такой своеобразный random - вероятность выполнения
// зависит от загруженности очереди триггеров.
set udg_FD_Units[GetURef(InFire)] = null
// После этого мы заново определяем номер горящего юнита в
// массиве и уничтожаем эту запись путем присваивания null.
endfunction

function CreateSplash takes nothing returns nothing
// Эта функция несмотря на свое название, не создает сам
// splash. Она применяется для запуска триггера, который
// отвечает за повреждения от огня для каждого юнита,
// к которому она применяется.
call TriggerExecute(udg_FD_Action)
// Вызываем сам триггер повреждений. Функцию действий
// триггера вызывать нельзя - не будет работать, надо
// делать именно как триггер, действующий на каждого юнита.
endfunction

function Trig_FireDmg_Enum takes nothing returns nothing
// А вот эта функция - действия триггера, отвечающего за огненную атаку.
// При атаке мы создаем спецэффект взрыва и большого огня, после чего
// берем всех юнитов на заданном расстоянии и для каждого вызываем
// предыдущую функцию.
local effect Expl = AddSpecialEffectLocBJ(GetUnitLoc(GetAttackedUnitBJ
())," Objects\\Spawnmodels\\Other\\NeutralBuildingExplos
ion\\NeutralBuildingExplosion.mdl")
// Создаем спецэффект на месте атакуемого юнита.
call ForGroupBJ(GetUnitsInRangeOfLocAll(350.00, GetUnitLoc(GetAttackedUnitBJ())), function CreateSplash)
// Берем юнитов в радиусе 350 ед. от места атаки огнеметчика и
// применяем к каждому функцию CreateSplash, которая запустит
// триггер поджога.
call TriggerSleepAction(5)
// Ждем 5 секунд - чтобы большой огонь погас.
call DestroyEffect(Expl)
// И удаляем этот спецэффект.
endfunction

function InitTrig_FireDmgSplash takes nothing returns nothing
// А эта функция инициализации всех триггеров, отвечающих за огнемет.
set udg_FD_Action = CreateTrigger()
// Создаем триггер, отвечающий за горение каждого юнита.
set udg_FD_Smoke = CreateTrigger()
// Создаем триггер, отвечающий за дым и остатки на месте
// сгоревшего юнита.
set gg_trg_FireDmgSplash = CreateTrigger()
// Создаем триггер, отвечающий за splash-повреждение
// области при атаке огнеметчика.
call TriggerRegisterAnyUnitEventBJ(udg_FD_Smoke, EVENT_PLAYER_UNIT_DEATH)
// Регистрируем событие для триггера, создающего дым и
// остатки - любо юнит убит.
call TriggerAddAction(udg_FD_Action, function Trig_FireDmg_Actions)
// Для триггера, отвечающего за горение юнитов
// регистрируем функцию - действие.
call TriggerRegisterAnyUnitEventBJ(gg_trg_FireDmgSplash
, EVENT_PLAYER_UNIT_ATTACKED)
// Для триггера, создающего splash регистрируем сюбытие -
// любой юнит атакован
call TriggerAddCondition(gg_trg_FireDmgSplash, Condition(function FireConditions))
// Регистрируем условие для splash-триггера: атакующий юнит - огнеметчик.
call TriggerAddAction(gg_trg_FireDmgSplash, function Trig_FireDmg_Enum)
// Для splash-триггера регистрируем функцию - действие.
call TriggerAddAction(udg_FD_Smoke, function CreateSmoke)
// То же самое делаем для триггера, отвечающего за дым.
endfunction

// ---------------------------- Конец скрипта -----------------------------

Пример прилагается. Если что-то непонятно, смотрите статью про старый огнемет - там я подробно расписывал многие детали.

Автор: Caсоdemon
© WC3.RU, 2002-2010 гг
Нашли ошибки и недоработки в статье? Сообщите нам в раздел Поддержки! С уважением, WC3.RU
__________________
Смотри подругому !
VAV вне форума   Ответить с цитированием
Старый 12.08.2010, 14:39   #2
VAV
Pandora Directive
 
Аватар для VAV
 
Регистрация: 04.09.2002
Адрес: Area 404
Сообщений: 3,611
VAV как роза среди колючек VAV как роза среди колючек VAV как роза среди колючек
Отправить сообщение для VAV с помощью ICQ
По умолчанию Ответ: Улучшенная версия огнемета...

Другие статьи:

Основы JASS.
JASS - Общие понятия.
Синтаксис JASS.
JASS - Библиотечные функции. Часть 1.
JASS - Библиотечные функции. Часть 2.
JASS - Операторы.
JASS - Выражения
JASS - Комментарии.
JASS - Функции
JASS - Типы
JASS - Заключение.
Faq по созданию рельефа.
Зачем так много триггеров?
Немного об оптимизации кода.
Текстовые триггеры
Создаем простейшие карты (карты для melee)
Событие с переменным периодом.
Познаем триггеры (Triggers)
Познаем регионы (locations)
Познаем переменные...
Отлавливаем двойной клик мышью.
Массивы и циклы в обычных и текстовых триггерах
Локальные переменные в Custom Text
Камеры, спецэффекты, карта, цвета, кэш...
Добавление своих функций в триггеры
Добавление своих моделей и др. в карту
Делаем очень большую карту...
Делаем огнемет.
Улучшенная версия огнемета...
Глюки worldeditor'a
Random с задаваемой вероятностью - аура.
__________________
Смотри подругому !
VAV вне форума   Ответить с цитированием
Ответ


Опции темы
Опции просмотра

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

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


Часовой пояс GMT +4, время: 19:53.

Design Developed by CompleteGFX
vBulletin® Version 3.6.7.
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.
Перевод: zCarot