前言
本篇文章的内容是实现实现角色的血量控制功能,在开始实现之前,我们需要知道角色血量控制功能
的需求是什么。
角色血量控制功能的需求
- 角色头上需要显示一个跟随角色移动的
血量条
,实时显示角色当前的血量- 角色的
最大血量
可以任意修改- 角色
接触怪物
时会受伤,并播放受伤音效- 角色受伤时,除了
减少相应的血量
,还需要有一个向后击退
的效果- 为了避免角色被怪物卡住时,出现不断受伤的问题,角色在受伤后,将在
短暂时间内
获得免伤效果- 当角色
血量为0
时,角色死亡,播放死亡动画,游戏结束
在弄清楚并整理好需求之后,我们开始一一实现这些功能。
制作血量条
首先,我们来制作血量条。因为血量条
要一直跟随移动,所以我们不妨将血量条
作为Player
的子物体。在Player
下新建一个名为HealthBarDisplay
的空物体,然后将Assets\Sprites\UI
下的Health
以及Health-bg
拖拽到HealthBarDisplay
下面。
它们的具体属性如下:
HealthBarDisplay
:
Position
: (0, 0, 0)Health
:
Position
: (-0.8, 1.5, 0)Color
: (0, 255, 0, 255)Sorting Layer
: Character,Order In Layer
: 4Health-bg
:
Position
: (0, 1.5, 0)Sorting Layer
: Character,Order In Layer
: 4
此时,将Health
的Scale
属性的X分量
缓慢从1减少至0
,我们可以看到血量条逐渐变短。但为了避免角色在转向时,血量条
跟着翻转,我们还需要在PlayerController.cs
中加入以下代码:
public class PlayerController : MonoBehaviour {
...
[Tooltip("显示血量条的物体")]
public Transform HealthBarDisplay;
...
private void Flip() {
...
if(HealthBarDisplay != null) {
// 在角色转向时翻转HealthBarDisplay,确保HealthBarDisplay不随角色转向而翻转
HealthBarDisplay.localScale = Vector3.Scale(
new Vector3(-1, 1, 1),
HealthBarDisplay.localScale
);
} else {
Debug.LogWarning("请设置HealthBarDisplay");
}
}
}
接着,我们将HealthBarDisplay
拖拽到PlayerController.cs
下HealthBarDisplay
属性的赋值框,然后Health
的Scale
设置为(0.5, 1, 1)
,运行游戏,让角色左右翻转,可以看到血量条
不随着角色转向而翻转。停止运行游戏,将Health
的Scale
设置为(1, 1, 1)
,然后保存游戏,将我们所做的修改应用至Player
对于的Prefab。
创建血量控制脚本
我们在Assets\Scripts\Player
下创建一个名为PlayerHealth.cs
的脚本。因为角色的最大血量
需要能被修改,角色受伤时不仅要播放受伤音效
,还要有向后击退
的效果,因此我们需要在PlayerHealth.cs
脚本中添加以下代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerHealth : MonoBehaviour {
[Tooltip("角色的最大生命值")]
public float MaxHP = 100f;
[Tooltip("角色被怪物伤害时受到的击退力大小")]
public float HurtForce = 100f;
[Tooltip("角色受伤后的免伤时间")]
public float FreeDamagePeriod = 0.35f;
[Tooltip("角色的受伤音效")]
public AudioClip[] OuchClips;
}
添加完毕之后,将PlayerHealth.cs
添加到物体Player
上,并将Assets\Audio\Player\Ouch
下的四个音频文件拖动到OuchClips
的赋值框,然后保存修改。
实现接触怪物时受伤
在Unity中,当一个带Collider2D
的物体和其他带有Collider2D
的物体发生了碰撞时,将会触发OnCollisionEnter2D
,我们可以通过OnCollisionEnter2D
这个函数来获取物体的碰撞信息。那我们如何判断碰撞的物体是怪物呢?答案是利用Unity提供的Tag
,通过设置Tag
这一属性,我们可以方便地对物体进行标识。选中AlienSlug
,点击Tag
下拉框,然后点击Add Tag
,创建一个名为Enemy
的Tag并将AlienSlug
和AlienShip
的Tag都设置为Enemy
。最后,将AlienSlug
和AlienShip
的修改应用至Prefab。
添加完成之后,我们在PlayerHealth.cs
脚本中添加以下代码:
private void OnCollisionEnter2D(Collision2D collision) {
//假如撞到怪物
if(collision.gameObject.tag == "Enemy") {
Debug.Log("Enemy");
}
}
运行游戏,控制人物移动去接触怪物,可以看到Console
输出Enemy
字符串,说明已经检测到了角色和怪物发生碰撞。接下来,我们在PlayerHealth.cs
加入角色受伤的代码:
public class PlayerHealth : MonoBehaviour {
...
[Tooltip("角色受伤时减少的血量")]
public float DamageAmount = 10f;
[Tooltip("角色受伤后的免伤时间")]
public float FreeDamagePeriod = 0.35f;
// 角色当前的血量
private float m_CurrentHP;
// 上一次受到伤害的时间
private float m_LastFreeDamageTime;
private void Start() {
// 初始化变量
m_CurrentHP = MaxHP;
m_LastFreeDamageTime = 0f;
}
private void OnCollisionEnter2D(Collision2D collision) {
// 判断此时是否处于免伤状态
if(Time.time > m_LastFreeDamageTime + FreeDamagePeriod) {
// 假如撞到怪物
if(collision.gameObject.tag == "Enemy") {
// 检测当前血量
if(m_CurrentHP > 0f) {
// 调用受伤函数
TakeDamage(collision.transform);
// 更新上次受伤害的时间
m_LastFreeDamageTime = Time.time;
} else {
// 角色死亡
}
}
}
}
// 受伤函数
public void TakeDamage(Transform enemy) {
// 给角色加上后退的力,制造击退效果
Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f;
GetComponent<Rigidbody2D>().AddForce(hurtVector * HurtForce);
// 更新角色的生命值
m_CurrentHP -= DamageAmount;
// 更新生命条
Debug.Log(m_CurrentHP);
// 随机播放音频
int i = Random.Range(0, OuchClips.Length);
AudioSource.PlayClipAtPoint(OuchClips[i], transform.position);
}
}
运行游戏,控制人物移动去接触怪物,可以看到角色在触碰怪物时,角色会受到一个击退力的作用,同时Console
窗口输出当前的生命值。
更新血量条的显示
接下来,我们要根据角色当前的生命值来实时更新血量条的显示,也就是我们需要根据角色当前的生命值,来更新HealthBarDisplay
的子物体Health
的Scale
和Color
。我们在PlayerHealth.cs
中加入以下代码:
public class PlayerHealth : MonoBehaviour {
...
[Tooltip("血量条")]
public SpriteRenderer HealthSprite;
...
// 血量条的初始长度
private Vector3 m_InitHealthScale;
private void Start() {
// 初始化变量
...
m_InitHealthScale = HealthSprite.transform.localScale;
}
//受伤函数
public void TakeDamage(Transform enemy) {
...
// 更新生命条
UpdateHealthBar();
...
}
private void UpdateHealthBar() {
if(HealthSprite != null) {
// 更新血量条颜色
HealthSprite.color = Color.Lerp(Color.green, Color.red, 1 - m_CurrentHP * 0.01f);
// 更新血量条长度
HealthSprite.transform.localScale = Vector3.Scale(m_InitHealthScale, new Vector3(m_CurrentHP * 0.01f, 1, 1));
} else {
Debug.LogError("请设置HealthSprite");
}
}
}
将HealthBarDisplay
的子物体Health
拖动到HealthSprite
的赋值框,运行游戏,控制人物移动去接触怪物,可以看到当角色的生命值变化时,血量条
也随之更新。
控制角色的死亡
最后,我们还需要控制角色的死亡。我们知道,当角色死亡时,不能再和场景中的任何物体发生交互
,玩家也不能再控制角色
。因此,我们在PlayerHealth.cs
中加入以下代码:
public class PlayerHealth : MonoBehaviour {
...
//受伤函数
public void TakeDamage(Transform enemy) {
...
// 检测当前血量
if(m_CurrentHP > 0f) {
...
} else {
// 角色死亡
Death();
}
...
}
private void Death() {
// 禁用碰撞体
Collider2D[] cols = GetComponents<Collider2D>();
foreach(Collider2D c in cols) {
c.enabled = false;
}
// 禁用脚本
GetComponent<PlayerController>().enabled = false;
// 播放死亡动画
GetComponent<Animator>().SetTrigger("Death");
}
}
运行游戏,控制人物移动去接触怪物,可以看到当角色的生命值减少至0
时,角色播放死亡动画,且不与场景中的其他物体发生交互,玩家也不能再控制角色。将Player
的修改应用至Prefab,并保存场景产生的修改。
PlayerHealth.cs的完整代码
PlayerHealth.cs
的完整代码如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerHealth : MonoBehaviour {
[Tooltip("角色的最大生命值")]
public float MaxHP = 100f;
[Tooltip("角色被怪物伤害时受到的击退力大小")]
public float HurtForce = 100f;
[Tooltip("角色的受伤音效")]
public AudioClip[] OuchClips;
[Tooltip("角色受伤时减少的血量")]
public float DamageAmount = 10f;
[Tooltip("角色受伤后的免伤时间")]
public float FreeDamagePeriod = 0.35f;
[Tooltip("血量条")]
public SpriteRenderer HealthSprite;
// 角色当前的血量
private float m_CurrentHP;
// 上一次受到伤害的时间
private float m_LastFreeDamageTime;
// 血量条的初始长度
private Vector3 m_InitHealthScale;
private void Start() {
// 初始化变量
m_CurrentHP = MaxHP;
m_LastFreeDamageTime = 0f;
m_InitHealthScale = HealthSprite.transform.localScale;
}
private void OnCollisionEnter2D(Collision2D collision) {
// 判断此时是否处于免伤状态
if(Time.time > m_LastFreeDamageTime + FreeDamagePeriod) {
// 假如撞到怪物
if(collision.gameObject.tag == "Enemy") {
// 检测当前血量
if(m_CurrentHP > 0f) {
// 调用受伤函数
TakeDamage(collision.transform);
// 更新上次受伤害的时间
m_LastFreeDamageTime = Time.time;
} else {
// 角色死亡
Death();
}
}
}
}
// 受伤函数
public void TakeDamage(Transform enemy) {
// 给角色加上后退的力,制造击退效果
Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f;
GetComponent<Rigidbody2D>().AddForce(hurtVector * HurtForce);
// 更新角色的生命值
m_CurrentHP -= DamageAmount;
// 更新生命条
UpdateHealthBar();
// 随机播放音频
int i = Random.Range(0, OuchClips.Length);
AudioSource.PlayClipAtPoint(OuchClips[i], transform.position);
}
private void UpdateHealthBar() {
if(HealthSprite != null) {
// 更新血量条颜色
HealthSprite.color = Color.Lerp(Color.green, Color.red, 1 - m_CurrentHP * 0.01f);
// 更新血量条长度
HealthSprite.transform.localScale = Vector3.Scale(m_InitHealthScale, new Vector3(m_CurrentHP * 0.01f, 1, 1));
} else {
Debug.LogError("请设置HealthSprite");
}
}
private void Death() {
// 禁用碰撞体
Collider2D[] cols = GetComponents<Collider2D>();
foreach(Collider2D c in cols) {
c.enabled = false;
}
// 禁用脚本
GetComponent<PlayerController>().enabled = false;
// 播放死亡动画
GetComponent<Animator>().SetTrigger("Death");
}
}
后言
至此,我们已经完成了角色的血量控制功能,本篇文章提到的数值参数都可以根据自己的喜好进行调整。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay7
分支下看到,读者可以clone这个仓库到本地进行查看。