Unityに関連する記事です

デバッグ機能の必要性


 デバッグとは、ソフトウェア開発の中で発生したバグを見つけ、その原因を探り、修正するためのプロセスです。
このプロセスは、ソフトウェアの品質を高めるために非常に重要です。

 Unityを使用したゲーム開発では、デバッグ機能の使用は特に重要です。
3D空間や物理エンジンを使った動作、AIの動きなど、複雑な動作をプログラムする場合、視覚的に挙動を確認することで、バグの原因究明が劇的にスムーズになります。


制御の必要性


 Unityはゲーム制作に必要なデバッグのための機能を多数用意しています。

 その一つにOnDrawGizmosというメソッドがあります。
このメソッドを使うことで、開発中のゲームシーンにさまざまな形状やラインを描画し、物体の位置や移動範囲、衝突範囲などを視覚的に確認することができます。
しかし、このメソッドはエディターでゲームを実行していない間でも Sceneビューにおいて常に描画を行ってしまうため、必要な時だけ描画を行うように制御することが求められます。



 以下に、OnDrawGizmosメソッドの描画を制御するサンプルコードを示します。
このスクリプトでは、SerializeField属性を使ってインスペクターから直接制御できるようにし、ギズモの描画をオンオフすることができます。

using UnityEngine;

public class DebugExample : MonoBehaviour
{
    [SerializeField] private bool isDebugMode = false;

    void OnDrawGizmos()
    {
        if (isDebugMode)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawSphere(transform.position, 1.0f);
        }
    }
}


 このスクリプトを適用したオブジェクトは、インスペクターからisDebugModeを制御することでギズモの描画を切り替えることができます。
このようなスイッチの役割を持つ変数を用意しておくことで、ソースコード自体を修正しなくても、インストラクターからギズモの描画をオン/オフ切り替えできます。

 OnDrawGizmos メソッドは、ゲームを実行していなくても、エディターが起動している間は常に Sceneビューで動き続けます。
そのため、エディターへの負荷がかかっている状態が続きます

 そこで、今回のような制御方法を導入することで、必要になったときだけギズモの描画を行い、不要な時には描画を行わないようにすることで
エディターへの負荷を軽減することが出来ます
 

<サンプルコード>


 上記の処理を利用した、実際にゲームおけるサンプルコードも提示しておきます。

 これは2Dゲームにおいて、コライダーの範囲内にいる対象から、最も近い敵のゲームオブジェクトを探して弾を発射する機能です。
1つのクラスにしていますが、適宜分割して記述してもよいでしょう。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

// Physics2D.OverlapCircleNonAlloc
// https://docs.unity3d.com/ja/current/ScriptReference/Physics2D.OverlapCircleNonAlloc.html

// Physics2D.OverlapCircleAll
// https://docs.unity3d.com/ja/current/ScriptReference/Physics2D.OverlapCircleAll.html

public class BulletGenerator_AutoAim : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;
    
    [SerializeField] private int damage;
    [SerializeField] private float speed = 1000.0f;
    [SerializeField] private float interval = 2.0f;
      //  =>  BulletDataSO を作成したら、そこから各データをもらって使う

    [SerializeField] private LayerMask enemyLayer;  // LayerMask 設定をインスペクターで行う
    [SerializeField] private float radius = 2.0f;
    
    [SerializeField] private bool isDrawGizmoOn;    // デバッグ用の機能のオンオフ切り替えを自分で作る
    
    private bool isShooting;    // 弾の生成制御
    
    
    void Start()
    {
        // TODO BulletData 作成後は、damage, speed, interval の値を BulletData の内容に更新する

    }
    
    void Update()
    {
        // 弾の連射防止
        if (isShooting) {
            return;
        }
        
        // 攻撃用のボタンを押したら
        if (Input.GetButtonDown("Fire1") || Input.GetKeyDown(KeyCode.Return)) { // Fire1 = Left Ctrl

            // 攻撃範囲内に敵がいるか判定
            DetectEnemiesInRange();
        }
    }
    
    /// <summary>
    /// 攻撃範囲内に敵がいるか判定し、存在する場合にはプレイヤーに最も近い敵を特定
    /// </summary>
    private void DetectEnemiesInRange()
    {
    // 範囲内の全ての敵のコライダーを取得


// OverlapCircleNonAlloc の場合(ただし、将来的に非推奨)


        // 範囲内の全ての敵のコライダーを取得するための配列を用意
        //Collider2D[] hitColliders = new Collider2D[10];  // 10は仮の数値です。実際には適切な数値を設定してください。
        //int numColliders = Physics2D.OverlapCircleNonAlloc(transform.position, radius, hitColliders, enemyLayer);
        
        // 攻撃範囲内に敵が見つからない場合、処理しない
        // if (numColliders == 0) {
        //     return;
        // }

        
// OverlapCircleAll の場合


    // 範囲内の全ての敵のコライダーを取得して配列に格納
        Collider2D[] hitColliders = Physics2D.OverlapCircleAll(transform.position, radius, enemyLayer);

        // 攻撃範囲内に敵が見つからない場合、処理しない
        if (hitColliders.Length == 0) {
            Debug.Log("敵が見つかりません");
            return;
        }


// ここから共通処理


        Debug.Log("敵を発見 : " + hitColliders.Length + " 体");
        
        // 最も近い敵とその距離の二乗を保存する変数
        Collider2D nearestEnemy = null;
        float nearestDistanceSqr = float.MaxValue;

        // 配列内の各敵に対して距離の二乗を計算し、最も近い敵を見つける(OverlapCircleNonAlloc の場合は numColliders を使う)
        for (int i = 0; i < hitColliders.Length; i++)
        {
            // プレイヤーの位置と配列に入っている敵の位置を計算して、平方根にする
            float distanceSqr = (transform.position - hitColliders[i].transform.position).sqrMagnitude;
            //distanceSqr = (transform.position - hitColliders[i].transform.position).magnitude;
            //distanceSqr = Vector2.Distance(transform.position - hitColliders[i].transform.position);
            
            // 今回の距離と、現在の最も近い距離を比較
            if (distanceSqr < nearestDistanceSqr)
            {
                nearestDistanceSqr = distanceSqr;
                nearestEnemy = hitColliders[i];
            }
        }
        
        // 上記の共通処理を Linq にした場合(用意するローカル変数は減り、for 文もなくなるため、可読性は高くなるが、負荷があがる可能性がある)
        // Collider2D nearestEnemy = 
        //     hitColliders
        //         .OrderBy(hit => (transform.position - hit.transform.position).sqrMagnitude)
        //         .FirstOrDefault(); // hitColliders[0]
        //
        // if (nearestEnemy == null) {
        //     Debug.Log("敵が見つかりません");
        //     return;
        // }

        Debug.Log("最も近い敵が見つかりました : " + nearestEnemy.gameObject.name);

    // 弾を発射している状態にする => 連射防止
        isShooting = true;

    // プレイヤーから見た敵の方向を算出し、正規化(距離による遠近で、弾の速度が変わらないようにする)
        Vector2 direction = (nearestEnemy.transform.position - transform.position).normalized;

        // 最も近い敵のいる方向に対して弾を発射する
        StartCoroutine(GenerateBullet(direction));
        
        // TODO アニメ同期させる場合、direction の値を利用すればプレイヤーから見た敵の方向になります。

    }
    
    /// <summary>
    /// 弾を生成して、発射命令をする
    /// </summary>
    /// <param name="direction"></param>
    /// <returns></returns>
    private IEnumerator GenerateBullet(Vector2 direction) {

    // クラスで生成
        Bullet bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);

    // 発射命令
        bullet.Shoot(damage, speed, direction);

    // 待機時間
        yield return new WaitForSeconds(interval);

    // 再度、弾を発射できる状態に戻す
        isShooting = false;
    }
    
    /// <summary>
    /// 攻撃範囲の可視化(Sceneビュー)
    /// </summary>
    void OnDrawGizmos()
    {
        if (!isDrawGizmoOn) {
            return;
        }
        
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, radius);
    }
}

 OnDrawGizmos メソッドでは、上記のように早期の return 文にすることで処理の可読性を高めることが出来ます。

 条件式を作成する場合には、〜ならば、と考えるだけではなく、〜ではないなら、という別の視点も持つことが大切です。
そうすることで柔軟な処理を作成していくことが出来るようになります。

 また、Gizmos.DrawWireSphere メソッド内の引数の情報を Physics2D.OverlapCircleAll メソッドの引数と同じ変数にしておくことで、
Physics2D.OverlapCircleAll メソッドの内容に連動したギズモの描画を行うようにしています。


// 範囲内の全ての敵のコライダーを取得して配列に格納
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(transform.position, radius, enemyLayer);

Gizmos.DrawWireSphere(transform.position, radius);

 このような形で利用すると、想定している通りのギズモ描画の挙動になります。
 
 メソッドの引数を理解した上で、変数を上手く活用しましょう
 

その他のデバッグ機能


 Unityにはその他にも多くのデバッグ機能があります。

 例えば、Debug.Log()を使うことでコンソールに情報を出力することができます。
また、Debug.DrawLine()や Debug.DrawRay()を使うことで、ゲームシーン上に直線やレイを描画することも可能です。

 これらの機能を上手く組み合わせて利用することで、デバッグ作業がより効率的になります。



 例えば、オブジェクト間の距離を測るデバッグ機能を作りたいときには、Debug.DrawLine()が役立ちます。

 以下にそのサンプルコードを示します。

using UnityEngine;

public class DebugLineExample : MonoBehaviour
{
    [SerializeField] private GameObject targetObject;
    [SerializeField] private bool isDebugMode = false;

    void Update()
    {
        if (isDebugMode && targetObject != null)
        {
            Debug.DrawLine(transform.position, targetObject.transform.position, Color.blue);
        }
    }
}

 このスクリプトを適用したオブジェクトは、インスペクターからisDebugModeとtargetObjectを設定することで、
対象のオブジェクトと自身との間に直線を引くことができます。これにより、オブジェクト間の距離や位置関係を視覚的に確認することが可能になります。

 これらのデバッグ機能を適切に使い分けることで、Unityでの開発効率とゲームの品質向上に大きく貢献することができます。


まとめ


 デバッグはゲーム開発の大切な一部です。
Unityには便利なデバッグ機能が多数用意されており、それらを上手く利用することで開発の効率化を図ることができます。

 ただし、無闇に使用するとパフォーマンスに影響を及ぼすことがあるので、適切な制御や使い分けが求められます

 上記のサンプルコードを参考に、自分の開発スタイルに合ったデバッグ機能を設計してみてください。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます