【自作ゲーム開発29】伸びる攻撃判定を追加【Unity】


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

はじめに

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

今回は伸びる攻撃判定を実装しました。

使用用途は【自作ゲーム開発17】で作成した向きを指定してエフェクトを発生させるシステムに攻撃判定を持たせます。

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

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

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

public class PlayerController : MonoBehaviour{
    static class Number {
        public const int Left = 0;
        public const int Center = 1;
        public const int Right = 2;
    }

    /// <summary>
    /// 0-Hero 1-Mage 2-Dragoon 3-Knight 4-Bishop 5-Gunner
    /// </summary>
    [SerializeField]
    private GameObject[] m_character_list = new GameObject[6];

    private Rigidbody[] m_character_body = new Rigidbody[6];
    private NavMeshAgent[] m_character_navi = new NavMeshAgent[6];
    private Animator[] m_character_animation = new Animator[6];

    [SerializeField]
    private CharacterManager m_character_script;

    [SerializeField]
    private CinemachineVirtualCamera m_character_camera;

    private GameObject m_range_object;
    private RangeSystem m_range_system;

    private GameObject m_direction_object;
    private DirectionSystem m_direction_system;

    private Vector3 m_vector3;

    private Quaternion m_rotation = Quaternion.identity;

    private const float m_turn_speed = 20.0f;

    private float m_move_speed = 0.07f;
    private float m_horizontal = 0.0f;
    private float m_vertical = 0.0f;

    private int m_character_number = 5;
    private int[] m_character_position = new int[6];

    private bool m_range_mode = false;
    private bool m_range_cancel = false;

    private bool m_direction_mode = false;
    private bool m_direction_cancel = false;

    enum STATE{
        MOVE,
        RANGE,
        DIRECTION
    }; STATE e_state;

    public void CharacterNumberInput(int[] a_number) {
        Array.Copy(a_number, m_character_position, m_character_position.Length);
    }

    private void CharacterSetting() {
        m_character_number = m_character_position[0];
        m_character_list[m_character_position[0]].transform.position = GameObject.Find("StartPositionP").transform.position;
        m_character_list[m_character_position[1]].transform.position = GameObject.Find("StartPositionL").transform.position;
        m_character_list[m_character_position[2]].transform.position = GameObject.Find("StartPositionC").transform.position;
        m_character_list[m_character_position[3]].transform.position = GameObject.Find("StartPositionR").transform.position;
        m_character_list[m_character_position[4]].transform.position = GameObject.Find("StartPositionA1").transform.position;
        m_character_list[m_character_position[5]].transform.position = GameObject.Find("StartPositionA2").transform.position;

        m_character_camera.Follow = m_character_list[m_character_number].transform;
        m_character_navi[m_character_number].enabled = false;
        m_character_body[m_character_number].useGravity = true;
        m_character_body[m_character_number].isKinematic = false;

        m_character_script.Initialize(m_character_position);
    }

    private void Start(){
        for(int l_loop = 0; l_loop < m_character_list.Length; l_loop += 1) {
            m_character_body[l_loop] = m_character_list[l_loop].GetComponent<Rigidbody>();
            m_character_navi[l_loop] = m_character_list[l_loop].GetComponent<NavMeshAgent>();
            m_character_animation[l_loop] = m_character_list[l_loop].GetComponent<Animator>();
        }

        CharacterSetting();

        m_direction_object = GameObject.Find("DirectionObject");
        m_direction_system = m_direction_object.GetComponent<DirectionSystem>();
        m_direction_system.DirectionSetting(false, this.transform);

        m_range_object = GameObject.Find("RangeObject");
        m_range_system = m_range_object.GetComponent<RangeSystem>();
        m_range_system.RangeSetting(false, this.transform.position, 0.0f);      
    }

    private void Update(){
        m_horizontal = Input.GetAxis("Horizontal"); // タスク2
        m_vertical = Input.GetAxis("Vertical"); // タスク2

        if ((Input.GetKeyDown("r")) && (!m_range_cancel) && (!m_direction_mode)) {
            m_range_mode = !m_range_mode;

            if (m_range_mode) {
                m_range_object.SetActive(m_range_mode);
                m_range_system.RangeSetting(m_range_mode, m_character_list[m_character_number].transform.position, 10.0f); // タスク3
                e_state = STATE.RANGE;
                m_character_animation[m_character_number].SetBool("animation_move", false);
            } else {
                m_range_cancel = true;    
            }
        }

        if ((Input.GetKeyDown("t")) && (m_range_mode)) m_range_system.TowerCreate(); // タスク4
        if ((Input.GetKeyDown("y")) && (m_range_mode)) m_range_system.FireCreate(); // タスク8

        if ((Input.GetKeyDown("f")) && (!m_direction_cancel) && (!m_range_mode)) {
            m_direction_mode = !m_direction_mode;

            if (m_direction_mode) {
                m_direction_object.SetActive(m_direction_mode);
                m_direction_system.DirectionSetting(m_direction_mode, m_character_list[m_character_number].transform);
                e_state = STATE.DIRECTION;
                m_character_animation[m_character_number].SetBool("animation_move", false);
            } else {
                m_direction_cancel = true;
            }
        }

        if ((Input.GetKeyDown("g")) && (m_direction_mode)) m_direction_system.AttackStart(); // タスク5

        if (Input.GetKeyDown("1")) m_character_script.AttackerCommand(1, Number.Left); // タスク6
        if (Input.GetKeyDown("2")) m_character_script.AttackerCommand(1, Number.Center); // タスク6
        if (Input.GetKeyDown("3")) m_character_script.AttackerCommand(1, Number.Right); // タスク6

        if (Input.GetKeyDown("8")) m_character_script.AttackerCommand(2, Number.Left); // タスク6
        if (Input.GetKeyDown("9")) m_character_script.AttackerCommand(2, Number.Center); // タスク6
        if (Input.GetKeyDown("0")) m_character_script.AttackerCommand(2, Number.Right); // タスク6
    }

    private void FixedUpdate(){
        switch(e_state){
            case STATE.MOVE:
                m_vector3.Set(m_horizontal, 0f, m_vertical);
                m_vector3.Normalize();

                bool l_horizontal_input = !Mathf.Approximately (m_horizontal, 0f);
                bool l_vertical_input = !Mathf.Approximately (m_vertical, 0f);
                bool l_move = l_horizontal_input || l_vertical_input;
                m_character_animation[m_character_number].SetBool ("animation_move", l_move);

                Vector3 l_rotation = Vector3.RotateTowards (m_character_list[m_character_number].transform.forward, m_vector3, m_turn_speed * Time.deltaTime, 0f);
                m_rotation = Quaternion.LookRotation (l_rotation);

                m_character_body[m_character_number].MovePosition (m_character_body[m_character_number].position + m_vector3 * m_move_speed);
                m_character_body[m_character_number].MoveRotation (m_rotation); 
            break;

            case STATE.RANGE:
                if ((m_range_cancel) && (m_range_system.RangeSetting(false, m_character_list[m_character_number].transform.position, 0.0f))) {
                    m_range_cancel = false;
                    e_state = STATE.MOVE;
                }
            break;

            case STATE.DIRECTION:
                m_character_list[m_character_position[0]].transform.rotation = m_direction_object.transform.rotation;

                if ((m_direction_cancel) && (m_direction_system.DirectionSetting(false, m_character_list[m_character_number].transform))) {
                    m_direction_cancel = false;
                    e_state = STATE.MOVE;
                }

                break;
        }

        m_horizontal = m_vertical = 0.0f;
    }

    public void PlayerSpeedSet(float a_speed){
        m_move_speed = a_speed; // タスク1
    }
}

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

    [説明]

    [バージョン]
    2020-12-30 キーボードによるプレイヤーキャラの移動実装(回転無し)
    2021-01-09 プレイヤーキャラのモデルとアニメーション反映及び回転動作
    2021-04-23 座標指定モードへの切り替えとタワーの設置
    2021-04-30 攻撃向き指定モードへの切り替えと臨時エフェクトの発生
    2021-05-19 遊撃隊へのライン変更指示追加
    2021-05-28 全ての指揮官キャラクターを操作出来るように変更
    2021-06-04 メニュー画面からのキャラクター配置を戦闘画面へ反映させる
    2021-07-01 キャラクターの初期化タイミングを位置をセットしてからに変更
    2021-07-15  座標指定攻撃の追加
    2021-07-30  直線指定攻撃時の回転しない不具合修正

    [タスク]
    タスク1 上限値の制限が必要
    タスク2 コントローラーによる動作も追加する
    タスク3 射程を制限する臨時半径
    タスク4 タワーの種類を渡す必要がある
    タスク5 攻撃の種類やステータスを渡すようにする
    タスク6 対応ボタンは臨時
    (済)タスク7 メニューシーンから渡されたデータに変更する
    タスク8 範囲攻撃の種類を渡す必要がある
*/
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 = 0.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;

            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  直線指定攻撃の追加

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

public class DirectionSystem : MonoBehaviour {
    [SerializeField]
    private ParticleSystem[] m_effect = new ParticleSystem[3];

    private GameObject m_attack_collider;
    private GameObject m_attack_collider_tag;

    private Vector3 m_vector3;
    private Rigidbody m_rigidbody;
    private Quaternion m_rotation = Quaternion.identity;

    private const float m_turn_speed = 20.0f;

    private float m_horizontal = 0.0f;
    private float m_vertical = 0.0f;

    private bool m_direction_mode = false;
    private bool m_execution_play = false;
    private bool m_execution_collider = false;

    private float[] m_attack_data = new float[5]; // 0 総時間 1 長さ 2 長さ到達時間 3 分割回数

    private void Start() {
        m_rigidbody = GetComponent<Rigidbody>();
        m_attack_collider = GameObject.Find("DirectionColliderSize");
        m_attack_collider_tag = GameObject.Find("DirectionCollider");
        m_attack_collider_tag.SetActive(false);
    }

    private void Update() {
        m_horizontal = Input.GetAxis("Horizontal");
        m_vertical = Input.GetAxis("Vertical");
    }

    private void FixedUpdate() {
        if ((m_direction_mode) && (!m_execution_play)) {
            m_vector3.Set(m_horizontal, 0f, m_vertical);
            m_vector3.Normalize();

            Vector3 l_rotation = Vector3.RotateTowards(transform.forward, m_vector3, m_turn_speed * Time.deltaTime, 0f);
            m_rotation = Quaternion.LookRotation(l_rotation);

            m_rigidbody.MoveRotation(m_rotation);

            m_horizontal = m_vertical = 0.0f;
        }
    }

    public bool DirectionSetting(bool a_state, Transform a_center) {
        if ((m_execution_play) || (m_execution_collider)) return false;

        m_direction_mode = a_state;

        if (a_state) {
            this.transform.position = a_center.position;
            this.transform.rotation = a_center.rotation;
        } else {
            this.gameObject.SetActive(false);
        }

        return true;
    }

    public void AttackStart() { // タスク1
        if (!m_execution_play) {
            m_execution_play = true;
            m_execution_collider = true;

            m_attack_collider_tag.SetActive(true);
            m_attack_collider_tag.tag = "PlayerSkill4";
            m_attack_data[0] = 2.5f;
            m_attack_data[1] = 8.0f;
            m_attack_data[2] = 1.0f;
            m_attack_data[3] = 4.0f;

            StartCoroutine("AttackPlay");
            StartCoroutine("AttackCollider");
        }
    }

    IEnumerator AttackPlay() { // タスク2
        m_effect[0].Stop();
        m_effect[1].Play();
        m_effect[2].Play();
        yield return new WaitForSeconds(5.0f);
        m_effect[0].Play();
        m_effect[1].Stop();
        m_effect[2].Stop();
        m_execution_play = false;
    }

    IEnumerator AttackCollider() { // タスク2
        for(int l_loop = 0; l_loop < m_attack_data[3]; l_loop += 1) {
            m_attack_collider.transform.localScale = new Vector3(1.0f, m_attack_data[1] / m_attack_data[3] * (l_loop + 1), 1.0f);
            yield return new WaitForSeconds(m_attack_data[2] / m_attack_data[3]);
        }

        yield return new WaitForSeconds(m_attack_data[0] - m_attack_data[2]);
        m_attack_collider_tag.SetActive(false);

        m_execution_collider = false;
    }
}

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

    [説明]

    [バージョン]
    2021-04-30 回転動作と臨時エフェクト
    2021-07-30  直線指定攻撃の追加

    [タスク]
    タスク1 引数の番号で攻撃を選択出来るようにする
    タスク2 攻撃はプレファブを生成するようにする
*/

伸びる攻撃判定について

タワーによる攻撃、座標指定による攻撃に続いて新しい攻撃方法を実装しました。

仕様としては当たり判定が自身の前方小範囲から筒状に伸びていく攻撃になっており、伸びる速度や距離等を設定出来るようになっているので最終的には様々な技を追加しやすくしています。


IEnumerator AttackCollider() { // タスク2
    for(int l_loop = 0; l_loop < m_attack_data[3]; l_loop += 1) {
        m_attack_collider.transform.localScale = new Vector3(1.0f, m_attack_data[1] / m_attack_data[3] * (l_loop + 1), 1.0f);
        yield return new WaitForSeconds(m_attack_data[2] / m_attack_data[3]);
    }

    yield return new WaitForSeconds(m_attack_data[0] - m_attack_data[2]);
    m_attack_collider_tag.SetActive(false);
}

m_attack_data】には当たり判定の時間や攻撃判定の伸びるまでの時間や距離等が入っており、それを使用して当たり判定発生から当たり判定が消えるまでをコルーチンで制御しています。

  • [0] 当たり判定が出ている時間
  • [1] 伸びる最大距離
  • [2] 最大距離までにかかる時間
  • [3] 最大距離までに広がる回数

吹き飛ばしやスタン有無及びダメージ等はタグで分けるようにしています。


動画


記事のリンク

コメントを残す

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