【Unity2D】ハートの増減処理を作る
ライフ増減システムも点滅システムも出来たのであとはHUDのハート増減処理だけですね。
正確に言うとノックバックも実装しなきゃならんのですが、これはモンスターの移動処理実装後に回しましょう。
ハートグラフィックの増減は、ライフ別の配列を参照したり数値を見て操作したりといろんな管理の方法があるとは思うのですが、
今回はライフの数値(INT型)を見て操作するタイプにしていこうと思います。
ハート自体は空・半・全と三段階使います。
となるとハート3つの場合、
- 1つめ「0空:1半:2全」
- 2つめ「2空:3半:4全」
- 3つめ「4空:5半:6全」
上記の通りint型で数値の6ということになりますね。
気を付けてほしいのが、1つ目の全状態と2つ目の空状態は数値が同じになります。
0になるとゲームオーバーという形にしましょう。
ハートのプレハブを作るために一旦ハートオブジェクトを1つにしましょう。
Life_ManagerはUpdateで常にプレイヤーのHPを監視します。
そしてライフ増減時に前回と現在のHPを比較するために「_beforeLife」変数を作っておきます。
using UnityEngine;
using System;
using System.Collections;
public class Life_Manager : MonoBehaviour
{
// プレイヤーのステータスを参照
private State_Player _State;
// 自身の子オブジェクト(ハート)の配列
private Life_Children[] _Hearts;
// ライフ比較用に保持するための変数
private int _beforeLife;
// ゲーム上のライフオブジェクト最大数
private const int MAXLIFE = 20;
void Start()
{
// プレイヤーステートマネージャースクリプトを取得
_State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();
// 比較用に現在ライフを保持しておく
_beforeLife = _State.getPlayerStatus(0);
// ライフ分の配列初期化
_Hearts = new Life_Children[20];
// 子オブジェクトのスクリプトを取得
_getChildObjScripts();
}
void Update()
{
_checkPlayerLife();
}
private void _checkPlayerLife()
{
// プレイヤーの現在ライフを取得
int nowLife = _State.getPlayerStatus(0);
// 比較用に保持したライフと現在値が変化したかどうか
if (nowLife == 0)
{
// 0になったときはゲームオーバー処理を呼ぶ
}
else if (_beforeLife != nowLife)
{
// 増えたか減ったかの判定をする
bool incDec;
if (_beforeLife > nowLife)
{
// 減る処理に決定
incDec = true;
}
else
{
// 増える処理に決定
incDec = false;
}
// 現在のライフがどこのオブジェクトに該当するかの添え字に変換する
// 現在値を20で割ったものを10倍する
float val = (((float)nowLife / (float)MAXLIFE) * 10.0f);
// 小数点を切り上げしてindexに変換
int objIndex = (int)Math.Ceiling(val);
if (val % 1 == 0)
{
// 割ったあまりが0なら
if (incDec)
{
// 1つ前のライフが0になったという証拠なのでハートを空にする
_Hearts[objIndex].HeartLess();
}
else
{
// 1つ前のライフが1になったという証拠なのでハートを全にする
_Hearts[objIndex].HeartFull();
}
}
else
{
// 割ったあまりが0.5なら
if (incDec)
{
// ライフを半分にする
_Hearts[objIndex - 1].HeartHalf();
}
else
{
// 1つ前のライフを半分にする
_Hearts[objIndex].HeartHalf();
}
}
// 次の比較用に現在ライフを保持しておく
_beforeLife = nowLife;
}
}
}
updateで呼び出してる「_checkPlayerLife()」を見ていきましょう。
まずプレイヤーのライフを取得します。
そして最初にライフが0でないかをチェックし、0であればゲームオーバー処理を呼び出すようにしましょう。(未実装)
ライフが0でなければ「_beforeLife」と現在のライフを比べて、増えたか減ったかを確認します。
次に現在LIFEがどのライフオブジェクトに該当するかを計算して調べます。
私の頭が悪くスマートな計算式が導き出せなかったので、もっと楽に判定できる方法があると思うのですが、
とりあえず理想的な動きをしてくれたので、このやり方でも間違いではないなと判断し実装しています。
ライフオブジェクトを判断する計算手順
現在値を最大値(今回は20固定)で割り、次に計算しやすいように10倍します。
この時float型にキャストしていることに気を付けてください。
割ると端数が出てfloatにしておかないと計算が狂います。
その後、端数を切り上げ(Math.Ceiling()で切り上げ)すると該当するハートオブジェクトの1つ次のindexになります。
配列のindexはINT型じゃないとだめなのでここで念のためにINT型にキャストしておきましょう。
ライフをどうするかの処理
該当するオブジェクトのindexを取得できたのでそのindexを使って、ハートを処理します。
float val = (((float)nowLife / (float)MAXLIFE) * 10.0f);
ここの計算式を詳しく解説するとライフの実数値は40ですが、
HUDの見た目的には20として0.5ずつで計算していくので、
- 20(ライフ最大値)
- 19.5(1ダメージ食らった場合)
- 19(2ダメージ食らった場合)
- etc…
と0.5刻みになるようにしています。
ハート1つめの全とハート2つ目の空は同じ数値といいましたね。
以下を再び載せておきます。
- 1つめ「0空:1半:2全」
- 2つめ「2空:3半:4全」
- 3つめ「4空:5半:6全」
このことから「(val % 1 == 0)」で割り切れるのか端数が出るのかの2択に絞ることができます。
例えば数値が「19」の時はハートが19個目が全状態なのとハートが20個目が空状態の2つ状態を判断できるわけです。
ここで最初から数値を20にしときゃよかったんじゃないか?って思ったのですが、
例えば最初は普通に食らうけど防御アップをとったらダメージを半分にしたいという処理の時楽になるんじゃないかなと思ったのですが、
考えれば考えるほどなんか無駄なことをしているなって思ってきたので考えるのを辞めます(笑)
とりあえず私としては理想の動きになってくれたのでこのまま進めます。
答えが1つじゃないのがプログラミングです(言い訳)
頭が良い方はプログラミングの訓練としてここの処理を改善してみてください。
ハートオブジェクトにメソッドを作成
ハートを処理する計算が出来上がったので、その後に呼び出す処理を作ります。
ハートのスプライトを変更するメソッドを子オブジェクトのスクリプトである「Life_Children」に作成します。
using UnityEngine;
public class Life_Children : MonoBehaviour
{
public Sprite[] m_sprite = new Sprite[4];
// 自身のスプライトコンポーネント
private SpriteRenderer selfSR;
// Start is called before the first frame update
void Start()
{
// 自身のスプライトコンポーネントを取得しておく
selfSR = transform.GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
}
public void HeartLess()
{
selfSR.sprite = m_sprite[1];
}
public void HeartHalf()
{
selfSR.sprite = m_sprite[2];
}
public void HeartFull()
{
selfSR.sprite = m_sprite[3];
}
}
このLife_Childrenスクリプトは実に単純なスクリプトで、
自身のスプライトレンダラーを取得して親側から子で用意したスクリプトを呼び出して
呼び出された内容により自身のスプライトを変更するだけです。
増えたときはFullとHalfの関数を呼び出す。
減ったときはLessとHalfの関数を呼び出す。
といった感じです。
子オブジェクトの取得する順番は予測しづらい
最後に子オブジェクトをまとめて取得しようとすると、ヒエラルキー上で名前に番号を振ろうが
生成した順番で並べようが取得時にぐちゃぐちゃになる可能性がかなり高いです。
というのもおそらくはUnity側で内部番号を都合よく振り分けている感じがします。
ですので開発者側が意図しない順番でオブジェクトを取得されてしまいます。
逆に内部番号が振り分けられているということは1度順番がわかってしまえば
基本的にオブジェクトを削除しない限りは変動しないということです。
なので取得した準にハートの座標を設定してやればいいわけです。
n個目は(x,y):(0+8n,0)…
という感じで順番に座標を変更するようにしてやればヒエラルキー上はまったく気にしなくてもOKです。
今回は10個目(ハート的には11個目)から次の段になっているので、
Unityではy座標が上に行くほど+なので添え字が10になったらy座標を-8してxを0から設定してやれば
次の行から設置されていくというわけです。
スクリプトにすると以下のような処理になります。
// 子オブジェクトを取得して格納
private void _getChildObjScripts()
{
// ループ用変数
int i = 0;
foreach (Transform child in transform)
{
//取得した順番に座標を決める
float px = i > 9 ? (8 * i) - (8 * 10) : 8 * i;
float py = i > 9 ? 0 : 8.0f;
child.transform.localPosition = new Vector3(px, py, 0);
_Hearts[i] = child.GetComponent<Life_Children>();
i++;
}
}
pxとpyの計算は三項演算子になっているのでしっかりとみてください。
もちろんわかりやすくifでちゃんと書いたほうが見易さ的には上ですが、
こういう時は三項演算子を使ったほうがスッキリかけていいなと個人的には思いました。
しかし他人にコードを見せる場合は三項演算子はあまり使うべきではないと思いますね。(反省なし)
余談ですが都合のいいことにオブジェクトをループで取得したら、
Unity側が自動でヒエラルキーを並べ替えしてくれます。(バージョンによってはしないかも?)
一度実行してから再び名前を付ければ視覚的にもちゃんと順番通りになるので良い感じです。
よし、ダメージ床とHP増減システム&HUD反映完了 pic.twitter.com/9kqnsVoRnl
— なおキーヌ@ゲームクリエイターLv5 (@naokeyzmt) January 31, 2020
次回はノックバックダメージを実装するために少しモンスターの動きを作っていきましょう。
それでは!