2020/11/09
今回はUnity上で「体力ゲージを一定時間のみ表示する」のお話です。
普段は体力ゲージを表示せず、被ダメージ時のみ表示され、一定時間後にまた非表示になります。
自作ゲームで言えば、敵キャラクターの体力ゲージで必要になりました。敵の数は多めにする予定なので、常に表示していると邪魔でしょうがないです。ここは「ダメージ後の一定時間のみ表示」するようにしてみます。
ただし「ダメージ発生時の処理」まで書くとあれこれ長くなるので、本記事ではインスタンス化されて呼び出された後の、表示・非表示の処理のみ紹介します。
「体力ゲージ自体の増減」については以下の記事を参考にしてみてください。
ソースコード
今回はソースコードがちょっと長いので、先に全体のコードを貼っておきます。
ゲージを管理する「EnemyLifeView」クラスと個々のゲージを表す「GameEnemyLife」クラスの2つ。
using System.Collections.Generic; using UniRx; using UnityEngine; /// <summary> /// 敵キャラクターの体力ゲージを表示するViewクラス /// </summary> public class EnemyLifeView : MonoBehaviour { [SerializeField] private GameEnemyLife enemyLifePrefab; [SerializeField] private Dictionary<int, GameEnemyLife> showingList = new Dictionary<int, GameEnemyLife>(); /// <summary> /// 敵キャラの体力ゲージを表示 /// </summary> /// <param name="enemy"></param> public void ShowEnemyLife(Enemy enemy) { var hashCode = enemy.GetHashCode(); if (showingList.ContainsKey(hashCode)) { showingList[hashCode].Show(enemy); return; } var enemyLife = Instantiate(enemyLifePrefab, this.transform); enemyLife.Show(enemy); showingList.Add(hashCode, enemyLife); } }
using DG.Tweening; using UnityEngine; public class GameEnemyLife : MonoBehaviour { [SerializeField] private LifeGaugeController lifeGaugeController; private RectTransform rectTransform; private CanvasGroup canvasGroup; private Enemy enemy; private Tween currentShowTween; private void Awake() { canvasGroup = GetComponent<CanvasGroup>(); rectTransform = GetComponent<RectTransform>(); } void Update() { if (enemy != null) { var showPosition = enemy.BodyBounds.center + new Vector3(0, enemy.BodyBounds.extents.y, 0); rectTransform.position = Camera.main.WorldToScreenPoint(showPosition); } } public void Show(Enemy enemy) { // 既にTweenがある場合は殺しておく if (currentShowTween != null) { currentShowTween.Kill(); } this.enemy = enemy; // ここで体力ゲージの増減処理 lifeGaugeController.Init(enemy, true); // 表示・非表示 currentShowTween = DOTween.Sequence() .AppendCallback(() => gameObject.SetActive(true)) .Append(canvasGroup.DOFade(0.8f, 0.25f)) .AppendInterval(0.5f) .Append(canvasGroup.DOFade(0, 0.25f)) .AppendCallback(() => gameObject.SetActive(false)) .SetLink(gameObject) .Play(); } }
この中でEnemy
クラスは敵キャラクターに設定するコンポーネントで、LifeGaugeController
は体力ゲージの増減を行うクラスです。
表示・非表示の処理とは関係ないため説明は割愛します。
体力ゲージのプレハブと表示用Viewの準備
まずは体力ゲージのオブジェクトを作り、プレハブ化します。
後述のフェードインアウト処理のため、親オブジェクトにCanvasGroup
コンポーネントを付け、Alphaを0にしておきます。
次にCanvas下に空オブジェクトを作り、コンポーネントに先ほどの「EnemyLifeView」を設定します。
「EnemyLifePrefab」には体力ゲージのプレハブを設定します。
表示処理
生成と削除
敵キャラクターの被ダメージ時にEnemyLifeView
クラスのShowEnemyLife()
を呼び出します。
public void ShowEnemyLife(Enemy enemy) { var hashCode = enemy.GetHashCode(); if (showingList.ContainsKey(hashCode)) { showingList[hashCode].Show(enemy); return; } var enemyLife = Instantiate(enemyLifePrefab, this.transform); enemyLife.Show(enemy); showingList.Add(hashCode, enemyLife); }
ポイントはGetHashCode()
で、敵キャラの体力ゲージが既に生成されているかを判定します。
既にあればGameEnemyLife
クラスのShow()
を呼び出し、なければInstantiate()
で生成してから呼び出します。
Instantiate()
の第2引数で生成時の親オブジェクトを指定できます。
UIなのでCanvas下に置きたいのはもちろん、生成時に子オブジェクトにしないとCanvasScalerの設定が適用されないようなので注意します。
フェードイン・アウト
DOTweenならCanvasGroup
に対してDOFade()
が使えるので、それが一番手っ取り早いです。
前述のShowEnemyLife()
からGameEnemyLife
クラスのShow()
が呼び出されます。
public void Show(Enemy enemy) { // 既にTweenがある場合は殺しておく if (currentShowTween != null) { currentShowTween.Kill(); } this.enemy = enemy; // ここで体力ゲージの増減処理 lifeGaugeController.Init(enemy, true); // 表示・非表示 currentShowTween = DOTween.Sequence() .AppendCallback(() => gameObject.SetActive(true)) .Append(canvasGroup.DOFade(0.8f, 0.25f)) .AppendInterval(0.5f) .Append(canvasGroup.DOFade(0, 0.25f)) .AppendCallback(() => gameObject.SetActive(false)) .SetLink(gameObject) .Play(); }
CanvasGroup
の参照はStart()
時に取っておきます。
DOTweenのSequenceでAppend()
とAppendInterval()
を組み合わせ、「DOFadeで表示→少し待つ→DOFadeで非表示」をしています。
また非表示時はUpdate()
での位置調整(後述)が要らないので、前後にSetActive()
を挟んでON/OFFしています。
またSequenceの返り値のTween
インスタンスをフィールドに保持するのも大切です。
これは敵キャラが連続でダメージを受けた場合、フェード実行前にTweenを殺しておかないと一瞬だけフェードインが行われてしまうためです。
表示位置の調整
単に生成しただけだと敵が移動したときにずれるので、GameEnemyLife
のUpdate()
で表示位置を更新し続けます。
void Update() { if (enemy != null) { var showPosition = enemy.BodyBounds.center + new Vector3(0, enemy.BodyBounds.extents.y, 0); rectTransform.position = Camera.main.WorldToScreenPoint(showPosition); } }
ポイントはEnemy
クラスから「体のオブジェクトのBounds」を参照可能にすることです。
私はEnemy
クラスにBodyBounds
プロパティを作り、そのゲッタから取れるようにしました。Enemy
クラスでの設定方法は割愛しますが、フィールド作ってインスペクター上で指定するのが楽かと。
単に「transform.position
から上に少しずらす」だと、敵キャラの大きさが異なる場合に表示位置が残念になります。
オブジェクトの原点が中心からずれている場合も同様です。
なので敵キャラの体のBoundsを見て、そのcenter
からextents
分だけ縦にずらします。
下の画像の枠がBoundsの大きさです。ちょっと分かりにくいですが、この中心から高さの半分だけずらすイメージ。
Boundsについては以下の記事で説明しているので、興味のある方はご参照ください。
動作テスト
実際に動かすとこんな感じ。
敵キャラの上に体力ゲージが表示されること、連続して攻撃した場合にフェードインが挟まらないことを確認します。