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屬性
效果如下
  
發佈了96 篇原創文章 · 獲贊 140 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章