【自作ゲーム開発17】向きを指定した攻撃を追加【Unity】


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

はじめに

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

今回は向き指定をして攻撃するためのシステムを作成しました。

向き指定時に【G】ボタンで攻撃エフェクトを臨時で発生させるようにしていますが、攻撃オブジェクトを生成するシステムに後々変更します。

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

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

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

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

    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 void Start() {
        m_rigidbody = GetComponent<Rigidbody>();
    }

    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) 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;
            StartCoroutine("AttackPlay");
        }
    }

    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;
    }
}

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

    [説明]

    [バージョン]
    2021-04-30 回転動作と臨時エフェクト

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

public class PlayerController : MonoBehaviour{
    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 Rigidbody m_rigidbody;
    private Animator m_animator;

    private Quaternion m_rotation = Quaternion.identity;

    private const float m_turn_speed = 20.0f;

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

    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;

    private void Start(){
        m_rigidbody = GetComponent<Rigidbody>();
        m_animator = GetComponent<Animator>();

        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, this.transform.position, 10.0f); // タスク3
                e_state = STATE.RANGE;
                m_animator.SetBool("animation_move", false);
            } else {
                m_range_cancel = true;    
            }
        }

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

        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, this.transform);
                e_state = STATE.DIRECTION;
                m_animator.SetBool("animation_move", false);
            } else {
                m_direction_cancel = true;
            }
        }

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

    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_animator.SetBool ("animation_move", l_move);

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

                m_rigidbody.MovePosition (m_rigidbody.position + m_vector3 * m_move_speed);
                m_rigidbody.MoveRotation (m_rotation); 
            break;

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

            case STATE.DIRECTION:
                this.transform.rotation = m_direction_object.transform.rotation;

                if ((m_direction_cancel) && (m_direction_system.DirectionSetting(false, this.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 攻撃向き指定モードへの切り替えと臨時エフェクトの発生

    [タスク]
    タスク1 上限値の制限が必要
    タスク2 コントローラーによる動作も追加する
    タスク3 射程を制限する臨時半径
    タスク4 タワーの種類を渡す必要がある
    タスク5 攻撃の種類やステータスを渡すようにする
*/

向きを指定するオブジェクト

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, this.transform);
        e_state = STATE.DIRECTION;
        m_animator.SetBool("animation_move", false);
    } else {
        m_direction_cancel = true;
    }
}

今回の動作は【F】ボタンを押す事で回転のみおこなうオブジェクトに操作を渡すようにしています。

既に向きを指定するモードの場合は本体に操作を戻るまで待機する変数【m_direction_cancel】を【true】にしています。

前回作成した座標システムを使用している場合は無視されます。


case STATE.DIRECTION:
    this.transform.rotation = m_direction_object.transform.rotation;

    if ((m_direction_cancel) && (m_direction_system.DirectionSetting(false, this.transform))) {
        m_direction_cancel = false;
        e_state = STATE.MOVE;
    }

    break;

本体は操作を渡してるオブジェクトに向きを合わせるだけの状態【enum – DIRECTION】に移行しています。

向き指定を終了するための【m_direction_cancel】が【true】の場合は向き指定オブジェクトが待機状態になるまで待ち続けて本体に操作を戻すようにします。


public bool DirectionSetting(bool a_state, Transform a_center) {
    if (m_execution_play) 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;
}

向き指定オブジェクトが何かアクションをおこしている場合【m_execution_play】を【true】に設定するため本体から操作を戻す命令が来た場合でも何かしらのアクションが終了しないと操作が戻らないようにしています。

攻撃エフェクトが発生したりはしていますが、ボタンを押しても操作が戻らないのはストレスになるかもしれないため今後の操作感等で変更していきます。


今回使用するアセット


動画


記事のリンク

コメントを残す

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