Unityコード リファクタリング資料

はじめに

本資料は、Unityプロジェクト内で利用される主要なクラス(MobStatus、MobAttack、EnemyStatus、CollisionDetector、EnemyMove、PlayerController)に対して実施したリファクタリング内容をまとめたものです。
リファクタリングの主な目的は、可読性の向上保守性の改善、および安全性の向上です。


リファクタリングの目的

  1. コードの整理とグループ化
    • シリアライズフィールド、プライベートフィールド、プロテクトフィールド、公開プロパティ、ライフサイクルメソッド、その他のメソッドを論理的な順序でグループ化。
  2. 命名規則の統一
    • 内部変数の名前(例: _life_state)を、意味が明確な currentLifecurrentState などに変更。
  3. 安全性の向上
    • null チェックの追加などにより、ランタイムエラー発生の可能性を低減。

具体的なリファクタリング内容

1. メンバーの前後入れ替えとグループ化

元のコード:

メンバー(シリアライズフィールド、内部状態、プロパティ、メソッドなど)がバラバラに記述されており、各役割ごとのまとまりが不明瞭でした。

リファクタリング後のコード:

シリアライズフィールド: クラス冒頭にまとめる

[SerializeField] private float lifeMax = 10f;

プライベートフィールド: 次に配置

private float currentLife;

プロテクトフィールド & 内部状態: その後にグループ化

protected Animator animator; 
protected enum State { Normal, Attack, Die } 
protected State currentState = State.Normal;

以下のコード行

public bool IsMovable => currentState == State.Normal;

は、C# の簡潔な書き方である「式形式プロパティ」を使って、オブジェクトの移動可能状態を判定するためのプロパティを定義しています。以下、網羅的にその各要素と意味、背景について説明します。


1. アクセス修飾子と戻り値の型

  • public
    このプロパティは外部のクラスやコードからアクセス可能です。
    つまり、クラスの利用者はこのプロパティを通じて移動可能かどうかの状態を確認できます。
  • bool
    戻り値の型は bool(ブール型)で、「true」または「false」の2値を返します。
    ここでは、対象のオブジェクトが移動可能であれば true、そうでなければ false を返します。

2. プロパティ名と式形式シンタックス

  • IsMovable
    プロパティ名は「IsMovable」となっており、英語の命名規則に則って「移動可能か」を意味しています。
    名前の前に「Is」が付いているため、論理値(bool)で状態を返すプロパティであることが一目で分かります。
  • => 記法(式形式プロパティ)
    式形式プロパティは、従来の get アクセサを簡潔に記述する方法です。
    具体的には、通常は下記のような形式で記述するところを省略して記述しています。
public bool IsMovable
{
    get
    {
        return currentState == State.Normal;
    }
}

この場合、=> の右側にある式の評価結果がそのまま返される仕組みになっています。
シンプルな条件判定や計算結果を直接返す場合、より簡潔で読みやすいコードになります。


3. 条件式の詳細

  • currentState == State.Normal
    この部分は、現在のオブジェクトの状態を管理するフィールド currentState と、事前に定義されている状態を示す列挙型(enum)の State.Normal を比較しています。
  • currentState: オブジェクトの内部状態を表す変数。通常、状態管理のために「Normal」「Attack」「Die」などの状態が列挙型で定義されます。
  • State.Normal: 「通常状態」を意味する定数。移動や攻撃など、通常の処理が許可されている状態を表します。 条件式は、currentStateState.Normal であれば true を、そうでなければ false を返します。

4. 背景と利用意図

状態管理との連携

  • 動作の制御:
    このプロパティは、キャラクターやオブジェクトが移動可能かどうかの判断に利用されます。
    例えば、攻撃中や死亡している状態(State.Attack や State.Die)の場合には移動を制限するため、currentStateState.Normal でなければ移動できないように実装されます。
  • コードの可読性:
    式形式プロパティによって、単一の条件チェックを見やすく記述しています。
    どの状態のときに移動が可能なのかが、この一行から直感的に理解できます。

利点

  • 簡潔性:
    単純な条件判定であれば、従来の get ブロックよりも記述量が少なく、コードの可読性を向上させます。
  • メンテナンス:
    状態の判定ロジックがこのプロパティに集約されているため、後で「移動可能な状態」を変更する場合は、この部分のみの変更で済むといった利点があります。

5. 他の記述方法との比較

式形式プロパティ(上記の例)

public bool IsMovable => currentState == State.Normal;

従来の get アクセサ

public bool IsMovable
{
    get
    {
        return currentState == State.Normal;
    }
}

if 文を用いた get アクセサ

public bool IsMovable
{
    get
    {
        if (currentState == State.Normal)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
  • どの例も同じ論理(currentStateState.Normal の場合は true そうでなければ false を返す)を実装していますが、
  • 式形式プロパティが最もシンプルかつ読みやすい形式です。
  • 従来の get アクセサは、将来的にさらに処理を追加する場合に柔軟です。
  • if 文を利用した場合は、ロジックが明示的になり、複数の条件分岐やデバッグ時のブレークポイント設定に有用です。

6. まとめ

この一行のコード

public bool IsMovable => currentState == State.Normal;


は、以下の要素を含んでいます。

  • 公開プロパティ: 外部からアクセス可能なプロパティである。
  • ブール値を返す: オブジェクトの移動可能状態を真偽値で表現する。
  • 式形式プロパティ: シンプルな処理を短く記述するための手法で、内部状態 currentState の判定結果を直接返す。
  • 状態管理との連携: State.Normal という状態と比較することで、オブジェクトが通常状態(移動可能状態)にあるかどうかを判断する。

この実装は、簡潔かつ直感的なコードを書くための C# のモダンな記述方法であり、メンテナンスや可読性の向上につながります。

公開プロパティ:

すぐ後ろに配置し、フィールドとの関連性を明確に

public bool IsMovable => currentState == State.Normal; 
public bool IsAttackable => currentState == State.Normal; 
public float LifeMax => lifeMax; 
public float Life => currentLife;

ライフサイクルメソッドおよびその他のメソッド:

最後にまとめる

例: MobStatus クラスの比較

元のコード(抜粋):

[SerializeField] private float lifeMax = 10; // ライフ最大値
protected Animator _animator;
protected StateEnum _state = StateEnum.Normal; // Mob状態
private float _life; // 現在のライフ値(ヒットポイント)

リファクタリング後のコード:

// シリアライズフィールド
[SerializeField] private float lifeMax = 10f;

// プライベートフィールド
private float currentLife;

// プロテクトフィールドおよび内部状態
protected Animator animator;
protected enum State { Normal, Attack, Die }
protected State currentState = State.Normal;

2. 命名規則の統一と改善

  • 元のコード:
    内部変数に _life_state などの名前が使われ、役割が直感的に伝わりにくい状態でした。
  • リファクタリング後:
    変数名を currentLifecurrentState に変更し、コードの意図が明確に。
    また、メソッド名も統一感のある命名に変更しています。

3. Nullチェックの追加

  • 元のコード:
    animatorattackCollider に対する null チェックがなく、例外発生のリスクがありました。
  • リファクタリング後:
    animator?.SetTrigger("Die") のように null チェックを追加することで、万一の参照エラーを回避。

リファクタリング後のコード例

以下は、リファクタリング後の MobStatus.cs の完全なコード例です。他のクラスも同様の方針で改善されています。

using UnityEngine;

/// <summary>
/// 動くオブジェクト(Mob)の状態管理の基本クラス
/// </summary>
public abstract class MobStatus : MonoBehaviour
{
    // シリアライズフィールド
    [SerializeField] private float lifeMax = 10f;

    // プライベートフィールド
    private float currentLife;

    // プロテクトフィールドおよび内部状態
    protected Animator animator;
    protected enum State { Normal, Attack, Die }
    protected State currentState = State.Normal;

    // 公開プロパティ
    public bool IsMovable => currentState == State.Normal;
    public bool IsAttackable => currentState == State.Normal;
    public float LifeMax => lifeMax;
    public float Life => currentLife;

    // Unityライフサイクルメソッド
    protected virtual void Start()
    {
        currentLife = lifeMax;
        animator = GetComponentInChildren<Animator>();
    }

    // 仮想メソッド(派生クラスで拡張可能)
    protected virtual void OnDie() { }

    // 公開メソッド
    public void Damage(int damage)
    {
        if (currentState == State.Die)
            return;

        currentLife -= damage;
        if (currentLife > 0)
            return;

        currentState = State.Die;
        animator?.SetTrigger("Die");
        OnDie();
    }

    public void GoToAttackStateIfPossible()
    {
        if (!IsAttackable)
            return;

        currentState = State.Attack;
        animator?.SetTrigger("Attack");
    }

    public void GoToNormalStateIfPossible()
    {
        if (currentState == State.Die)
            return;

        currentState = State.Normal;
    }
}

まとめ

  • メンバーの整理:
    フィールドやプロパティ、メソッドを論理的な順序でグループ化することで、コード全体の見通しが良くなりました。
  • 命名規則の改善:
    変数名およびメソッド名を明確化することで、コードの意図が直感的に理解できるようになりました。
  • 安全性の向上:
    Nullチェックの追加などにより、実行時のエラーリスクを低減しました。

本資料を参考に、同様のリファクタリングを他のクラスにも適用することで、プロジェクト全体のコード品質向上と保守性の強化を実現できます。


Unity

Posted by hidepon