[Unity][R3]ゲームオーバー処理が複数呼ばれることを防ぐ
はじめに
Unityでゲームオーバー処理が複数回呼ばれてしまい戸惑った経験はありませんか?そんな問題は、R3のThrottleLastオペレーターを使うことで解決できます。
R3とは
R3については下記の記事を引用してみます。
「R3」はReactiveExtensions(Rx)を現代に合わせてより洗練した形に再定義/再実装した、C#用のライブラリです。
https://qiita.com/toRisouP/items/e7be5a5a43058556db8f
ThrottleLastとは
ThrottleLastは、メッセージが到達したら非同期処理を開始します。非同期処理中はメッセージが来ても遮断します。非同期が終わった時に届いていた最後のメッセージを発行する処理です。
なので、ゲームオーバー処理で言えば、ゲームオーバー演出の非同期処理中にダメージ処理が何度も入り、ゲームオーバー処理が複数回行われることを防ぐことができます。
実装
実際にゲームオーバー処理処理を何度も呼ばれることを防ぐ処理を下記に示します。
using System.Threading;
using Cysharp.Threading.Tasks;
using R3;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Player : MonoBehaviour
{
private readonly ReactiveProperty<int> currentHealth = new(5);
[SerializeField] private Button button;
[SerializeField] private Text healthText;
[SerializeField] private Slider processSlider;
private void Start()
{
// ボタンを押下するとダメージ処理を行う
button.OnClickAsObservable()
.Subscribe(_=>TakeDamage(1));
currentHealth.
SubscribeToText(healthText);
// ダメージが0以下の時に死亡処理を呼び出す
currentHealth
.Where(health=>health<=0)
.ThrottleLast((_, ct) => GameOverStaging(ct))
.Subscribe(_=>LoadSceneToGameOver())
.AddTo(this);
}
/// <summary>
/// ゲームオーバー演出
/// </summary>
private async UniTask GameOverStaging(CancellationToken ct)
{
processSlider.value = 0;
var currentTime = 0f;
var waitSecond=5;
while (!ct.IsCancellationRequested && currentTime < waitSecond)
{
await UniTask.Yield();
currentTime += Time.deltaTime;
processSlider.value = Mathf.Clamp01(currentTime / waitSecond);
}
}
/// <summary>
/// ゲームオーバーシーンへの遷移
/// </summary>
private void LoadSceneToGameOver()
{
SceneManager.LoadScene("GameOver");
}
/// <summary>
/// ダメージ処理
/// </summary>
private void TakeDamage(int damage)
{
currentHealth.Value-=damage;
Debug.Log("現在のHP"+currentHealth.Value);
}
}
処理の解説
ダメージ処理
この処理ではUIボタンを押下した際に、ダメージ処理を発生させています。
button.OnClickAsObservable()
.Subscribe(_=>TakeDamage(1));
/// <summary>
/// ダメージ処理
/// </summary>
private void TakeDamage(int damage)
{
currentHealth.Value-=damage;
Debug.Log("現在のHP"+currentHealth.Value);
}
ゲームオーバー処理
体力が変化した時、且つ、体力が0以下の時にダメージ演出「GameOverStaging」を呼び出します。その間、体力が変化してるときに発生するメッセージを遮断します。そして最後に届いていたメッセージを元に、「LoadSceneToGameOver」を行います。ポイントとして、非同期中メッセージを遮断しているのが重要です。この間に、何度体力が変化しようと無視してくれます。
// ダメージが0以下の時に死亡処理を呼び出す
currentHealth
.Where(health=>health<=0)
.ThrottleLast((_, ct) => GameOverStaging(ct))
.Subscribe(_=>LoadSceneToGameOver())
.AddTo(this);
/// <summary>
/// ゲームオーバー演出
/// </summary>
private async UniTask GameOverStaging(CancellationToken ct)
{
processSlider.value = 0;
var currentTime = 0f;
var waitSecond=5;
while (!ct.IsCancellationRequested && currentTime < waitSecond)
{
await UniTask.Yield();
currentTime += Time.deltaTime;
processSlider.value = Mathf.Clamp01(currentTime / waitSecond);
}
}
/// <summary>
/// ゲームオーバーシーンへの遷移
/// </summary>
private void LoadSceneToGameOver()
{
SceneManager.LoadScene("GameOver");
}
実際の動作
実際の動作例にある様に、ダメージ処理が何度入ろうと、死亡演出が繰り返さないことを確認できました。たとえば死亡したキャラクターにも、ダメージ演出だけ通したい時などには今回の手法が有効です。
おわりに
筆者もR3を触って期間が浅いので、この手法が最適化分かりかねています。コメント欄などでこうした方が良いなどあれば、ご教授頂きたいです。もしこの記事が良いなと思った方は、いいねとフォローをお願いします。
Discussion