WarCraft 3: Улучшение спелла

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

Улучшение спелла

Более реалистичное движение

Теперь, когда основа спелла готова, давайте, попытаемся добавить в спелл несколько спецэффектов, для придания ему большей красочности.
Сейчас, движение выглядит нереалистично, потому что юниты имеют одинаковую скорость, на протяжении всего времени движения. Сейчас мы попытаемся сделать движение после толчка, более быстрым в начале и затем ослабляющимся, как это происходит в реальной жизни.
Начнем с прикрепления начальной скорости юнита к таймеру в виде переменной типа real.
...
    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 StoreReal(gc, s, "speed", 50)
    call TimerStart(t, 0.05, true, function Stomp_Move)
...

Таким образом, начальная скорость равна 50. Теперь перейдем к функции Stomp_Move и изменим кое-что:
...
    local real ux
    local real uy
    local real a
    local unit f
    local real p = GetStoredReal(gc, s, "speed")-0.5/(1+0.5*i)
    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+p*Cos(a), uy+p*Sin(a))
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
        call StoreReal(gc, s, "speed", p)
    else
...
Мы загружаем переменную и немного уменьшаем ее значение (значение на которое мы уменьшаем скорость, зависит от уровня спелла, так как продолжительность тоже от него зависит. Иначе бы скорость падала бы до очень низких значений, из-за добавочной продолжительности, на высоких уровнях спелла).
Теперь в строке с SetUnitPosition, мы просто используем переменную p, вместо использования константы (40).
Мы также сохраняем новое, уменьшенное значение скорости в кэш.

Добавление эффекта пыли

ЗАМЕЧАНИЕ: Я продолжаю работать с кодом спелла, который мы сделали, УЖЕ С изменениями, которые мы добавили для скорости.
Чтобы сделать эффект толчка более реалистичным, мы добавим эффект пыли.
Я собираюсь использовать для этих целей модель Impale Target Dust (Objects\Spawnmodels\Undead\ImpaleTargetDust\ImpaleTargetDust.mdl), так что давайте добавим ее подзагрузку в функции InitTrig:
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")
    call Preload("Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl")
endfunction
Теперь перейдем в функцию таймера. Мы не хотим, чтобы эффект создавался при каждом срабатывании таймера, так что мы собираемся добавить переменную типа real, чтобы отследить момент, когда нужно создать эффект:
...
    local real ux
    local real uy
    local real a
    local unit f
    local real p = GetStoredReal(gc, s, "speed")-0.5/(1+0.5*i)
    local real fx = GetStoredReal(gc, s, "fx")+0.05
    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+p*Cos(a), uy+p*Sin(a))
            if fx >= 1 then
                call DestroyEffect(AddSpecialEffectTarget("Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTar​getDust.mdl", f, "origin"))
            endif
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
        call StoreReal(gc, s, "speed", p)
        call StoreReal(gc, s, "fx", fx)
        if fx >= 1 then
            call StoreReal(gc, s, "fx", 0)
        endif
    else
...
Сначала мы добавили переменную fx типа real. Мы загружаем в нее значение, прикрепленное к таймеру с меткой "fx". Мы немного увеличиваем ее значение, и если значение больше или равно 1, мы создаем (и сразу же уничтожаем, так как модель эффекта содержит лишь одну анимацию) спецэффект для каждого юнита из группы.

Упрощение заменяемости спецэффектов

ЗАМЕЧАНИЕ: Как и ранее, мы продолжаем работать с тем же кодом, в который уже внесены улучшения, рассмотренные выше.
Эта часть туториала, наглядно покажет вам, пример использования native функции GetAbilityEffectById, которая может извлекать строки из полей спецэффектов любой абилки (ability) в редакторе объектов.
Ее использование бывает очень полезно, когда вы хотите сделать заменямость спецэффектов ваших нестандартных более простой, потому что изменение поля в редакторе объектов, намного проще, чем аналогичные действия в кое спелла.
native GetAbilityEffectById         takes integer abilityId, effecttype t, integer index returns string
Функция проста, дам небольшое описание ее аргументов:
integer abilityId – Равкод спелла, из полей которого вы хотите извлечь спецэффект.
effecttype t – Поле в редакторе объектов, из которого будет извлечен спецэффект. Вот список типов эффектов:
  • EFFECT_TYPE_AREA_EFFECT
  • EFFECT_TYPE_CASTER
  • EFFECT_TYPE_EFFECT
  • EFFECT_TYPE_LIGHTNING
  • EFFECT_TYPE_MISSILE
  • EFFECT_TYPE_SPECIAL
  • EFFECT_TYPE_TARGET

Имена типов говорят сами за себя, какие из полей в редакторе объектов они представляют.
integer index – номер эффекта в поле, который вы хотите извлечь, начиная с 0.
Для этого спелла я использую поле EFFECT_TYPE_MISSILE. Наш спелл кастуется мгновенно, и не использует это поле. Наилучшим вариантом считается использовать для своих целей те поля эффектов, которые не используются самим спеллом.
Теперь добавим следующие значения в поле нашего спелла: модель War Stomp (Abilities\Spells\Orc\WarStomp\WarStompCaster.mdl), как первый эффект поля, эффект пыли (Objects\Spawnmodels\Undead\ImpaleTargetDust\ImpaleTargetDust.mdl), как второй эффект поля и точку прикрепления эффекта пыли (строку origin) как третий.
Все верно, мы можем использовать поля эффектов, для хранения любых строк, которые мы можем в дальнейшем использовать в наших JASS спелах.
Теперь давайте, заменим строки путей к моделям эффектов в триггере на вызовы native функции GetAbilityEffectById:
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
    local real p = GetStoredReal(gc, s, "speed")-0.5/(1+0.5*i)
    local real fx = GetStoredReal(gc, s, "fx")+0.05
    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+p*Cos(a), uy+p*Sin(a))
            if fx >= 1 then
                call DestroyEffect(AddSpecialEffectTarget(GetAbilityEffectById('A000', EFFECT_TYPE_MISSILE, 1), f, GetAbilityEffectById('A000', EFFECT_TYPE_MISSILE, 2)))
            endif
            call GroupRemoveUnit(g, f)
        endloop
        call StoreReal(gc, s, "dur", dur)
        call StoreReal(gc, s, "speed", p)
        call StoreReal(gc, s, "fx", fx)
        if fx >= 1 then
            call StoreReal(gc, s, "fx", 0)
        endif
    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(GetAbilityEffectById('A000', EFFECT_TYPE_MISSILE, 0), 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(GetAbilityEffectById('A000', EFFECT_TYPE_MISSILE, 0))
    call Preload(GetAbilityEffectById('A000', EFFECT_TYPE_MISSILE, 1))
endfunction

Создание спелла в соответствии со стандартом JESP

Стандарт JESP – это стандарт, упрощающий процесс обмена спеллами. Ничего не заставляет вас придерживаться этого стандарта, но если вы планируете опубликовать ваш спелл в дальнейшем, то было бы лучше, если бы стандарт был соблюден. Как правила спеллы, соответствующие стандарту, легче импортировать, настраивать и использовать, чем несоответствующие этому стандарту спеллы.
Вы можете прочитать спецификацию на стандарт здесь.
(Прим. перев. Русский вариант вы можете найти здесь)
Эта часть туториала, является коротким руководством о том, как сделать, чтобы спелл удовлетворял стандарту. Как пример я разберу спелл, который мы с вами только что создали.
Во-первых, вам нужно сменить имена всех ваших функций, таким образом, чтобы они начинались с рабочего имени спелла отделенного нижним пробелом, например, Stomp + _ + имя функции.
Также измените функции Trig_Stomp_Actions и Trig_Stomp_Conditions. Не забудьте, потом изменить их имена в месте их вызова в функции InitTrig_Stomp.
Стандарт JESP, требует, чтобы спелл содержал функции настройки, так что давайте, добавим их.
По возможности все функции настройки должны быть constant функциями.
Вы можете подробно ознакомится с constant функциями здесь.
(Прим. перев. Constant функции как правило работают немного быстрее, чем обычные функции, если не берут параметров и не вызывают обычные функции. Однако механика constant функций до сих пор не ясна. Возможно, интерпретатор создает отдельную таблицу смещений для constant функций, поэтому, при их сравнительно небольшом количестве, поиск их смещения происходит быстрее.
Мое личное мнение на этот счет, заключается в том, что constant функции в некоторых случаях аналогичны inline функциям в C)
Первая функция настройки, которую мы добавим, позволит легче изменять равкод спелла.
constant function Stomp_SpellId takes nothing returns integer
    return 'A000'
endfunction
Добавьте эту функцию, в самое начало кода триггера спелла, и выполните "Replace all" (Заменить все), чтобы заменить id спелла 'A000', функцией Stomp_SpellId().
Эта функция, пример того, что вам нужно сделать. Разумно написать подобные функции и для урона, скорости и продолжительности. Эти функции должны брать уровень спелла как параметр, это облегчит настройку многоуровневых спеллов.
Чтобы спелл удовлетворял JESP стандарту, код должен включать имя автора. Просто добавьте комментарий с вашим именем в самом верху кода спелла:
// Stomp spell by Blade.dk

constant function Stomp_SpellId takes nothing returns integer
    return 'A000'
endfunction
Также можете добавить свою контактную информацию.
Вы можете использовать системы подобные системам Local Handle Variables или CSCache.
(Прим. перев. Или собственную разработку XGM, систему Сергея – SCV)
Не забудьте, что вы должны вставить отключенный триггер, содержащий текст JESP стандарта, в вашу карту, таким образом, распространяется информация о нем. Это также обязательное требование стандарта.

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

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