【Unity连招思路其三】优化连招逻辑

最近做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;
}
}
}
}