RPG游戏《黑暗之光》流程介绍与代码分析之(十四):角色技能系统的实现

十四章:角色技能系统的实现

技能系统是本游戏开发的最后一部分内容,与普通攻击系统不同,我们需要添加释放技能的特效、动画以及播放时间。并将动画分为回复、Buff、单体和群体。

14.1添加技能的特效名称

释放技能时要使用攻击特效以及攻击动画,因此我们对SkillInfoInList进行修改。
其中特效名称表示释放时的特效,动画名称对应技能的效果

添加完毕后如下
5001,魔法弹,skill-09,伤害 350%,SingleTarget,Attack,350,0,20,10,Magician,1,Enemy,5,Efx_CriticalStrike,Skill-MagicBall,1.1
5002,治疗,skill-13,治愈30HP,Passive,HP,30,0,10,20,Magician,1,Self,0,Heal_Effect,Attack1,0.83
5003,冥想,skill-10,魔法恢复20,Passive,MP,20,0,0,30,Magician,5,Self,0,Effect_BlueHeal,Attack1,0.83
5004,法力涌动,skill-08,攻击力为200%持续15秒,Buff,Attack,200,15,30,30,Magician,7,Self,0,RangeMagic_Effect,Skill-GroundImpact,1.1
5005,战斗热诚,skill-12,攻击力速度为200%持续30,Buff,AttackSpeed,200,30,30,30,Magician,8,Self,0,Efx_One-handQuicken,Skill-GroundImpact,1.1
5006,究极风暴,skill-11,攻击力400% 所有敌人,MultiTarget,Attack,400,0,50,40,Magician,9,Position,10,MagicSphere_effect,AttackCritical,0.8
在SkillsInfo脚本中,我们为class SkillInfo添加几个成员变量,用以表示我们新增的3个值
        public string effectName;
        public string aniName;
        public float aniTime;
并在读取技能列表的函数ReadInfo中添加这几个新的变量
            info.effectName = proArray[14];
            info.aniName = proArray[15];
            info.aniTime = proArray[16];
之后,我们通过与技能快捷键Shortcut互动实现技能释放,在Shortcut脚本中判断当前快捷栏中是否含有技能。
    void Update()
    {
        if (type = ShortType.Skill)
        {
            OnDrugUse();    
        }
    }
OnDrugUse()函数通过判断是否有足够的MP,决定是否释放技能。代码如下
    void OnDrugUse()
    {
        bool success = ps.isEnoughMP (info.mpCost);    //判断是否有足够的MP
        if(!success)
        {
        }
        else
        {
            pa.UseSkill(info);    //释放技能
        }
    }
isEnoughMP在PlayerStatus脚本中对应代码如下
    public bool isEnoughMP(int value)
    {
        if (mp_remain >= value)    //是否成功获取
        {
            mp_remain -= value;
            FaceUI._instance.SetFaceProperty();    //更新显示
            return true;
        }
        else
        {
            return false;
        }
    }
技能的释放通过PlayerAttack中的UseSkil()实现并在Shortcut中进行调用,在设计函数之前,我们需要导入所有动画的技能名称,储存在PlayerAttack中,通过info中的名称(effectName)播放对应动画。
public GameObject[] effectArray;

将动画信息用字典存储起来,之后通过动画名字调用动画的GameObject
    private Dictionary<string,GameObject> effectDict = new Dictionary<string,GameObject>();

    void Awake()
    {
        foreach(GameObject go in effectArray)
        {
            effectDict.Add(go.name,go);
        }
    }

14.2 回复技能

有了上述铺垫,正式开始UseSkill()函数的设计。因为技能种类分为 Passive, Buff, SingleTarget, MultiTarget四类,我们先分析第一类,即回复技能。
步骤为
  1. 按下技能键
  2. 设置状态为施法,此时无法移动
  3. 播放特效并计时
  4. 计时结束改变状态并播放动画
  5. 回复HP/MP

因此,当info.applyType为Passive时,即

public void UseSkill(SkillsInfo.SkillInfo info)
    {
        if(ps.role == playerRole.Magician)    //判断当前角色是否是Magician
        {
            if(info.applyRole == SkillsInfo.ApplyRole.Magician)    //当前技能是否适用Magician
            {
                switch(info.applyType)    //判断
                {
                case SkillsInfo.ApplyType.Passive:    //为恢复技能时
                    StartCoroutine(OnPassiveSkillUse(info));    //利用协程的计时功能实现技能的使用。
                    break;
                }

            }
        }

    }

    IEnumerator OnPassiveSkillUse(SkillsInfo.SkillInfo info)    
    {
        state = PlayerState.skillAttack;    //state设置为skillAttack,无法进行移动
        animation.CrossFade (info.aniName);    //播放特效,注意这里的动画为抬手施法的特效,如下左图
        yield return new WaitForSeconds(info.aniTime);
        state = PlayerState.normalWalk;    //当技能持续时间结束后,才能进行移动等操作
        int hp = 0, mp = 0;
        if(info.applyProperty == SkillsInfo.ApplyProperty.HP)
        {
            hp = info.applyValue;
        }
        else if(info.applyProperty == SkillsInfo.ApplyProperty.MP)
        {
            mp = info.applyValue;
        }
        ps.GetDrug (hp, mp);    //加HP或MP

        GameObject prefab = null;
        effectDict.TryGetValue (info.effectName, out prefab);    //从字典中获取施法动画,如下右图
        GameObject.Instantiate (prefab, transform.position, Quaternion.identity);
    }
 

14.3 Buff技能

Buff技能可以参照passive技能的模式,步骤为
  1. 按下技能键
  2. 设置状态为施法,此时无法移动
  3. 播放特效并计时
  4. 计时结束改变状态并播放动画
  5. 增加对应属性并计时
  6. 持续时间结束后还原
代码如下
IEnumerator OnBuffSkillUse(SkillsInfo.SkillInfo info)
    {
        state = PlayerState.skillAttack;
        animation.CrossFade (info.aniName);
        yield return new WaitForSeconds(info.aniTime);
        GameObject prefab = null;
        effectDict.TryGetValue (info.effectName, out prefab);
        GameObject.Instantiate (prefab, transform.position, Quaternion.identity);
        state = PlayerState.normalWalk;
        switch(info.applyProperty)    //Buff技能要考虑增加的属性,因此用case处理
        {
        case SkillsInfo.ApplyProperty.Attack:
            ps.attack *= (info.applyValue/100f);
            break;
        case SkillsInfo.ApplyProperty.AttackSpeed:
            attackRate *= (info.applyValue/100f);
            break;
        case SkillsInfo.ApplyProperty.Defense:
            ps.defense *= (info.applyValue/100f);
            break;
        case SkillsInfo.ApplyProperty.Speed:
            pm.speed *= (info.applyValue/100f);
            break;
        }
        yield return new WaitForSeconds (info.applyTime);    //持续一段时间
        switch(info.applyProperty)    //取消Buff效果
        {
        case SkillsInfo.ApplyProperty.Attack:
            ps.attack /= (info.applyValue/100f);
            break;
        case SkillsInfo.ApplyProperty.AttackSpeed:
            attackRate /= (info.applyValue/100f);
            break;
        case SkillsInfo.ApplyProperty.Defense:
            ps.defense /= (info.applyValue/100f);
            break;
        case SkillsInfo.ApplyProperty.Speed:
            pm.speed /= (info.applyValue/100f);    //控制PlayerMove中的移动速度
            break;
        }
    }

14.4 单目标技能

单目标技能需要选定目标,因此步骤为
  1. 按下技能键
  2. 修改鼠标为技能锁定图标
  3. 判断是否点击敌人(射线检测)
  4. 设置状态为施法,此时无法移动
  5. 播放特效并计时
  6. 计时结束改变状态并播放动画
  7. 造成伤害
在PlayerAttack中新增OnSingleTargetSkillUse()控制技能释放,首先修改图标,在MouseSetting中,添加
    public void SetSkillAttackCursor()
    {
        Cursor.SetCursor (cursor_lockTarget, hotspot, mode);
    }
并在OnSingleTargetSkillUse()中调用,在这里我们需要存储info信息并增加一个标志位,当技能为单体技能时,我们有
    private bool isLockingTarget = false;
    private SkillsInfo.SkillInfo info;

    void OnSingleTargetSkillUse(SkillsInfo.SkillInfo info)
    {
        state = PlayerState.skillAttack;
        MouseSetting._instance.SetSkillAttackCursor ();
        isLockingTarget = true;
        this.info = info;
    }
之后在Update()中,根据标志位和info信息进行技能处理,即
void Update()
    {
        if(isLockingTarget && Input.GetMouseButtonDown(0))
        {
              OnLockTargetButtonDown();      //处理技能释放功能
        }

    }
在OnLockTargetButtonDown()中,
    void OnLockTargetButtonDown()
    {
        isLockingTarget = false;
        switch(info.applyType)
        {
        case SkillsInfo.ApplyType.SingleTarget:
            StartCoroutine(SkillAttackSingleTarget());
            break;
        case SkillsInfo.ApplyType.MultiTarget:
            //todo
            break;
        }
    }
先考虑单个目标的功能,先判断鼠标是否接触到敌人
    IEnumerator SkillAttackSingleTarget()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);    
        RaycastHit hitInfo;   
        bool isCollider = Physics.Raycast(ray,out hitInfo);   
        if(isCollider && hitInfo.collider.tag == Tags.enemy)    
        {  
            state = PlayerState.skillAttack;
            animation.CrossFade (info.aniName);
            yield return new WaitForSeconds(info.aniTime);
            GameObject prefab = null;
            effectDict.TryGetValue (info.effectName, out prefab);
            GameObject.Instantiate (prefab, hitInfo.collider.transform.position, Quaternion.identity);    //使用hitInfo.collider得到怪物
        
            hitInfo.collider.GetComponent<WolfBaby>().BeDamaged((int)(GetAttack() * (info.applyValue/100f)));    //造成伤害
        }  
    }
即可完成单体技能的释放

14.5 群体技能

群体技能与单体技能的差异主要体现在群体技能指定范围,并要判断范围内的敌人。因此步骤为

  1. 按下技能键
  2. 修改鼠标为技能锁定图标
  3. 点击一个位置
  4. 设置状态为施法,此时无法移动
  5. 播放特效并计时
  6. 计时结束改变状态并播放动画
  7. 判断技能接触单位(运用collider)
  8. 造成伤害
    IEnumerator SkillAttackMultiTarget()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);    
        RaycastHit hitInfo;   
        bool isCollider = Physics.Raycast(ray,out hitInfo);   
        if(isCollider)    //只要发生碰撞即可
        {  
            state = PlayerState.skillAttack;
            animation.CrossFade (info.aniName);
            yield return new WaitForSeconds(info.aniTime);
            GameObject prefab = null;
            effectDict.TryGetValue (info.effectName, out prefab);
            GameObject.Instantiate (prefab, hitInfo.point + Vector3.up, Quaternion.identity);    //用hitInfo.point代替hitInfo.collider.transform.position

            //todo:范围内敌人受伤效果
        }  
        else
        {
            state = PlayerState.normalAttack;
        }
        MouseSetting._instance.SetNormalCursor ();
    }
为了检测碰撞到的敌人,我们将特效拖入场景

    
并给它一个MagicSphere脚本

public class MagicSphere : MonoBehaviour {

    public int attack = 0;
    private List<WolfBaby>wolfList = new List<WolfBaby> ();    //通过List判断当前野怪是否被攻击

    public void OnTriggerEnter(Collider col)
    {
        if(col.tag == Tags.enemy)
        {
            WolfBaby baby = col.GetComponent<WolfBaby>();    
            int index = wolfList.IndexOf(baby);
            if(index == -1)    //若还未被攻击,造成伤害,并添加到wolfList之中
            {
                baby.BeDamaged(attack); 
                wolfList.Add(baby);
            }
        }
    }

}
之后就可以处理todo中的受伤害效果了
GameObject go = GameObject.Instantiate (prefab, hitInfo.point + Vector3.up * 0.5f, Quaternion.identity) as GameObject;    创建技能
 go.GetComponent<MagicSphere>().attack = GetAttack() * (info.applyValue/100f);    //赋值给attack属性
效果如下
  

猜你喜欢

转载自blog.csdn.net/s1314_jhc/article/details/80182485
今日推荐