WarCraft 3: Функция обработки движения

Создание простого stomp спелла

Функция обработки движения

Теперь, когда главная функция, которая наносит урон и запускает таймер, сделана, мы приступаем к следующему этапу: созданию циклической функции движения.
А начнем мы с извлечения из кеша данных, сохраненных другой функцией:
function Stomp_Move takes nothing returns nothing
    local string s = I2S(H2I(GetExpiredTimer()))
    local gamecache gc = udg_AbilityCache
    local real x = GetStoredReal(gc, s, "x")
    local real y = GetStoredReal(gc, s, "y")
    local integer i = GetStoredInteger(gc, s, "level")
    local group g = Stomp_CopyGroup(I2G(GetStoredInteger(gc, s, "group")))
...
Вам следует обратить особое внимание на некоторые участки этого фрагмента кода.
Во-первых, мы не записываем истекший таймер в отдельную переменную, мы напрямую используем значение, сохраненное в кэше (за исключением единственного случая, когда нам надо будет уничтожить таймер), так как он будет нужен нам только один раз при каждом выполнении функции. Мы не записываем группу в отдельную переменную по тем же причинам. Мы берем значение из кэша и копируем его в переменную g.
Загрузка из кэша оригинала группы и его модифицирование, является очень распространенной ошибкой JASS спеллов, вроде этого. Некоторые думают, что при следующем срабатывании таймера, загрузится та же группа, содержащая тех же юнитов, даже если они удаляют юнитов из группы и уничтожаю ее. Но это далеко не так.
Например, если вы убьете юнита, прикрепленного к таймеру, юнит будет мертв и при следующем срабатывании этого таймера. То же самое происходит и с группами и всеми другими объектами.
Спелл отталкивает юнита назад, в течении определенного промежутка времени, таким образом, чтобы отследить сколько времени спелл уже отталкивает юнита, нам потребуется еще одна переменная.
function Stomp_Move takes nothing returns nothing
    local string s = I2S(H2I(GetExpiredTimer()))
    local gamecache gc = udg_AbilityCache
    local real x = GetStoredReal(gc, s, "x")
    local real y = GetStoredReal(gc, s, "y")
    local integer i = GetStoredInteger(gc, s, "level")
    local group g = Stomp_CopyGroup(I2G(GetStoredInteger(gc, s, "group")))
    local real dur = GetStoredReal(gc, s, "dur")+0.05
    if dur < 1+0.5*i then
    else
    endif
...
Я добавил переменную dur типа real. В нее загружается значение, прикрепленное к таймеру с меткой 'dur' и затем ее значению увеличивается на +0.05 (временной интервал таймера).
Если вы загружаете НЕ сохраненное значение из кэша, то загруженное значение всегда будет равно 0/0.0/"" или null, в зависимости от типа.
Этот спелл толкает юнитов на протяжении 1+0.5*i (i – уровень спелла) секунд, так что нам требуется добавить блок if/then/else.
Ну что ж, давайте, добавим часть кода, которая непосредственно двигает юнитов, на которых действует спелл. Для этого добавим следующие переменные: real ux, real uy, real a, unit f.
function Stomp_Move takes nothing returns nothing
    local string s = I2S(H2I(GetExpiredTimer()))
    local gamecache gc = udg_AbilityCache
    local real x = GetStoredReal(gc, s, "x")
    local real y = GetStoredReal(gc, s, "y")
    local integer i = GetStoredInteger(gc, s, "level")
    local group g = Stomp_CopyGroup(I2G(GetStoredInteger(gc, s, "group")))
    local real dur = GetStoredReal(gc, s, "dur")+0.05
    local real ux
    local real uy
    local real a
    local unit f
    if dur < 1+0.5*i then
        loop
            set f = FirstOfGroup(g)
            exitwhen f == null
            set ux = GetUnitX(f)
            set uy = GetUnitY(f)
            set a = Atan2(uy-y, ux-x)
            call SetUnitPosition(f, ux+40*Cos(a), uy+40*Sin(a))
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
    else
    endif
...
Как и в главной функции, мы циклически перебираем всех юнитов группы, используя функцию FirstOfGroup().
Для начала, нам нужно сохранить координаты юнита.
Затем мы вычисляем угол (в радианах) между эпицентром спелла и позицией юнита, используя функцию Atan2.
Я не буду рассказывать, как эта функция устроена, я лучше расскажу, как нам ее правильно использовать.
Попросту используйте конструкцию вида Atan2(otherPointY-centerPointY, otherPointX-centerPointX), чтобы получить угол (в радианах) между точками centerPoint и otherPoint.
Спелл должен передвигать юнита. Существует два различных (лучших) способа передвигать юнита:
SetUnitPosition – Эта native функция передвигает юнита в точку с координатами X и Y. Пока юнит передвигается, он не может двигаться и кастовать канальные (channeling) спеллы и вообще ведет себя как будто остановлен. Эта функция не требует дополнительных проверок, и она полностью безопасна.
SetUnitX/Y – Native функции SetUnitX и SetUnitY также меняют X и Y координаты юнита. Однако, юнит не прекращает двигаться, кастовать канальные спеллы и так далее, во время движения. Эти функции работают быстрее, чем SetUnitPosition, но если вы используете координаты за пределами карты, игра вылетит.
Здесь я использую функцию SetUnitPosition для передвижения юнита на 40 единиц, всякий раз, когда таймер срабатывает. Это значит, что скорость движения будет 40*100*0.05 = 800. Применение именно этой функции, в данном случае, проще, так как не требует дополнительных проверок, но главной причиной, по которой я использую эту функцию, является то, что юниты не могут двигаться, пока таймер толкает их, и все возможные касты канальных спеллов, будут остановлены. Следовательно, эта функция идеальна для этого спелла.
Я также сохраняю переменную dur, значение которой увеличивается и сохраняется в кэш всякий раз, когда таймер срабатывает. Иначе действие спелла длилось бы бесконечно долго.
Когда время действия спелла истекает, он должен остановится. Так что нам требуется добавить код, который очистит значения в кэше, уничтожит группу и остановит таймер.
function Stomp_Move takes nothing returns nothing
    local string s = I2S(H2I(GetExpiredTimer()))
    local gamecache gc = udg_AbilityCache
    local real x = GetStoredReal(gc, s, "x")
    local real y = GetStoredReal(gc, s, "y")
    local integer i = GetStoredInteger(gc, s, "level")
    local group g = Stomp_CopyGroup(I2G(GetStoredInteger(gc, s, "group")))
    local real dur = GetStoredReal(gc, s, "dur")+0.05
    local real ux
    local real uy
    local real a
    local unit f
    if dur < 1+0.5*i then
        loop
            set f = FirstOfGroup(g)
            exitwhen f == null
            set ux = GetUnitX(f)
            set uy = GetUnitY(f)
            set a = Atan2(uy-y, ux-x)
            call SetUnitPosition(f, ux+40*Cos(a), uy+40*Sin(a))
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
    else
        call DestroyGroup(I2G(GetStoredInteger(gc, s, "group")))
        call FlushStoredMission(gc, s)
        call DestroyTimer(GetExpiredTimer())
    endif
...
Сначала, мы уничтожаем группу юнитов, сохраненную в кэше.
Затем полностью очищаем категорию s в кэше. Это действие очищает все данные, которые мы 'прикрепили' к таймеру, таким образом, мы исключаем повторное использование этих данных другими спеллами в дальнейшем.
И в завершении, мы уничтожаем истекший таймер.
Теперь все что нам осталось сделать в этой функции – устранить утечки памяти. Давайте сделаем это:
function Stomp_Move takes nothing returns nothing
    local string s = I2S(H2I(GetExpiredTimer()))
    local gamecache gc = udg_AbilityCache
    local real x = GetStoredReal(gc, s, "x")
    local real y = GetStoredReal(gc, s, "y")
    local integer i = GetStoredInteger(gc, s, "level")
    local group g = Stomp_CopyGroup(I2G(GetStoredInteger(gc, s, "group")))
    local real dur = GetStoredReal(gc, s, "dur")+0.05
    local real ux
    local real uy
    local real a
    local unit f
    if dur < 1+0.5*i then
        loop
            set f = FirstOfGroup(g)
            exitwhen f == null
            set ux = GetUnitX(f)
            set uy = GetUnitY(f)
            set a = Atan2(uy-y, ux-x)
            call SetUnitPosition(f, ux+40*Cos(a), uy+40*Sin(a))
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
    else
        call DestroyGroup(I2G(GetStoredInteger(gc, s, "group")))
        call FlushStoredMission(gc, s)
        call DestroyTimer(GetExpiredTimer())
    endif
    set gc = null
    call DestroyGroup(g)
    set g = null
    set f = null
endfunction

Вот и все! Вы успешно завершили создании своего собственного stomp спелла.
Вот полный код нашего триггера:
function Trig_Stomp_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

function Stomp_Filter takes nothing returns boolean
    return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetFilterUnit())) and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)
endfunction

function Stomp_CopyGroup takes group g returns group
    set bj_groupAddGroupDest = CreateGroup()
    call ForGroup(g, function GroupAddGroupEnum)
    return bj_groupAddGroupDest
endfunction

function Stomp_Move takes nothing returns nothing
    local string s = I2S(H2I(GetExpiredTimer()))
    local gamecache gc = udg_AbilityCache
    local real x = GetStoredReal(gc, s, "x")
    local real y = GetStoredReal(gc, s, "y")
    local integer i = GetStoredInteger(gc, s, "level")
    local group g = Stomp_CopyGroup(I2G(GetStoredInteger(gc, s, "group")))
    local real dur = GetStoredReal(gc, s, "dur")+0.05
    local real ux
    local real uy
    local real a
    local unit f
    if dur < 1+0.5*i then
        loop
            set f = FirstOfGroup(g)
            exitwhen f == null
            set ux = GetUnitX(f)
            set uy = GetUnitY(f)
            set a = Atan2(uy-y, ux-x)
            call SetUnitPosition(f, ux+40*Cos(a), uy+40*Sin(a))
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
    else
        call DestroyGroup(I2G(GetStoredInteger(gc, s, "group")))
        call FlushStoredMission(gc, s)
        call DestroyTimer(GetExpiredTimer())
    endif
    set gc = null
    call DestroyGroup(g)
    set g = null
    set f = null
endfunction

function Trig_Stomp_Actions takes nothing returns nothing
    local unit c = GetTriggerUnit()
    local real x = GetUnitX(c)
    local real y = GetUnitY(c)
    local integer i = GetUnitAbilityLevel(c, 'A000')
    local boolexpr b = Condition(function Stomp_Filter)
    local group g = CreateGroup()
    local group n
    local unit f
    local gamecache gc = udg_AbilityCache
    local timer t = CreateTimer()
    local string s = I2S(H2I(t))
    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl", x, y))
    call GroupEnumUnitsInRange(g, x, y, 100+50*i, b)
    set n = Stomp_CopyGroup(g)
    loop
        set f = FirstOfGroup(n)
        exitwhen f == null
        call UnitDamageTarget(c, f, 25*i, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
        call GroupRemoveUnit(n, f)
    endloop
    call StoreInteger(gc, s, "level", i)
    call StoreInteger(gc, s, "group", H2I(g))
    call StoreReal(gc, s, "x", x)
    call StoreReal(gc, s, "y", y)
    call TimerStart(t, 0.05, true, function Stomp_Move)
    set c = null
    call DestroyBoolExpr(b)
    set b = null
    set g = null
    call DestroyGroup(n)
    set n = null
    set f = null
    set gc = null
    set t = null
endfunction

//===========================================================================
function InitTrig_Stomp takes nothing returns nothing
    set gg_trg_Stomp = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Stomp, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Stomp, Condition( function Trig_Stomp_Conditions ) )
    call TriggerAddAction( gg_trg_Stomp, function Trig_Stomp_Actions )
    call Preload("Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl")
endfunction

Просмотров: 4 877

Комментарии пока отсутcтвуют