パラメーター調整の最適化

マジックナンバー(コード中に直接数値や文字を埋め込む)を改善して、保守性や拡張性を向上するようにしましょう

マジックナンバーをコード中から削除

ステップ1:初期値のみインスペクターで調整できるようにしたケース

インスペクタでGameDirectorクラスのSetParameterメソッドの数値パラメータを調整できるようにするためには、これらのパラメータをGameDirectorクラスで公開し、実行時にItemGeneratorクラスに適用する必要があります。具体的には、次のステップに従ってください。

  1. GameDirectorクラス内で調整可能なパラメータ用の変数を定義します。
  2. これらの変数をUpdateメソッド内で条件に基づいてItemGeneratorに適用します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class GameDirector : MonoBehaviour
{
    GameObject timerText;
    GameObject pointText;
    public float time = 30.0f; // 公開してインスペクタから調整可能に
    int point = 0;
    GameObject generator;

    // インスペクタから調整可能なパラメータ
    public float endGameSpan = 10000.0f;
    public float endGameSpeed = 0.0f;
    public int endGameRatio = 0;

    public void GetApple()
    {
        this.point += 100;
    }

    public void GetBomb()
    {
        this.point /= 2;
    }

    void Start()
    {
        this.timerText = GameObject.Find("Time");
        this.pointText = GameObject.Find("Point");
        this.generator = GameObject.Find("ItemGenerator");
    }

    void Update()
    {
        this.time -= Time.deltaTime;

        if (this.time < 0)
        {
            this.time = 0;
            // インスペクタから設定したパラメータを使用
            this.generator.GetComponent<ItemGenerator>().SetParameter(endGameSpan, endGameSpeed, endGameRatio);
        }
        else if (0 <= this.time && this.time < 4)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(0.3f, -0.06f, 0);
        }
        else if (4 <= this.time && this.time < 12)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(0.5f, -0.05f, 6);
        }
        else if (12 <= this.time && this.time < 23)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(0.8f, -0.04f, 4);
        }
        else if (23 <= this.time && this.time < 30)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(1.0f, -0.03f, 2);
        }

        this.timerText.GetComponent<TextMeshProUGUI>().text = this.time.ToString("F1");
        this.pointText.GetComponent<TextMeshProUGUI>().text = this.point.ToString() + " point";
    }
}

この変更により、GameDirectorクラスのendGameSpanendGameSpeedendGameRatioの値をUnityのインスペクタから調整できるようになります。Updateメソッド内でこれらの値をItemGeneratorSetParameterメソッドに適用することで、ゲームの実行中にこれらのパラメータが反映されます。

ステップ2:初期値以外の調整値をインスペクターでできるようにしたケース

GameDirectorクラスで異なるタイミングでItemGeneratorSetParameterメソッドを異なるパラメータで呼び出している部分もインスペクタから調整可能にするためには、各タイミングごとのパラメータを事前に定義しておく必要があります。これにより、インスペクタから各タイミングでのパラメータを直接調整できるようになります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class GameDirector : MonoBehaviour
{
    GameObject timerText;
    GameObject pointText;
    public float time = 30.0f;
    int point = 0;
    GameObject generator;

    // インスペクタから調整可能な各タイミングのパラメータ
    public float initialSpan = 1.0f;
    public float initialSpeed = -0.03f;
    public int initialRatio = 2;

    // ゲーム終了時のパラメータ
    public float endGameSpan = 10000.0f;
    public float endGameSpeed = 0.0f;
    public int endGameRatio = 0;

    // その他のタイミングでのパラメータ
    public float[] spans = new float[4];
    public float[] speeds = new float[4];
    public int[] ratios = new int[4];

    public void GetApple()
    {
        this.point += 100;
    }

    public void GetBomb()
    {
        this.point /= 2;
    }

    void Start()
    {
        this.timerText = GameObject.Find("Time");
        this.pointText = GameObject.Find("Point");
        this.generator = GameObject.Find("ItemGenerator");
    }

    void Update()
    {
        this.time -= Time.deltaTime;

        if (this.time < 0)
        {
            this.time = 0;
            this.generator.GetComponent<ItemGenerator>().SetParameter(endGameSpan, endGameSpeed, endGameRatio);
        }
        else if (0 <= this.time && this.time < 4)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[0], speeds[0], ratios[0]);
        }
        else if (4 <= this.time && this.time < 12)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[1], speeds[1], ratios[1]);
        }
        else if (12 <= this.time && this.time < 23)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[2], speeds[2], ratios[2]);
        }
        else if (23 <= this.time && this.time < 30)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[3], speeds[3], ratios[3]);
        }

        this.timerText.GetComponent<TextMeshProUGUI>().text = this.time.ToString("F1");
        this.pointText.GetComponent<TextMeshProUGUI>().text = this.point.ToString() + " point";
    }
}

インスペクターでの登録サンプル

注意

UnityEngineの不具合と思われますが、実行中、GameDirecterを選択していると、次のエラーが発生します
選択中にしないようにしましょう

Assertion failed on expression: 'IsInSyncWithParentSerializedObject()'
UnityEditor.RetainedMode:UpdateSchedulers ()

このコードでは、ゲームの異なるタイミングでItemGeneratorに適用されるパラメータのセットをインスペクタから調整可能にしています。spansspeedsratiosの各配列には、異なるゲームのフェーズで使用されるspan、speed、ratioの値を順に格納します。これにより、インスペクタからこれらの値を簡単に変更し、ゲームプレイの調整を行うことができます。

ステップ3:経過時間の調整値もインスペクターでできるようにしたケース

経過時間ごとの条件分岐で使われているマジックナンバーもインスペクタから調整できるようにするためには、これらの値をGameDirectorクラス内で公開し、Unityのインスペクタから設定できるようにします。以下は、そのためのコードの一例です

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class GameDirector : MonoBehaviour
{
    GameObject timerText;
    GameObject pointText;
    public float time = 30.0f;
    int point = 0;
    GameObject generator;

    // タイムフェーズの閾値
    public float phase1EndTime = 4f;
    public float phase2EndTime = 12f;
    public float phase3EndTime = 23f;
    public float phase4EndTime = 30f;

    // 各フェーズのパラメータ
    public float[] spans;
    public float[] speeds;
    public int[] ratios;

    // ゲーム終了時のパラメータ
    public float endGameSpan = 10000.0f;
    public float endGameSpeed = 0.0f;
    public int endGameRatio = 0;

    public void GetApple()
    {
        this.point += 100;
    }

    public void GetBomb()
    {
        this.point /= 2;
    }

    void Start()
    {
        this.timerText = GameObject.Find("Time");
        this.pointText = GameObject.Find("Point");
        this.generator = GameObject.Find("ItemGenerator");
    }

    void Update()
    {
        this.time -= Time.deltaTime;

        if (this.time < 0)
        {
            this.time = 0;
            this.generator.GetComponent<ItemGenerator>().SetParameter(endGameSpan, endGameSpeed, endGameRatio);
        }
        else if (0 <= this.time && this.time < phase1EndTime)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[0], speeds[0], ratios[0]);
        }
        else if (phase1EndTime <= this.time && this.time < phase2EndTime)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[1], speeds[1], ratios[1]);
        }
        else if (phase2EndTime <= this.time && this.time < phase3EndTime)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[2], speeds[2], ratios[2]);
        }
        else if (phase3EndTime <= this.time && this.time < phase4EndTime)
        {
            this.generator.GetComponent<ItemGenerator>().SetParameter(spans[3], speeds[3], ratios[3]);
        }

        this.timerText.GetComponent<TextMeshProUGUI>().text = this.time.ToString("F1");
        this.pointText.GetComponent<TextMeshProUGUI>().text = this.point.ToString() + " point";
    }
}

インスペクターでの登録サンプル

このコードでは、各タイミングでゲームがどのように振る舞うかを決定するために使用される閾値(phase1EndTimephase2EndTimephase3EndTimephase4EndTime)を公開しています。これらの値はUnityエディタのインスペクタから調整できるため、ゲームの振る舞いを簡単に調整できます。

また、spansspeedsratios配列を用いて、各フェーズでのItemGeneratorへのパラメータ設定もインスペクタから行えるようになっています。これにより、ゲームの難易度やプレイ感を柔軟に調整できるようになります。

おまけ(リファクタリング)

リファクタリングを行い、コードの可読性とメンテナンス性を向上させるために、経過時間に基づく条件分岐を改善し、マジックナンバーや重複コードの削減を目指します。具体的には、各フェーズの設定をクラスにまとめて、インスペクタからの調整を容易にし、コードの構造を明確にします。

ステップ1: フェーズ設定用のクラスを作成

まず、各フェーズのパラメータとその適用時間を格納するためのクラスを作成します。これにより、各フェーズの設定を一元管理でき、インスペクタから簡単に調整できるようになります。

このファイルはゲームオブジェクトのアタッチする必要がありません

[System.Serializable] // インスペクタで編集可能にするため
public class GamePhase
{
    public float startTime = 0f; // フェーズ開始時間
    public float span = 1.0f;    // アイテム生成間隔
    public float speed = -0.03f; // アイテムの速度
    public int ratio = 2;        // 爆弾の出現比率
}

ステップ2: GameDirectorクラスをリファクタリング

次に、GameDirectorクラスをリファクタリングして、GamePhaseクラスのインスタンスを使用するように変更します。これにより、インスペクタから各フェーズのパラメータを直接調整できるようになり、コードの可読性も向上します。

public class GameDirector : MonoBehaviour
{
    GameObject timerText;
    GameObject pointText;
    public float time = 30.0f; // ゲームタイマー
    int point = 0;             // 現在のポイント
    GameObject generator;      // アイテム生成器

    public GamePhase[] phases; // フェーズの設定

    public void GetApple()
    {
        this.point += 100;
    }

    public void GetBomb()
    {
        this.point /= 2;
    }

    void Start()
    {
        this.timerText = GameObject.Find("Time");
        this.pointText = GameObject.Find("Point");
        this.generator = GameObject.Find("ItemGenerator");
    }

    void Update()
    {
        this.time -= Time.deltaTime;
        
        foreach (var phase in phases)
        {
            if (this.time >= phase.startTime)
            {
                this.generator.GetComponent<ItemGenerator>().SetParameter(phase.span, phase.speed, phase.ratio);
                break; // 最初に条件を満たしたフェーズの設定を適用してループを抜ける
            }
        }

        this.timerText.GetComponent<TextMeshProUGUI>().text = this.time.ToString("F1");
        this.pointText.GetComponent<TextMeshProUGUI>().text = this.point.ToString() + " point";
    }
}

ステップ3: インスペクタでの設定

この変更後、Unityのインスペクタではphases配列に各フェーズの設定を追加できます。各GamePhaseインスタンスには、フェーズが開始する時間(startTime)、アイテム生成間隔(span)、アイテムの速度(speed)、爆弾の出現比率(ratio)を設定します。

このリファクタリングにより、コードの構造が明確になり、将来の拡張やメンテナンスが容易になります。また、インスペクタからゲームの挙動を簡単に調整できるようになり、デザイナーや他のチームメンバーがコードを変更することなく、ゲームプレイの調整を行えるようになります。

Unity

Posted by hidepon