【Unity2D】ダメージによる点滅表現処理を作る

Unity2D

Unity2D ARPG

ライフの増減の仕組みができたので今度はダメージを受けたときの処理を作りましょう。

2Dのダメージ表現の定番といえば高速点滅ですね。

作っているうちになんで2Dゲームのダメージは点滅表現なんだ?ってしょうもない疑問を抱くようになりつつも実装しました。

2Dゲームをやってきた人間からするともはや常識ともいえる点滅というダメージ表現。

しかしいざ実装するとなるとどうやるんだ……?ってなるのも事実。

頭のいいひとならここで三角関数が浮かんでサイン波つかえばいいな!ってなります。

そう、今回は三角関数のsinを使って点滅表現を作ります。

色々調べているとこれが定番のようですね。

やっぱりゲームを作っていると数学の知識がとても重要に感じてきますので私も日々勉強するようにしています。

それでは早速作っていきましょう!

状態異常の配列を作る

すでにプレイヤーのステータスを作ってしまったので本来はそちらにまとめるべきなのですが、
状態異常は基本的に0か1で管理するので真偽値の配列にしてしまいましょう。

State_Playerのインスタンス変数とそのプロパティを作りましょう。

    // プレイヤーの状態異常
    private bool[] _playerHealth = {
        false,
        false,
        false,
        false,
        false,
        false,
    };
    public bool[] PlayerHealthList { get { return _playerHealth; } }
    public void setPlayerHelthList(int index, bool flag) { _playerHealth[index] = flag; }

プロパティであるゲッターとセッターですが、配列の時ってどうやるのが正解なんですかね……

ゲッターの場合は配列そのものをやり取りできるようにして取得した側で添え字にアクセスすればいいですが、
セッター側の場合は配列そのものを差し替える形になってしまうので処理的にどうなのでしょうか。

ゲッターで取得した配列の値を書き換え、その配列をセッターにセットするという形がベストなのか……

それともセッター作らずに専用関数を書いて引数にindexと値を渡して更新させるようにするのがベストなのか……

このあたりはもうちょっとC#についてもっと勉強しないといけないなと感じました。

少し脱線しましたが状態異常の配列が出来上がりました。

次はダメージを受けたときにこの配列の0番目をON状態にして、そのON状態の時に動く仕組みを作ります。

点滅処理を作る

状態異常配列を使って点滅処理を制御しましょう。

まずはダメージを受けたときにHPを減らしていた箇所に状態異常を変更する処理を書きます。

    private void OnTriggerEnter2D(Collider2D other)
    {
        switch (other.tag)
        {
            case "DamageTrap":
                _State.setPlayerStatus(_State.getPlayerStatus(0) - 1, 0);
                _State.setPlayerHelthList(0, true); // 追加
                Debug.Log("HP残り");
                Debug.Log(_State.getPlayerStatus(0));
                break;
        }
    }

これでダメージを受けたときに状態異常を変更することができました。

この状態異常がONになっているときは基本的にダメージを受けない無敵状態にしたいですね。

でないと連続してダメージを受けてしまいかなりのハードモードどころか理不尽な状態になります。

次にこのステータスがONになっているときに駆動する点滅関数を作ります。

一旦Damage_Playerのコードをすべて張ります。

using UnityEngine;
using System.Collections;

public class Damage_Player : MonoBehaviour
{
    // プレイヤーステートスクリプトの取得
    private State_Player _State;
    // 自身のスプライト
    private SpriteRenderer _Sprite;

    // 点滅カウント
    private uint _flashCount;
    // 点滅用透過度
    private float _flashAlpha;

    // Start is called before the first frame update
    void Start()
    {
        _State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();
        _Sprite = transform.GetComponent<SpriteRenderer>();
        _flashCount = 0;
        _flashAlpha = 1;
    }

    // Update is called once per frame
    void Update()
    {
        _DamageFlash();
    }

    // ダメージ時のフラッシュ処理
    private void _DamageFlash()
    {
        // ダメージを受けている状態か
        if (_State.PlayerHealthList[0])
        {
            // 透明度を時間によってサイン派で決める
            _flashAlpha = Mathf.Sin(Time.time * 100) / 2 + 0.5f;

            // 透明度を適用する
            Color _color = _Sprite.color;
            _color.a = _flashAlpha;
            _Sprite.color = _color;

            // 点滅カウントを増やす
            _flashCount++;

            // 点滅カウントが一定を越えたら解除する
            if (_flashCount > 60)
            {
                // フラッシュカウントをリセット
                _flashCount = 0;
                // プレイヤーの透明度を戻す
                _Sprite.color = new Color(255, 255, 255, 255);
                // 点滅状態を解除
                _State.setPlayerHelthList(0, false);
            }

        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        switch (other.tag)
        {
            case "DamageTrap":
                _State.setPlayerStatus(_State.getPlayerStatus(0) - 1, 0);
                _State.setPlayerHelthList(0, true);
                Debug.Log("HP残り");
                Debug.Log(_State.getPlayerStatus(0));
                break;
        }
    }

}

_DamageFlash関数がメインです。

Mathf.Sin(Time.time * 100) / 2 + 0.5f;

この部分が点滅処理のキモですね。

Timeクラスのtimeはアプリケーションからの経過時間(ms)が格納されています。

最後のフレームからの経過時間が格納されているTime.deltatimeでは都合が悪いのでコチラを使っています。

それをMathf.Sin関数に渡すと-1.0f~1.0fの範囲で値が返ってきます。

なんでだ?って思う方は三角関数を勉強してください。

アルファ値はColor関数だとint型で0~255の値が必要になります。

ですが取れた値は-1.0f~1.0fの範囲なのでダメですね。

しかしColor関数のRGBAのあるうち1つだけを取り出すとfloat型の0.0f~1.0fの範囲で指定できるようになります。

ですが取れる値はマイナスがついてしまっていますのでマイナス補正をする必要がありますね。

まず値を半分にしてみましょう。

-1.0f ~ 1.0f / 2 = -0.5f ~ 0.5f

という感じに結果を2で割ると範囲が半分になりますよね。

これだとまだマイナスが出てしまっています。

この結果に+0.5をしてやると見事「0.0f~1.0f」の範囲になるわけですね!

ということは

Mathf.Sin(Time.time) / 2 + 0.5f;

という計算式になります。

しかしこれをアルファ値にしてやるとめちゃくちゃゆっくりとフェードインフェードアウトします。

これでは点滅といえないので速度を早くしてやる必要があるわけですね。

Mathf.Sin(Time.time*100) / 2 + 0.5f;

sin関数は値を増やし続けても-1~1の範囲で波を打っているわけなのでその速度を上げてやればいいわけです。

やった感じ100倍速ぐらいがよくある2Dゲームの点滅になりました。

ここを変数化しておいて点滅速度を調整できるようにしておくと大本をいじらなくていい感じですね。

あとはその数値をもとに透明度を適用していくだけです。

そして点滅カウントを増やし、点滅カウントが60以上になったら
ステータスとカウントを戻して透明度も中途半端な状態だとダメなので255にしてリセットしてやります。

なぜカウント60なのかというと60FPSでみてるからですね。

おそらく大体1秒ぐらいになると思います。

30FPSのゲームの場合は30にしましょう。

点滅はフレーム単位で行っているので動画にしちゃうとフレームレートによっては
点滅しているようにみえないので今回は動画にはしていません。

結構簡単に実装できるのでご自分で確かみて見ろ!

コルーチンを使った点滅処理の実装

ここまでで完成はしたのですが、他にも調べてみるとどうやらコルーチンで実装するのが
取り回ししやすそうな感じでしたので先輩たちにならって組み替えてみましょう。

コルーチンを使ったことがないという人にも、コルーチン入門的には最適なんじゃないでしょうか?

それではさっそく作っていきましょう。

    private void _DamageFlash()
    {
        // ダメージを受けている状態か
        if (_State.PlayerHealthList[0])
        {
            // 透明度を時間によってサイン派で決める
            _flashAlpha = Mathf.Sin(Time.time * 100) / 2 + 0.5f;

            // 透明度を適用する
            Color _color = _Sprite.color;
            _color.a = _flashAlpha;
            _Sprite.color = _color;
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        switch (other.tag)
        {
            case "DamageTrap":
                _State.setPlayerStatus(_State.getPlayerStatus(0) - 1, 0);
                _State.setPlayerHelthList(0, true);
                StartCoroutine("FlashStopWait1sCount");
                break;
        }
    }

    // 呼び出されたら1秒待って点滅フラグを解除する
    IEnumerator FlashStopWait1sCount()
    {
        // 1秒間処理を止める
        yield return new WaitForSeconds(1);

        // 1秒後ダメージフラグをfalseにして点滅を戻す
        _flashCount = 0;
        // プレイヤーの透明度を戻す
        _Sprite.color = new Color(255, 255, 255, 255);
        // 点滅状態を解除
        _State.setPlayerHelthList(0, false);
    }

FlashStopWait1sCountというIEnumerator関数を作り、ダメージを受けたときにStartCoroutineで呼び出しましょう。

呼び出すときは関数名を文字列にして渡します。

そして関数の最初に「WaitForSeconds(1)」でyieldしているので
一旦コルーチンの処理を止めて1秒間経ってから次の処理を行うといった感じです。

その間に点滅処理が行われており、1秒後に点滅リセット処理が動いて止まるというわけですね。

こうすることで点滅用のカウント変数が不要になりましたので削除しましょう。

今回はここまでにしましょう。

ハートの処理も書こうと思ったのですが思ったより長くなったので次に回します。

最後に今回のソースコードと参考にさせていただいたURLを載せておきます。

【Unity(C#)】ゲームオブジェクトを点滅させる方法

[Unity] 一定の時間間隔で処理を実行する方法まとめ(時間制御)

Unity2D入門 スクロールアクションゲームを作る ダメージを受けると点滅し、1秒間無敵な処理を作る

using UnityEngine;
using System.Collections;

public class Damage_Player : MonoBehaviour
{
    // プレイヤーステートスクリプトの取得
    private State_Player _State;
    // 自身のスプライト
    private SpriteRenderer _Sprite;
    // 点滅用透過度
    private float _flashAlpha;

    // Start is called before the first frame update
    void Start()
    {
        _State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();
        _Sprite = transform.GetComponent<SpriteRenderer>();
        _flashAlpha = 1;
    }

    // Update is called once per frame
    void Update()
    {
        _DamageFlash();
    }

    // ダメージ時のフラッシュ処理
    private void _DamageFlash()
    {
        // ダメージを受けている状態か
        if (_State.PlayerHealthList[0])
        {
            // 透明度を時間によってサイン派で決める
            _flashAlpha = Mathf.Sin(Time.time * 100) / 2 + 0.5f;

            // 透明度を適用する
            Color _color = _Sprite.color;
            _color.a = _flashAlpha;
            _Sprite.color = _color;
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        switch (other.tag)
        {
            case "DamageTrap":
                _State.setPlayerStatus(_State.getPlayerStatus(0) - 1, 0);
                _State.setPlayerHelthList(0, true);
                StartCoroutine("FlashStopWait1sCount");
                break;
        }
    }

    // 呼び出されたら1秒待って点滅フラグを解除する
    IEnumerator FlashStopWait1sCount()
    {
        // 1秒間処理を止める
        yield return new WaitForSeconds(1);

        // プレイヤーの透明度を戻す
        _Sprite.color = new Color(255, 255, 255, 255);
        // 点滅状態を解除
        _State.setPlayerHelthList(0, false);
    }

}

それでは!