Unity—技能系統(一)


技能系統(一)

一.Demo展示

二.功能介紹

集成了技能,冷卻,buff,UI顯示,倒計時,動畫等;

技能類型:彈道技能,動畫事件根據幀數采用延遲調用技能,自定義釋放位置(偏移,發射點兩種),buff類型技能(自身增益buff,敵人減益buff,比如加防御和毒);

技能傷害判定:碰撞判定,圓形判定(自定義圓心和半徑),扇形(角度和半徑),線性(長寬),選中目標才可釋放;

技能傷害支持多段;

Buff類型:燃燒,減速,感電,眩暈,中毒,擊退,擊飛,拉拽;增益:回血,加防御;

三.工具類介紹

CollectionHelper——數組工具,泛型,可以傳入數組和條件委托,返回數組中符合條件的所有對象,以及排序功能;

TransformHelper——遞歸查找指定父節點下所有子節點,返回找到的目標;

SingletonMono——繼承了MonoBehaviour的單例;

GameObjectPool——對象池

DamagePopup——掉血數值顯示

四.基類

1.Skill

技能數據類,所有可以外部導入的技能數據都放在這個類中,以便於可以外部導入數據;

由於測試demo,我另外寫了一個SkillTemp類,繼承了ScriptaleObject,方便填寫測試數據;

/// <summary>
/// 技能類型,可疊加
/// </summary>
public enum DamageType
{
    Bullet = 4,             //特效粒子碰撞傷害
    None = 8,               //無傷害,未使用,為none可以不選
    Buff = 32,              //buff技能
    
    //二選一
    FirePos = 128,          //有發射位置點
    FxOffset = 256,         //發射偏移,無偏移偏移量為0
    
    //四選一
    Circle = 512,          //圈判定
    Sector = 1024,         //扇形判定
    Line = 4096,           //線性判定
    Select = 8192,         //選中才可釋放
}

DamageType用來確定技能的行為,賦值都是2的倍數,可以使用與或非來減少變量個數;

后來發現直接用List好像也行,后面的Buff就使用了List來存儲疊加的情況;

[CreateAssetMenu(menuName="Create SkillTemp")]
public class SkillTemp : ScriptableObject
{
    public Skill skill = new Skill();

    /// <summary>技能類型,可用 | 拼接</summary>>
    public DamageType[] damageType;
}

繼承了ScriptableObject可以右鍵創建技能模板,直接在inspector界面編輯;

image-20211111010925395

2.SkillData

組合了Skill類,在Skill類的基礎上,添加了更多的不可外部傳參的數據;

比如技能特效的引用,技能所有者引用,存儲技能攻擊目標對象用來在技能模塊之間傳遞,以及技能等級冷卻等動態變化的數據;

public class SkillData
{
    [HideInInspector] public GameObject Owner;
   
    /// <summary>技能數據</summary>
    [SerializeField]
    public Skill skill;

    /// <summary>技能等級</summary>
    public int level;
    
    /// <summary>冷卻剩余</summary>
    [HideInInspector]
    public float coolRemain;
    
    /// <summary>攻擊目標</summary>
    [HideInInspector] public GameObject[] attackTargets;

    /// <summary>是否激活</summary>
    [HideInInspector]
    public bool Activated;

    /// <summary>技能預制對象</summary>
    [HideInInspector] 
    public GameObject skillPrefab;
    
    [HideInInspector] 
    public GameObject hitFxPrefab;
}

3.CharacterStatus

准確來說這個類不屬於技能系統,他用來記錄人物屬性數據,以及提供受傷,刷新UI條等接口;

同時這個類存儲着技能系統必須用到的受擊特效掛載點HitFxPos,發射點FirePos,選中Mesh或特效物體selected,傷害數值出現點hudPos,自身頭像血條UI物體uiPortrait;

最好是英雄和敵人單獨寫一個類繼承這個基類,但是測試的話這個類就夠用了;

public class CharacterStatus : MonoBehaviour
{
    /// <summary>生命 </summary>
    public float HP = 100;
    /// <summary>生命 </summary>
    public float MaxHP=100;
    /// <summary>當前魔法 </summary>
    public float SP = 100;
    /// <summary>最大魔法 </summary>
    public float MaxSP =100;
    /// <summary>傷害基數</summary>
    public float damage = 100;
    ///<summary>命中</summary>
    public float hitRate = 1;
    ///<summary>閃避</summary>
    public float dodgeRate = 1;
    /// <summary>防御</summary>  
    public float defence = 10f;
    /// <summary>主技能攻擊距離 ,用於設置AI的攻擊范圍,與目標距離此范圍內發起攻擊</summary>
    public float attackDistance = 2;
    /// <summary>受擊特效掛點 掛點名為HitFxPos </summary>
    [HideInInspector]
    public Transform HitFxPos;
    [HideInInspector]
    public Transform FirePos;
    
    public GameObject selected;

    private GameObject damagePopup;
    private Transform hudPos;

    public UIPortrait uiPortrait; 
    
    public virtual void Start()
    {
        if (CompareTag("Player"))
        {
            uiPortrait = GameObject.FindGameObjectWithTag("HeroHead").GetComponent<UIPortrait>();
        }
        else if (CompareTag("Enemy"))
        {
            Transform canvas = GameObject.FindGameObjectWithTag("Canvas").transform;
            uiPortrait = Instantiate(Resources.Load<GameObject>("UIEnemyPortrait"), canvas).GetComponent<UIPortrait>();
            uiPortrait.gameObject.SetActive(false);
            //存儲所有的uiPortarit在單例中
            MonsterMgr.I.AddEnemyPortraits(uiPortrait);
        }
        uiPortrait.cstatus = this;
        //更新血藍條
        uiPortrait.RefreshHpMp();
        
        damagePopup = Resources.Load<GameObject>("HUD");
      	//初始化數據
        selected = TransformHelper.FindChild(transform, "Selected").gameObject;
        HitFxPos = TransformHelper.FindChild(transform, "HitFxPos");
        FirePos = TransformHelper.FindChild(transform, "FirePos");
        hudPos = TransformHelper.FindChild(transform, "HUDPos");
    }
    
    /// <summary>受擊 模板方法</summary>
    public virtual void OnDamage(float damage, GameObject killer,bool isBuff = false)
    {
        //應用傷害
        var damageVal = ApplyDamage(damage, killer);
        
        //應用PopDamage
        DamagePopup pop = Instantiate(damagePopup).GetComponent<DamagePopup>();
        pop.target = hudPos;
        pop.transform.rotation = Quaternion.identity;
        pop.Value = damageVal.ToString();
        
        //ApplyUI畫像
        if (!isBuff)
        {
            uiPortrait.gameObject.SetActive(true);
            uiPortrait.transform.SetAsLastSibling();
            uiPortrait.RefreshHpMp();
        }
    }

    /// <summary>應用傷害</summary>
    public virtual float ApplyDamage(float damage, GameObject killer)
    {
        HP -= damage;
        //應用死亡
        if (HP <= 0)
        {
            HP = 0;
            Destroy(killer, 5f);
        }
        
        return damage;
    }
}

4.IAttackSelector

目標選擇器接口,只定義了一個方法,選擇符合條件的目標並返回;

//策略模式 將選擇算法進行抽象
/// <summary>攻擊目標選擇算法</summary>
public interface IAttackSelector
{
    ///<summary>目標選擇算法</summary>
    GameObject[] SelectTarget(SkillData skillData, Transform skillTransform);
}

LineAttackSelector,CircleAttackSelector,SectorAttackSelector線性,圓形,扇形目標選擇器,繼承該接口;

就只展示一個了CircleAttackSelector;

class CircleAttackSelector : IAttackSelector
{
    public GameObject[] SelectTarget(SkillData skillData, Transform skillTransform)
    {
        //發一個球形射線,找出所有碰撞體
        var colliders = Physics.OverlapSphere(skillTransform.position, skillData.skill.attackDisntance);
        if (colliders == null || colliders.Length == 0) return null;

        //通過碰撞體拿到所有的gameobject對象
        String[] attTags = skillData.skill.attckTargetTags;
        var array = CollectionHelper.Select<Collider, GameObject>(colliders, p => p.gameObject);
      	//挑選出對象中能攻擊的,血量大於0的
        array = CollectionHelper.FindAll<GameObject>(array,
            p => Array.IndexOf(attTags, p.tag) >= 0
                 && p.GetComponent<CharacterStatus>().HP > 0);

        if (array == null || array.Length == 0) return null;

        GameObject[] targets = null;
        //根據技能是單體還是群攻,決定返回多少敵人對象
        if (skillData.skill.attackNum == 1)
        {
            //將所有的敵人,按與技能的發出者之間的距離升序排列,
            CollectionHelper.OrderBy<GameObject, float>(array,
                p => Vector3.Distance(skillData.Owner.transform.position, p.transform.position));
            targets = new GameObject[] {array[0]};
        }
        else
        {
            int attNum = skillData.skill.attackNum;
            if (attNum >= array.Length)
                targets = array;
            else
            {
                for (int i = 0; i < attNum; i++)
                {
                    targets[i] = array[i];
                }
            }
        }

        return targets;
    }
}

這里有個問題,技能的目標選擇器每次釋放技能都會調用,因此會重復頻繁的創建,但其實這只是提供方法而已;

解決:使用工廠來緩存目標選擇器;

//簡單工廠  
//創建敵人選擇器
public class SelectorFactory
{
    //攻擊目標選擇器緩存
    private static Dictionary<string, IAttackSelector> cache = new Dictionary<string, IAttackSelector>();

    public static IAttackSelector CreateSelector(DamageMode mode)
    {
        //沒有緩存則創建
        if (!cache.ContainsKey(mode.ToString()))
        {
            var nameSpace = typeof(SelectorFactory).Namespace;
            string classFullName = string.Format("{0}AttackSelector", mode.ToString());

            if (!String.IsNullOrEmpty(nameSpace))
                classFullName = nameSpace + "." + classFullName;

            Type type = Type.GetType(classFullName);
            cache.Add(mode.ToString(), Activator.CreateInstance(type) as IAttackSelector);
        }

        //從緩存中取得創建好的選擇器對象
        return cache[mode.ToString()];
    }
}

小結

所有基類,前期准備數據只有這些,另外想Demo更有體驗感,還需要有角色控制,相機跟隨腳本;

之后就是技能管理系統,技能釋放器等;

Unity技能系統(二)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM