【自作ゲーム開発14】戦闘レーンを3レーンへ増加【Unity】


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

はじめに

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

今回は拠点や敵味方キャラクター共に増やして戦闘するレーンを1つから3つに増やします。

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

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class HeroPlayer : MonoBehaviour {
    [SerializeField]
    private GameObject m_defense_player;

    private List<GameObject> m_defense_prefab_list = new List<GameObject>();
    private List<DefensePlayer> m_defense_prefab_script = new List<DefensePlayer>();

    private Transform[] m_serch_object = new Transform[5];

    private NavMeshAgent m_navmesh_agent;
    private Animator m_animator;

    private int m_base_state = 2;

    enum STATE {
        SET,
        SERCH,
    }; STATE e_state;

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

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            m_defense_prefab_script[l_loop].BaseChange(m_base_state);
        }

        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
        m_animator.SetBool("animation_move", true);
        e_state = STATE.SET;
    }

    public void Initialize(string a_line) {
        m_navmesh_agent = GetComponent<NavMeshAgent>();
        m_animator = GetComponent<Animator>();

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            var l_instance = Instantiate(m_defense_player, this.transform);
            m_defense_prefab_list.Add(l_instance);
            m_defense_prefab_script.Add(l_instance.GetComponent<DefensePlayer>());
            m_defense_prefab_script[l_loop].Initialize(a_line, l_loop);
        }

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
            m_serch_object[l_loop] = l_object.transform.Find("SetPositionL");
        }

        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
        m_animator.SetBool("animation_move", true);
    }

    private void FixedUpdate() {
        switch (e_state) {
            case STATE.SET:
                if ((this.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude < 1.0f) {
                    m_animator.SetBool("animation_move", false);
                    e_state = STATE.SERCH;
                }
                break;

            case STATE.SERCH:

                break;
        }
    }
}

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

    [説明]

    [バージョン]
    2021-03-25 3体の味方呼び出しと防衛拠点変更動作

    [タスク]
    
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class DragoonPlayer : MonoBehaviour {
    [SerializeField]
    private GameObject m_defense_player;

    private List<GameObject> m_defense_prefab_list = new List<GameObject>();
    private List<DefensePlayer> m_defense_prefab_script = new List<DefensePlayer>();

    private Transform[] m_serch_object = new Transform[5];

    private NavMeshAgent m_navmesh_agent;
    private Animator m_animator;

    private int m_base_state = 2;

    enum STATE {
        SET,
        SERCH,
    }; STATE e_state;

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

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            m_defense_prefab_script[l_loop].BaseChange(m_base_state);
        }

        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
        m_animator.SetBool("animation_move", true);
        e_state = STATE.SET;
    }

    public void Initialize(string a_line) {
        m_navmesh_agent = GetComponent<NavMeshAgent>();
        m_animator = GetComponent<Animator>();

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            var l_instance = Instantiate(m_defense_player, this.transform);
            m_defense_prefab_list.Add(l_instance);
            m_defense_prefab_script.Add(l_instance.GetComponent<DefensePlayer>());
            m_defense_prefab_script[l_loop].Initialize(a_line, l_loop);
        }

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
            m_serch_object[l_loop] = l_object.transform.Find("SetPositionL");
        }

        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
        m_animator.SetBool("animation_move", true);
    }

    private void FixedUpdate() {
        switch (e_state) {
            case STATE.SET:
                if ((this.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude < 1.0f) {
                    m_animator.SetBool("animation_move", false);
                    e_state = STATE.SERCH;
                }
                break;

            case STATE.SERCH:

                break;
        }
    }
}

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

    [説明]

    [バージョン]
    2021-03-25 3体の味方呼び出しと防衛拠点変更動作

    [タスク]
    
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CharacterManager : MonoBehaviour {
    [SerializeField]
    private GameObject m_mob_prefab;

    [SerializeField]
    private HeroPlayer m_hero_script;

    [SerializeField]
    private MagePlayer m_mage_script;

    [SerializeField]
    private DragoonPlayer m_dragoon_script;

    [SerializeField]
    private PopSystem[] m_pop_point = new PopSystem[3];

    private List<GameObject> m_mob_prefab_list = new List<GameObject>();
    private List<AssultEnemy> m_mob_prefab_script = new List<AssultEnemy>();

    private GameObject[] m_left_base = new GameObject[5];
    private GameObject[] m_center_base = new GameObject[5];
    private GameObject[] m_right_base = new GameObject[5];

    private float m_time_count = 0.0f;

    private int m_left_state = 2;
    private int m_center_state = 2;
    private int m_right_state = 2;

    private short m_mob_count = 0;

    private void Start() {
        m_mob_count = 30;

        string l_line = "Left";

        for (int l_loop = 0; l_loop < m_mob_count; l_loop += 1) {
            if (l_loop == m_mob_count / 3) l_line = "Center";
            if (l_loop == m_mob_count - (m_mob_count / 3)) l_line = "Right";
            var l_instance = Instantiate(m_mob_prefab);
            l_instance.SetActive(false);
            m_mob_prefab_list.Add(l_instance);
            m_mob_prefab_script.Add(l_instance.GetComponent<AssultEnemy>());
            m_mob_prefab_script[l_loop].Initialize(l_line);
        }

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            // タスク2
            m_left_base[l_loop] = GameObject.Find(string.Format("LeftBase{0}", l_loop));
            m_center_base[l_loop] = GameObject.Find(string.Format("CenterBase{0}", l_loop));
            m_right_base[l_loop] = GameObject.Find(string.Format("RightBase{0}", l_loop));
        }

        m_hero_script.Initialize("Left"); // タスク3
        m_mage_script.Initialize("Center"); // タスク3
        m_dragoon_script.Initialize("Right"); // タスク3
    }

    private void Update() {
        m_time_count += Time.deltaTime;

        if (m_time_count > 1.0f) {
            BaseCheck();
            ActiveObject();
        }
    }

    private void BaseCheck() {
        if (!m_left_base[m_left_state].activeSelf) {
            m_left_state -= 1;

            if (m_left_state < 0) m_left_state = 0;

            switch (m_left_state) {
                case 1:
                    m_pop_point[0].MoveObject(new Vector3(10.0f, 0.5f, 30.0f));
                    break;

                case 0:
                    m_pop_point[0].MoveObject(new Vector3(10.0f, 0.5f, 20.0f));
                    break;
            }

            for (int l_loop = 0; l_loop < m_mob_count / 3; l_loop += 1) {
                m_mob_prefab_script[l_loop].BaseChange(m_left_state);
            }

            m_hero_script.BaseChange(m_left_state); // タスク3
        }

        if (!m_center_base[m_center_state].activeSelf) {
            m_center_state -= 1;

            if (m_center_state < 0) m_center_state = 0;

            switch (m_center_state) {
                case 1:
                    m_pop_point[1].MoveObject(new Vector3(20.0f, 0.5f, 30.0f));
                    break;

                case 0:
                    m_pop_point[1].MoveObject(new Vector3(20.0f, 0.5f, 20.0f));
                    break;
            }

            for (int l_loop = m_mob_count / 3; l_loop < m_mob_count - (m_mob_count / 3); l_loop += 1) {
                m_mob_prefab_script[l_loop].BaseChange(m_center_state);
            }

            m_mage_script.BaseChange(m_center_state); // タスク3
        }

        if (!m_right_base[m_right_state].activeSelf) {
            m_right_state -= 1;

            if (m_right_state < 0) m_right_state = 0;

            switch (m_right_state) {
                case 1:
                    m_pop_point[2].MoveObject(new Vector3(30.0f, 0.5f, 30.0f));
                    break;

                case 0:
                    m_pop_point[2].MoveObject(new Vector3(30.0f, 0.5f, 20.0f));
                    break;
            }

            for (int l_loop = m_mob_count - (m_mob_count / 3); l_loop < m_mob_count; l_loop += 1) {
                m_mob_prefab_script[l_loop].BaseChange(m_right_state);
            }

            m_dragoon_script.BaseChange(m_right_state); // タスク3
        }
    }

    private void ActiveObject() {
        m_time_count = 0.0f;

        for (int l_loop = 0; l_loop < m_mob_count / 3; l_loop += 1) {
            if (m_mob_prefab_list[l_loop].activeSelf == false) {
                m_mob_prefab_list[l_loop].SetActive(true);
                m_mob_prefab_script[l_loop].MobState(true);
                break;
            }
        }

        for (int l_loop = m_mob_count / 3; l_loop < m_mob_count - (m_mob_count / 3); l_loop += 1) {
            if (m_mob_prefab_list[l_loop].activeSelf == false) {
                m_mob_prefab_list[l_loop].SetActive(true);
                m_mob_prefab_script[l_loop].MobState(true);
                break;
            }
        }

        for (int l_loop = m_mob_count - (m_mob_count / 3); l_loop < m_mob_count; l_loop += 1) {
            if (m_mob_prefab_list[l_loop].activeSelf == false) {
                m_mob_prefab_list[l_loop].SetActive(true);
                m_mob_prefab_script[l_loop].MobState(true);
                break;
            }
        }
    }
}

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

    [説明]

    [バージョン]
    2020-12-12 指定数生成及びプーリング呼び出し
    2021-03-12 管理拠点を配列化し拠点管理を行いモブに伝える
    2021-03-17 センターライン指揮官キャラの生成と名前変更(旧CenterMobManagerEnemy)
    2021-03-25 戦闘ラインを1から3ラインに変更

    [タスク]
    (済)タスク1 プレイヤーキャラのステータスを反映させる
    (済)タスク2 臨時防衛拠点数
    タスク3 メニュー画面から各ラインのキャラクターを受け取るようにする
*/
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;

    [SerializeField]
    private ParticleSystem m_smoke_effect;

    [SerializeField]
    private ParticleSystem m_bomb_effect;

    [SerializeField]
    private GameObject m_put_base;

    enum STATE {
        WAIT,
        SMOKE,
        BOMB
    }; STATE e_state;

    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;

        BaseState(l_value);
    }

    private void BaseState(float a_hitpoint) {
        switch (e_state) {
            case STATE.WAIT:
                if(a_hitpoint < 0) a_hitpoint = 0;

                if(a_hitpoint < m_hitpoint_bar.maxValue / 2) {
                    m_smoke_effect.Play();
                    e_state = STATE.SMOKE;
                }

                m_hitpoint_bar.value = a_hitpoint;
                    
                break;

            case STATE.SMOKE:
                if (a_hitpoint < 0) {
                    a_hitpoint = 0;
                    StartCoroutine("BaseBomb");
                    e_state = STATE.BOMB;
                }

                m_hitpoint_bar.value = a_hitpoint;

                break;

            case STATE.BOMB:
                break;
        }
    }

    IEnumerator BaseBomb() {
        m_bomb_effect.Play();
        m_smoke_effect.Stop();
        m_put_base.SetActive(false);
        yield return new WaitForSeconds(2.0f);
        this.gameObject.SetActive(false);
    }
}

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

    [説明]
    

    [バージョン]
    2021-02-19 関数呼び出しによる自身へのダメージ及び体力ゲージの可視化
    2021-03-12 HP低下による煙とHP0による爆発とオブジェクトの休息
    2021-03-25 煙エフェクト違和感解消のためコルーチンを2秒に

    [タスク]
  タスク1 スタート関数は使用せずに設置者から呼び出す    
  タスク2 オブジェクトの起動処理も必要
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class DefensePlayer : MonoBehaviour{
    private const float m_serch_speed = 1.0f; // 補足1

    [SerializeField]
    private SerchObject m_serch_system;

    [SerializeField]
    private BoxCollider m_attack_area;

    private NavMeshAgent m_navmesh_agent;
    private Animator m_animator;
    private GameObject m_target_object;
    private Transform[] m_serch_object = new Transform[5];

    private float m_time_count = 0.0f;

    private int m_attack_switch = 0;
    private int m_base_state = 2;

    private bool m_serch_state = false;
    private bool m_attack_state = false;

    enum STATE {
        SET,
        SERCH,
        MOVE,
        ATTACK
    }; STATE e_state;

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

        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
        m_attack_state = false;
        m_attack_area.enabled = false;
        m_animator.SetInteger("animation_attack", 0);
        m_animator.SetBool("animation_move", true);
        e_state = STATE.SET;
    }

    public void Initialize(string a_line, int a_number) {
        m_navmesh_agent = GetComponent<NavMeshAgent>();
        m_animator = GetComponent<Animator>();

        for (int l_loop = 0; l_loop < 3; l_loop += 1) {
            // タスク4
            GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
            m_serch_object[l_loop] = l_object.transform.Find(string.Format("SetPosition{0}", a_number));
        }

        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
        m_animator.SetBool("animation_move", true);
    }

    private void FixedUpdate() {
        switch (e_state) {
            case STATE.SET:
                if ((this.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude < 1.0f) {
                    m_animator.SetBool("animation_move", false);
                    e_state = STATE.SERCH;
                }
            break;

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

                if ((m_time_count > m_serch_speed) && (!m_serch_state)) {
                    m_navmesh_agent.ResetPath();
                    m_navmesh_agent.velocity = Vector3.zero;
                    m_animator.SetBool("animation_move", false);
                    m_serch_state = true;
                    m_serch_system.SerchStart(m_serch_object[m_base_state], "Enemy");
                }

                if ((m_time_count > m_serch_speed * 2.0f) && (m_serch_state)) {
                    m_serch_state = false;
                    m_time_count = 0.0f;
                    m_target_object = m_serch_system.SerchEnd(m_serch_object[m_base_state], 0); // タスク3

                    if (m_target_object != null) {
                        m_navmesh_agent.SetDestination(m_target_object.transform.position);
                        e_state = STATE.MOVE;
                    }
                }
            break;

            case STATE.MOVE:
                if (m_target_object.activeSelf == false) {
                    m_time_count = m_serch_speed * 2.0f;
                    m_navmesh_agent.ResetPath();
                    m_navmesh_agent.velocity = Vector3.zero;
                    m_animator.SetBool("animation_move", false);
                    m_serch_state = true;
                    m_serch_system.SerchStart(m_serch_object[m_base_state], "Enemy");
                    e_state = STATE.SERCH;
                } else if ((this.transform.position - m_target_object.transform.position).sqrMagnitude < 1.0f) {
                    if (((this.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude) < ((m_target_object.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude)) {
                        m_attack_switch = 2;
                        m_attack_area.tag = "Player"; // タスク5
                    } else {
                        m_attack_switch = 1;
                        m_attack_area.tag = "PlayerSkill2"; // タスク5
                    }

                    this.transform.LookAt(m_target_object.transform);
                    m_navmesh_agent.ResetPath();
                    m_navmesh_agent.velocity = Vector3.zero;
                    m_animator.SetInteger("animation_attack", m_attack_switch);
                    m_time_count = 0.0f;
                    e_state = STATE.ATTACK;
                } else {
                    m_navmesh_agent.SetDestination(m_target_object.transform.position);
                    m_animator.SetBool("animation_move", true);
                }
            break;

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

                if ((!m_attack_state) && m_time_count > 0.3f) {
                    m_time_count = 0.0f;
                    m_attack_state = true;
                    m_attack_area.enabled = true;
                }

                if ((m_attack_state) && m_time_count > 0.3f) {
                    m_attack_state = false;
                    m_attack_area.enabled = false;
                    m_animator.SetBool("animation_move", false);
                    m_animator.SetInteger("animation_attack", 0);

                    if (m_attack_switch == 1) {
                        m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
                        m_animator.SetBool("animation_move", true);
                    }

                    m_time_count = 0.0f;
                    e_state = STATE.SERCH;
                }
            break;
        }
    }
}

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

    [説明]
    補足1 数値を下げると行動の切り替えが早くなる(強くなる)

    [バージョン]
    2021-02-25 感知エリアから一番近いオブジェクトを追いかける
    2021-03-03 2種類の攻撃を追加しスタン時は拠点に下がる
    2021-03-12 防衛拠点を配列化(防衛拠点の移行はしない)
    2021-03-17 防衛拠点変更機能を追加
    2021-03-25 攻撃判定が残る不具合を修正

    [タスク]
    (済)タスク1 初期化は指揮官キャラが呼び出す
    (済)タスク2 臨時の動作(対象に向かって歩くだけ)
    タスク3 攻撃キャラは自身から近い対象にする分岐を入れる
    タスク4 臨時防衛拠点数
    タスク5 臨時タグ
*/

戦闘レーンを増やす

前回まではシーン上に【CenterBase】や【CenterPop】のみでしたが今回【Left】と【Right】を追加しました。

m_hero_script.Initialize("Left"); // タスク3
m_mage_script.Initialize("Center"); // タスク3
m_dragoon_script.Initialize("Right"); // タスク3

各キャラクターの初期化処理に防衛させるレーンの文字列を引数で渡しています。

どのキャラクターがどのレーンを守るかは戦闘開始前に入れ替え出来るようにする予定ですが現在は固定です。


今回3キャラクターの初期化をしていますが、前回実装した【Mage】と同じ内容【Hero】と【Dragoon】に反映させています。

for (int l_loop = 0; l_loop < 3; l_loop += 1) {
    var l_instance = Instantiate(m_defense_player, this.transform);
    m_defense_prefab_list.Add(l_instance);
    m_defense_prefab_script.Add(l_instance.GetComponent<DefensePlayer>());
    m_defense_prefab_script[l_loop].Initialize(a_line, l_loop);
}

for (int l_loop = 0; l_loop < 3; l_loop += 1) {
    GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
    m_serch_object[l_loop] = l_object.transform.Find("SetPositionL");
}

部下であるキャラクターを3体生成自身を含めて引数で指定されたレーンの拠点を全て設定するようにしています。

【a_line】に引数である【Left Center Right】が入っておりシーン上の拠点オブジェクトを全て取得するようにしています。


今回使用するアセット


動画


記事のリンク

コメントを残す

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