【Unity2D】ダメージを連続で受けないように無敵時間を実装する方法|点滅処理も追加【初心者向け】

Unity2D実装解説

こんにちは!
個人でゲームを作っている怪獣と申します🦖

Unityで敵との接触ダメージを実装したとき、次のような問題が起きることがあります。

「敵に触れている間、HPが一気に減ってしまう。」
「一度接触しただけなのに何度もダメージを受ける。」
「ダメージを受けたことが画面上で分かりにくい。」

そこで今回の記事では、

プレイヤーが一度ダメージを受けたあと、
一定時間ダメージを受けない「無敵時間」を実装させる』方法を当記事で解説しようと思います!

さらに、
無敵時間中にプレイヤーを点滅させて、
現在ダメージを受けない状態であることを視覚的に分かりやすくしていきます!

今回実装する処理の流れは、次のとおりです。

1.プレイヤーが敵に接触する
2.HPを減らす
3.一定時間、無敵状態にする
4.無敵時間中はプレイヤーを点滅させる
5.一定時間が経過したら、再びダメージを受けられるようにする

この記事では、まず簡単な敵との接触ダメージを使って仕組みを解説します。

記事の後半では、実際に制作中のゲームでボスのマシンガン攻撃に無敵時間を適用した例も紹介します🦖


今回の完成イメージ

今回の実装が完成すると、
敵に触れたときにHPが1だけ減り、その後は一定時間ダメージを受けなくなります。
無敵時間中はプレイヤーが点滅し、無敵時間が終了すると再びダメージを受けられる状態に戻ります!

無敵時間がない場合に起きる問題とは?

最初に、無敵時間を設定していない場合の動きを確認してみましょう。
今回は、プレイヤーが敵に触れている間、ダメージ処理を呼び出す簡単な実装を使用します。

private void OnCollisionStay2D(Collision2D collision)
{
    if (collision.gameObject.CompareTag("Enemy"))
    {
        _TakeDamage(1);
    }
}

OnCollisionStay2Dは、オブジェクト同士が接触している間、繰り返し呼び出されます。
そのため、このままではプレイヤーが敵に触れている間、何度も_TakeDamageが実行されてしまいます。
例えばHPが5あったとしても、敵に少し触れただけで次のようにHPが減少します。

HP5
↓
HP4
↓
HP3
↓
HP2
↓
HP1
↓
HP0

プレイヤーから見ると、一度しか敵に触れていないように見えても、
内部では短時間にこのように何度もダメージ処理をくらってしまいます。

この問題を防ぐために、ダメージを受けたあとは一定時間、次のダメージ処理を受け付けないようにします!


①無敵状態を管理する変数を追加する

最初に、Playerスクリプトへ無敵時間に必要な変数を追加します。

[SerializeField, Header("無敵時間")]
private float _invincibleDuration = 1.0f;

[SerializeField, Header("点滅間隔")]
private float _blinkInterval = 0.1f;

private bool _isInvincible;

private SpriteRenderer _spriteRenderer;
private Color _defaultColor;

それぞれの変数には、次の役割があります。

_invincibleDuration

ダメージを受けたあと、無敵状態を継続する時間です。
今回は初期値を1.0fにしているため、1秒間ダメージを受けなくなります。

_blinkInterval

無敵時間中にプレイヤーを点滅させる間隔です。
値を小さくすると速く点滅し、値を大きくするとゆっくり点滅します。

_isInvincible

現在プレイヤーが無敵状態かどうかを管理する変数です。

  • true:無敵状態
  • false:通常状態

となります。

_spriteRenderer

プレイヤーの表示を点滅させるために使用するSpriteRendererです。

_defaultColor

点滅処理が終わったあと、プレイヤーの色を元に戻すために使用します。


②AwakeでSpriteRendererを取得する

続いて、AwakeでプレイヤーのSpriteRendererを取得するようにします。

private void Awake()
{
    _spriteRenderer = GetComponent<SpriteRenderer>();

    if (_spriteRenderer != null)
    {
        _defaultColor = _spriteRenderer.color;
    }
}

GetComponent<SpriteRenderer>()を使用して、Playerオブジェクトに設定されているSpriteRenderer(プレイヤーの画像)を取得しています。
取得したSpriteRendererの色は、_defaultColorへ保存しておきます。
無敵時間終了後にこの色を再設定することで、点滅によって変更した透明度を元に戻すことができます

すでにAwake関数がある場合は、新しくAwakeを追加するのではなく、既存のAwake内へ処理を追加してください!


③ダメージ処理に無敵判定を追加する

現在のダメージ処理は、次のようになっているとします。

private void _TakeDamage(int damage)
{
    _currentHP -= damage;
    _currentHP = Mathf.Clamp(_currentHP, 0, _maxHP);

    _UpdateHPBar();

    if (_currentHP <= 0)
    {
        // 操作を止める
        if (_input != null) _input.enabled = false;

        // ゲームオーバーを表示
        if (_mainManager != null) _mainManager.Show();
    }
}

この処理へ、無敵状態の確認と無敵時間を開始する処理を追加します。

修正後のコードは次のとおりです。

private void _TakeDamage(int damage)
{
    // 無敵時間中、またはHPが0の場合はダメージを受けない
    if (_isInvincible || _currentHP <= 0)
    {
        return;
    }

    // ダメージを受けた直後から無敵状態にする
    _isInvincible = true;

    _currentHP -= damage;
    _currentHP = Mathf.Clamp(_currentHP, 0, _maxHP);

    _UpdateHPBar();

    if (_currentHP <= 0)
    {
        // 点滅で変更した色を元に戻す
        if (_spriteRenderer != null)
        {
            _spriteRenderer.color = _defaultColor;
        }

        // 操作を止める
        if (_input != null)
        {
            _input.enabled = false;
        }

        // ゲームオーバーを表示
        if (_mainManager != null)
        {
            _mainManager.Show();
        }

        return;
    }

    // 無敵時間と点滅処理を開始する
    StartCoroutine(_InvincibleCoroutine());
}

次に、追加した処理を順番に確認していきましょう!


無敵時間中はダメージ処理を終了する

最初に、次の条件を追加しています。

if (_isInvincible || _currentHP <= 0)
{
    return;
}

_isInvincibletrueの場合は、すでに無敵時間中です。
そのため、これ以降のHPを減らす処理を実行せず、returnでメソッドを終了します。
また、HPがすでに0の場合も、それ以上ダメージ処理を実行しないようにしています。


ダメージを受けた直後に無敵状態にする

続いて、次の処理で無敵状態を開始します。

_isInvincible = true;

ダメージを受けた直後にtrueへ変更することで、次に_TakeDamageが呼び出されたときは、先ほど追加した条件によって処理が終了します。

if (_isInvincible)
{
    return;
}

これにより、敵に触れ続けて_TakeDamageが何度も呼び出された場合でも、HPが連続で減らなくなります!


④コルーチンで無敵時間を作る

次に、無敵時間と点滅を管理するコルーチンを作成します。
Player側のスクリプトへ次の処理を追加してください。

private IEnumerator _InvincibleCoroutine()
{
    // 無敵時間が始まってからの経過時間を管理する
    float elapsedTime = 0f;

    // 経過時間が設定した無敵時間を超えるまで点滅処理を繰り返す
    while (elapsedTime < _invincibleDuration)
    {
        // SpriteRendererが取得できている場合
        if (_spriteRenderer != null)
        {
            // プレイヤーの元の色をコピーする
            Color transparentColor = _defaultColor;

            // 透明度(Alpha値)を0.3にして半透明にする
            transparentColor.a = 0.3f;

            // 半透明にした色をプレイヤーに設定する
            _spriteRenderer.color = transparentColor;
        }

        // 設定した点滅間隔だけ処理を待つ
        yield return new WaitForSeconds(_blinkInterval);

        // SpriteRendererが取得できている場合
        if (_spriteRenderer != null)
        {
            // プレイヤーの色を元の色に戻す
            _spriteRenderer.color = _defaultColor;
        }

        // 再び設定した点滅間隔だけ処理を待つ
        yield return new WaitForSeconds(_blinkInterval);

        // 「半透明で待った時間 + 元の色で待った時間」を経過時間に加算する
        elapsedTime += _blinkInterval * 2;
    }

    // SpriteRendererが取得できている場合
    if (_spriteRenderer != null)
    {
        // 点滅終了後、念のためプレイヤーの色を元に戻す
        _spriteRenderer.color = _defaultColor;
    }

    // 無敵状態を解除して、再びダメージを受けられるようにする
    _isInvincible = false;
}

IEnumeratorを使用するため、スクリプトの上部に次の記述が必要です。
※すでに記述されている場合は、追加する必要はありません。

using System.Collections;

コルーチンとは?

一言でいうと、
「途中で一時停止し、あとで続きを実行できる処理」です。

普通の関数は、

スタート
 ↓
処理①
 ↓
処理②
 ↓
処理③
 ↓
終了

という風に上から順に実行されてきますが、

コルーチンの場合は、

スタート
 ↓
処理①
 ↓
一時停止
 ↓
(1秒後)
 ↓
処理②
 ↓
終了

という違いがあり、コルーチンなら途中で止めることができます!
今回のように無敵時間を実装する場合に最適です!

ダメージ
 ↓
無敵ON
 ↓
1秒待つ
 ↓
無敵OFF

点滅処理の仕組み

点滅処理では、プレイヤー画像の透明度を変更しています。

Color transparentColor = _defaultColor;
transparentColor.a = 0.3f;

_spriteRenderer.color = transparentColor;

Colorのaは、画像の透明度を表します。

  • 1.0f:完全に表示される
  • 0.5f:半透明になる
  • 0.0f:完全に透明になる

今回は0.3fを設定しているため、プレイヤーが薄く表示されます。
一定時間待ったあと、保存しておいた元の色へ戻します。

yield return new WaitForSeconds(_blinkInterval);

_spriteRenderer.color = _defaultColor;

この処理を無敵時間が終了するまで繰り返すことで、プレイヤーが点滅しているように見えます✨


無敵時間終了後に通常状態へ戻す

点滅処理が終了したら、次の処理で無敵状態を解除します。

_isInvincible = false;

これによって、次に敵へ接触したときは再びダメージを受けられるようになります。
↓無敵時間中の状態は、次のように変化します。

敵に接触する
↓
HPが1減る
↓
_isInvincibleをtrueにする
↓
プレイヤーが点滅する
↓
一定時間経過
↓
_isInvincibleをfalseに戻す

⑤敵との接触判定を追加する

今回は例として、プレイヤーがEnemyタグの付いた敵に触れている間、ダメージ処理を呼び出します。
Playerスクリプトへ次の処理を追加します!

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            _TakeDamage(1);
        }
    }

OnCollisionStay2Dは、Collider2D同士が接触している間、繰り返し呼び出されます。
しかし、③の手順で_TakeDamage内に以下の無敵判定を追加しているため、HPが減るのは最初の1回だけになります。
無敵時間が終了したあとも敵に接触し続けている場合は、再びダメージを受けます。

if (_isInvincible)
{
    return;
}

⑥敵オブジェクトの設定

敵オブジェクトの用意をしておきます。
敵オブジェクトには、次の設定はしておきましょう!

  • SpriteRenderer
  • Collider2D
  • 必要に応じてRigidbody2D
  • Enemyタグ

Enemyタグを設定する

敵オブジェクトを選択し、Inspector上部の「Tag」からEnemyを設定します。
まだEnemyタグがない場合は、以下の手順に沿って新しく追加しましょう!

  1. 敵オブジェクトを選択する
  2. Inspector 上部の Tag を開く
  3. Enemy がなければ Add Tag で追加する
  4. 敵オブジェクトに Enemy タグを設定する
Enemyのタグ設定の画面

Collider2Dを設定する

敵オブジェクトには、次のようなCollider2Dを設定します。

  • Box Collider 2D
  • Capsule Collider 2D
  • Circle Collider 2D

敵の形に合わせて選びましょう。
また、PlayerオブジェクトにもCollider2DとRigidbody2Dが必要です。


⑦Inspectorで無敵時間を調整する

Unityへ戻り、Playerオブジェクトを選択します。
Playerスクリプトに、次の項目が表示されます。

  • 無敵時間
  • 点滅間隔

まずは、次の設定ぐらいにしておきます。

無敵時間:1
点滅間隔:0.1

点滅が速すぎる場合は、点滅間隔を0.150.2へ変更してみてください。
無敵時間が短いと感じる場合は、1.5などへ変更できます。
ゲームの難易度や敵の攻撃方法に合わせて調整しましょう。


完成した全コード

今回追加した処理をまとめると、次のようになります!

using System.Collections;
using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField, Header("最大HP")]
    private int _maxHP = 5;

    [SerializeField, Header("HPバーのImage")]
    private Image _hpBarFill;

    private int _currentHP;
    
    [SerializeField, Header("最大HP")]
    private int _maxHP = 5;

    [SerializeField, Header("無敵時間")]
    private float _invincibleDuration = 1.0f;

    [SerializeField, Header("点滅間隔")]
    private float _blinkInterval = 0.1f;

    private int _currentHP;
    private bool _isInvincible;

    private SpriteRenderer _spriteRenderer;
    private Color _defaultColor;

    private void Awake()
    {
        _spriteRenderer = GetComponent<SpriteRenderer>();

        if (_spriteRenderer != null)
        {
            _defaultColor = _spriteRenderer.color;
        }
    }

    private void Start()
    {
        _currentHP = _maxHP;
    }

    private void OnCollisionStay2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            _TakeDamage(1);
        }
    }

    private void _TakeDamage(int damage)
    {
        // 無敵時間中、またはHPが0の場合はダメージを受けない
        if (_isInvincible || _currentHP <= 0)
        {
            return;
        }

        // ダメージを受けた直後から無敵状態にする
        _isInvincible = true;

        _currentHP -= damage;
        _currentHP = Mathf.Clamp(_currentHP, 0, _maxHP);

        _UpdateHPBar();

        if (_currentHP <= 0)
        {
            // 点滅で変更した色を元に戻す
            if (_spriteRenderer != null)
            {
                _spriteRenderer.color = _defaultColor;
            }

            // 操作を止める
            if (_input != null)
            {
                _input.enabled = false;
            }

            // ゲームオーバーを表示
            if (_mainManager != null)
            {
                _mainManager.Show();
            }

            return;
        }

        // 無敵時間と点滅処理を開始する
        StartCoroutine(_InvincibleCoroutine());
    }

    private IEnumerator _InvincibleCoroutine()
    {
        // 無敵時間が始まってからの経過時間を管理する
        float elapsedTime = 0f;
    
        // 経過時間が設定した無敵時間を超えるまで点滅処理を繰り返す
        while (elapsedTime < _invincibleDuration)
        {
            // SpriteRendererが取得できている場合
            if (_spriteRenderer != null)
            {
                // プレイヤーの元の色をコピーする
                Color transparentColor = _defaultColor;
    
                // 透明度(Alpha値)を0.3にして半透明にする
                transparentColor.a = 0.3f;
    
                // 半透明にした色をプレイヤーに設定する
                _spriteRenderer.color = transparentColor;
            }
    
            // 設定した点滅間隔だけ処理を待つ
            yield return new WaitForSeconds(_blinkInterval);
    
            // SpriteRendererが取得できている場合
            if (_spriteRenderer != null)
            {
                // プレイヤーの色を元の色に戻す
                _spriteRenderer.color = _defaultColor;
            }
    
            // 再び設定した点滅間隔だけ処理を待つ
            yield return new WaitForSeconds(_blinkInterval);
    
            // 「半透明で待った時間 + 元の色で待った時間」を経過時間に加算する
            elapsedTime += _blinkInterval * 2;
        }
    
        // SpriteRendererが取得できている場合
        if (_spriteRenderer != null)
        {
            // 点滅終了後、念のためプレイヤーの色を元に戻す
            _spriteRenderer.color = _defaultColor;
        }
    
        // 無敵状態を解除して、再びダメージを受けられるようにする
        _isInvincible = false;
    }

    private void _UpdateHPBar()
    {
        // 既存のHPバー更新処理など
       if (_hpBarFill == null) return;

        _hpBarFill.fillAmount = (float)_currentHP / _maxHP;
    }
}

このコードの体力UIを減らす関数、「_UpdateHPBar」については、それぞれのゲームで使用している既存の処理に合わせてください!
_UpdateHPBarの処理のソースコードは別記事で紹介しているので、詳しくはそちらをご覧ください🦖


TriggerをONにしている場合の方法

敵のCollider2Dで「Is Trigger」を有効にしている場合は、CollisionではなくTrigger用のメソッドを使用します。

private void OnTriggerStay2D(Collider2D collision)
{
    if (collision.CompareTag("Enemy"))
    {
        _TakeDamage(1);
    }
}

処理の考え方はOnCollisionStay2Dと同じです。
使用しているCollider2Dの設定に合わせて選択してください!


プレイヤーが点滅しない場合の原因

プレイヤーが点滅しない場合は、PlayerスクリプトとSpriteRendererが別のオブジェクトに設定されている可能性があります。

例えば、次のような構成になっている場合です。

Player
└── PlayerSprite
    └── SpriteRenderer

この場合、Playerオブジェクトで次の処理を実行しても、SpriteRendererを取得できません。

_spriteRenderer = GetComponent<SpriteRenderer>();

子オブジェクトにSpriteRendererがある場合は、次のように変更します。

_spriteRenderer = GetComponentInChildren<SpriteRenderer>();

または、InspectorからSpriteRendererを設定できる形にしてもよいでしょう🦖

[SerializeField]
private SpriteRenderer _spriteRenderer;

こちらの方法では、PlayerSpriteオブジェクトをInspectorの項目へドラッグ&ドロップして設定します。


制作中のゲームで実装した例

制作しているゲームではマシンガン攻撃を行う敵を実装していますが、
弾が連続的に当たるとその分ダメージを食らってしまい、即死になる場合がありました。
複数の弾が短時間に連続で当たることで、プレイヤーのHPが一瞬で減ってしまっていました・・

マシンガンは連続攻撃であるため、弾ごとにそのままダメージを受けると、避ける余裕がほとんどありませんでした。
そこで今回紹介した無敵時間をプレイヤーのダメージ処理へ追加し、一度ダメージを受けたあとは、一定時間ほかの弾に当たってもダメージを受けないようにしました!

無敵時間中にはプレイヤーを点滅させ、現在ダメージを受けない状態であることも分かるようにしています。

↓無敵時間有↓

無敵時間を追加したことで、マシンガン攻撃の危険さを残しながらも、一度の接触だけでHPがすべてなくなることを防げました。

今回のように、基本的な無敵時間の仕組みをPlayer側のダメージ処理へ入れておくと、マシンガンだけでなく、次のような攻撃にも共通して使用できます!

  • 通常の敵との接触
  • 敵が発射する弾
  • ミサイル攻撃
  • トゲなどの障害物
  • 炎や電流などの継続ダメージ

まとめ

最後まで読んでいただきありがとうございました!

今回は、敵からダメージを連続で受けないように、無敵時間点滅処理を実装しました。

今回のポイントをまとめると、以下のとおりです。

  • _isInvincibleを使って、現在無敵状態かどうかを管理する
  • 無敵時間中にダメージ処理が呼ばれた場合は、returnで処理を終了する
  • コルーチンを使って、一定時間だけ無敵状態を継続する
  • 無敵時間中はSpriteRendererの透明度を変更して、プレイヤーを点滅させる
  • 無敵時間が終了したら、_isInvinciblefalseに戻して再びダメージを受けられるようにする

無敵時間を実装しておくことで、敵に触れ続けたときや複数の攻撃が短時間に当たったときに、HPが一気に減ってしまうことを防げます。

今回の仕組みは通常の敵との接触だけでなく、弾・マシンガン・ミサイル・トゲなど、さまざまな攻撃にも応用できます。

ゲームの難易度や攻撃の間隔に合わせて、無敵時間と点滅速度を調整してみてください🦖

コメント

タイトルとURLをコピーしました