其實技能系統有很多種設計方式,暫且列出一種以前項目的設計。
目標:
1.使用盡可能少的類,實現游戲里所有種類的技能。
2.滿足打擊感的可調節性、可配置性。
3.能基本滿足絕大部分角色扮演類游戲(比如ARPG、卡牌、Moba、ACT等),更換項目只需要對代碼做微調。 ACT類,連擊技能需要在技能類調用之前做計數,剩余的代碼一樣。
需求分析:
根據上面的目標,我們把攻擊分為幾個模塊
1.目標選擇 2.攻擊准備 3. 開始攻擊 4.受擊特效與動作 5.傷害結算、飆血浮動條、血條減少。 6.技能打斷
注意。技能打斷,可配置該技能在XXX秒--XXX秒之間可被打斷。
就不詳細解釋了,具體實現看代碼,有注釋。
[mw_shl_code=applescript,true]public abstract class SkillBase
{
//技能狀態
public SkillState State { get; protected set; }
public int SkillID { get { return dataVo == null ? 0 : dataVo.ID; } }
public SkillDataVo dataVo { get; protected set; }
//特效
protected GameObject effect;
protected bool ifInitEffect = false; //是否已經播放了特效
protected float effectBeginTime;
protected float effectTime = 5f; //特效時長
//傷害
protected bool damaged = false; //是否已經計算了傷害
//動畫
protected float curAnimalLength;//當前動畫時長
protected string animalName;
protected float CurCd;
protected SpriteController owner;
protected float timer;
//回收動作
protected bool IfUseActionReturn;
protected float ActionReturnTime;
//攻擊目標、角度范圍、距離
protected bool InForward(Transform trans, float _angle, float _dis)
{
if (null == trans)
{
return false;
}
// 獲得從當前Obj到目標Obj的方向,然后求和當前Obj的朝向夾角。
Vector2 tarPos = new Vector2(trans.position.x, trans.position.z);
Vector2 myPos = new Vector2(owner.transform.position.x, owner.transform.position.z);
Vector2 mydir = new Vector2(owner.transform.forward.x, owner.transform.forward.z);
Vector2 dir = tarPos - myPos;
float angle = Vector2.Angle(mydir.normalized, dir.normalized);
if (angle < _angle / 2f)
{
float toPosDis = Vector2.Distance(tarPos, myPos);
if (toPosDis <= _dis)
{
return true;
}
else
{
return false;
}
}
return false;
}
public SkillBase(SpriteController obj, int skillID)
{
State = SkillState.None;
this.owner = obj;
this.CurCd = 0f;
dataVo = ConfigDataManager.GetSkillDataByID(skillID);
if (owner.ActionMng != null && owner.ActionMng.TryGetAnimationLength(dataVo.actionName, out curAnimalLength))
{
//curAnimalLength -= 0f;
}
else
curAnimalLength = 1f;
if (dataVo.BufferID.Length > 0)
{
dataVo.bufferEntity = new BufferDataVo[dataVo.BufferID.Length];
for (int i = 0; i < dataVo.bufferEntity.Length; i++)
{
dataVo.bufferEntity = ConfigDataManager.GetConfigBufferData(dataVo.BufferID);
}
}
animalName = dataVo.actionName;
//加載特效
GameObject effectPre = LoadCache.LoadEffect(dataVo.effectName);
if (effectPre != null)
{
effect = GameObject.Instantiate(effectPre) as GameObject;
effect.SetActive(false);
}
}
// Interrupts
virtual public void SetInterrupts() { State = SkillState.Interrupts; }
virtual public void ProcessBuffer(int index) { }
virtual public bool IsCanInterrupts()
{
if (State == SkillState.None)
return true;
if (dataVo.minInterrupt != -1 && timer <= dataVo.minInterrupt)
return true;
if (dataVo.maxInterrupt != -1 && timer >= dataVo.maxInterrupt)
return true;
return false;
}
virtual public void Execute()
{
switch (State)
{
case SkillState.Start:
break;
case SkillState.Execution:
break;
case SkillState.Interrupts:
break;
case SkillState.Finish:
break;
}
}
virtual public void Begin()
{
State = SkillState.Start;
}
//這是一個模
virtual public void DoUpdate()
{
if (State == SkillState.None)
{
return;
}
// if (owner.mAIState == AIState.Dead)
// return;
Execute();
CalcCd();
}
public bool IsCanUse()
{
return State == SkillState.None;
}
//CD計時
public void CalcCd()
{
CurCd += RealTime.deltaTime;
if ( CurCd >= dataVo.CD)
{
CurCd = 0f;
}
}
}[/mw_shl_code]
我們編寫了一個超類,規定了技能的實現過程和方式、以及保存了技能的公有信息。
然后我們實現一個子類,能涵蓋絕大部分的技能。
[mw_shl_code=applescript,true]public class Skill_Default : SkillBase
{
private bool isMove;
SkillEzMoveData moveData;
Vector3 moveDir;
public Skill_Default(SpriteController obj, int skillID)
: base(obj, skillID)
{
}
//遍歷場景所有怪物
void ForALLEnmy()
{
Dictionary<long, SpriteController> dic = BattlerDataManager.Instance.DicMonsters;
//List<long> tmpList = new List<long>();
foreach (SpriteController controller in dic.Values)
{
if (controller.mAIState == AIState.Dead)
continue;
//技能的傷害判斷和處理
bool inforward = InForward(controller.transform, dataVo.attackFanAngle, dataVo.attackFanRange);
if (inforward)
{
int damageValue = dataVo.harm;
owner.AttackTo(controller, damageValue);
Util.CallMethod("FightingPanel", "UpdateHeroHpMp", BattlerDataManager.Instance.SelfPlayer.SpiritVO.CurHp, BattlerDataManager.Instance.SelfPlayer.SpiritVO.CurMp); //刷新角色血條
// if (BattlerDataManager.Instance.SelfPlayer.mAIState == AIState.Dead)
// {
// //玩家死亡
//
// }
if (BattlerDataManager.Instance.SelfPlayer.SpiritVO.CurHp <= 0)
{
//玩家死亡
// Util.CallMethod("FightingPanel", "Lose");
}
}
}
}
public override void Execute()
{
switch (State)
{
case SkillState.Start:
moveDir = owner.MoveToPostion;
timer = 0f;
effectBeginTime = 0;
owner.MoveMng.StopMove();
isMove = false;
// IfUseActionReturn = false;
damaged = false;
ifInitEffect = false;
//執行
owner.mAIState = AIState.Attack;
owner.ActionMng.PlayAnimation(animalName);
State = SkillState.Execution;
Debug.Log("skill ID "+dataVo.ID);
break;
case SkillState.Interrupts:
{
Debug.Log("被打斷");
State = SkillState.None;
}
break;
case SkillState.Execution:
{
timer += Time.deltaTime;
//觸發傷害
if (!damaged && timer > dataVo.damageTime)
{
damaged = true;
//傷害結算
ForALLEnmy();
}
//觸發特效
if (!ifInitEffect && timer > dataVo.fxStartTime)
{
if (effect != null)
{
effect.transform.position = owner.transform.position + new Vector3(0, 0.5f, 0)+owner.transform.forward*2;
effect.transform.rotation = owner.transform.rotation;
effect.SetActive(false);
effect.SetActive(true);
}
ifInitEffect = true;
}
//動畫播放結束,並且觸發了特效,觸發了傷害
if (timer >= curAnimalLength && damaged && ifInitEffect)
{
State = SkillState.Finish;
}
}
break;
case SkillState.Finish:
{
Debug.Log("Finish");
if (effect != null)
effect.SetActive(false);
owner.mAIState = AIState.Idle;
State = SkillState.None;
}
break;
}
}
}
[/mw_shl_code]
有注釋的情況下,應該不難看懂,拋磚引玉。