【自作ゲーム開発35】気絶回数に上限値を追加【Unity】


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

はじめに

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

今回は気絶の回数に上限を設定出来るようにしました。

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

以下に動画でのコピー用にソースコードを掲載しています。

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

public class AssultEnemy : 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;

    [SerializeField]
    private ParticleSystem m_stun_effect;

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

    private GameObject m_script_manager;
    private GameObject m_pop_point;
    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 = new BaseSystem[5];

    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 float m_stun_set = 0.0f;
    private float m_stun_use = 0.0f;

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

    [SerializeField]
    private int m_stun_limit = 3;

    private int m_stun_limit_count = 0;

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

    public void Initialize(string a_line) {
        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 = GameObject.Find(string.Format("{0}Pop", a_line));

        for (int l_loop = 0; l_loop < 4; l_loop += 1) {
            // タスク5
            m_end_point[l_loop] = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
            m_base_system[l_loop] = m_end_point[l_loop].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.transform.position; // タスク3
            m_navmesh_agent.enabled = true; // 補足1
            m_time_count = 0.0f;
            e_state = STATE.WAIT;
        } else {
            m_stun_limit_count = 0;
            m_navmesh_agent.enabled = false; // 補足1
            this.gameObject.SetActive(false);
        }
    }

    public void BaseChange(int a_number) {
        m_base_state = a_number;
    }

    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[m_base_state].transform.position);
                    e_state = STATE.MOVE;
                }
                break;

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

                if (m_time_count > 1.0f) m_navmesh_agent.SetDestination(m_end_point[m_base_state].transform.position);
                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.STUN:
                m_time_count += Time.deltaTime;

                if (m_time_count > m_stun_use) {
                    m_time_count = 0.0f;
                    m_stun_effect.Stop();
                    m_navmesh_agent.enabled = true;
                    m_rigidbody.isKinematic = true;
                    m_navmesh_agent.SetDestination(m_end_point[m_base_state].transform.position);
                    e_state = STATE.MOVE;
                }
                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[m_base_state].transform.position);
                    e_state = STATE.MOVE;
                }
                break;
        }
    }

    private void OnTriggerEnter(Collider a_collider) {
        if ((e_state == STATE.BOMB) || (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();

            if(m_base_state == 0) {
                m_base_system[m_base_state].MainBaseDamage(m_hitpoint_bar.value);
            } else {
                m_base_system[m_base_state].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, ref m_stun_set);

        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;
            m_stun_effect.Stop();
            e_state = STATE.IMPACT;
            return;
        }

        if ((m_stun_set != 0.0f) && (e_state != STATE.IMPACT)) {
            m_stun_limit_count += 1;

            if(m_stun_limit_count > m_stun_limit) {
                m_stun_limit_count = m_stun_limit;
                return;
            }

            m_stun_use = m_stun_set;
            m_navmesh_agent.enabled = true;
            m_rigidbody.isKinematic = true;
            m_navmesh_agent.ResetPath();
            m_stun_effect.Play();
            m_time_count = 0.0f;
            e_state = STATE.STUN;
            return;
        }
    }

    private void DamageTextShow(float a_damage) {
        // タスク6
        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 味方拠点へのダメージ及び自身の休息
    2021-03-03 スタン動作の追加
    2021-03-12 標的の拠点の変更機能
    2021-03-17 拠点数と沸き拠点移動対応と名前の変更(旧CenterMobEnemy)
    2021-07-22 プレイヤー側の本拠地システムに対応
    2021-12-09 気絶耐性の追加

    [タスク]
    (済)タスク1 拠点の占拠状態で分岐が必要
    (済)タスク2 プーリング動作確認用のため消去する
    (済)タスク3 沸く場所全ての設定が必要
    タスク4 今後タグを拠点に統一させる
    タスク5 臨時防衛拠点数
    タスク6 コルーチンを使用したオブジェクトプーリングに変更する
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColliderManager : MonoBehaviour {
    private float m_main_player_impact = 30.0f; // タスク1
    private float m_main_player_impact_damage = 100.0f; // タスク1
    private float m_main_player_skill1_damage = 50.0f; // タスク1

    public void ColliderDataInput(Collider a_collider, GameObject a_object, ref Vector3 a_vector, ref float a_damage, ref float a_stun) {
        switch (a_collider.gameObject.tag) {
            case "Player":
                a_vector.Set(a_object.transform.position.x - a_collider.transform.position.x, 0f, a_object.transform.position.z - a_collider.transform.position.z);
                a_vector.Normalize();
                a_vector *= m_main_player_impact;
                a_stun = 0.0f;
                a_damage = Random.Range(m_main_player_impact_damage * 0.8f, m_main_player_impact_damage * 1.2f);
                break;

            case "Finish":
                a_vector.Set(0f, 0f, 0f);
                a_stun = 0.0f;
                a_damage = 9999.0f;
                break;

            case "PlayerSkill1":
                a_vector.Set(0f, 0f, 0f);
                a_stun = 3.0f;
                a_damage = Random.Range(m_main_player_skill1_damage * 0.8f, m_main_player_skill1_damage * 1.2f);
                break;

            case "PlayerSkill2":
                a_vector.Set(0f, 0f, 0f);
                a_stun = 3.0f;
                a_damage = 30.0f;
                break;

            case "PlayerSkill3":
                a_vector.Set(a_object.transform.position.x - a_collider.transform.position.x, 0f, a_object.transform.position.z - a_collider.transform.position.z);
                a_vector.Normalize();
                a_vector *= 60.0f;
                a_stun = 0.0f;
                a_damage = Random.Range(m_main_player_impact_damage * 0.8f, m_main_player_impact_damage * 1.2f);
                break;

            case "PlayerSkill4":
                a_vector.Set(0f, 0f, 0f);
                a_stun = 8.0f;
                a_damage = Random.Range(250.0f * 0.8f, 250.0f * 1.2f);
                break;

            case "PlayerSkill5":
                a_vector.Set(a_object.transform.position.x - a_collider.transform.position.x, 0f, a_object.transform.position.z - a_collider.transform.position.z);
                a_vector.Normalize();
                a_vector *= 10.0f;
                a_stun = 0.0f;
                a_damage = Random.Range(300.0f * 0.8f, 300.0f * 1.2f);
                break;

            default:
                a_vector.Set(0f, 0f, 0f);
                a_stun = 0.0f;
                a_damage = 0.0f;
                break;
        }
    }

    public void MainPlayerColliderSet(float a_impact, float a_impact_damage, float a_skill1_damage) {
        m_main_player_impact = a_impact;
        m_main_player_impact_damage = a_impact_damage;
        m_main_player_skill1_damage = a_skill1_damage;
    }
}

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

    [説明]

    [バージョン]
    2021-01-28 吹き飛ばし機能の実装
    2021-02-03 体力システムの追加とそれに伴うコライダーの分岐
    2021-02-12 操作キャラクターのスキル1を追加
    2021-03-03 スタン動作の追加
    2021-07-15  座標指定攻撃の追加
    2021-07-30  直線指定攻撃の追加
    2021-08-13  近接攻撃用の臨時データ追加
    2021-12-09  気絶上限値の動作確認用に気絶値を追加

    [タスク]
    タスク1 戦闘開始時にMainPlayerColliderSetでステータスを反映させる
    
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LockonBullet : MonoBehaviour{
    [SerializeField]
    private ParticleSystem[] m_bullet_effect;

    private GameObject m_target_position;

    private BoxCollider m_collider;

    private Vector3 m_collider_size;

    private string m_delete_tag;

    private float m_time_count = 0.0f;
    private float m_bullet_speed = 1.0f;

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

    public void Initialize(string a_target_tag, string a_bullet_tag) {
        m_collider = this.gameObject.GetComponent<BoxCollider>();
        m_delete_tag = a_target_tag;
        this.tag = a_bullet_tag;
    }

    private void FixedUpdate() {
        switch (e_state) {
            case STATE.WAIT:
            break;

            case STATE.MOVE:
                if (m_target_position.activeSelf == false) {
                    m_bullet_effect[0].Stop(); m_bullet_effect[1].Play();
                    e_state = STATE.DIE;
                    break;
                }

                Vector3 l_target_position = m_target_position.transform.position;
                l_target_position.y += 0.4f;
                this.transform.position = Vector3.MoveTowards(this.transform.position, l_target_position, Time.deltaTime * m_bullet_speed);
            break;

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

                if (m_time_count > 1.0f) {
                    m_time_count = 0.0f;
                    e_state = STATE.WAIT;
                    m_collider.enabled = false;
                    this.gameObject.SetActive(false);
                }
            break;
        }
    }

    public void BulletSetup(GameObject a_target, Transform a_position, float a_bullet_speed, Vector3 a_collider_area) {
        this.transform.position = a_position.position;
        this.gameObject.SetActive(true);
        m_target_position = a_target;
        m_bullet_speed = a_bullet_speed;
        m_collider_size = a_collider_area;
        m_collider.size = new Vector3(0.1f, 0.1f, 0.1f);
        m_bullet_effect[0].Play();
        e_state = STATE.MOVE;
        m_collider.enabled = true;
    }

    private void OnTriggerEnter(Collider a_collider) {
        if (e_state == STATE.DIE) return;

        if(a_collider.gameObject.tag == m_delete_tag) {
            m_collider.size = m_collider_size;
            m_bullet_effect[0].Stop(); m_bullet_effect[1].Play();
            e_state = STATE.DIE;
        }
    }
}

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

    [説明]

    [バージョン]
    2021-02-12 引数ターゲットを追尾し着弾時や休息時に爆発し自身を終了させる
    2021-12-09 関数で爆発範囲の変更追加

    [タスク]
    タスク1(済) 爆発範囲によってコライダーのサイズを可変させる
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TowerLockon : MonoBehaviour{
    [SerializeField]
    private ParticleSystem m_muzzle_flash;

    [SerializeField]
    private GameObject m_attack_object;

    [SerializeField]
    private Transform m_this_transform;

    private BoxCollider m_collider;

    private List<GameObject> m_object_list = new List<GameObject>();
    private List<Transform> m_position_list = new List<Transform>();
    private List<GameObject> m_bullet_list = new List<GameObject>();
    private List<LockonBullet> m_bullet_script = new List<LockonBullet>();

    private string m_target_tag = null;
    private string m_bullet_tag = null;

    private float m_lockon_speed = 0.05f;
    private float m_blazing_speed = 2.0f;
    private float m_time_count = 0.0f;

    private int m_array_set = 0;

    private bool m_lockon_state = false;

    private void Start(){
        m_collider = this.GetComponent<BoxCollider>();
        LockonStatusSet("Enemy", "PlayerSkill1", 20.0f, 3.0f); // タスク1
    }

    private void FixedUpdate(){
        if(m_lockon_state){
            m_this_transform.rotation = Quaternion.Slerp(m_this_transform.rotation, Quaternion.LookRotation(m_position_list[m_array_set].position - m_this_transform.position), m_lockon_speed);

            if(Vector3.Dot((m_this_transform.position - m_position_list[m_array_set].position).normalized, m_this_transform.forward.normalized) < -0.8f) {
                m_time_count += Time.deltaTime;

                if(m_time_count > m_blazing_speed) {
                    m_time_count = 0.0f;

                    int l_array = BulletArrayGet();
                    m_bullet_script[l_array].Initialize(m_target_tag, m_bullet_tag);
                    m_bullet_script[l_array].BulletSetup(m_object_list[m_array_set], m_muzzle_flash.transform, 3.0f, new Vector3(6.0f, 10.0f, 6.0f));

                    m_muzzle_flash.Play();
                }
            }

            if ((m_collider.ClosestPoint(m_position_list[m_array_set].position) != m_position_list[m_array_set].position) || (m_object_list[m_array_set].activeSelf == false)) {
                m_object_list.Clear();
                m_position_list.Clear();
                m_array_set = 0;
                m_lockon_state = false;
                m_collider.enabled = false;
                m_collider.enabled = true;
            }
        }else{
            if(m_object_list.Count > 0){
                float l_distance_check = 999.0f;

                for(int l_loop = 0; l_loop < m_object_list.Count; l_loop += 1){
                    if((m_this_transform.position - m_position_list[l_loop].position).sqrMagnitude < l_distance_check){
                        m_array_set = l_loop;
                        l_distance_check = (m_this_transform.position - m_position_list[l_loop].position).sqrMagnitude;
                    }
                }

                m_lockon_state = true;
            }
        }
    }

    private int BulletArrayGet() {
        int l_array = -1;

        for (int l_loop = 0; l_loop < m_bullet_list.Count; l_loop += 1) {
            if (m_bullet_list[l_loop].activeSelf == false) {
                l_array = l_loop;
                break;
            }
        }

        if (l_array == -1) {
            m_bullet_list.Add(Instantiate(m_attack_object, this.transform));
            m_bullet_script.Add(m_bullet_list[m_bullet_list.Count - 1].GetComponent<LockonBullet>());
            l_array = m_bullet_list.Count - 1;
        }

        return l_array;
    }

    private void OnTriggerEnter(Collider a_collider){
        if(m_lockon_state) return;

        if(a_collider.gameObject.tag == m_target_tag){
            m_object_list.Add(a_collider.gameObject);
            m_position_list.Add(a_collider.gameObject.GetComponent<Transform>());
        }
    }

    public void LockonStatusSet(string a_target_tag, string a_bullet_tag, float a_area, float a_blazing){
        m_target_tag = a_target_tag;
        m_bullet_tag = a_bullet_tag;
        m_collider.size = new Vector3(a_area, 10.0f, a_area);
        m_blazing_speed = a_blazing;
    }
}

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

    [説明]

    [バージョン]
    2021-01-18 ロックオン機能とロックオンが外れた場合に一番近いターゲットに切り替える機能
    2021-02-12 弾のオブジェクトプーリング及びターゲットまでの角度を参照して発射
    2021-12-09 気絶上限値の動作確認用に連射速度を変更

    [タスク]
    タスク1 キャラクターのタワー設置時に別スクリプトからセットする
*/

気絶の上限値

if ((m_stun_set != 0.0f) && (e_state != STATE.IMPACT)) {
    m_stun_limit_count += 1;

    if(m_stun_limit_count > m_stun_limit) {
        m_stun_limit_count = m_stun_limit;
        return;
    }

    m_stun_use = m_stun_set;
    m_navmesh_agent.enabled = true;
    m_rigidbody.isKinematic = true;
    m_navmesh_agent.ResetPath();
    m_stun_effect.Play();
    m_time_count = 0.0f;
    e_state = STATE.STUN;
    return;
}

気絶を重ねた場合に敵が停止したままで何もできなくなってしまうため上限を設定出来るようにしました。

スタン効果のある攻撃を受けた際に変数をインクリメントして上限に達していた場合は気絶動作をスルーしているだけになります。

上書きでもカウントされるため気絶効果が大量に飛び交う状況ではすぐに上限に達してしまいます。

【AssultEnemy.cs】以外のソースコード変更は気絶上限の動作確認用に攻撃に気絶効果を付けたり攻撃範囲を調整しているだけです。


今回使用したアセット


動画


記事のリンク

コメントを残す

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