【自作ゲーム開発9】敵キャラクターから拠点へのダメージ【Unity】


注意
本記事で掲載されている動作の実装方法及びプログラムのソースコードは最適な方法ではない可能性があります。
今後不具合等が判明した場合には修正及び改良をおこなう可能性があります。
また今後自分で同機能を実装する場合の参考にする可能性もあるためソースコードだけではなく説明しつつ記事を進めていきます。

はじめに

Unityによる自作ゲーム開発進捗その9になります!

今回は敵キャラクターが拠点に到達した場合に拠点へダメージが発生するようにしました。

新しいスクリプトが1つ既存のスクリプト1つを更新します。

以下にソースコードを掲載します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
using TMPro;
using Febucci.UI;

public class CenterMobEnemy : MonoBehaviour{
    private const int m_const_text_array = 10;

    [SerializeField]
    private Canvas m_hitpoint_canvas;

    [SerializeField]
    private Slider m_hitpoint_bar;

    [SerializeField]
    private TextAnimatorPlayer m_damage_text;

    [SerializeField]
    private ParticleSystem m_bomb_effect;

    private List<TextAnimatorPlayer> m_text_animator = new List<TextAnimatorPlayer>();

    private GameObject m_script_manager;
    private GameObject[] m_pop_point = new GameObject[5];
    private GameObject[] m_end_point = new GameObject[5];

    private ColliderManager m_collider_manager;

    private NavMeshAgent m_navmesh_agent;
 
    private Rigidbody m_rigidbody;

    private Animator m_animator;

    private BaseSystem m_base_system;

    private Vector3 m_impact_vector;
    private Vector3 m_impact_power;

    private float m_time_count = 0.0f;
    private float m_hitpoint = 0.0f;
    private float m_damagepoint = 0.0f;

    private int m_transition = 0;
    private int m_iskinematic_change_frame = 0;
    private int m_text_array = 0;

    enum STATE{
        WAIT,
        MOVE,
        DIE,
        BOMB,
        IMPACT
    }; STATE e_state;

    private void FixedUpdate(){
        m_hitpoint_canvas.transform.rotation = m_hitpoint_canvas.worldCamera.transform.rotation;

        switch(e_state){
            case STATE.WAIT:
                m_time_count += Time.deltaTime;

                if(m_time_count > 1.0f){
                    m_animator.SetBool("animation_move", true);
                    m_navmesh_agent.SetDestination(m_end_point[0].transform.position); // タスク1
                    e_state = STATE.MOVE;
                }
            break;

            case STATE.MOVE:

            break;

            case STATE.DIE:
                m_time_count += Time.deltaTime;

                if(m_time_count > 2.0f){
                    m_animator.SetBool("animation_die", false);
                    MobState(false);
                }
            break;

            case STATE.BOMB:
                m_time_count += Time.deltaTime;

                if (m_time_count > 0.3f) MobState(false);
            break;

            case STATE.IMPACT:
                if(m_transition == 0){
                    m_transition = 1;
                    m_iskinematic_change_frame = Time.frameCount; // 補足2
                    m_navmesh_agent.enabled = false;
                    m_rigidbody.isKinematic = false;
                    m_rigidbody.AddForce(m_impact_power, ForceMode.Impulse);
                }

                if((m_transition == 1) && (m_rigidbody.IsSleeping())){
                    m_transition = 0;
                    m_iskinematic_change_frame = Time.frameCount; // 補足2
                    m_navmesh_agent.enabled = true;
                    m_rigidbody.isKinematic = true;
                    m_navmesh_agent.SetDestination(m_end_point[0].transform.position); // タスク1
                    e_state = STATE.MOVE;
                }
            break;
        }
    }

    private void OnTriggerEnter(Collider a_collider){
        if((e_state == STATE.WAIT) || (e_state == STATE.DIE) || (m_iskinematic_change_frame == Time.frameCount)) return; // 補足2

        if (a_collider.CompareTag("Finish")) { // タスク4
            m_bomb_effect.Play();
            m_base_system.BaseDamage(m_hitpoint_bar.value);
            m_navmesh_agent.enabled = true;
            m_rigidbody.isKinematic = true;
            m_navmesh_agent.ResetPath();
            m_time_count = 0.0f;
            e_state = STATE.BOMB;
            return;
        }

        m_collider_manager.ColliderDataInput(a_collider, this.gameObject, ref m_impact_vector, ref m_damagepoint);

        if(m_damagepoint != 0){
            DamageTextShow((int)m_damagepoint);
            m_hitpoint -= (int)m_damagepoint;
            if(m_hitpoint < 0) m_hitpoint = 0;
            m_hitpoint_bar.value = m_hitpoint;
        }

        if(m_hitpoint == 0){ // タスク2
            m_navmesh_agent.enabled = true;
            m_rigidbody.isKinematic = true;
            m_navmesh_agent.ResetPath();
            m_animator.SetBool("animation_move", false);
            m_animator.SetBool("animation_die", true);
            m_time_count = 0.0f;
            e_state = STATE.DIE;
            return;
        }

        if(m_impact_vector != Vector3.zero){
            m_impact_power = m_impact_vector; // 補足3
            m_transition = 0;
            e_state = STATE.IMPACT;
        }
    }

    public void Initialize(){
        for(int l_loop = 0; l_loop < m_const_text_array; l_loop += 1){
            var l_create = m_hitpoint_canvas.transform;
            var l_object = Instantiate(m_damage_text, Vector3.zero, Quaternion.identity, l_create);
            m_text_animator.Add(l_object.GetComponent<TextAnimatorPlayer>());
        }

        m_hitpoint_canvas.worldCamera = Camera.main;
        
        m_script_manager = GameObject.Find("ScriptManager");
        m_collider_manager = m_script_manager.GetComponent<ColliderManager>();

        m_navmesh_agent = GetComponent<NavMeshAgent>();
        m_rigidbody = GetComponent<Rigidbody>();
        m_animator = GetComponent<Animator>();
        m_pop_point[0] = GameObject.Find("PopPoint"); // タスク3
        m_end_point[0] = GameObject.Find("PlayerBase"); // タスク3
        m_base_system = m_end_point[0].GetComponent<BaseSystem>();

        m_hitpoint_bar.maxValue = 1000;
    }

    public void MobState(bool a_bool){
        if(a_bool){
            m_hitpoint_bar.value = m_hitpoint = m_hitpoint_bar.maxValue;
            this.gameObject.transform.position = m_pop_point[0].transform.position; // タスク1
            m_navmesh_agent.enabled = true; // 補足1
            m_time_count = 0.0f;         
            e_state = STATE.WAIT;
        }else{
            m_navmesh_agent.enabled = false; // 補足1
            this.gameObject.SetActive(false);
        }
    }

    private void DamageTextShow(float a_damage){
        m_text_animator[m_text_array].gameObject.transform.localPosition = new Vector3(Random.Range(-0.1f, 0.1f), Random.Range(0.9f, 1.1f), 0f);
        m_text_animator[m_text_array++].ShowText(string.Format("<FADE><BOUNCE>{0}</BOUNCE></FADE>", a_damage));

        if(m_const_text_array <= m_text_array) m_text_array = 0;
    }
}

/*
    [規則]
    p_ 外部アクセス
    m_ メンバー変数
    l_ ローカル変数
    a_ 引数

    [説明]
    補足1 真だと座標移動時に障害物が干渉して初期位置にズレが発生する
    補足2 IsKinematic切り替え時と同フレームのコライダー判定を無視する
    補足3 コライダー内で取得した吹き飛ばし変数をそのまま使用すると吹き飛ぶ前に0で上書きする事が有るため実行用変数を用意

    [バージョン]
    2020-12-12 プーリングとナビゲーション
    2021-01-03 アセットストアのモデル反映とアニメーション
    2021-01-28 吹き飛ばし機能の実装
    2021-02-03 体力システムの実装及びゲージとダメージの可視化
    2021-02-19 味方拠点へのダメージ及び自身の休息

    [タスク]
    タスク1 拠点の占拠状態で分岐が必要
    (済)タスク2 プーリング動作確認用のため消去する
    タスク3 沸く場所全ての設定が必要
    タスク4 今後タグを拠点に統一させる
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BaseSystem : MonoBehaviour{
    [SerializeField]
    private Canvas m_hitpoint_canvas;

    [SerializeField]
    private Slider m_hitpoint_bar;

    private void Start() {
        BaseInitialize(3000); // タスク1
    }

    public void BaseInitialize(float a_hitpoint) {
        // タスク2
        m_hitpoint_canvas.worldCamera = Camera.main;
        m_hitpoint_bar.maxValue = a_hitpoint;
        m_hitpoint_bar.value = a_hitpoint;
    }

    public void BaseDamage(float a_damage) {
        float l_value = m_hitpoint_bar.value - a_damage;

        if(l_value < 0) {
            l_value = 0;
            m_hitpoint_bar.value = l_value;
        } else {
            m_hitpoint_bar.value = l_value;
        }
    }
}

/*
    [規則]
    p_ 外部アクセス
    m_ メンバー変数
    l_ ローカル変数
    a_ 引数

    [説明]
    

    [バージョン]
    2021-02-19 関数呼び出しによる自身へのダメージ及び体力ゲージの可視化

    [タスク]
  タスク1 スタート関数は使用せずに設置者から呼び出す    
  タスク2 オブジェクトのオンオフも必要
*/

体力の可視化

拠点に付ける体力ゲージは【自作ゲーム開発7】と同じ仕組みを使用しゲージの色(FillのColor)をからにしているだけです。

以下の記事と同じ仕様です。


拠点へのダメージ

public void BaseDamage(float a_damage) {
    float l_value = m_hitpoint_bar.value - a_damage;

    if(l_value < 0) {
        l_value = 0;
        m_hitpoint_bar.value = l_value;
    } else {
        m_hitpoint_bar.value = l_value;
    }
}

拠点オブジェクトにアタッチされたスクリプト内の関数【BaseDamage】でダメージ量を受け取り体力バーの減少をおこなっています。


m_end_point[0] = GameObject.Find("PlayerBase"); // タスク3
m_base_system = m_end_point[0].GetComponent<BaseSystem>();

敵キャラクター(モブ)の生成時に目標拠点を取得しつつアタッチされているスクリプト(BaseDamage)も取得しておき、拠点に到着出来た場合にダメージ量をセットして関数を呼び出すようにしています。


if (a_collider.CompareTag("Finish")) { // タスク4
    m_bomb_effect.Play();
    m_base_system.BaseDamage(m_hitpoint_bar.value);
    m_navmesh_agent.enabled = true;
    m_rigidbody.isKinematic = true;
    m_navmesh_agent.ResetPath();
    m_time_count = 0.0f;
    e_state = STATE.BOMB;
    return;
}
case STATE.BOMB:
    m_time_count += Time.deltaTime;

    if (m_time_count > 0.3f) MobState(false);
break;

拠点到達時の処理内容2行目にある関数を呼び出し現在の残り体力をダメージとして拠点にダメージを与えるようにしています。

爆発エフェクトを発生させて敵キャラクターの状態は【BOMB】に変更しエフェクト終了時に敵オブジェクトを自身で休息させるようにしています。


作成している敵キャラクターについて

今回で1つめの敵キャラクターの挙動はほぼ完成となります。

味方拠点にひたすら向かってくるタイプで数が多く拠点に到着されると被害を受けるタワーディフェンスチックな敵となります。

受けるダメージは残り体力としているため体力を減らす状況が構築されていれば拠点へ到達されても被害は少なくなります。

拠点数は変動するため拠点を設置した場合や破壊した場合に次の拠点を設定する動作を入れるようにします。


動画


記事のリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です