在講技能之前,先介紹一下游戲特效,遊戲特效製作方式主要分爲三種:序列幀特效、2D 骨骼動畫特效、粒子特效。序列幀特效和 2D 骨骼動畫特效大量運用在 2D 遊戲中,而粒子特效主要是運用在 3D 遊戲和 2D 遊戲中。遊戲特效可以提升整個遊戲的畫面品質,市場上的每款遊戲都會有大量特效,可以讓整個畫面絢麗多彩。在遊戲場景中,技能特效是伴隨着角色動作播放的,角色動作可以使用我們前面介紹的角色系統創建,利用 FSM 播放動作,技能就是將動作和特效合在一起播放,在講解技能之前,先給讀者展示一下我們的技能模塊設計圖:
上圖中,我們先介紹 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);
}
}
}
再將該腳本掛接到對象上面,然後釋放技能,實現效果如下所示: