unity 網絡遊戲架構設計(第08課:技能系統設計)之美

在講技能之前,先介紹一下游戲特效,遊戲特效製作方式主要分爲三種:序列幀特效、2D 骨骼動畫特效、粒子特效。序列幀特效和 2D 骨骼動畫特效大量運用在 2D 遊戲中,而粒子特效主要是運用在 3D 遊戲和 2D 遊戲中。遊戲特效可以提升整個遊戲的畫面品質,市場上的每款遊戲都會有大量特效,可以讓整個畫面絢麗多彩。在遊戲場景中,技能特效是伴隨着角色動作播放的,角色動作可以使用我們前面介紹的角色系統創建,利用 FSM 播放動作,技能就是將動作和特效合在一起播放,在講解技能之前,先給讀者展示一下我們的技能模塊設計圖:

enter image description here

上圖中,我們先介紹 IEffect 模塊,所有的技能肯定有共同的屬性,爲了避免這些屬性被重複的定義,我們將其放到一個父類中定義,在技能的父類就是 IEffect。大家跟着我的思路走,首先遊戲中會有很多技能,這麼多技能如何區分? 這就涉及到一個技能類型定義,技能類型定義可以使用字符串,也可以使用枚舉。我們使用了枚舉表示:

        public enum ESkillEffectType
    {
        eET_Passive,
        eET_Buff,
        eET_BeAttack,
        eET_FlyEffect,
        eET_Normal,
        eET_Area,
        eET_Link,
    }

另外,技能還有一些共同的屬性和方法,我們先定義屬性,比如特效的運行時間、資源路徑、生命週期、技能釋放者和受擊者、播放的音效等。這些我們可以自己根據需求去定義,代碼如下所示:

    //基本信息
    public GameObject obj = null;           //特效GameObject物體
    public Transform mTransform = null;

    protected float currentTime = 0.0f;     //特效運行時間
    public bool isDead = false;                //特效是否死亡
    public string resPath;                  //特效資源路徑        
    public string templateName;             //特效模板名稱
    public Int64 projectID = 0;             //特效id  分爲服務器創建id 和本地生成id        
    public uint skillID;                    //特效對應的技能id

    public float cehuaTime = 0.0f;         //特效運動持續時間或者是特效基於外部設置的時間    策劃配置      
    public float artTime = 0.0f;            //美術設置的特效時間                            美術配置

    public float lifeTime = 0;              //特效生命週期, 0爲無限生命週期

    public UInt64 enOwnerKey;            //技能釋放者
    public UInt64 enTargetKey;           //技能受擊者        
    public AudioSource mAudioSource = null; //聲音

另外,特效屬性有了,它播放後,朝着那個方向,以及發射位置,發射距離等等運動信息,這也需要我們去定義的,代碼如下:

     //運動信息
    public Vector3 fPosition;
    public Vector3 fixPosition;
    public Vector3 dir;
    public Vector3 distance;
    public ESkillEffectType mType;

特效的共同屬性定義完了,下面定義它的共同方法,要使用特效,首先要創建特效,代碼如下所示:

   //特效創建接口
    public void Create()
    {
        //創建的時候檢查特效projectId,服務器沒有設置生成本地id
        CheckProjectId();
        //獲取特效模板名稱
        templateName = ResourceCommon.getResourceName(resPath);

        //使用特效緩存機制           
        obj = GameObjectPool.Instance.GetGO(resPath);

        if (null == obj)
        {
            Debugger.LogError("load effect object failed in IEffect::Create" + resPath);
            return;
        }

        //創建完成,修改特效名稱,便於調試
        obj.name = templateName + "_" + projectID.ToString();

        OnLoadComplete();

        //獲取美術特效腳本信息                
        effectScript = obj.GetComponent<EffectScript>();
        if (effectScript == null)
        {
            Debugger.LogError("cant not find the effect script in " + resPath);
            return;
        }
        artTime = effectScript.lifeTime;

        //美術配置時間爲0,使用策劃時間
        if (effectScript.lifeTime == 0)
            lifeTime = cehuaTime;
        //否則使用美術時間
        else
            lifeTime = artTime;

        //特效等級不同,重新設置
        EffectLodLevel effectLevel = effectScript.lodLevel;
        EffectLodLevel curLevel = EffectManager.Instance.mLodLevel;

        if (effectLevel != curLevel)
        {
            //調整特效顯示等級
            AdjustEffectLodLevel(curLevel);
        }
    }

該函數的實現流程是先加載特效,也是從對象池生成,並且將其重新命名,然後調用函數 OnLoadComplete() 去設置特效的發射點,特效發射點要根據不同的技能去設置,在 IEffect 類中只是定義了一個虛函數:

        public virtual void OnLoadComplete()
    {
    }

具體的功能要在特效子類去實現,當然 IEffect 類並不是只有這一個函數實現,其他的功能函數可以根據需求自己定義了,如果沒有可以不實現,比如函數 AdjustEffectLodLevel 用於設置特效等級,如果我們需求沒有設置特效等級,那這個函數可以刪除掉了。

特效父類 IEffect 已定義完成,接下來就要編寫具體的子類了,上圖圖中列舉了幾個技能,根據需求可以擴展下去,我們先拿出 BeAttackEffect 被動技能舉例說明,子類是如何編寫的?在 IEffect 類的編寫中,首先要繼承 IEffect 類,在這裏就略過去了,在父類 IEffect 中函數 OnLoadComplete 沒有具體實現,只是提供了接口,子類重點就是實現這個函數:

public override void OnLoadComplete()
    {
        //判斷enTarget
        IEntity enTarget;
        EntityManager.AllEntitys.TryGetValue(enTargetKey, out enTarget);

        if (enTarget != null && obj != null)
        {
            //擊中點
            Transform hitpoit = enTarget.RealEntity.transform.FindChild("hitpoint");
            if (hitpoit != null)
            {
               //設置父類和位置
                GetTransform().parent = hitpoit;
                GetTransform().localPosition = new Vector3(0.0f, 0.0f, 0.0f);
            }
        }
    }

這個函數的功能是先在表裏查找,如果找到了,則去查找對象的虛擬點,然後將該虛擬點設置給特效。爲了搞清楚子類的編寫,我們再舉一個子類的實現 NormalEffect 正常特效,它也是繼承自 IEffect 類,普通技能的 OnLoadComplete 函數實現如下:

public override void OnLoadComplete()
    {
        if (Target != null)
        {
            GetTransform().parent = Target.transform;
        }

        //無偏移位置,旋轉
        GetTransform().localPosition = new Vector3(0.0f, 0.0f, 0.0f);
        GetTransform().localRotation = Quaternion.identity;
    }     

普通技能是設置技能釋放的位置和旋轉角度,是不是很簡單?父類和子類設計完成,一個問題又擺在我們面前這麼多技能,我們同樣也需要一個管理器 EffectManager 類,用於對外提供創建特效接口,我們還是以 BeAttackEffect 類爲例,我們要創建該特效,這個是在 EffectManager 類中實現的,代碼實現函數如下:

 public BeAttackEffect CreateTimeBasedEffect(string res, float time, IEntity entity)
    {
        if (res == "0")
            return null;

        BeAttackEffect effect = new BeAttackEffect();
        //加載特效信息
        effect.skillID = 0;             //技能id=0     
        effect.cehuaTime = time;
        effect.enTargetKey = entity.GameObjGUID;
        effect.resPath = res;           
        //創建
        effect.Create();

        AddEffect(effect.projectID, effect);
        return effect;
    }

該函數功能首先將 BeAttackEffect 初始化,然後調用 Create 創建加載特效,已在上文實現過,爲了便於管理我們調用了函數 AddEffect,目的是將特效加到表中,實現代碼如下:

//添加特效到EffectMap表
    public void AddEffect(Int64 id, IEffect effect)
    {
        if (!m_EffectMap.ContainsKey(id))
        {
            m_EffectMap.Add(id, effect);
        }
        else
        {
            Debug.LogError("the id: " + id.ToString() + "effect: " + effect.resPath + "has already exsited in EffectManager::AddEffect");
        }
    }

下面通過案例,給讀者介紹如何使用?如果我們要創建 NormalEffect,只需要調用函數接口:

NormalEffect absortSkillEffect = EffectManager.Instance.CreateNormalEffect(absortActPath, RealEntity.objAttackPoint.gameObject);

這樣我們就創建了普通技能,當然它是配合角色動作一起創建的,角色在播放動作時釋放技能,可以結合着我們的有限狀態機一起使用,比如創建一個角色,釋放技能效果,代碼如下所示:

            if (EntityManager.AllEntitys.TryGetValue(sGUID, out entity))
        {
            pos.y = entity.realObject.transform.position.y;
            entity.EntityFSMChangeDataOnPrepareSkill(pos, dir, pMsg.skillid, target);
            entity.OnFSMStateChange(EntityReleaseSkillFSM.Instance);
        }

以上是創建角色,同時將角色的狀態轉換到釋放技能動作,調用一個協成創建技能,代碼如下:

    IEffect effect = EffectManager.Instance.CreateNormalEffect(GameConstDefine.LoadGameSkillEffectPath + "release/" + skillconfig.effect, entitynpc.RealEntity.objPoint.gameObject);

將上述語句放到一個協成中,函數如下所示:

    IEnumerator ReleaseNormalSkill()
{
        yield return 1;
    IEffect effect = EffectManager.Instance.CreateNormalEffect(GameConstDefine.LoadGameSkillEffectPath + "release/" + skillconfig.effect, entitynpc.RealEntity.objPoint.gameObject);

}

然後在角色釋放動作下面調用該函數即可:

StartCoroutine(ReleaseNormalSkill())

在使用特效時,還有一個腳本需要注意,就是將 EffectScript 腳本掛到對象上面,EffectScript 主要是用於設置特效的生命週期,便於美術和策劃調試,EffectScript 腳本代碼實現如下:

    public class EffectScript : MonoBehaviour
{
    /// <summary>
    /// 特效生命週期
    /// </summary>
    public float lifeTime = 2.0f;

    //特效顯示等級
    public EffectLodLevel lodLevel = EffectLodLevel.High;

    // Use this for initialization
    void Start()
    {
        //編輯器模式
        if (Application.isEditor && !Application.isPlaying)
        {
            if (lifeTime == 0)
                lifeTime = 10000000;

            DestroyObject(gameObject, lifeTime);
        }
    }
}

再將該腳本掛接到對象上面,然後釋放技能,實現效果如下所示:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章