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については以下の記事で説明しているので、興味のある方はご参照ください。
動作テスト
実際に動かすとこんな感じ。
敵キャラの上に体力ゲージが表示されること、連続して攻撃した場合にフェードインが挟まらないことを確認します。












