技術資料: Unityにおける所持アイテム管理システムのリファクタリング
本資料では、Unityを用いたゲーム開発においてプレイヤーが所持するアイテムを管理するシステム (OwnedItemsData
クラス) のリファクタリングについて解説します。特に、コード入力時に発生しやすいエラー(IntelliSenseの未定義エラー)を回避し、スムーズにコーディングを進められるような設計に改良します。
元のコードの概要
元のコードは、プレイヤーが所有するアイテムの種類と数量を管理するシステムです。主な機能として以下があります:
- アイテムの追加 (
Add
メソッド) - アイテムの使用 (
Use
メソッド) - データの保存と読み込み (
Save
メソッドとInstance
プロパティ) - 所有アイテムの一覧取得 (
OwnedItems
プロパティ)
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class OwnedItemsData
{
// ... (元のコード全体)
}
このシステムはシングルトンパターンを採用しており、OwnedItemsData.Instance
を通じて一つのインスタンスを共有します。
リファクタリングの目的とポイント
目的
- エラー回避: クラスやメソッドが未定義の状態で使用されることによるIntelliSenseのエラーを防ぐ。
- コードの可読性向上: 変数名やメソッド名を分かりやすく変更し、コメントを充実させる。
- 保守性の向上: クラスの依存関係を整理し、将来的な拡張や修正を容易にする。
主なリファクタリングポイント
- クラスの順序変更:
OwnedItem
クラスをOwnedItemsData
クラスの前に定義する。 - メソッド名の明確化:
Add
をAddItem
、Use
をUseItem
に変更。 - 変数名の統一:
number
をquantity
に変更。 - エラーハンドリングの改善: 例外の種類を具体的に (
InvalidOperationException
)。 - コメントの充実: 各クラス、メソッド、プロパティに詳細な説明を追加。
リファクタリング後のコード
以下にリファクタリング後のコードを示します。OwnedItem
クラスを OwnedItemsData
クラスの前に配置し、変数名やメソッド名を分かりやすく変更しています。
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// アイテムの所持数管理用モデル
/// </summary>
[Serializable]
public class OwnedItem
{
/// <summary>
/// アイテムの種類を取得するプロパティ
/// </summary>
public Item.ItemType Type => type;
/// <summary>
/// アイテムの所持数量を取得するプロパティ
/// </summary>
public int Quantity => quantity;
/// <summary>
/// アイテムの種類
/// </summary>
[SerializeField]
private Item.ItemType type;
/// <summary>
/// アイテムの所持数量
/// </summary>
[SerializeField]
private int quantity;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="type">アイテムの種類</param>
public OwnedItem(Item.ItemType type)
{
this.type = type;
this.quantity = 0;
}
/// <summary>
/// アイテムの数量を増やすメソッド
/// </summary>
/// <param name="amount">増加させる数量</param>
public void IncreaseQuantity(int amount = 1)
{
quantity += amount;
}
/// <summary>
/// アイテムの数量を減らすメソッド
/// </summary>
/// <param name="amount">減少させる数量</param>
public void DecreaseQuantity(int amount = 1)
{
quantity -= amount;
if (quantity < 0)
{
quantity = 0;
}
}
}
/// <summary>
/// プレイヤーが所有しているアイテムのデータを管理するクラス
/// </summary>
[Serializable]
public class OwnedItemsData
{
/// <summary>
/// PlayerPrefsに保存する際のキー
/// </summary>
private const string PlayerPrefsKey = "OWNED_ITEMS_DATA";
/// <summary>
/// シングルトンインスタンスを保持する静的変数
/// </summary>
private static OwnedItemsData _instance;
/// <summary>
/// シングルトンインスタンスを取得するプロパティ
/// </summary>
public static OwnedItemsData Instance
{
get
{
if (_instance == null)
{
// PlayerPrefsにデータが存在する場合は読み込む
if (PlayerPrefs.HasKey(PlayerPrefsKey))
{
string json = PlayerPrefs.GetString(PlayerPrefsKey);
_instance = JsonUtility.FromJson<OwnedItemsData>(json);
}
else
{
// データが存在しない場合は新しいインスタンスを作成
_instance = new OwnedItemsData();
}
}
return _instance;
}
}
/// <summary>
/// 所持しているアイテムのリスト
/// </summary>
[SerializeField]
private List<OwnedItem> ownedItems = new List<OwnedItem>();
/// <summary>
/// 所持アイテムを配列として取得するプロパティ
/// </summary>
public OwnedItem[] OwnedItems => ownedItems.ToArray();
/// <summary>
/// コンストラクタ
/// 外部からインスタンス化できないようにするためにprivateに設定
/// </summary>
private OwnedItemsData()
{
}
/// <summary>
/// データをJSON形式でPlayerPrefsに保存するメソッド
/// </summary>
public void Save()
{
string json = JsonUtility.ToJson(this);
PlayerPrefs.SetString(PlayerPrefsKey, json);
PlayerPrefs.Save();
}
/// <summary>
/// アイテムを所持リストに追加するメソッド
/// </summary>
/// <param name="type">追加するアイテムの種類</param>
/// <param name="quantity">追加する数量(デフォルトは1)</param>
public void AddItem(Item.ItemType type, int quantity = 1)
{
OwnedItem item = GetOwnedItem(type);
if (item == null)
{
// 所持リストにアイテムが存在しない場合は新しく追加
item = new OwnedItem(type);
ownedItems.Add(item);
}
// アイテムの数量を増やす
item.IncreaseQuantity(quantity);
}
/// <summary>
/// アイテムを消費するメソッド
/// </summary>
/// <param name="type">消費するアイテムの種類</param>
/// <param name="quantity">消費する数量(デフォルトは1)</param>
/// <exception cref="InvalidOperationException">アイテムが不足している場合にスローされる</exception>
public void UseItem(Item.ItemType type, int quantity = 1)
{
OwnedItem item = GetOwnedItem(type);
if (item == null || item.Quantity < quantity)
{
throw new InvalidOperationException("アイテムが足りません");
}
// アイテムの数量を減らす
item.DecreaseQuantity(quantity);
}
/// <summary>
/// 指定した種類のアイテムを取得するメソッド
/// </summary>
/// <param name="type">取得したいアイテムの種類</param>
/// <returns>指定した種類のOwnedItemオブジェクト。存在しない場合はnull。</returns>
public OwnedItem GetOwnedItem(Item.ItemType type)
{
return ownedItems.FirstOrDefault(item => item.Type == type);
}
}
リファクタリングの詳細解説
1. クラスの順序を変更
OwnedItem
クラスを OwnedItemsData
クラスの前に定義することで、OwnedItemsData
内で OwnedItem
を使用する際に未定義エラーが発生しないようにしました。これにより、コードを一行ずつ入力(写経)する際のエラーを防ぎます。
2. メソッド名と変数名の明確化
Add
メソッドをAddItem
に変更し、メソッドの機能を明確化。Use
メソッドをUseItem
に変更。number
変数をquantity
に変更し、意味を明確に。
3. エラーハンドリングの改善
UseItem
メソッドでアイテムが不足している場合に InvalidOperationException
をスローするように変更しました。これにより、例外の種類が具体的になり、エラーハンドリングがしやすくなります。
4. コメントの充実
各クラス、メソッド、プロパティに詳細なコメントを追加し、役割や動作を説明しました。これにより、コードの理解がしやすくなります。
使用方法の例
以下は、このリファクタリング後のクラスを使用してアイテムを追加・使用する例です。
using UnityEngine;
public class GameManager : MonoBehaviour
{
void Start()
{
// アイテムを追加
OwnedItemsData.Instance.AddItem(Item.ItemType.Potion, 5);
OwnedItemsData.Instance.AddItem(Item.ItemType.Sword);
// アイテムを使用
try
{
OwnedItemsData.Instance.UseItem(Item.ItemType.Potion, 2);
}
catch (InvalidOperationException ex)
{
Debug.LogError(ex.Message);
}
// データを保存
OwnedItemsData.Instance.Save();
}
void OnApplicationQuit()
{
// ゲーム終了時にデータを保存
OwnedItemsData.Instance.Save();
}
}
説明
- アイテムの追加
AddItem
メソッドを使用して、ポーションを5つ、剣を1つ追加しています。
- アイテムの使用
UseItem
メソッドを使用して、ポーションを2つ消費しています。- アイテムが不足している場合は例外がスローされ、エラーメッセージがログに出力されます。
- データの保存
Save
メソッドを呼び出して、現在のアイテムデータをPlayerPrefs
に保存しています。- ゲーム終了時にも同様にデータを保存します。
エディタでのエラー回避方法
コードを一行ずつ入力(写経)する際、クラスやメソッドがまだ定義されていないとIntelliSenseが未定義エラーを表示することがあります。以下の方法でこれを回避できます。
1. クラスの順序を守る
先述の通り、OwnedItem
クラスを OwnedItemsData
クラスの前に定義することで、OwnedItemsData
内で OwnedItem
が未定義になるエラーを防ぎます。
2. 一度にコードをコピー&ペーストする
全体のコードを一度にコピーしてエディタに貼り付けることで、クラスやメソッドの依存関係がすべて揃っているため、エラーが発生しません。
3. セクションごとにコピー&ペーストする
OwnedItem
クラスをまず入力または貼り付ける。- 次に
OwnedItemsData
クラスを入力または貼り付ける。
これにより、OwnedItemsData
内で OwnedItem
がすでに定義されているため、未定義エラーが発生しません。
4. IDEの設定を確認する
使用しているIDE(例: Visual Studio, Rider)の設定を確認し、以下を確認します。
- 自動ビルド: ファイル保存時に自動的にビルドされる設定にする。
- コード補完機能: IntelliSenseが正しく機能するように設定する。
- エラーチェックのタイミング: コード入力時ではなく、保存時やビルド時にエラーを表示する設定にする。
注意点とベストプラクティス
1. PlayerPrefsの使用
- 利点: 小規模なデータの保存に適しており、簡単に使用できます。
- 注意点: 大量のデータやセキュリティが重要な場合は、別の保存方法(例: ファイル、データベース)を検討してください。
2. シリアライズ可能なクラス
OwnedItemsData
とOwnedItem
クラスには[Serializable]
属性が付与されています。これにより、JsonUtility
を使用してJSON形式で簡単に保存・読み込みが可能です。
3. シングルトンパターンの使用
- シングルトンパターンを使用することで、どこからでも同じインスタンスにアクセスでき、データの一貫性を保ちやすくなります。
- 注意点: シングルトンは便利ですが、過度に使用すると依存関係が複雑になる場合があります。必要な場面で適切に使用しましょう。
4. クラスの分割
- 小規模プロジェクト: 同じファイル内に関連クラスをまとめると理解しやすいです。
- 大規模プロジェクト: 各クラスを別ファイルに分割することで、コードの管理が容易になります。
5. エラーハンドリング
- 適切な例外をスローし、エラーメッセージを明確にすることで、デバッグが容易になります。
- 例外処理を適切に行い、ユーザーに分かりやすいフィードバックを提供しましょう。
まとめ
本資料では、Unityにおける所持アイテム管理システムのリファクタリングについて解説しました。特に、コード入力時に発生しやすいエラーを回避し、可読性と保守性を向上させるための具体的な手法を紹介しました。以下のポイントを押さえることで、より安定したコーディング環境を整えることができます。
- クラスの順序を整理し、依存関係を明確にする。
- メソッド名や変数名を分かりやすく変更し、コードの意図を明確にする。
- 詳細なコメントを追加し、コードの理解を助ける。
- エラーハンドリングを改善し、例外の種類を具体的にする。
- シングルトンパターンを適切に使用し、データの一貫性を保つ。
これらのベストプラクティスを取り入れることで、効率的かつ効果的な開発が可能になります。今後の開発にお役立てください。
ディスカッション
コメント一覧
まだ、コメントがありません