是时候编写脚本来控制角色的死亡重生了。在众多平台跳跃游戏中,我们不难看到有“地刺”一类的能够造成玩家即死的地形,这次我就来记录如何实现这一功能。话不多说,我们直接进入正题。
死亡逻辑
首先我们需要把造成玩家即死的贴图放在地形中,并添加碰撞器。为了与其他会碰撞的物体做区分,将其标签更改为“InstantDeath”,意为即死。如果这里的碰撞物体像我一样使用瓦片贴图的话,可以添加瓦片碰撞器(Tilemap Collider 2D),Unity会自动帮你调整好碰撞器的轮廓。
然后在此基础之上编写我们将要挂载到人物身上的死亡脚本。人物死亡时,销毁该物体。
//碰撞检测
private void OnCollisionEnter2D(Collision2D collision)
{ //这是用来检测标签的方法
if (collision.gameObject.CompareTag("InstantDeath"))
{
Destroy(gameObject);
}
}
重生逻辑
现在角色碰到这坨危险物体就会立马被销毁,现在来编写重生逻辑,好让我们继续操纵我们的角色。首先创建一个空物体,这个物体用来挂载我们的重生脚本,通过这种方式可以指定人物重生的位置。将我们之前的人物物体拖入资产中,成为预制件,方便我们在销毁人物后重生时,得到一个一模一样的人物。
然后编写以下的重生脚本:
public class SpawnPoint : MonoBehaviour
{
//用来存放玩家的预制件
public GameObject player;
public void NewPlayer()
{
if (player != null)
{ //用来初始化人物 括号里分别为(对象、位置、旋转)
Instantiate(player, transform.position, Quaternion.identity);
}
}
}
然后我们将挂载重生脚本的这个物体挪到想要让角色重生的位置。同时在角色死亡脚本中添加一串“public SpawnPoint spawnPoint”的声明,在角色销毁后调用其包含的NewPlayer()方法。与此同时将挂载重生脚本的物体也制为预制件,并拖入到死亡脚本的“重生点”栏里。
这样一来我们的人物就可以在死亡后无限复活啦!
更新游戏状态
但在每次角色死亡后,某些组件仍然维持着原有的状态。比如,我的虚拟摄像机(Cinemachine)在角色第一次死亡后就缺失了跟随对象,导致其停止工作。
这时间,考虑在我们重生点脚本中获取虚拟摄像机的组件,从而能够在角色重生时直接修改跟随的对象。我们将虚拟摄像机的标签更改为“VirtualCamera”,方便我们在脚本中找到它。
[HideInInspector]
public CinemachineVirtualCamera virtualCamera;
private void Awake()
{
//获取虚拟摄像机的组件
virtualCamera = GameObject.FindWithTag("VirtualCamera").GetComponent<CinemachineVirtualCamera>();
}
那么这时,我们角色死亡脚本中的重生点成员就不能像之前一样使用预制件了,否则就是对象仅存在于资源当中,并没有实例化,不会调用到重生点类的Awake()方法。所以也像虚拟摄像机一样,将重生点物体的标签改为“SpawnPoint”,在场景中寻找存在的重生点。
[HideInInspector]
public CinemachineVirtualCamera virtualCamera;
private void Awake()
{
//获取虚拟摄像机的组件
virtualCamera = GameObject.FindWithTag("VirtualCamera").GetComponent<CinemachineVirtualCamera>();
}
public void NewPlayer()
{
if (player != null)
{
GameObject newPlayer = Instantiate(player, transform.position, Quaternion.identity);
//重新设置跟随目标
virtualCamera.Follow = newPlayer.transform;
}
}
现在我们的摄像机就可以重新跟上人物了。
协程 Coroutine
现在角色死亡重生算是弄好了,但是人物就这样转瞬即逝还瞬间死而复生,多少有点不太自然。所以我们将要用到协程(Coroutine),来延长角色死亡的时间,并在这段时间中添加受伤闪烁,在彻底消失后再重生。
协程是一种可以暂停执行并在稍后恢复的函数,依赖于 IEnumerator 接口,其定义在 System.Collections 命名空间中。返回类型必须是 IEnumerator,并使用 yield 关键字定义暂停点。同时协程依赖于 MonoBehaviour 的生命周期,如果 MonoBehaviour 被销毁,协程也会停止。以下是几种常见的协程暂停方法。
IEnumerator MyCoroutine()
{ //暂停一帧
yield return null;
//暂停一秒
yield return new WaitForSeconds(1);
//暂停到这一帧结束
yield return new WaitForEndOfFrame();
//暂停到bool变量为true
yield return new WaitUntil(isTrue);
//暂停到bool变量为false
yield return new WaitWhile(isFalse);
//暂停,等待另一个协程完成后继续
yield return new StartCoroutine(otherCoroutine);
}
//通过以下两个方法开始与终止
Coroutine myCoroutine = StartCoroutine(MyCoroutine());
StopCoroutine(myCoroutine);
通过这种方式延迟角色死亡,并添加闪烁效果。将刚体的位置冻结,让玩家无法进行移动。
private IEnumerator DeadProcess()
{
Renderer renderer = gameObject.GetComponent<Renderer>();
Rigidbody2D rb = gameObject.GetComponent<Rigidbody2D>();
// 闪烁效果
for (int i = 0; i < 6; i++)
{
//冻结刚体,让玩家无法操作
rb.constraints = RigidbodyConstraints2D.FreezeAll;
//交替闪烁
renderer.enabled = false;
yield return new WaitForSeconds(0.06f);
renderer.enabled = true;
yield return new WaitForSeconds(0.06f);
}
//让角色变透明
renderer.enabled = false;
//停个半秒钟
yield return new WaitForSeconds(0.5f);
//再生成
spawnPoint.NewPlayer();
//最后销毁当前对象,这个脚本也会跟着销毁
Destroy(gameObject);
}
现在我们角色的死亡以及重生逻辑就写好了!
以下为完整脚本。
using System.Collections;
using UnityEngine;
public class Dead : MonoBehaviour
{
[HideInInspector]
public SpawnPoint spawnPoint;
private void Awake()
{
//寻找场景中存在的重生点
spawnPoint = GameObject.FindWithTag("SpawnPoint").GetComponent<SpawnPoint>();
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("InstantDeath"))
{
StartCoroutine(DeadProcess());
//当协程当中所有代码执行完毕后,协程会自动停止
}
}
private IEnumerator DeadProcess()
{
Renderer renderer = gameObject.GetComponent<Renderer>();
Rigidbody2D rb = gameObject.GetComponent<Rigidbody2D>();
// 闪烁效果
for (int i = 0; i < 6; i++)
{
//冻结刚体,让玩家无法操作
rb.constraints = RigidbodyConstraints2D.FreezeAll;
//交替闪烁
renderer.enabled = false;
yield return new WaitForSeconds(0.06f);
renderer.enabled = true;
yield return new WaitForSeconds(0.06f);
}
//让角色变透明
renderer.enabled = false;
//停个半秒钟
yield return new WaitForSeconds(0.5f);
//再生成
spawnPoint.NewPlayer();
//最后销毁当前对象,这个脚本也会跟着销毁
Destroy(gameObject);
}
}
using Cinemachine;
using UnityEngine;
public class SpawnPoint : MonoBehaviour
{
public GameObject player;
[HideInInspector]
public CinemachineVirtualCamera virtualCamera;
private void Awake()
{
//获取虚拟摄像机的组件
virtualCamera = GameObject.FindWithTag("VirtualCamera").GetComponent<CinemachineVirtualCamera>();
}
private void Start()
{
NewPlayer();
}
public void NewPlayer()
{
if (player != null)
{
GameObject newPlayer = Instantiate(player, transform.position, Quaternion.identity);
//重新设置跟随目标
virtualCamera.Follow = newPlayer.transform;
}
}
}
小结
关于死亡的逻辑,可以在协程中加入各种其他的功能,比如角色渐变效果等等。在编写脚本时,我发现关于碰撞器与触发器的使用有挺深的学问,还有,如果角色死亡重生过多,可以建立对象池进行优化,在此之后我会仔细讨论,玩去喽~
如有补充纠正欢迎留言。