最近做ARPG小项目,新写了一套连招逻辑,感觉要比较好用些。优点是不需要再考虑索引值什么东西了,代码很好理解,于是分享一下。
思路分享
我的思路是这样的,做一个连招表,连招表上最重要的新功能有两个:一个是nextCombo,即按下鼠标左键做的招式;另一个是branchCombo,即按下鼠标右键做的招式,对于伤害处理,考虑到一个动画可能会触发多段伤害,我们是用List数据结构+动画事件来配置。这个List里配置好受伤动画名称、伤害值等数据。
首先我们写连招表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| using System.Collections.Generic; using UnityEngine;
[CreateAssetMenu(fileName = "ComboData", menuName = "Scriptable Objects/ComboData")] public class ComboData : ScriptableObject { [SerializeField] private string _comboName; [SerializeField] private List<ComboDamage> _comboDamage; [SerializeField] private float _coldTime; [SerializeField] private ComboData _nextCombo; [SerializeField] private ComboData _branchCombo; [SerializeField] private DamageType _damageType;
public string ComboName => _comboName; public List<ComboDamage> ComboDamage => _comboDamage; public float ColdTime => _coldTime; public ComboData NextCombo => _nextCombo; public ComboData BranchCombo => _branchCombo; public DamageType DamageType => _damageType; }
public enum DamageType { weapon, punch, }
[System.Serializable] public class ComboDamage { public string hitName; public string parryName; public float damage; }
|
接着是最主体的连招逻辑,这里我分成多个脚本去写了(因为攻击系统比较复杂,写了几个基类脚本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| using UnityEngine;
public abstract class CharacterAttackBase : MonoBehaviour { protected Animator _animator; protected bool _canAttack = true; //敌人 [SerializeField, Header("自动锁敌距离")] private float lock_enemy; protected Transform currentEnemy; protected virtual void Awake() { _animator = this.GetComponent<Animator>(); }
protected virtual void Update() { CanAttack(); RotateTowardsEnemy(); }
//判断是否能攻击 protected bool CanAttack() { if (_canAttack == false) return false; if (_animator.AnimationAtTag("Parry")) return false; if (_animator.AnimationAtTag("Hit")) return false; if (_animator.AnimationAtTag("Sprint")) return false; return true; } //锁敌旋转 protected void RotateTowardsEnemy() { //角度和距离合适 if (currentEnemy == null) return; if (Vector3.Dot(ExpandClass.GetTransformVector3(currentEnemy.transform, this.transform), this.transform.forward) > 0.73 && ExpandClass.GetTransformDistance(currentEnemy.transform, this.transform) <= lock_enemy) { if (_animator.AnimationAtTag("Attack")) ExpandClass.RotateTowards(this.transform, currentEnemy); } } }
using Unity.VisualScripting; using UnityEngine;
public abstract class CharacterComboBase : CharacterAttackBase { protected ComboData currentCombo; [SerializeField, Header("基础组合技")] protected ComboData baseCombo;
protected override void Awake() { base.Awake(); currentCombo = baseCombo; }
//更新Combo数据 protected void Reset_Combo() { _canAttack = true; } }
using NUnit.Framework; using System.Collections.Generic; using UnityEngine;
public class PlayerCombatController : CharacterComboBase { //敌人检测 private Collider[] hits; [SerializeField, Header("敌人检测范围")] private float enemy_detect; protected override void Awake() { base.Awake(); //敌人 currentEnemy = null; } protected override void Update() { base.Update(); EnemyDetect(); Attack(); }
//播放攻击动画 private void Attack() { //不能攻击就不执行 if(!CanAttack()) return; //combo重置 if (_animator.AnimationAtTag("Idle")) currentCombo = baseCombo;
if(GameInputManager.Instance.LeftAttack) { if (currentCombo.NextCombo == null) return; currentCombo = currentCombo.NextCombo; _canAttack = false; _animator.CrossFade(currentCombo.ComboName, 0.1f); TimerManager.Instance.TryGetOneTimer(currentCombo.ColdTime,Reset_Combo); } if (GameInputManager.Instance.RightAttack) { if (currentCombo.BranchCombo == null) return; currentCombo = currentCombo.BranchCombo; _canAttack = false; _animator.CrossFade(currentCombo.ComboName, 0.1f); TimerManager.Instance.TryGetOneTimer(currentCombo.ColdTime,Reset_Combo); } }
//敌人检测 private void EnemyDetect() { hits = Physics.OverlapSphere(this.transform.position,enemy_detect,3,QueryTriggerInteraction.Ignore); //找到最近的敌人 if (hits.Length == 0) return; currentEnemy = hits[0].transform; for (int i = 0; i < hits.Length; i++) { if (ExpandClass.GetTransformDistance(currentEnemy, this.transform) > ExpandClass.GetTransformDistance(hits[i].transform, this.transform)) { currentEnemy = hits[i].transform; } } } }
|