Unity——技能系统(二)

Unity技能系统(二) Unity技能系统(一)
Demo展示:
五.技能管理和释放 1.CharacterSkillSystem
技能系统类,给外部(技能按钮,按键)提供技能释放方法;
技能释放逻辑:
【Unity——技能系统(二)】Unity——技能系统(二)
文章图片

按顺序判定条件,成立怎继续,否则返回;
最终调用CharacterSkillManager中的DeploySkill方法;传递参数为SkillData;
提供了随机技能方法;

/// /// 角色系统 /// [RequireComponent(typeof(CharacterSkillManager))] public class CharacterSkillSystem : MonoBehaviour { //技能管理 public CharacterSkillManager chSkillMgr; //角色状态 private CharacterStatus chStatus; //角色动画 private Animator mAnimator; //当前使用的技能 private SkillData currentUseSkill; //当前攻击的目标 private Transform currentSelectedTarget; //初始化 public void Start() { mAnimator = GetComponent(); chSkillMgr = GetComponent(); chStatus = GetComponent(); }/// /// 使用指定技能 /// /// 技能编号 /// 是否连击 public void AttackUseSkill(int skillid, bool isBatter = false) { //如果是连击,找当前技能的下一个连击技能 if (currentUseSkill != null && isBatter) skillid = currentUseSkill.skill.nextBatterId; //准备技能 currentUseSkill = chSkillMgr.PrepareSkill(skillid); if (currentUseSkill != null) { //选中释放技能调用 if ((currentUseSkill.skill.damageType & DamageType.Select) == DamageType.Select) { var selectedTaget = SelectTarget(); if (currentUseSkill.skill.attckTargetTags.Contains("Player")) selectedTaget = gameObject; if (selectedTaget != null) { CharacterStatus selectStatus = null; //修改成获取characterStatus中的Selected节点设置隐藏; if (currentSelectedTarget != null) { selectStatus = currentSelectedTarget.GetComponent(); selectStatus.selected.SetActive(false); } currentSelectedTarget = selectedTaget.transform; selectStatus = currentSelectedTarget.GetComponent(); selectStatus.selected.SetActive(true); //buff技能 if ((currentUseSkill.skill.damageType & DamageType.Buff) == DamageType.Buff) { foreach (var buff in currentUseSkill.skill.buffType) { //加bufficon GameObject uiPortrait = selectStatus.uiPortrait.gameObject; MonsterMgr.I.HideAllEnemyPortraits(); uiPortrait.SetActive(true); uiPortrait.transform.SetAsLastSibling(); selectStatus.uiPortrait.AddBuffIcon(buff, currentUseSkill.skill.buffDuration); //已有该buff刷新 bool exist = false; var buffs = selectedTaget.GetComponents(); foreach (var it in buffs) { if (it.bufftype == buff) { it.Reset(); exist = true; break; } }if (exist) continue; //添加新buff var buffRun = selectedTaget.AddComponent(); buffRun.InitBuff(buff, currentUseSkill.skill.buffDuration, currentUseSkill.skill.buffValue, currentUseSkill.skill.buffInterval); } return; }//转向目标 //transform.LookAt(currentSelectedTarget); chSkillMgr.DeploySkill(currentUseSkill); mAnimator.Play(currentUseSkill.skill.animtionName); } } else { chSkillMgr.DeploySkill(currentUseSkill); mAnimator.Play(currentUseSkill.skill.animtionName); } } }/// /// 随机选择技能 /// public void RandomSelectSkill() { if (chSkillMgr.skills.Count > 0) { int index = UnityEngine.Random.Range(0, chSkillMgr.skills.Count); currentUseSkill = chSkillMgr.PrepareSkill(chSkillMgr.skills[index].skill.skillID); if (currentUseSkill == null) //随机技能未找到或未冷却结束 currentUseSkill = chSkillMgr.skills[0]; //用技能表中第一个(默认技能)做补充 } }//选择目标 private GameObject SelectTarget() { //发一个球形射线,找出所有碰撞体 var colliders = Physics.OverlapSphere(transform.position, currentUseSkill.skill.attackDisntance); if (colliders == null || colliders.Length == 0) return null; //从碰撞体列表中挑出所有的敌人 String[] attTags = currentUseSkill.skill.attckTargetTags; var array = CollectionHelper.Select(colliders, p => p.gameObject); //正前方,tag正确,血量大于0,处于正前方的敌人 array = CollectionHelper.FindAll(array, p => Array.IndexOf(attTags, p.tag) >= 0 && p.GetComponent().HP > 0 && Vector3.Angle(transform.forward, p.transform.position - transform.position) <= 90); if (array == null || array.Length == 0) return null; //将所有的敌人,按与技能的发出者之间的距离升序排列, CollectionHelper.OrderBy(array, p => Vector3.Distance(transform.position, p.transform.position)); return array[0]; } }

2.CharacterSkillManager
技能数据的管理,加载所有技能特效模板进入对象池;
给CharacterSkillSystem提供技能释放接口DeploySkill;
提供技能冷却计算,预留获取cd剩余时间接口给UI,以及获取技能是否在cd中;
[RequireComponent(typeof(CharacterSkillSystem))] public class CharacterSkillManager : MonoBehaviour { /// 管理所有技能的容器 public List skills = new List(); /// 技能的拥有者 private CharacterStatus chStatus = null; private SkillData curSkill; //添加技能数据 private void AddSkill(string path) { SkillTemp skTemp = Instantiate(Resources.Load(path)); Skill sk = LoadSkill(skTemp); ; SkillData skd = new SkillData(); skd.skill = sk; skills.Add(skd); }//初始化技能数据(有什么技能) public void Start() { chStatus = GetComponent(); AddSkill("Skill_1"); AddSkill("Skill_2"); AddSkill("Skill_3"); AddSkill("Skill_4"); AddSkill("Skill_5"); foreach (var item in skills) { //动态加载技能特效预制体//Resources/Skill -- 技能特效预制体 if (item.skillPrefab == null && !string.IsNullOrEmpty(item.skill.prefabName)) item.skillPrefab = LoadFxPrefab("Skill/" + item.skill.prefabName); //Resources/Skill/HitFx技能伤害特效预制体 if (item.hitFxPrefab == null && !string.IsNullOrEmpty(item.skill.hitFxName)) item.hitFxPrefab = LoadFxPrefab("Skill/" + item.skill.hitFxName); } }//将特效预制件载入到对象池,以备将来使用 private GameObject LoadFxPrefab(string path) { var key = path.Substring(path.LastIndexOf("/") + 1); var go = Resources.Load(path); GameObjectPool.I.Destory( GameObjectPool.I.CreateObject( key, go, transform.position, transform.rotation) ); return go; }//准备技能 public SkillData PrepareSkill(int id) { //从技能容器中找出相应ID的技能 var skillData = https://www.it610.com/article/skills.Find(p => p.skill.skillID == id); if (skillData != null && //查找到技能 chStatus.SP >= skillData.skill.costSP && //检查角色SP是否够使用该技能 skillData.coolRemain == 0) //且该技能已经冷却结束 { skillData.Owner = gameObject; return skillData; }return null; }//释放技能 public void DeploySkill(SkillData skillData) { //开始冷却计时 StartCoroutine(CoolTimeDown(skillData)); //动画某一帧触发技能特效,这里写一个延迟调用的方法,使用动画时间的百分解决特效释放时间问题 if (skillData.skill.delayAnimaTime != 0) { curSkill = skillData; Invoke("DelayDeploySkill", skillData.skill.delayAnimaTime); return; }GameObject tempGo = null; //创建技能预制体+创建位置的偏移 if ((skillData.skill.damageType & DamageType.FxOffset) == DamageType.FxOffset) tempGo = GameObjectPool.I.CreateObject(skillData.skill.prefabName, skillData.skillPrefab, transform.position + transform.forward * skillData.skill.fxOffset, transform.rotation); //技能有发射点 else if ((skillData.skill.damageType & DamageType.FirePos) == DamageType.FirePos) tempGo = GameObjectPool.I.CreateObject(skillData.skill.prefabName, skillData.skillPrefab, chStatus.FirePos.position, chStatus.FirePos.rotation); if(tempGo == null) return; //从预制体对象上找到技能释放对象 var deployer = tempGo.GetComponent(); if (deployer == null) deployer = tempGo.AddComponent(); //设置要释放的技能————划重点 deployer.skillData = https://www.it610.com/article/skillData; //调用释放方法 deployer.DeploySkill(); //技能持续时间过后,技能要销毁 if ((skillData.skill.damageType & DamageType.Bullet) != DamageType.Bullet) { if (skillData.skill.durationTime> 0) GameObjectPool.I.Destory(tempGo, skillData.skill.durationTime); else GameObjectPool.I.Destory(tempGo, 0.5f); } } //延迟释放技能 private void DelayDeploySkill() { GameObject tempGo = null; //创建技能预制体+创建位置的偏移 if ((curSkill.skill.damageType & DamageType.FxOffset) == DamageType.FxOffset) tempGo = GameObjectPool.I.CreateObject(curSkill.skill.prefabName, curSkill.skillPrefab, transform.position + transform.forward * curSkill.skill.fxOffset, transform.rotation); else if ((curSkill.skill.damageType & DamageType.FirePos) == DamageType.FirePos) tempGo = GameObjectPool.I.CreateObject(curSkill.skill.prefabName, curSkill.skillPrefab, chStatus.FirePos.position, chStatus.FirePos.rotation); //从预制体对象上找到技能释放对象 var deployer = tempGo.GetComponent(); if (deployer == null) deployer = tempGo.AddComponent(); //设置要释放的技能 deployer.skillData = https://www.it610.com/article/curSkill; //调用释放方法 deployer.DeploySkill(); //技能持续时间过后,技能要销毁 if ((curSkill.skill.damageType & DamageType.Bullet) != DamageType.Bullet) { if (curSkill.skill.durationTime> 0) GameObjectPool.I.Destory(tempGo, curSkill.skill.durationTime); else GameObjectPool.I.Destory(tempGo, 0.5f); } }//冷却时间倒计时 public IEnumerator CoolTimeDown(SkillData skillData) { skillData.coolRemain = skillData.skill.coolTime; while (skillData.coolRemain > 0) { yield return new WaitForSeconds(0.1f); skillData.coolRemain -= 0.1f; }skillData.coolRemain = 0; }//取得冷却倒计时的剩余时间(秒) public float GetSkillCoolRemain(int id) { return skills.Find(p => p.skill.skillID == id).coolRemain; }private Skill LoadSkill(SkillTemp skillTemp) { Skill sk = skillTemp.skill; int count = skillTemp.damageType.Length; for (int i = 0; i < count; ++i) { sk.damageType = sk.damageType | skillTemp.damageType[i]; } return sk; } }

3.SkillDeployer
挂载在技能特效上, 执行技能对释放者造成的影响(消耗MP,刷新MPUI);
对命中目标执行伤害计算,加载受伤特效添加debuff等;
伤害触发分为碰撞触发和目标选择器选中触发;
上面划得重点:
给技能释放器中skillData属性赋值的同时,创建目标选择器,给CharacterStatrus字段赋值;
中间有很多坑点:
1.刷新敌人头像显示,必须要设置显示层级在ui的最下层,同时设置其他UI位置,不能设置Active,禁用buff倒计时计算会失效,也可以将buff倒计时单独管理;
2.检测已有相同buff存在刷新buff时间;
3.多段伤害,每段伤害要重新检测攻击目标,有击退等buff存在;
4.伤害计算单独写方法,方便修改;
5.弹道和碰撞触发伤害的技能,受击特效挂载点不应该是HitFxPos,而是碰撞的接触点,然而使用触发器碰撞没办法返回碰撞点坐标,所以又做了射线检测;但是又会存在新的问题,射线检测只有一条线,没有体积,会造成边缘碰撞时射线未检测到,却已经触发碰撞了;
这里做了处理,射线未检测到却碰撞特效生成在HitFxPos;
可以自行尝试一下在技能特效的前段设置HitFxPos来设置受击特效的位置;
public class SkillDeployer : MonoBehaviour { private SkillData m_skillData; ///敌人选区,选择目标的算法 public IAttackSelector attackTargetSelector; private DamageMode damageMode; //发出者 private CharacterStatus status; ///要释放的技能 public SkillData skillData { set { m_skillData = https://www.it610.com/article/value; damageMode = 0; if ((skillData.skill.damageType & DamageType.Sector) == DamageType.Sector) damageMode = DamageMode.Sector; else if ((skillData.skill.damageType & DamageType.Circle) == DamageType.Circle) damageMode = DamageMode.Circle; else if ((skillData.skill.damageType & DamageType.Line) == DamageType.Line) damageMode = DamageMode.Line; if (damageMode != 0) attackTargetSelector = SelectorFactory.CreateSelector(damageMode); status = value.Owner.GetComponent(); } get { return m_skillData; } }/// 技能释放 public virtual void DeploySkill() { if (m_skillData =https://www.it610.com/article/= null) return; //对自身的影响 SelfImpact(m_skillData.Owner); //执行伤害的计算 if (damageMode != 0) StartCoroutine(ExecuteDamage()); }//执行伤害的计算 protected virtual IEnumerator ExecuteDamage() { //按持续时间及,两次伤害间隔, float attackTimer = 0; //已持续攻击的时间ResetTargets(); if (skillData.attackTargets != null && skillData.attackTargets.Length> 0) { //Debug.Log(skillData.attackTargets[0].name); foreach (var item in skillData.attackTargets) { //刷新敌人头像显示 CharacterStatus targetStatus = item.GetComponent(); GameObject uiPortrait = targetStatus.uiPortrait.gameObject; MonsterMgr.I.HideAllEnemyPortraits(); uiPortrait.SetActive(true); uiPortrait.transform.SetAsLastSibling(); //加buff foreach (var buff in skillData.skill.buffType) { //加bufficon targetStatus.uiPortrait.AddBuffIcon(buff, skillData.skill.buffDuration); //已有该buff刷新 bool exist = false; var buffs = item.GetComponents(); foreach (var it in buffs) { if (it.bufftype == buff) { it.Reset(); exist = true; break; } }if (exist) { continue; }//添加新buff var buffRun = item.AddComponent(); buffRun.InitBuff(buff, skillData.skill.buffDuration, skillData.skill.buffValue, skillData.skill.buffInterval); } } }do { //通过选择器选好攻击目标 ResetTargets(); if (skillData.attackTargets != null && skillData.attackTargets.Length > 0) { //Debug.Log(skillData.attackTargets[0].name); foreach (var item in skillData.attackTargets) { //对敌人的影响 TargetImpact(item); } }yield return new WaitForSeconds(skillData.skill.damageInterval); attackTimer += skillData.skill.damageInterval; //做伤害数值的计算 } while (skillData.skill.durationTime > attackTimer); }private void ResetTargets() { if (m_skillData =https://www.it610.com/article/= null) return; m_skillData.attackTargets = attackTargetSelector.SelectTarget(m_skillData, transform); }private float CirculateDamage(GameObject goTarget) { CharacterStatus goStatus = goTarget.GetComponent(); //是否命中计算 float rate = status.hitRate / (float) goStatus.dodgeRate; if (rate < 1) { int max = (int) (rate * 100); int val = Random.Range(0, 100); if (val < max) { //Debug.Log("Miss"); return 0; } }//普攻的技能伤害为0; 技能有固定伤害*等级加成 + 普攻伤害 var damageVal = status.damage * (1000 / (1000 + goStatus.defence)) + skillData.skill.damage * (1 + skillData.level * skillData.skill.damageRatio); return damageVal; }///对敌人的影响nag public virtual void TargetImpact(GameObject goTarget) { //出受伤特效 if (skillData.hitFxPrefab != null) { //找到受击特效的挂点 Transform hitFxPos = goTarget.GetComponent().HitFxPos; var go = GameObjectPool.I.CreateObject( skillData.skill.hitFxName, skillData.hitFxPrefab, hitFxPos.position, hitFxPos.rotation); go.transform.SetParent(hitFxPos); GameObjectPool.I.Destory(go, 2f); }//受伤 var damageVal = CirculateDamage(goTarget); var targetStatus = goTarget.GetComponent(); targetStatus.OnDamage((int) damageVal, skillData.Owner); } //碰撞触发目标影响 public virtual void TargetImpact(GameObject goTarget, Collider collider) { //敌人buff foreach (var buff in skillData.skill.buffType) { //已有该buff刷新 bool exist = false; var buffs = goTarget.GetComponents(); foreach (var it in buffs) { if (it.bufftype == buff) { it.Reset(); exist = true; break; } }if (exist) continue; //添加新buff var buffRun = goTarget.AddComponent(); buffRun.InitBuff(buff, skillData.skill.buffDuration, skillData.skill.buffValue, skillData.skill.buffInterval); }//出受伤特效 if (skillData.hitFxPrefab != null) { //找到受击特效的挂点,碰撞但未检测到射线点,生成受击特效在hitFxPos处 Ray ray = new Ray(transform.position, transform.forward); RaycastHit hit; Physics.Raycast((Ray) ray, out hit, 1000); if (hit.collider == collider) { var go = GameObjectPool.I.CreateObject( skillData.skill.hitFxName, skillData.hitFxPrefab, hit.point, transform.rotation); GameObjectPool.I.Destory(go, 2f); } else { Transform hitFxPos = goTarget.GetComponent().HitFxPos; var go = GameObjectPool.I.CreateObject( skillData.skill.hitFxName, skillData.hitFxPrefab, hitFxPos.position, hitFxPos.rotation); GameObjectPool.I.Destory(go, 2f); } }//受伤 var damageVal = CirculateDamage(goTarget); var targetStatus = goTarget.GetComponent(); targetStatus.OnDamage((int) damageVal, skillData.Owner); }///对自身的影响 public virtual void SelfImpact(GameObject goSelf) { //释放者: 消耗SP var chStaus = goSelf.GetComponent(); if (chStaus.SP != 0) { chStaus.SP -= m_skillData.skill.costSP; chStaus.uiPortrait.RefreshHpMp(); //add+2 魔法条更新 } }private void OnTriggerEnter(Collider other) { if ((skillData.skill.damageType & DamageType.Bullet) == DamageType.Bullet) { if (skillData.skill.attckTargetTags.Contains(other.tag)) { if (skillData.skill.attackNum == 1) { CharacterStatus targetStatus = other.GetComponent(); GameObject uiPortrait = targetStatus.uiPortrait.gameObject; MonsterMgr.I.HideAllEnemyPortraits(); uiPortrait.SetActive(true); uiPortrait.transform.SetAsLastSibling(); //加buff foreach (var buff in skillData.skill.buffType) { //加bufficon targetStatus.uiPortrait.AddBuffIcon(buff, skillData.skill.buffDuration); }TargetImpact(other.gameObject, other); } else { //通过选择器选好攻击目标 IAttackSelector selector = new CircleAttackSelector(); selector.SelectTarget(m_skillData, transform); if (skillData.attackTargets != null && skillData.attackTargets.Length > 0) { foreach (var item in skillData.attackTargets) { //刷新敌人头像显示 CharacterStatus targetStatus = item.GetComponent(); GameObject uiPortrait = targetStatus.uiPortrait.gameObject; MonsterMgr.I.HideAllEnemyPortraits(); uiPortrait.SetActive(true); uiPortrait.transform.SetAsLastSibling(); //加buff foreach (var buff in skillData.skill.buffType) { //加bufficon targetStatus.uiPortrait.AddBuffIcon(buff, skillData.skill.buffDuration); }//对敌人的影响 TargetImpact(item, other); } } }GameObjectPool.I.Destory(gameObject); } else if (other.CompareTag("Wall")) { if (skillData.hitFxPrefab != null) { Ray ray = new Ray(transform.position, transform.forward); RaycastHit hit; Physics.Raycast((Ray) ray, out hit, 1000); if (hit.collider != other) return; //找到受击特效的挂点 var go = GameObjectPool.I.CreateObject( skillData.skill.hitFxName, skillData.hitFxPrefab, hit.point, other.transform.rotation); //go.transform.SetParent(hitFxPos); GameObjectPool.I.Destory(go, 2f); }GameObjectPool.I.Destory(gameObject); } } }public static Dictionary buffIconName = new Dictionary(); public static void InitBuffIconName() { buffIconName.Add(BuffType.Burn,"Buff_13"); buffIconName.Add(BuffType.Slow,"Buff_15"); buffIconName.Add(BuffType.Stun,"Buff_12"); buffIconName.Add(BuffType.Poison,"Buff_14"); buffIconName.Add(BuffType.BeatBack,"Buff_5"); buffIconName.Add(BuffType.BeatUp,"Buff_4"); buffIconName.Add(BuffType.Pull,"Buff_6"); buffIconName.Add(BuffType.AddDefence,"Buff_3"); buffIconName.Add(BuffType.RecoverHp,"Buff_7"); buffIconName.Add(BuffType.Light,"Buff_8"); } }

小结 到目前,所有技能逻辑都结束;下一节介绍buff系统和UI显示相关;

    推荐阅读