Unity基础连招思路

今天试着自己实现了简单的连招系统,先来介绍一下该系统的逻辑:角色按左键是轻攻击、右键是重攻击,重攻击四组、轻攻击三组动画。根据轻攻击连按次数+右键的点击打出不同的重攻击。

技能编辑器

首先做一个最基础的属性编辑器,用ScriptableObject做,为每一个攻击动画配置好我们需要的参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using UnityEngine;

[CreateAssetMenu(fileName = "ComboData", menuName = "Scriptable Objects/ComboData")]
public class ComboData : ScriptableObject
{
[SerializeField, Header("招式名称")] private string comboName;
[SerializeField, Header("招式伤害")] private int comboDamage;
[SerializeField, Header("被招式受击名称")] private string hitName;
[SerializeField, Header("被招式格挡名称")] private string parryName;
[SerializeField, Header("招式冷却时间")] private float coldTime;

public string ComboName => comboName;
public int ComboDamage => comboDamage;
public string HitName => hitName;
public string ParryName => parryName;
public float ColdTime => coldTime;
}

上面就是我们给每个攻击动作配置的属性了,光有属性还不够,需要创建技能编辑器来进行组合。我们对外提供一些方法,可以根据索引来快速获取每个动作的参数:

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
using NUnit.Framework;
using System.Collections.Generic;
using Unity.Burst.Intrinsics;
using UnityEngine;

[CreateAssetMenu(fileName = "Combo", menuName = "Scriptable Objects/Combo")]
public class Combo : ScriptableObject
{
//显示可编辑的招式
[SerializeField]private List<ComboData> _combo = new List<ComboData>();

public string TryGetComboName(int index)
{
if (_combo.Count == 0 || _combo.Count < index + 1)
{
return null;
}
else
{
return _combo[index].ComboName;
}
}

//获得受伤动画名称
public string TryGetHitName(int index)
{
if (_combo.Count == 0 || _combo.Count < index + 1)
{
return null;
}
else
{
return _combo[index].HitName;
}
}

//获得格挡动画名称
public string TryGetParryName(int index)
{
if (_combo.Count == 0 || _combo.Count < index + 1)
{
return null;
}
else
{
return _combo[index].ParryName;
}
}


//获得招式伤害
public int TryGetComboDamage(int index)
{
if (_combo.Count == 0 || _combo.Count < index + 1)
{
return 0;
}
else
{
return _combo[index].ComboDamage;
}
}

public float TryGetColdTime(int index)
{
if(_combo.Count == 0 || _combo.Count < index + 1)
{
return 0;
}
else
{
return _combo[index].ColdTime;
}
}
//获得招式动画数量
public int TryGetComboCount() => _combo.Count;
}

之后我们就可以将不同动作进行组合、打出不同的组合技了。

编写核心代码

我的思路是,给出基础组合技和变招表,声明两个索引值分别记录轻攻击索引和重攻击索引。首先声明一个变量canAttck来帮助我们控制角色能否攻击,比如我们在打出第一段攻击后、有0.4秒时间我们是不希望角色进行第二段攻击的(怕点击次数太快造成抽搐),再写一个函数CanAttack()来判断能不能进行攻击。

我们默认使用基础组合技,当按下鼠标左键,comboIndex和changeComboIndex都会递增,动画播放后canAttack = false;之后通过时间管理器进行延迟调用函数,延迟调用ResetCombo();

对于重攻击,默认是不能连按右键打出重攻击组合技的,需要配合轻攻击,那就每次打出一个重攻击就把它的索引值归为0。具体参考以下代码:

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
131
132
133
134
135
136
137
138
139
using UnityEngine;

public class PlayerComboController : MonoBehaviour
{
[SerializeField, Header("基础组合技")] private Combo baseCombo;
[SerializeField, Header("变招表")] private Combo ChangeCombo;
private Combo currentCombo;
private Animator animator;
private int comboIndex = 0;
private int changeComboIndex = 0;
private int currentIndex = 0;
private bool canAttack = true;
private float coldTime = 0f;
/// <summary>
/// 上面是连招逻辑的,接下来写相机
/// </summary>
[SerializeField, Header("检测球半径")] private float detectSphereRadius;
[SerializeField,Header("检测最大距离")]private float detectSphereDistance;
private Transform currentEnemy;
private Transform _mainCamera;
private Vector3 detectDir;
private void Awake()
{
animator = GetComponent<Animator>();
currentCombo = baseCombo;
_mainCamera = Camera.main.transform;
currentIndex = comboIndex;
}

private void Update()
{
ExcuteAttack();
EndAttack();
}

private void FixedUpdate()
{
DetectDirection();
}

private void AttackExcute()
{
//检测范围不对,直接return
if (Vector3.Dot(this.transform.forward, ExpandClass.GetTargetVector3(this.transform, currentEnemy)) < 0.85f) return;
if(ExpandClass.GetTargetVector3(this.transform,currentEnemy).magnitude > 1.3f) return;
//如果攻击了,执行下边逻辑
Vector3 targetDir = -currentEnemy.transform.forward;
Quaternion targetRotation = Quaternion.LookRotation(targetDir);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, 5f * Time.deltaTime);

}
private void DetectDirection()
{
//判断检测方向
detectDir = _mainCamera.transform.forward * GameInputManager.Instance().movement.y + _mainCamera.transform.right * GameInputManager.Instance().movement.x;
detectDir.Set(detectDir.x,0,detectDir.z);
detectDir = detectDir.normalized;
if (Physics.SphereCast(this.transform.position + this.transform.up * 0.7f, detectSphereRadius, detectDir, out var hit, detectSphereDistance,1 << 6, QueryTriggerInteraction.Ignore))
{
//说明检测到了敌人
currentEnemy = hit.collider.transform;
}
}

private void OnDrawGizmos()
{
Gizmos.DrawWireSphere((this.transform.position + this.transform.up * 0.7f + detectDir * detectSphereDistance), detectSphereRadius);
}
#region 连招逻辑
private bool CanAttack()
{
if (!canAttack) return false;
if (animator.AnimationAtTag("Parry")) return false;
if (animator.AnimationAtTag("Hit")) return false;
return true;
}

private void ExcuteAttack()
{
if (!CanAttack()) return;
if (GameInputManager.Instance().leftAttack)
{
currentIndex = comboIndex;
currentCombo = baseCombo;
animator.CrossFade(currentCombo.TryGetComboName(comboIndex), 0.15f);
coldTime = currentCombo.TryGetColdTime(comboIndex);
canAttack = false;
TimerManager.Instance().TryGetOneTimer(coldTime, ResetCombo);
}

if(GameInputManager.Instance().RightAttack)
{
currentIndex = changeComboIndex;
currentCombo = ChangeCombo;
animator.CrossFade(currentCombo.TryGetComboName(currentIndex),0.1f);
coldTime = currentCombo.TryGetColdTime(currentIndex);
canAttack = false;
changeComboIndex = 0;
comboIndex = 0;
TimerManager.Instance().TryGetOneTimer(coldTime, ResetHeavy);
}
}

private void ResetCombo()
{
comboIndex++;
changeComboIndex++;
canAttack = true;
coldTime = 0f;
if(comboIndex == currentCombo.TryGetComboCount())
{
comboIndex = 0;
}
//关于这点我解释一下,如果角色打完一套轻击、不停下来又开始打轻击,此时就得重置一下changeComboIndex。你问为什么打重击或者停止时?我们都已经重置了。
//至于为什么是1,因为我们设置的changeComboIndex == 0 时,那个动画只有静止时、或者打完一个重击再打才能用。这里轻击我们采用从1开始的重置
if(changeComboIndex == ChangeCombo.TryGetComboCount())
{
changeComboIndex = 1;
}
}

private void ResetHeavy()
{
canAttack = true;
coldTime = 0f;
}

private void EndAttack()
{
if (animator.AnimationAtTag("Move") || animator.AnimationAtTag("Idle"))
{
comboIndex = 0;
changeComboIndex = 0;
}
}
#endregion
}