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

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

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

Делаем огнемет.

В этой статье я расскажу о том, как сделать огнемет на скрипте. Речь пойдет не о создании красивых спецэффектов (ограничимся только небольшим огнем на горящем юните), а об алгоритме повреждения от огня. Саму огненную атаку можно сделать несколькими способами, например установить нужный projectile art в свойствах юнита или сделать перехват на триггерах (если делаем огнемет как магию). Однако обычно возникает проблема - как сделать, чтобы юнит горел заданной время с нанесением повреждений от огня. Сразу скажу, что это достаточно просто реализуется на обычных триггерах. Можно сделать триггер, который запускается при атаке огнеметчика на юнита, а действиями этого триггера будет насесение повреждений от огня через Unit - SetUnitLife и Wait в цикле заданное число раз. Однако для сбалансированной игры такой огнемет не подойдет - можете это проверить - при каждой новой атаке на одного и того же юнита повреждения от огня будут суммироваться, а кроме того, спецэффекты будут накладываться друг на друга и портить всю картину. Если у такого огнемета общее повреждение равно 500, то после 10 атак огнеметчика оно станет равным 5000, а это уже больше походит на чит. Поэтому мы займемся созданием огнемета, для которого повреждения от огня не суммируются. Если юнит горит, то новые атаки огнеметчика будут наносить только повреждение от атаки, но не от огня.
Есть много алгоритмов, при помощи которых можно сделать огнемет. Я расскажу о самом простом, но и обладающем минимальными возможностями, которых однако хватит для большинства случаев.
Перед рассказом об алгоритме я более подробно расскажу о триггерах и массивах.

- Очередь триггеров.
Многие мапперы, создающие карты, не всегда имеют представление об очереди триггеров. Очередь триггеров (trigger queue) можно представить как аналог многозадачности Windows. В очереди находятся триггеры, которые выполняются параллельно, причем очень часто в очереди находятся несколько копий одного и того же триггра, но работающие с разными объектами. Пример: триггер срабатывает когда юнит заходит в регион, а в действиях триггера используется ссылка на Entering Unit. При каждом вхождении юнита в регион (например, зашли несколько юнитов или один за другим) запускается этот триггер, добавляется в очередь, но Entering unit в функциях каждого будет указывать только на того юнита, который вызвал событие. Если зашли DH, MK и талон, то в очередь добавятся три копии одного и того же триггера, но каждый будет работать со 'своим юнитом'. В нашем случае очередь понадобится нам для нанесения повреждений каждому горящему юниту - мы сделаем один триггер, который срабатывает при атаке на юнита. Понятно, что копия этого триггера будет применятся к каждому поджигаемому юниту и добавляться в очередь.

- Массивы.

JASS представляет колоссальные возможности для работы с массивами по сравнению с редактором триггеров. Сначала я расскажу более подробно, как устроены массивы. Начальные сведения о них можно найти в первой статье. Итак, массив - набор элементов одинакового типа. Каждому элементу присваивается номер в массиве - 1,2,3 и т.д. Допустим, мы поместили в первый элемент integer-массива целое число. Потом можно будет это число прочитать или изменить. Но вот что будет, если мы обратимся ко второму элементу массива, куда ничего не заносили? Если это сделать на триггерах, игра радостно вылетает с ошибкой "memory couldn't be read". Почему? Дело в том, что все элементы массива изначально содержат в себе значение null независимо от типа массива. Null - это понятие, которое можно представить как 'пустота'. Когда мы пытаемся прочитать или работать с таким значением, возникает ошибка в программе и Варкрафт вылетает в OS. Однако это значение можно очень выгодно использовать в своих целях (подробнее см. в статье 'Опасный секс и двумерные массивы на JASS', которую я может быть напишу ). Null содержат не только пустые элементы массива, но также и некоторые переменные (тоже писал об этом раньше), например, unit или special effet. Пока им не присвоить какое-то конкретное значение, они будут равны null. Когда мы присваиваем какому-то элементу массива значение, оно заменяет собой null и с ним уже можно нормально работать. Массив в начале можно представить так:

nnnnnnnnnnnn...... (состоит из одних null)

Если мы запишем в третий элемент значение, то массив будет таким (V - значение):

nnVnnnnnnnnn..... (3-й элемент не null)

Значение null можно использовать в операторах, как аргумент у функций (если аргумент можно представить как null) или приравнивать переменную этому значению (но надо делать это осторожно).

Итак, сначала продумаем основу алгоритма.

1. Делаем триггер, который срабатывает при атаке на юнита. Он будет применятся к каждому поджигаемому юниту.

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

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

Для тех, кто хочет сам написать алгоритм и не знает функций JASS, советую: сделайте обычный триггер с cобытием - юнит, принадлежащий врагу огнеметчика атакован. Если игроков - врагов много, надо добавить нужное количество событий для каждого игрока. В скрипте можно сделать все проще - проверять в игре, является ли игрок врагом игрока, которому принадлежит огнеметчик. В примере я сделал самое простое событие - триггер будет срабатывать при атаке на крипов (neutral hostile). Условие - проверка на то, что атакующий юнит - огнеметчик. Затем тирггер переводим в текст и начинаем редактировать. Далее я привожу текст этого скрипта с подробными комментариями. Скрипт надо смотреть с ПОСЛЕДНЕЙ ФУНКЦИИ!!! (почему - см. статью вначале об отсутствии форвардинга в JASS).

Сам скрипт называется FireDmg
Использована глобальная переменная: FD_Units - unit array.

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


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

function GetURef takes unit URef returns integer
// Эта функция ищет юнита URef в массиве юнитов
// FD_Units и возвращает integer, который является
// номером юнита в массиве. Если юнит не найдет, но
// возвращает номер первого пустого (null) значения
// в этом массиве (размер массива + 1).
local integer Size = 1
// Это рабочая локальная переменная, указывает
// номер текущего элемента.
loop
// Цикл, к котором последовательно перебираем все
// элементы массива, начиная с первого.
exitwhen (udg_FD_Units[Size] == null) or (udg_FD_Units[Size] == URef)
// Выходим из цикла когда элемент массива с
// номером Size пустой - равен null или когда он
// совпадает с юнитом, который мы ищем (URef).
// ВНИМАНИЕ!!! В таких конструкциях сначала
// должна идти проверка на null, а потом на юнита!!!
// Иначе игра может вылететь - дело в том, что если
// первый агрумент оператора 'или' (or) равен true,
// то нет смысла проверять второй. Все равно or в
// этом случае будет равен true. Таким образом, если
// мы нашли null, то второе условие проверять не
// будем. Иногда это критически важно - избежать
// проверки второго условия. Когда играетесь с null -
// помните это!
set Size = Size + 1
// Если не вышли из цикла в предыдущей строке, то
// прибавляем к Size единицу и начинаем все снова.
endloop
// Обозначает конец цикла.
return Size
// Возвращаем значение Size, на котором произошел
// выход из цикла по условию - или элемент совпал с
// заданным или мы достигли конца массива (значение
// null)
endfunction

function RemoveRecord takes integer Ref returns nothing
// Эта функция удаляет из того же массива запись с
// номером Ref. Работает очень просто - начиная с Ref,
// перемещает все записи в цикле к концу массива.
// Это можно представить себе так. Пусть Ref == 3, Y -
// надо удалить.
// Вначале: 12Y45nn..
// Шаг 1: 124Y5nn...
// Шаг 2: 1245Ynn...
// Последний шаг: 1245nn... (мы присвоили Y на
// последнем шаге null, который идет следующим
// элементом)
local integer MaxSize = GetURef(null)
// MaxSize - размер массива, когда мы вызываем
// GetURef с null в качестве аргумента, она не найдет
// null cреди юнитов в массиве и вернет первое null
// значение (конец массива)- см. пред. функцию.
loop
set udg_FD_Units[Ref] = udg_FD_Units[Ref+1]
// Присваиваем текущему элементу значение
// следующего, начиная с номера Ref.
exitwhen Ref == MaxSize
// Выходим, когда дойдем до конца массива.
set Ref = Ref + 1
// Ref - не локальная переменная, а аргумент
// функции. Но никто не мешает ее менять.
endloop
// Конец цикла - мы удалили элемент с номером Ref,
// перебрав все элементы массива, начинающиеся с
// этого номера.
endfunction

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

function Trig_FireDmg_Actions takes nothing returns nothing
// Это главная функция - действие огнемета на
// юнитов. Применяется к каждому атакуемому юниту,
// если выполнена предудущая функция-условие.
local unit InFire = GetAttackedUnitBJ()
// В локальной переменной InFire (типа unit) храним
// ссылку на горящего юнита (которого атакуют).
local integer Index = GetURef(InFire)
// Номер юнита в массиве, если его нет в массиве - то
// номер первого null.
local boolean CanSleep = UnitCanSleep(InFire)
// Здесь храним значение, может ли юнит спать
// ночью. Сделаем так, чтобы крипы, когда горели, не
// могли спать
local effect UEffect
// Здесь мы будем хранить ссылку на спецэффект огня
// на юните (пусть он будет один)
local real Life
// Здесь храним текущее значение жизни атакуемого
// юнита.
if (udg_FD_Units[Index] != null) then
// Если элемент массива под номером Index - не
// пустой,то выходим. Если он не пустой - значит он
// уже содержит атакуемого юнита. Таким образом,
// мы предохранились от повторных поджогов. Тут
// есть один недостаток (см. далее).
return
// Выходим из функции действия (ничего дальше не
// делаем)
else
set udg_FD_Units[Index] = InFire
// В противном случае - заносим юнит в массив под
// номером Index. Из предыдущего условия ясно, что
// там хранится null, т.е. мы добавили юнита в массив.
endif
call UnitAddSleep(InFire,false)
// Если юнит может спать ночью (крип), лишаем его
// этой возможности
set Index = 1
// Переменная Index будет тепеь использована для
// других целей. Юниту будут наноситься 10
// повреждений каждую 0.1 секунду в течение 5
// секунд. В этой переменной будем хранить номер
// шага (5/0.1 = 50 шагов всего)
set UEffect = AddSpecialEffectTargetUnitBJ("head",InFire,"Doodad s\\Cinematic\\FireRockSmall\\FireRockSmall.mdl")
// Добавляем малый огонь на голову юнита и
// одновременно запоминаем его в локальной
// переменной т.к. AddSpecialEffectTargetUnitBJ
// возвращает ссылку на созданный ей эффект.
loop
// Цикл повреждения от огня.
set Life = GetUnitState(InFire,UNIT_STATE_LIFE) - 10
// В переменную на каждом шаге записываем
// значение жизней юнита минус повреждения от
// огня (10)
exitwhen (Index > 50) or (Life <= 0)
// Выходим когда жизней не осталось (<=0) или когда
// истекло время горения.
call SetUnitState(InFire,UNIT_STATE_LIFE,Life)
// Если не вышли, то уменьшаем жизни - Life
// содержит уже уменьшенное от огня значение.
call TriggerSleepAction(0.1)
// Ждем 0.1 секунду
set Index = Index + 1
// Прибавляем к номеру шага единицу.
endloop
// Конец цикла.
if Life <= 0 then
// После выхода проверяем условие - если вышли с
// жизнями, меньшими или равными нулю, то
// взрываем юнит.
call ExplodeUnitBJ(InFire)
endif
// В любом случае - взорвался юнит или время
// истекло, нам надо убрать огонь и вернуть юниту
// возможность спать (если она была).
call UnitAddSleep(InFire, CanSleep)
// возвращаем возможность спать ночью, записанную
// в переменную CanSleep типа boolean.
call RemoveRecord(GetURef(InFire))
// вызываем функцию удаления записи из массива -
// после этого юнита можно вновь поджигать.
call DestroyEffect(UEffect)
// Уничтожаем спецэффект огня.
endfunction

function InitTrig_FireDmg takes nothing returns nothing
// Эта функция создает в памяти триггер, который
// отвечает за поджигание - инициализация.
set gg_trg_FireDmg = CreateTrigger()
// Создаем триггер, до этой строки он был равен null.
call TriggerRegisterPlayerUnitEventSimple( gg_trg_FireDmg, Player(PLAYER_NEUTRAL_AGGRESSIVE), EVENT_PLAYER_UNIT_ATTACKED )
// Добавляем функцию регистрации события,
// ассоциированную с этим триггером
// (gg_trg_FireDmg). Событие будет такое - крип
// (Neutral Hostile) атакован.
call TriggerAddCondition(gg_trg_FireDmg, Condition(function FireConditions))
// Добавляем уже написаную ранее функцию условия
// - проверки, что атакующий является огнеметчиком.
call TriggerAddAction(gg_trg_FireDmg, function Trig_FireDmg_Actions)
// Добавляем уже написанную функцию действий.
endfunction


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


Вопросы, которые могли возникнуть:

1. Почему проверку на существование юнита в массиве нельзя было вынести в функцию условия?
Дело в том, что при каждой атаке проверять весь массив бессмысленно. Если атакует не огнеметчик, это будет пустая трата времени. Поэтому эта проверка находится в действиях. Также таким образом можно сократить алгоритм - нам нужно знать номер юнита. Иначе прийдется повторно вызывать эту функцию из действий когда надо удалить запись из массива.

2. Если юниту осталось гореть 0.5 секунды, а мы его снова подожгли, то счетчик горения не сбросится. Т.е. он погаснет через 0.5 секунды?
Да, это недостаток такого простого алгоритма. Если другой, более сложный алгоритм, который к тому же позволяет вызывать его из обычных триггеров. Но его в другой раз можно рассмотреть.

3. Возможна ли ошибка, когда параллельно сгоряют несколько юнитов и вызывается функция RemoveRecord разными копиями триггера из очереди в одно и то же время?
Теоретически - да, в начале действия мы запоминали номер юнита в массиве, а к концу массив мог поменяться. Ведь вся очередь триггеров работает с одним массивом. Для предотвращения этого используется конструкция call RemoveRecord(GetURef(InFire)), т.е мы повторно определяем номер горящего юнита в массива в момент вызова. Это уменьшает вероятность такого случая.

4. Как добавить спецэффекты на юнита, напирмер, дым?
Для этого надо добавить локальные переменные для каждого нового эффекта в функции действия триггера и заносить в них все созданные эффекты, чтобы потом можно было их удалить. Можно реализовать как локальный массив эффектов - тогда возможно создавать разное количество для каждого поджигания. В конце функции надо не забыть их всех удалить.

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

P.P.S. Оригинальный рельеф карты + юниты были сделаны NooDy. Я только разработал и добавил этот алгоритм.

Автор: Caсоdemon
© WC3.RU, 2002-2010 гг
Нашли ошибки и недоработки в статье? Сообщите нам в раздел Поддержки! С уважением, WC3.RU
__________________
Смотри подругому !
VAV вне форума   Ответить с цитированием
Старый 12.08.2010, 14:37   #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, время: 00:07.

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