最近在研究dota2的技能制作,记录下实现的过程,看完教程后自己制作了一个天火 Boom!!!的技能,每隔一段时间以自己为中心随机范围内的目标释放一个阳炎冲击(之后管他叫天火).

  • 伤害为类型为:真实
  • 伤害目标:所有单位
  • 伤害团队:所有团队

得知一些设置后就可以开始设置了

首先在npc_abilities_custom.txt中添加一个tianhuo_boom的技能

"tianhuo_boom"
{
    // General
    //-------------------------------------------------------------------------------------------------------------
    "BaseClass"                     "ability_lua"
    "AbilityTextureName"            "invoker/magus_apex/invoker_sun_strike"
    //技能逻辑脚本
    "ScriptFile"                      "abilities/tianhuo_boom"
    //技能是一个被动
    "AbilityBehavior"               "DOTA_ABILITY_BEHAVIOR_PASSIVE"
    //技能伤害团队为所有,在代码中会用到
    "AbilityUnitTargetTeam"         "DOTA_UNIT_TARGET_TEAM_BOTH"
    //技能目标类型为全部,在代码中会用到
    "AbilityUnitTargetType"         "DOTA_UNIT_TARGET_ALL"

    //-------------------------------------------------------------------------------------------------------------
    "AbilitySpecial"
    {
        "01"
        {
          "var_type"                "FIELD_INTEGER"
          //释放间隔
          "interval"          "5 4 3 2"
        }

        "03"
        {
          "var_type"                "FIELD_FLOAT"
          //伤害
          "damage"         "60 80 100 120"
        }

        "03"
        {
          "var_type"                "FIELD_FLOAT"
          //释放检测范围
          "range"         "300 500 800 1500"
        }

        "04"
        {
            "var_type"                "FIELD_FLOAT"
            //伤害范围
            "damageRange"         "175"
        }

        "05"
        {
            "var_type"                "FIELD_FLOAT"
            //伤害产生延迟
            "delay"         "1.7"
        }
    }
}

接下来是创建技能逻辑tianhuo_boom,需要放在game/scripts/vscripts/abilities目录中

import {BaseAbility, registerAbility} from "../lib/dota_ts_adapter";

@registerAbility()
export class tianhuo_boom extends BaseAbility
{
    private _interval:number;
    private _range:number;
    private _damage:number;
    private _damageRange:number;
    private _delay:number;
    
    //技能释放
    OnSpellStart()
    {
        if (!IsClient())
            this.SetThink(()=> this._sun_strike(), this, tianhuo_boom.name, this._interval)
    }
    
    //技能升级,需要重新读取一下数值和重新释放技能
    OnUpgrade()
    {
        this._interval = this.GetSpecialValueFor("interval");
        this._range = this.GetSpecialValueFor("range");
        this._damage = this.GetSpecialValueFor("damage");
        this._damageRange = this.GetSpecialValueFor("damageRange");
        this._delay = this.GetSpecialValueFor("delay");
        
        this.StopThink(tianhuo_boom.name);
        this.OnSpellStart();
    }
    
    //英雄复活后释放技能启动轮询
    OnOwnerSpawned()
    {
        this.OnSpellStart();
    }
    
    //英雄死亡,停止技能轮询
    OnOwnerDied()
    {
        this.StopThink(tianhuo_boom.name);
    }

    //天火的准备阶段特效
    private readonly _sunS = "particles/econ/items/invoker/invoker_apex/invoker_sun_strike_team_big_ray_immortal1.vpcf";
    ////天火的爆炸特效
    private readonly _sunBoom = "particles/units/heroes/hero_invoker/invoker_sun_strike.vpcf";
    //天火的准备阶段音效
    private readonly _sunSSound = "Hero_Invoker.SunStrike.Charge";
    //天火的爆炸音效
    private readonly _sunBoomSound = "Hero_Invoker.SunStrike.Ignite";
    
    //技能轮询逻辑
    private _sun_strike()
    {
        const caster = this.GetCaster();
        
        //查找range范围内的所有目标,以自己为中心,这里就使用到在kv中设置的AbilityUnitTargetTeam和AbilityUnitTargetType
        const targets = FindUnitsInRadius(DOTATeam_t.DOTA_TEAM_NEUTRALS, caster.GetAbsOrigin(), undefined, this._range,
            this.GetAbilityTargetTeam(),
            this.GetAbilityTargetType(),
            this.GetAbilityTargetFlags(), FindOrder.FIND_ANY_ORDER, false);
        
        if (targets.length > 0)
        {
            //随机一个单位
            const index = math.floor(math.random() * targets.length);

            const target = targets[index];

            let origin = target.GetAbsOrigin();

            //创建天火准备特效
            const pid = ParticleManager.CreateParticle(this._sunS, ParticleAttachment_t.PATTACH_CUSTOMORIGIN, caster);
            ParticleManager.SetParticleControl(pid, 0, origin);
            EmitSoundOnLocationWithCaster(origin, this._sunSSound, caster);
            
            //缓存伤害和伤害范围,避免伤害开始但技能升级,使用了最新等级的数值
            const damageRange = this._damageRange;
            const damage = this._damage;
            
            //等待延迟
            GameRules.GetGameModeEntity().SetThink(()=>
            {
                //创建爆炸特效
                const pid = ParticleManager.CreateParticle(this._sunBoom, ParticleAttachment_t.PATTACH_CUSTOMORIGIN, caster);
                ParticleManager.SetParticleControl(pid, 0, origin);
                EmitSoundOnLocationWithCaster(origin, this._sunBoomSound, caster);
                //搜索以目标点为中心伤害范围的目标,这里也使用到在kv中设置的AbilityUnitTargetTeam和AbilityUnitTargetType
                const targets = FindUnitsInRadius(DOTATeam_t.DOTA_TEAM_NEUTRALS, origin, undefined, damageRange,
                    this.GetAbilityTargetTeam(),
                    this.GetAbilityTargetType(),
                    this.GetAbilityTargetFlags(), FindOrder.FIND_ANY_ORDER, false);
                
                if (targets.length > 0)
                {
                    //平摊伤害,最大目标为10个,这里这个10个目标的数值可以暴露在kv的AbilitySpecial中
                    const damageValue = math.max(damage / targets.length, damage / 10);
                    
                    //只拿前10个
                    const maxLenght = math.min(targets.length, 10);

                    for (let i = 0; i < maxLenght; i++)
                    {
                        const target = targets[i];

                        //创建伤害表
                        const damageTable:ApplyDamageOptions = {
                            damage: damageValue,
                            damage_flags:DOTADamageFlag_t.DOTA_DAMAGE_FLAG_NONE,
                            damage_type: DAMAGE_TYPES.DAMAGE_TYPE_PURE,
                            ability: this,
                            attacker: caster,
                            victim:target
                        }  
                        
                        //应用伤害
                        ApplyDamage(damageTable);
                    }
                }
                
                //这里返回undefined就会停止,也就是单次的
                return undefined;
            }, this, "_boom"+GameRules.GetGameTime(), this._delay)
        }
        
        return this._interval;
    }
}

使用隐藏技能的版本(无法改变伤害单位类型和团队类型,控制力较弱,使用简单,但是缺点大于优点),这里只贴代码段,具体代码已经删了=-=

tianhuo_boom中的OnUpgrade_sun_strike替换为如下代码,不一定能正确,在写这篇文章时回忆着写的,大概逻辑就是添加技能后,释放添加的技能,核心是SetCursorPositionOnSpellStart,SetCursorPosition设置施法点,OnSpellStart释放技能

private _sunAbility:CDOTABaseAbility;
    
OnUpgrade()
{
    const caster = this.GetCaster();

    if (!caster.HasAbility("tianhuo_sun_strike"))
    {
        this._sunAbility = caster.AddAbility("tianhuo_sun_strike");
        this._sunAbility.SetHidden(true);//隐藏起来
    }

    this._sunAbility.SetLevel(this.GetLevel());//等级同步
}

private _sun_strike()
{
    const caster = this.GetCaster();

    const targets = FindUnitsInRadius(DOTATeam_t.DOTA_TEAM_NEUTRALS, caster.GetAbsOrigin(), undefined, this._range,
        this.GetAbilityTargetTeam(),
        this.GetAbilityTargetType(),
        this.GetAbilityTargetFlags(), FindOrder.FIND_ANY_ORDER, false);

    if (targets.length > 0)
    {
        const index = math.floor(math.random() * targets.length);

        const target = targets[index];

        let origin = target.GetAbsOrigin();

        caster.SetCursorPosition(origin);
        this._sunAbility.OnSpellStart();
    }

    return this._interval;
}

tianhuo_sun_strike技能的kv,这个技能kv比较特殊,是从invoker_sun_strike派生,这个是祈求者的阳炎冲击技能,无法覆盖AbilityUnitTargetTeamAbilityUnitTargetType

"tianhuo_sun_strike" {
        "BaseClass" "invoker_sun_strike"
        "ScriptFile"                      "abilities/over_invoker_sun_strike"
        "AbilityFunctionType" "HarmSkill"
        "AbilityType" "DOTA_ABILITY_TYPE_BASIC"
        "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT"
        "AbilityUnitTargetTeam"         "DOTA_UNIT_TARGET_TEAM_BOTH"
        "AbilityUnitTargetType"         "DOTA_UNIT_TARGET_ALL"
        "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL"
        "MaxLevel" "1"
        //"IsHidden" "1"
        "AbilityCastPoint" "0"
        "AbilityManaCost" "0"
        "AbilityCooldown" "15"
        "AbilityCastRange" "3000"
        "AbilitySpecial" 
        {
            "01" 
            {
                "var_type" "FIELD_INTEGER"
                "delay" "1"
            }
            "02" 
            {
                "var_type" "FIELD_INTEGER"
                "area_of_effect" "200"
            }
            "03" 
            {
                "var_type" "FIELD_INTEGER"
                "damage" "3000"
            }
            "04" 
            {
                "var_type" "FIELD_INTEGER"
                "vision_distance" "400"
            }
            "05" 
            {
                "var_type" "FIELD_INTEGER"
                "vision_duration" "3"
            }
            "06" 
            {
                "var_type" "FIELD_INTEGER"
                "cataclysm_cooldown" "90"
            }
            "07" 
            {
                "var_type" "FIELD_INTEGER"
                "cataclysm_min_range" "160"
            }
            "08" 
            {
                "var_type" "FIELD_INTEGER"
                "cataclysm_max_range" "200"
            }
        }
    }

效果