【ARPG/MOBA类游戏游戏系列】技能系统设计
其实技能系统有很多种设计方式,暂且列出一种以前项目的设计。
目标:
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]
有注释的情况下,应该不难看懂,抛砖引玉。