十四章:角色技能系统的实现
技能系统是本游戏开发的最后一部分内容,与普通攻击系统不同,我们需要添加释放技能的特效、动画以及播放时间。并将动画分为回复、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属性
效果如下