Unity之伤害检测

本文用于记录伤害检测,算是一套比较完整的伤害检测系统。人物有两个属性,一个是生命值、一个体力值,当体力值小于10时是格挡不了的。那么姑且开始写代码吧。

思路分析

首先我们需要做伤害判定——判断敌人是否受伤害,那就用Physics.SphereCast方法,角色往前一段距离进行一个碰撞检测,声明一个Transform变量currentEnemy = hit.collider.transform,用来保存敌人数据。如果检测到了,并且我们进行了攻击,就可以执行敌人受伤或是格挡的逻辑了。
在写逻辑前,我们可以优化一下,一般游戏都有锁敌机制。我们也写一套锁敌机制,锁敌机制判断条件有二,一是敌人和我们的距离小于1.3,二是敌人在我们前方60度的角度内可以索敌。距离判断很简单,直接Vector3.Distance().magnitude就可以了;角度判断用Vector3.Dot(this,transform.forwar,(currentenemy.transform.position - this.transform.position)),返回一个float值,当其大于根号3/2时可以索敌(点积的值等于cos角度值)。
接下来写核心逻辑,我们之前写过一套简单的技能编辑器(参考文章“Unity基础连招思路”),也写过一套事件管理中心,我们把角色受击做成一个事件,当攻击时调用这个事件就好。事件传入参数:float damage,float enemy,string hitName,string parryName(伤害,体力值消耗、受伤动画、格挡动画);之后敌人只需要判断就好了。
在这个思路下,写的脚本大概是以下,中间我最先是跟着up鬼鬼鬼ii的视频做的,写的可能有点不一样,但大致思路都一样的,我自己写了个ExpandClass类,拓展了一些方法: 以下是综合内容,我把一些脚本写一起了,看起来比较乱,用Ctrl+F进行关键词搜索:伤害判定、能否锁敌、核心逻辑
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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();
AttackExcute();
}

private void FixedUpdate()
{
DetectDirection();
}

// 核心逻辑
private void AttackExcute()
{
if (currentEnemy == null) return;
//检测范围不对,直接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;
//说明已经攻击了,并且检测到敌人
if(animator.AnimationAtTag("Attack"))
{
//先确定旋转
Vector3 targetDir = ExpandClass.GetTargetVector3(this.transform, currentEnemy);
Quaternion targetRotation = Quaternion.LookRotation(targetDir);
transform.rotation = Quaternion.RotateTowards(this.transform.rotation,targetRotation,1000f);
//之后调用事件,我们的逻辑都在这个事件里写
GameEventManager.Instance().InvokeEvent<float, string, string, Transform, Transform>(
"DamageTrigger",
currentCombo.TryGetComboDamage(currentIndex),
currentCombo.TryGetHitName(currentIndex, 0),
currentCombo.TryGetParryName(currentIndex, 0),
this.transform,
currentEnemy
);
Debug.Log("AttackExcute被执行");
}
}
// 伤害判定
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;
TimerManager.Instance().TryGetOneTimer(coldTime, ResetHeavy);
}
}

private void ResetCombo()
{
comboIndex++;
changeComboIndex++;
canAttack = true;
coldTime = 0f;
if(comboIndex == currentCombo.TryGetComboCount())
{
comboIndex = 0;
}
if(changeComboIndex == ChangeCombo.TryGetComboCount())
{
changeComboIndex = 1;
}
}

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

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

using System.Xml.Serialization;
using UnityEngine;

public abstract class CharacterHealthBase : MonoBehaviour
{
protected Animator animator;
protected Transform _currentAttacker;
protected float enemy = 0;
//角色受伤行为
protected virtual void CharacterHitAction(float damage,string hitName, string parryName)
{
Debug.Log("我被调用了");
}

protected void TakeDamage(float damage)
{

}
private void Awake()
{
animator = GetComponent<Animator>();
}
// 核心逻辑
private void OnCharacterHitEventHandler(float damage,string hitName,string parryName,Transform attack,Transform self)
{
if (self != this.transform) return;
SetAttacker(attack);
CharacterHitAction(damage, hitName, parryName);
TakeDamage(damage);
}

private void SetAttacker(Transform attacker)
{
if (_currentAttacker != attacker || _currentAttacker == null)
{
_currentAttacker = attacker;
}
}
private void OnEnable()
{
GameEventManager.Instance().AddEventListening<float,string,string,Transform,Transform>("DamageTrigger", OnCharacterHitEventHandler);
}

private void OnDisable()
{
GameEventManager.Instance().RemoveEvent<float, string, string, Transform, Transform>("DamageTrigger", OnCharacterHitEventHandler);
}
}

using UnityEngine;

public class EnemyHealthSystem : CharacterHealthBase
{
// 核心逻辑
protected override void CharacterHitAction(float damage, string hitName, string parryName)
{
Debug.Log("我被调用了");
if (damage < 30 && enemy > 0)
{
animator.CrossFadeInFixedTime(parryName, 0.15f);
enemy -= 20;
}
else
{
animator.CrossFadeInFixedTime(hitName, 0.15f);
}

}
}