十四章:角色技能系统的实现
技能系统是本游戏开发的最后一部分内容,与普通攻击系统不同,我们需要添加释放技能的特效、动画以及播放时间。并将动画分为回复、Buff、单体和群体。
14.1添加技能的特效名称
释放技能时要使用攻击特效以及攻击动画,因此我们对SkillInfoInList进行修改。
其中特效名称表示释放时的特效,动画名称对应技能的效果
添加完毕后如下
5001,魔法弹,skill-09,伤害 350%,SingleTarget,Attack,350,0,20,10,Magician,1,Enemy,5,Efx_CriticalStrike,Skill-MagicBall,1.1 5002,治疗,skill-13,治愈30HP,Passive,HP,30,0,10,20,Magician,1,Self,0,Heal_Effect,Attack1,0.83 5003,冥想,skill-10,魔法恢复20,Passive,MP,20,0,0,30,Magician,5,Self,0,Effect_BlueHeal,Attack1,0.83 5004,法力涌动,skill-08,攻击力为200%持续15秒,Buff,Attack,200,15,30,30,Magician,7,Self,0,RangeMagic_Effect,Skill-GroundImpact,1.1 5005,战斗热诚,skill-12,攻击力速度为200%持续30,Buff,AttackSpeed,200,30,30,30,Magician,8,Self,0,Efx_One-handQuicken,Skill-GroundImpact,1.1 5006,究极风暴,skill-11,攻击力400% 所有敌人,MultiTarget,Attack,400,0,50,40,Magician,9,Position,10,MagicSphere_effect,AttackCritical,0.8
在SkillsInfo脚本中,我们为class SkillInfo添加几个成员变量,用以表示我们新增的3个值
public string effectName; public string aniName; public float aniTime;
并在读取技能列表的函数ReadInfo中添加这几个新的变量
info.effectName = proArray[14]; info.aniName = proArray[15]; info.aniTime = proArray[16];
之后,我们通过与技能快捷键Shortcut互动实现技能释放,在Shortcut脚本中判断当前快捷栏中是否含有技能。
void Update() { if (type = ShortType.Skill) { OnDrugUse(); } }
OnDrugUse()函数通过判断是否有足够的MP,决定是否释放技能。代码如下
void OnDrugUse() { bool success = ps.isEnoughMP (info.mpCost); //判断是否有足够的MP if(!success) { } else { pa.UseSkill(info); //释放技能 } }
isEnoughMP在PlayerStatus脚本中对应代码如下
public bool isEnoughMP(int value) { if (mp_remain >= value) //是否成功获取 { mp_remain -= value; FaceUI._instance.SetFaceProperty(); //更新显示 return true; } else { return false; } }
技能的释放通过PlayerAttack中的UseSkil()实现并在Shortcut中进行调用,在设计函数之前,我们需要导入所有动画的技能名称,储存在PlayerAttack中,通过info中的名称(effectName)播放对应动画。
public GameObject[] effectArray;
将动画信息用字典存储起来,之后通过动画名字调用动画的GameObject
private Dictionary<string,GameObject> effectDict = new Dictionary<string,GameObject>(); void Awake() { foreach(GameObject go in effectArray) { effectDict.Add(go.name,go); } }
14.2 回复技能
有了上述铺垫,正式开始UseSkill()函数的设计。因为技能种类分为 Passive, Buff, SingleTarget, MultiTarget四类,我们先分析第一类,即回复技能。
步骤为
- 按下技能键
- 设置状态为施法,此时无法移动
- 播放特效并计时
- 计时结束改变状态并播放动画
- 回复HP/MP
因此,当info.applyType为Passive时,即
public void UseSkill(SkillsInfo.SkillInfo info) { if(ps.role == playerRole.Magician) //判断当前角色是否是Magician { if(info.applyRole == SkillsInfo.ApplyRole.Magician) //当前技能是否适用Magician { switch(info.applyType) //判断 { case SkillsInfo.ApplyType.Passive: //为恢复技能时 StartCoroutine(OnPassiveSkillUse(info)); //利用协程的计时功能实现技能的使用。 break; } } } } IEnumerator OnPassiveSkillUse(SkillsInfo.SkillInfo info) { state = PlayerState.skillAttack; //state设置为skillAttack,无法进行移动 animation.CrossFade (info.aniName); //播放特效,注意这里的动画为抬手施法的特效,如下左图 yield return new WaitForSeconds(info.aniTime); state = PlayerState.normalWalk; //当技能持续时间结束后,才能进行移动等操作 int hp = 0, mp = 0; if(info.applyProperty == SkillsInfo.ApplyProperty.HP) { hp = info.applyValue; } else if(info.applyProperty == SkillsInfo.ApplyProperty.MP) { mp = info.applyValue; } ps.GetDrug (hp, mp); //加HP或MP GameObject prefab = null; effectDict.TryGetValue (info.effectName, out prefab); //从字典中获取施法动画,如下右图 GameObject.Instantiate (prefab, transform.position, Quaternion.identity); }
14.3 Buff技能
Buff技能可以参照passive技能的模式,步骤为
- 按下技能键
- 设置状态为施法,此时无法移动
- 播放特效并计时
- 计时结束改变状态并播放动画
- 增加对应属性并计时
- 持续时间结束后还原
代码如下
IEnumerator OnBuffSkillUse(SkillsInfo.SkillInfo info) { state = PlayerState.skillAttack; animation.CrossFade (info.aniName); yield return new WaitForSeconds(info.aniTime); GameObject prefab = null; effectDict.TryGetValue (info.effectName, out prefab); GameObject.Instantiate (prefab, transform.position, Quaternion.identity); state = PlayerState.normalWalk; switch(info.applyProperty) //Buff技能要考虑增加的属性,因此用case处理 { case SkillsInfo.ApplyProperty.Attack: ps.attack *= (info.applyValue/100f); break; case SkillsInfo.ApplyProperty.AttackSpeed: attackRate *= (info.applyValue/100f); break; case SkillsInfo.ApplyProperty.Defense: ps.defense *= (info.applyValue/100f); break; case SkillsInfo.ApplyProperty.Speed: pm.speed *= (info.applyValue/100f); break; } yield return new WaitForSeconds (info.applyTime); //持续一段时间 switch(info.applyProperty) //取消Buff效果 { case SkillsInfo.ApplyProperty.Attack: ps.attack /= (info.applyValue/100f); break; case SkillsInfo.ApplyProperty.AttackSpeed: attackRate /= (info.applyValue/100f); break; case SkillsInfo.ApplyProperty.Defense: ps.defense /= (info.applyValue/100f); break; case SkillsInfo.ApplyProperty.Speed: pm.speed /= (info.applyValue/100f); //控制PlayerMove中的移动速度 break; } }
14.4 单目标技能
单目标技能需要选定目标,因此步骤为
- 按下技能键
- 修改鼠标为技能锁定图标
- 判断是否点击敌人(射线检测)
- 设置状态为施法,此时无法移动
- 播放特效并计时
- 计时结束改变状态并播放动画
- 造成伤害
在PlayerAttack中新增OnSingleTargetSkillUse()控制技能释放,首先修改图标,在MouseSetting中,添加
public void SetSkillAttackCursor() { Cursor.SetCursor (cursor_lockTarget, hotspot, mode); }
并在OnSingleTargetSkillUse()中调用,在这里我们需要存储info信息并增加一个标志位,当技能为单体技能时,我们有
private bool isLockingTarget = false; private SkillsInfo.SkillInfo info; void OnSingleTargetSkillUse(SkillsInfo.SkillInfo info) { state = PlayerState.skillAttack; MouseSetting._instance.SetSkillAttackCursor (); isLockingTarget = true; this.info = info; }
之后在Update()中,根据标志位和info信息进行技能处理,即
void Update() { if(isLockingTarget && Input.GetMouseButtonDown(0)) { OnLockTargetButtonDown(); //处理技能释放功能 } }
在OnLockTargetButtonDown()中,
void OnLockTargetButtonDown() { isLockingTarget = false; switch(info.applyType) { case SkillsInfo.ApplyType.SingleTarget: StartCoroutine(SkillAttackSingleTarget()); break; case SkillsInfo.ApplyType.MultiTarget: //todo break; } }
先考虑单个目标的功能,先判断鼠标是否接触到敌人
IEnumerator SkillAttackSingleTarget() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hitInfo; bool isCollider = Physics.Raycast(ray,out hitInfo); if(isCollider && hitInfo.collider.tag == Tags.enemy) { state = PlayerState.skillAttack; animation.CrossFade (info.aniName); yield return new WaitForSeconds(info.aniTime); GameObject prefab = null; effectDict.TryGetValue (info.effectName, out prefab); GameObject.Instantiate (prefab, hitInfo.collider.transform.position, Quaternion.identity); //使用hitInfo.collider得到怪物 hitInfo.collider.GetComponent<WolfBaby>().BeDamaged((int)(GetAttack() * (info.applyValue/100f))); //造成伤害 } }
即可完成单体技能的释放
14.5 群体技能
群体技能与单体技能的差异主要体现在群体技能指定范围,并要判断范围内的敌人。因此步骤为
- 按下技能键
- 修改鼠标为技能锁定图标
- 点击一个位置
- 设置状态为施法,此时无法移动
- 播放特效并计时
- 计时结束改变状态并播放动画
- 判断技能接触单位(运用collider)
- 造成伤害
IEnumerator SkillAttackMultiTarget() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hitInfo; bool isCollider = Physics.Raycast(ray,out hitInfo); if(isCollider) //只要发生碰撞即可 { state = PlayerState.skillAttack; animation.CrossFade (info.aniName); yield return new WaitForSeconds(info.aniTime); GameObject prefab = null; effectDict.TryGetValue (info.effectName, out prefab); GameObject.Instantiate (prefab, hitInfo.point + Vector3.up, Quaternion.identity); //用hitInfo.point代替hitInfo.collider.transform.position //todo:范围内敌人受伤效果 } else { state = PlayerState.normalAttack; } MouseSetting._instance.SetNormalCursor (); }
为了检测碰撞到的敌人,我们将特效拖入场景
并给它一个MagicSphere脚本
public class MagicSphere : MonoBehaviour { public int attack = 0; private List<WolfBaby>wolfList = new List<WolfBaby> (); //通过List判断当前野怪是否被攻击 public void OnTriggerEnter(Collider col) { if(col.tag == Tags.enemy) { WolfBaby baby = col.GetComponent<WolfBaby>(); int index = wolfList.IndexOf(baby); if(index == -1) //若还未被攻击,造成伤害,并添加到wolfList之中 { baby.BeDamaged(attack); wolfList.Add(baby); } } } }
之后就可以处理todo中的受伤害效果了
GameObject go = GameObject.Instantiate (prefab, hitInfo.point + Vector3.up * 0.5f, Quaternion.identity) as GameObject; 创建技能 go.GetComponent<MagicSphere>().attack = GetAttack() * (info.applyValue/100f); //赋值给attack属性
效果如下