【Unity】プロジェクトの分析サンプル
プレイヤーと敵との戦いのサンプルプロジェクトの読み方についてみていきましょう
イメージをわかりやすくするため、コードは一部リファクラリングをしています
ゲームオブジェクトの構成
各ゲームオブジェクトの構成を確認します
各オブジェクトがUnityプロジェクト内でどのような役割を果たしているかを以下に詳述します。
Item ゲームオブジェクト
インスタンス生成のためのPrefab
- Item: ゲーム内でプレイヤーが拾うことができるオブジェクトです。アイテムの種類(例:回復アイテム、パワーアップアイテムなど)に基づいて特定の効果をプレイヤーに提供します。
Enemy ゲームオブジェクト
インスタンス生成のためのPrefab
- EnemyStatus: 敵キャラクターの状態を管理します。生命力、現在の状態(例:攻撃中、死亡中など)、ダメージの処理を担当します。
- NavMeshAgent: 敵のナビゲーションを管理するためのコンポーネントです。プレイヤーを追跡するための経路計算や移動を自動で行います。
- EnemyMove: 敵の移動ロジックを実装します。プレイヤーの位置を検出し、適切な行動(追跡や回避)を行うための判断をします。
- MobItemDropper: 敵が倒された時にアイテムをドロップする機能を持ちます。どのアイテムを、どれだけの確率でドロップするかを管理します。
Player ゲームオブジェクト
インスタンス生成のためのPrefab
- PlayerController: プレイヤーの操作に直接関連する機能を提供します。移動速度、走行速度、ジャンプ力などのパラメータを管理し、ユーザーの入力に応じてキャラクターを制御します。
- PlayerStatus: プレイヤーの状態(例えば、健康状態や生命力)を管理します。ダメージ受けたときの処理や、ゲームオーバーのトリガーなどを担います。
- MobAttack: プレイヤーの攻撃機能を扱います。攻撃の実行、クールダウンの管理、攻撃がヒットした時の処理などが含まれます。
- CharacterController: Unityの標準コンポーネントで、物理計算を使わないキャラクターの移動を管理します。地形の衝突判定や障害物とのインタラクションを扱います。
これらのオブジェクトは、プレイヤーと敵のインタラクション、ゲームプレイの動的な要素(アイテムの収集やキャラクターの状態変化)、そしてゲーム内の戦略的な判断を提供するために相互に作用します。これにより、ゲームはよりリッチでインタラクティブな体験をプレイヤーに提供することができます。
オブジェクト図
プレイヤーと敵に特化
- コンポジション関係: クラス間の「持つ(has-a)」関係を示しています。たとえば、
PlayerController
はPlayerStatus
とMobAttack
を持っています。これはPlayerController
クラスがこれらのオブジェクトの生存期間を管理することを意味します。 - クラスの拡張: 例えば、
EnemyStatus
はMobStatus
を継承しており、これはIS-A関係を表しています。 - クラスの生成:
MobItemDropper
がItem
クラスのインスタンスを生成する関係も示されています。
このクラス図は、コードの各部分がどのように連携して機能するかをより詳細に理解するのに役立ちます。
アイテムも追加
構成の詳細(アイテム)
Item
Colliderコンポーネントが必要であり、アイテムの種類や位置、アニメーションなどを管理します。AwakeメソッドでColliderを無効に初期設定し、Initializeメソッドでアイテムの位置をランダムに設定し、移動とスケールのアニメーションを行います。アニメーション終了後、Colliderを有効にします。OnTriggerEnterメソッドは、アイテムにプレイヤーが触れたときに呼び出され、アイテムをプレイヤーのインベントリに追加し、その後アイテムを破棄します。このスクリプトは、ゲーム内でのアイテムのインタラクションと管理を担う重要な部分です。
using DG.Tweening;
using UnityEngine;
// Collider コンポーネントが必要な Item クラスを定義
[RequireComponent(typeof(Collider))]
public class Item : MonoBehaviour
{
[SerializeField]
private ItemType type; // アイテムの種類を設定
private Collider colliderCache; // コライダーへのキャッシュ
private Transform transformCache; // トランスフォームへのキャッシュ
// オブジェクトが初めて有効になった時に一度だけ呼ばれるメソッド
private void Awake()
{
colliderCache = GetComponent<Collider>(); // 現在のGameObjectからColliderコンポーネントを取得
transformCache = transform; // 現在のトランスフォームをキャッシュ
// 初期状態ではアイテムのコライダーを無効にしておく
colliderCache.enabled = false;
}
// アイテムの初期化を行う公開メソッド
public void Initialize()
{
// ドロップ時の位置をランダムに設定
Vector3 dropPosition = transformCache.localPosition + new Vector3(Random.Range(-1f, 1f), 0, Random.Range(-1f, 1f));
// アイテムのスケールをアニメーションしながら、ドロップ位置へ移動させる
transformCache.DOScale(transformCache.localScale, 0.5f).SetEase(Ease.OutBounce);
transformCache.DOLocalMove(dropPosition, 0.5f).OnComplete(() =>
{
// アニメーションが完了したらコライダーを有効にする
colliderCache.enabled = true;
});
}
// アイテムにプレイヤーが触れた時に呼ばれるメソッド
private void OnTriggerEnter(Collider other)
{
// 触れたオブジェクトがプレイヤーでなければ処理をしない
if (!other.CompareTag("Player")) return;
// アイテムをプレイヤーの所持データに追加し、保存
OwnedItemsData.Instance.AddItem(type);
OwnedItemsData.Instance.Save();
// 所持しているアイテムの種類と数をログに出力
foreach (var item in OwnedItemsData.Instance.OwnedItems)
{
Debug.Log($"{item.Type}を{item.Number}個所持");
}
// アイテムをゲーム内から破棄する
Destroy(gameObject);
}
}
構成の詳細(敵)
ステータスの管理
MobStatus
このコードは、抽象クラスを定義しています。このクラスは、Unityのエディター内でのみ利用される機能を含んでおり、オブジェクトのステータスを管理するためのものです。特に、「OnDrawGizmos」メソッドを通じて、エディター上にオブジェクトの名前やライフ、ステータスを表示させる機能があります。また、オブジェクトの現在の状態を表す「StateEnum」という列挙型も定義されており、通常状態、攻撃中、死亡といったステータスを管理します。このクラスは具体的な行動やアニメーションの管理など、さまざまな機能を拡張するための基盤として使用されることが想定されています。
using UnityEngine;
// Unityエディタ時のみusingする必要がある
#if UNITY_EDITOR
using UnityEditor;
#endif
// 動くオブジェクト用のステータスを管理するクラス
public abstract class MobStatus : MonoBehaviour
{
// Unityエディタ時のみGizmo表示を行う
#if UNITY_EDITOR
private void OnDrawGizmos()
{
// 表示色を指定
GUI.color = Color.black;
// オブジェクトの位置に「オブジェクト名とライフをでラベル表示
Handles.Label(transform.position, $"{name}\n Life:{Life}\n Status:{_state}");
}
#endif
// 状態の定義
protected enum StateEnum
{
Normal, // 通常 (攻撃中でも死亡中でもない)
Attack, // 攻撃中
Die // 死亡
}
// 今(現在)の状態 (最初はノーマル状態)
protected StateEnum _state = StateEnum.Normal;
// 状態の取得
// 移動可能かどうかの判定 (StateEnumがNormal時:現在の状態 == ノーマル)
public bool IsMovable => (_state == StateEnum.Normal);
// 攻撃可能かどうかの判定 (StateEnumがNormal時:現在の状態 == ノーマル)
public bool IsAttackable => (_state == StateEnum.Normal);
// 現在のライフ(ヒットポイント)
float _life;
// ライフ情報の取得
public float Life => _life;
// ライフの最大値(インスペクタから登録できる)
[SerializeField]
float lifeMax = 10;
// ライフの最大値の取得
public float LifeMax => lifeMax;
// アニメーション遷移管理のためにアニメータを取得
protected Animator _animater;
// 派生クラスでもStartメソッドを使えるようにする
protected virtual void Start()
{
_life = lifeMax;
_animater = GetComponentInChildren<Animator>();
}
// damage分だけ、ダメージを受ける
public void Damage(int damage)
{
// 今、死んでれば戻る(何もしない)
if (_state == StateEnum.Die)
{
return;
}
// ダメージ分、ライフが減る
_life -= damage;
// 減らしても生きていれば戻る(死んでる処理をしない)
if (_life > 0)
{
return;
}
// 死んでる状態に移行
_state = StateEnum.Die;
// 「死んでるアニメーション」に移行
_animater.SetTrigger("Die");
// 引き続き、死んでたらどうしたいか処理を追加できるように、メソッドを呼べるようにしておく
OnDie();
}
// 派生クラスでも死んだ時の処理を書けるようにしておく
protected virtual void OnDie()
{
// 死んだ場合の処理(共通)
}
// 攻撃状態に移行(メソッド名は、GoToAttackState()でもいいのでは?)
public void GoToAttackStateIfPossible()
{
// 攻撃できなければ(通常でなければ)戻る:何もしない
if (!IsAttackable)
{
return;
}
_state = StateEnum.Attack;
_animater.SetTrigger("Attack");
}
// 通常状態に移行
public void GoToNormalStateIfPossible()
{
// 今、死んでれば、戻る:何もしない
if (_state == StateEnum.Die)
{
return;
}
_state = StateEnum.Normal;
}
}
EnemyStatus
このコードは、敵キャラクターの動作を制御するためのものです。敵キャラクターは、MobStatus
クラスを継承し、ナビゲーションエージェントのコンポーネントを使用しています。敵の速度に基づいてアニメーションの遷移を制御するため、_agent.velocity.magnitude
を使って速度を取得し、アニメーションパラメーターMoveSpeed
に設定しています。敵が死亡した場合の処理として、OnDie
メソッドをオーバーライドし、DestroyCoroutine
コルーチンを呼び出して3秒後に敵オブジェクトを破壊します。これにより、動的にゲーム内の敵の振る舞いを管理し、リアルタイムでの対応が可能になります。
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
// 敵の状態管理
// MobStatusを継承しています
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyStatus : MobStatus
{
// アニメーションを遷移させたい
// 速度が0.01より大きければ、Moveアニメーションに遷移したい
// 速度が0.01より小さければ、Idleアニメーションに遷移したい
// ナビメッシュエージェントから速度を得ることができます
// _agent.velocityでベクトルが取得できます(Vector3型)
// _agent.velocity.magnitude;で速度が取得できます(float型)
NavMeshAgent _agent;
// 基底クラスのStartメソッドも実行する
protected override void Start()
{
// 基底クラスのStartメソッドを実行
base.Start();
_agent = GetComponent<NavMeshAgent>();
}
void Update()
{
// 敵のアニメーションの遷移(速度が条件)
_animater.SetFloat("MoveSpeed", _agent.velocity.magnitude);
}
// 死んだ時の処理
protected override void OnDie()
{
// 基底クラスのOnDieメソッドを実行
base.OnDie();
// 3秒後にデストロイされる
StartCoroutine(DestroyCoroutine());
}
// 3秒後にデストロイされるコルーチン
IEnumerator DestroyCoroutine()
{
// 3秒待つ
yield return new WaitForSeconds(3);
// スクリプトがアタッチされているゲームオブジェクト(敵)をデストロイ
Destroy(gameObject);
}
}
移動する
EnemyMove
NavMeshAgent
(AIの経路探索用)とEnemyStatus
(敵の状態管理用)の2つのコンポーネントを使用しています。Start
メソッドでこれらのコンポーネントを初期化します。オブジェクトが検出された際にはOnDetectObject
メソッドがトリガーされ、敵が動けるかどうかを確認します。検出されたオブジェクトがプレイヤーである場合、プレイヤーへの方向と距離を計算します。次に、敵とプレイヤーの間に障害物がないかを確認するためにレイキャストを実行します。障害物がない場合は、敵はプレイヤーに向かって移動し(緑のレイで示される)、障害物がある場合は移動を停止し(赤のレイで示される)、プレイヤーを追いかけることができます。この設定により、敵は視界にプレイヤーがいる場合にリアルに追跡することができ、ゲームプレイのリアリズムが向上します。
using UnityEngine;
using UnityEngine.AI;
// NavMeshAgentとEnemyStatusのコンポーネントが必要です
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(EnemyStatus))]
public class EnemyMove : MonoBehaviour
{
[SerializeField]
LayerMask raycastLayerMask;
RaycastHit[] _raycastHits = new RaycastHit[10];
NavMeshAgent _agent;
EnemyStatus _status;
// コンポーネントの初期化
void Start()
{
_agent = GetComponent<NavMeshAgent>();
_status = GetComponent<EnemyStatus>();
}
// オブジェクトを検出した際の処理
public void OnDetectObject(Collider collider)
{
// 移動できない状態であれば処理を中断
if (!_status.IsMovable)
{
_agent.isStopped = true;
return;
}
// プレイヤーを検出した場合の処理
if (collider.CompareTag("Player"))
{
var positionDiff = collider.transform.position - transform.position;
var distance = positionDiff.magnitude;
var directiton = positionDiff.normalized;
var hitCount = Physics.RaycastNonAlloc(transform.position, directiton, _raycastHits, distance, raycastLayerMask);
// プレイヤーとの間に障害物がなければ目的地を設定
if (hitCount == 0)
{
_agent.isStopped = false;
_agent.destination = collider.transform.position;
// 緑のRayを描画
Debug.DrawRay(transform.position, positionDiff, Color.green);
}
else
{
_agent.isStopped = true;
// 赤のRayを描画
Debug.DrawRay(transform.position, positionDiff, Color.red);
}
}
}
}
攻撃する
MobAttack
このコードは、モンスターがプレイヤーを攻撃する際に使われるスクリプトです。まず、モンスターが攻撃範囲にプレイヤーが入ると、攻撃可能であれば攻撃を行います。攻撃の成否はMobStatus
クラスを通じて管理され、攻撃のアニメーションが開始された時点で、攻撃対象のコライダーが有効になります。攻撃がヒットすると、対象のモンスターにダメージを与えます。また、攻撃後はクールダウンが発生し、一定時間攻撃ができなくなります。このクールダウンはコルーチンを使用して実装されており、設定された時間後に通常状態へ戻ることができます。このスクリプトはUnityでのゲーム開発において、キャラクターの行動パターンを制御するための典型的な例と言えます。
using System.Collections;
using UnityEngine;
[RequireComponent(typeof(MobStatus))]
public class MobAttack : MonoBehaviour
{
// 攻撃後のクールダウン時間の登録
[SerializeField]
float attackCooldown = 0.5f;
[SerializeField]
Collider attackCollider;
MobStatus _status;
void Start()
{
_status = GetComponent<MobStatus>();
}
// 1.相手が攻撃するエリアに入った場合(TriggerEnter)に呼ばれるイベントハンドラ
public void OnAttackRangeEnter(Collider collider)
{
// 攻撃する
AttackIfPossible();
}
// 攻撃メソッド(OnAttackRangeEnterイベントハンドラから呼ばれる)
public void AttackIfPossible()
{
// 攻撃できなければ戻る(何もしない)
if (!_status.IsAttackable)
{
return;
}
// MobStatusクラスに攻撃メソッドが呼ばれる
_status.GoToAttackStateIfPossible();
}
// 2.アニメーションウィンドウで指定されたアニメーションの位置に来た時、呼ばれる
public void OnAttackStart()
{
// 口の辺りの攻撃成功用のコライダーを有効にする
attackCollider.enabled = true;
}
// 3.口の辺りの攻撃成功用のコライダーにヒットした
public void OnHitAttack(Collider collider)
{
// 口に咥えられた相手のMobStatusを取得
MobStatus targetMob = collider.GetComponent<MobStatus>();
// 咥えられた相手にMobStatusがアタッチされていない!!時は戻る
if (targetMob == null)
{
return;
}
// 相手にダメージを与える
targetMob.Damage(1);
}
// 4.アニメーションウィンドウで指定されたアニメーションの位置に来た時、呼ばれる
public void OnAttackfinished()
{
attackCollider.enabled = false;
// しばらくは、攻撃しない(クールダウン)
StartCoroutine(CooldownCoroutine());
}
IEnumerator CooldownCoroutine()
{
yield return new WaitForSeconds(attackCooldown);
_status.GoToNormalStateIfPossible();
}
}
死亡時にアイテムを落とす
MobItemDropper
このクラスは、MobStatusというコンポーネントが必要であり、敵キャラクターが倒された際にアイテムをドロップする機能を持ちます。ドロップ率やドロップするアイテムの種類、数などを設定できます。具体的には、ドロップする確率を0から1の範囲で設定し、どのアイテムをドロップするかのプレハブと、ドロップするアイテムの数を指定できます。Updateメソッドでは、キャラクターのライフが0以下になり、かつアイテムがまだドロップされていない状態でアイテムのドロップを試みます。アイテムのドロップ処理は、ランダムな確率で決定され、条件を満たすと指定された数だけアイテムを生成してゲームの世界に配置します。
using UnityEngine;
// MobStatus コンポーネントが必須の MobItemDropper クラスを定義します。
[RequireComponent(typeof(MobStatus))]
public class MobItemDropper : MonoBehaviour
{
[SerializeField, Range(0, 1)]
private float dropRate = 0.1f; // アイテムをドロップする確率を設定(0から1の間)
[SerializeField]
private Item itemPrefab; // ドロップするアイテムのプレハブ
[SerializeField]
private int number = 1; // ドロップするアイテムの数
private MobStatus _status; // Mobのステータスを格納する変数
private bool _isDropInvoked; // アイテムがドロップされたかどうかをチェックするフラグ
// コンポーネントの初期化時に一度だけ呼ばれるメソッド
void Start()
{
// このゲームオブジェクトに付属するMobStatusコンポーネントを取得
_status = GetComponent<MobStatus>();
}
// 毎フレームごとの更新を行うメソッド
void Update()
{
// Mobのライフが0以下で、まだアイテムがドロップされていない場合にドロップ処理を試行
if (_status.Life <= 0 && !_isDropInvoked)
{
TryDropItems();
}
}
// アイテムのドロップ試行を行うメソッド
private void TryDropItems()
{
_isDropInvoked = true; // ドロップ処理実行済みとマーク
if (ShouldDropItems()) // ドロップするべきかの判定を行う
{
DropItems(); // アイテムをドロップする
}
}
// アイテムをドロップするかどうかをランダムに決定するメソッド
private bool ShouldDropItems()
{
// 0から1のランダム値が設定されたドロップ率未満であればtrue(ドロップする)
return Random.Range(0f, 1f) < dropRate;
}
// アイテムを生成し配置するメソッド
private void DropItems()
{
for (int i = 0; i < number; i++)
{
InstantiateAndInitializeItem(); // アイテムをインスタンス化し初期化
}
}
// アイテムをインスタンス化し、初期化するメソッド
private void InstantiateAndInitializeItem()
{
// アイテムプレハブから新しいアイテムオブジェクトを生成し、現在の位置で初期化
Item item = Instantiate(itemPrefab, transform.position, Quaternion.identity);
item.Initialize(); // アイテムの初期化メソッドを呼び出す
}
}
構成の詳細(プレイヤー)
ステータスの管理
MobStatus
敵と同じ基本クラスを使います
PlayerStatus
このクラスはMobStatus
クラスを継承しており、プレイヤーが死亡した際の処理をオーバーライドしています。具体的には、OnDie
メソッド内で基底クラスの同名メソッドを呼び出した後、3秒間の待機時間を経てゲームオーバー画面へ遷移するコルーチンGoToGameOverCoroutine
を開始します。この方法により、プレイヤーの死亡が適切に処理され、ゲームのフローがスムーズにゲームオーバー画面へと移行するように設計されています。
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerStatus : MobStatus
{
// 死亡時に実行される処理
protected override void OnDie()
{
base.OnDie();
// ゲームオーバー画面へ遷移するコルーチンを開始
StartCoroutine(GoToGameOverCoroutine());
}
// ゲームオーバー画面へ遷移するコルーチン
IEnumerator GoToGameOverCoroutine()
{
// 3秒待機
yield return new WaitForSeconds(3);
// ゲームオーバーシーンをロード
SceneManager.LoadScene("GameOverScene");
}
}
移動させる
PlayerController
CharacterController
、PlayerStatus
、MobAttack
の3つのコンポーネントが必要で、これらは自動的にこのスクリプトがアタッチされたオブジェクトに追加されます。主な機能として、プレイヤーの移動、ジャンプ、攻撃の管理が含まれています。移動速度やジャンプ力などのパラメーターはシリアライズされており、インスペクターから調整可能です。Update
メソッドでは、ユーザーの入力に基づいてキャラクターを動かし、地面に接地しているかどうかでジャンプの可否を判断し、左シフトキーでダッシュ状態の切り替えを行います。また、Animator
コンポーネントを用いて移動速度に応じたアニメーションの再生を行います。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerStatus))]
[RequireComponent(typeof(MobAttack))]
public class PlayerController : MonoBehaviour
{
[SerializeField] private float defaultSpeed = 3; //デフォルトの歩き速度
[SerializeField] private float moveSpeed = 3; //移動速度
[SerializeField] private float runSpeed = 5; //ダッシュ速度
[SerializeField] private float jumpPower = 3; //ジャンプ力
[SerializeField] private Animator animator; //アニメーション
private CharacterController _characterController; //CharacterControllerのキャッシュ
private Transform _transform; // Transformのキャッシュ
private Vector3 _moveVelocity; //キャラクター移動速度情報
private PlayerStatus _status;
private MobAttack _attack;
Vector3 rayGround; //地面との接地面を用意
Vector3 normalizeVelocity;
void Start()
{
//フレーム毎にアクセスする為、負荷処理を下げるためキャッシュする
_characterController = GetComponent<CharacterController>();
_transform = transform; //Transformも多少負荷が下がるためキャッシュする
_status = GetComponent<PlayerStatus>();
_attack = GetComponent<MobAttack>();
}
void Update()
{
Vector3 moveDirection = Vector3.zero;
Vector3 jampDirection = Vector3.zero;
//Debug.Log(_characterController.isGrounded ? "地上にいる" : "空中");
if (Input.GetButtonDown("Fire1"))
{
//Fire1ボタンで攻撃(デフォで左クリック)
_attack.AttackIfPossible();
}
//入力軸による移動処理(慣性は無視している)
//移動可能であれば、ユーザー入力を移動に反映する
if (_status.IsMovable)
{
_moveVelocity.x = Input.GetAxis("Horizontal");
_moveVelocity.z = Input.GetAxis("Vertical");
//Vector3の情報をnormalizeVelocityに入れる
normalizeVelocity = new Vector3(_moveVelocity.x, 0, _moveVelocity.z);
//normalizeVelocityのベクトルをNormalizeで正規化する
normalizeVelocity.Normalize();
//正規化したベクトルで計算をする為、√2走行にならない。
normalizeVelocity *= moveSpeed;
//移動スピードをanimatorに反映させる
animator.SetFloat("MoveSpeed", normalizeVelocity.magnitude);
//移動している方向に向く
_transform.LookAt(_transform.position + normalizeVelocity);
}
else
{
normalizeVelocity.x = 0;
normalizeVelocity.z = 0;
}
//Debug.Log(isGrounded);
if (isGrounded)
{
if (Input.GetButtonDown("Jump"))
{
//ジャンプ処理
Debug.Log("ジャンプ");
_moveVelocity.y = jumpPower;
}
}
else
{
//重力加速
_moveVelocity.y += Physics.gravity.y * Time.deltaTime;
}
//ダッシュ処理
if (Input.GetKey(KeyCode.LeftShift))
{
moveSpeed = runSpeed;
}
else
{
moveSpeed = defaultSpeed;
}
//オブジェクトを動かす
_characterController.Move(new Vector3(normalizeVelocity.x, _moveVelocity.y, normalizeVelocity.z) * Time.deltaTime);
//Debug.Log(normalizeVelocity.magnitude);// 現状の速度計算
}
bool isGrounded
{
get
{
//元のtransformの位置から上に0.1fだけ上げた座標に引数渡す。
rayGround = _transform.position + new Vector3(0, 0.1f);
//Rayの設定
var ray = new Ray(rayGround, Vector3.down);
return Physics.Raycast(ray, 0.2f);
}
}
}
攻撃する
MobAttack
敵と同じ基本クラスを使います
クラス図
以下はPlantUMLクラス図です。この図はクラスの構造と関係を表しています
継承(--|>
表記で示される)、合成(*--
表記で示される)、および列挙型定義が含まれています。各クラスは、依存しているか含まれているコンポーネントにリンクされており、提供されたコードの設定を反映しています。これにより、Unityプロジェクト内の関係や依存関係を視覚化するのに役立ちます
プレイヤーが所持しているアイテムの管理
このコードは、アイテム所持データを管理するクラス構造を示しています。メインクラスOwnedItemsData
は、シングルトンパターンを使用してインスタンスを管理し、プレイヤーが持つアイテムのリストを保持します。各アイテムはOwnedItem
クラスで表され、アイテムの種類と数をプロパティで管理します。アイテムの追加や使用のロジックもこのクラスに実装されています。データはJSON形式で保存・読み込みが可能で、UnityのPlayerPrefs
を利用して永続化されます。このように、アイテムの種類ごとに数を管理し、簡単にデータを保存・読み込みできる構造は、ゲーム開発において非常に役立ちます。
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable] // UnityJSONユーティリティで変換するために必要
public class OwnedItemsData
{
[Serializable] // UnityJSONユーティリティで変換するために必要
public class OwnedItem
{
[SerializeField] // UnityJSONユーティリティで変換するために必要
ItemType type;
public ItemType Type => type; // アイテムの種類を取得するプロパティ
[SerializeField] // UnityJSONユーティリティで変換するために必要
int number;
public int Number => number;// アイテムの数を取得するプロパティ
// コンストラクタ:アイテムの種類と初期数を設定
public OwnedItem(ItemType type, int initialNumber = 0)
{
this.type = type; // アイテムの種類を設定
this.number = initialNumber; // 初期数を設定
}
// アイテムを追加するメソッド
public void Add(int number = 1)
{
this.number += number; // 指定された数だけアイテム数を増やす
}
// アイテムを使用するメソッド
public bool TryUse(int number = 1)
{
if (Number >= number)
{
this.number -= number; // 指定された数だけアイテム数を減らす
return true; // 使用成功
}
return false; // 数が足りないため使用失敗
}
}
// プライベートコンストラクタ
private OwnedItemsData() { }
private static OwnedItemsData _instance; // シングルトンインスタンス
// シングルトンにアクセスするためのプロパティ
public static OwnedItemsData Instance
{
get
{
if (_instance == null)
{
Load(); // インスタンスがなければロードを試みる
}
return _instance; // インスタンスを返す
}
}
[SerializeField] // UnityJSONユーティリティで変換するために必要
private List<OwnedItem> _ownedItems = new List<OwnedItem>(); // 所有アイテムのリスト
public OwnedItem[] OwnedItems => _ownedItems.ToArray(); // 保有アイテムのリストを配列として取得
private const string PlayerPrefsKey = "OWNED_ITEMS_DATA"; // PlayerPrefsに保存するためのキー
// データをロードする静的メソッド
private static void Load()
{
// PlayerPrefsに指定されたキーが存在するか確認
if (PlayerPrefs.HasKey(PlayerPrefsKey))
{
string json = PlayerPrefs.GetString(PlayerPrefsKey, "{}"); // PlayerPrefsからJSON形式でデータを取得
Debug.Log(json);
_instance = JsonUtility.FromJson<OwnedItemsData>(json); // JSONを解析してインスタンスを作成
}
else
{
// キーが存在しない場合、新規インスタンスを作成
_instance = new OwnedItemsData();
}
}
// データを保存するメソッド
public void Save()
{
string json = JsonUtility.ToJson(this, prettyPrint: true); // 現在のオブジェクトをJSON形式に変換
Debug.Log(json);
PlayerPrefs.SetString(PlayerPrefsKey, json); // PlayerPrefsにJSONを保存
PlayerPrefs.Save(); // 変更を確定
}
// アイテムを追加するメソッド
public void AddItem(ItemType type, int number = 1)
{
var item = _ownedItems.Find(i => i.Type == type); // 指定されたタイプのアイテムを検索
if (item == null)
{
item = new OwnedItem(type); // 見つからなければ新しく作成
_ownedItems.Add(item); // リストに追加
}
item.Add(number); // アイテムの数を増やす
}
// アイテムを使用するメソッド
public bool UseItem(ItemType type, int number = 1)
{
var item = _ownedItems.Find(i => i.Type == type); // 指定されたタイプのアイテムを検索
if (item != null)
{
return item.TryUse(number); // アイテムを使用し、結果を返す
}
throw new InvalidOperationException("不足しているアイテム"); // アイテムが足りない場合は例外を投げる
}
}
プレイヤーがゲーム内で所有するアイテムの種類と数量を管理するためのクラスが定義されています。ここでは主要なコンポーネントとしてOwnedItemsData
クラスとその内部クラスOwnedItem
があります
OwnedItem クラス
- プロパティ:
Type
: アイテムの種類を表すプロパティです。読み取り専用。Number
: アイテムの数量を表すプロパティです。プライベートセット可能で、外部からの直接変更は制限されています。
- コンストラクタ:
OwnedItem(ItemType type, int initialNumber = 0)
: アイテムの種類と初期数量を設定できるコンストラクタです。デフォルトでは数量は0に設定されています。
- メソッド:
Add(int number = 1)
: アイテムの数量を増やすメソッドです。デフォルトで1増やす設定ですが、引数を変更することで任意の数を増やすことができます。TryUse(int number = 1)
: アイテムを使用する試みをするメソッドです。必要な数量が十分にある場合にのみ使用が可能で、成功したかどうかの真偽値を返します。
OwnedItemsData クラス
- シングルトン実装:
_instance
:OwnedItemsData
の唯一のインスタンスを保持するための静的プライベート変数です。Instance
: シングルトンインスタンスにアクセスするためのプロパティです。インスタンスが未作成の場合は新たにロードします。
- アイテム管理:
_ownedItems
: 所有しているアイテムのリストを保持するプライベートリストです。OwnedItems
: 所有アイテムリストを配列形式で取得するプロパティです。
- 永続化:
PlayerPrefsKey
: PlayerPrefsで使用するためのキー文字列です。Load()
: PlayerPrefsからアイテムデータを読み込む静的メソッドです。JSON形式で保存されたデータを読み取り、OwnedItemsData
インスタンスに変換します。Save()
: 現在のオブジェクト状態をJSON形式に変換してPlayerPrefsに保存するメソッドです。
- ユーティリティメソッド:
AddItem(ItemType type, int number = 1)
: 新しいアイテムをリストに追加または既存のアイテム数を増やすメソッドです。UseItem(ItemType type, int number = 1)
: 指定したタイプのアイテムを使用するメソッドです。不足している場合は例外が投げられます。
これらの翻訳により、コードの構造と機能がより明確に理解できると思います。
クラス図
主要なクラスと依存関係
このクラス図では、以下の継承とコンポジションの関係を示しています:
- 継承(Inheritance):
MobStatus
は抽象クラスであり、EnemyStatus
とPlayerStatus
が継承しています。これにより、共通の機能(例:状態管理、ダメージ処理)をこれらのサブクラスで再利用できます。
- コンポジション(Composition):
OwnedItemsData
クラスはOwnedItem
のリストを持っています(集約)。これにより、複数のアイテムの状態を一元管理できます。PlayerController
はCharacterController
,PlayerStatus
, およびMobAttack
のインスタンスを持ち、これによりプレイヤーの動き、状態、攻撃機能が一つのコントローラーで管理されます。
これらの関係は、Unityでの効率的なゲーム開発とコードの再利用を促進するために重要です。
この分析で得られるスキル
これらのコードから学べるスキルや知識のセットは、C#言語とUnityエンジンの使用方法に焦点を当てた、ゲーム開発の基本的なコンセプトと応用技術を網羅しています。具体的には以下のような技能が習得できます:
1. C# プログラミングスキル
- 基本的な構文とデータ型: C#の変数、条件文、ループなどの基礎から始めて、より複雑な構造とデータ型の操作方法を学びます。
- メソッドとパラメータ: メソッドの定義、オーバーロード、デフォルト引数の使い方。
- クラスとオブジェクト指向プログラミング: クラスの定義、継承、抽象クラス、インターフェース、ポリモーフィズムなどのオブジェクト指向の概念。
- 例外処理: エラーハンドリングと例外の管理方法。
2. Unity 特有のスキル
- Unity APIの使用:
MonoBehaviour
を継承したクラスの作成、Unityライフサイクルメソッド(例:Awake
,Start
,Update
)の理解と活用。 - コンポーネントベースのアーキテクチャ: Unityのコンポーネントシステムを利用して、再利用可能なゲームオブジェクトを構築する方法。
- アニメーションとステート管理:
Animator
コンポーネントとステートマシンの設定、アニメーションのトリガーと制御。 - 物理とコリジョン: Unityの物理エンジンを使用した動きと衝突の管理。
- ユーザー入力の処理: キーボード、マウス、その他の入力デバイスからの入力を受け取り、ゲーム内でのプレイヤーの操作に反映させる方法。
3. ゲームデザインの基本
- ゲームループとイベント駆動: ゲームのメインループとイベントベースのプログラミングの理解。
- ゲームの状態管理: ゲームの異なる状態(例:通常、攻撃、死亡)の管理と遷移。
- UIとUXの基本: ゲームのインターフェイス設計とユーザー体験の向上。
4. デバッグと最適化
- パフォーマンス分析: Unityプロファイラーの使用方法を学び、パフォーマンスボトルネックを特定し改善します。
- バグの特定と修正: エラーを効果的に識別し、デバッグ技術を使用して問題を解決します。
これらのスキルを習得することで、Unityでのゲーム開発の全般にわたる知識と技能を実践的に深めることができます。これはゲーム開発者としてのキャリアを形成し、多様なプロジェクトやチャレンジに対応できる基盤を築くのに役立ちます。
アルゴリズム(設計するパターン)で得られるスキル
1. コンポーネントベースのデザイン
Unityの強力な機能の一つは、コンポーネントベースのアプローチです。このコードを通じて、異なる機能をコンポーネントとして組み合わせる方法を理解し、効果的に利用することができます。例えば、MobStatus
やPlayerController
などのクラスが、特定の機能を実行する独立したコンポーネントとして設計されています。
2. ステートマネジメント
ゲームの各キャラクターの状態(例えば、攻撃中、移動中、死亡など)を管理する方法を学ぶことができます。これはゲームのロジックとプレイヤーのインタラクションの両方を流れるようにするために重要です。
3. イベント駆動プログラミング
Unityのイベントシステムを利用することで、より動的でインタラクティブなゲームを作成できます。例えば、OnTriggerEnter
メソッドを通じて、プレイヤーがアイテムに接触した際のイベントを捕捉し、所持品データにアイテムを追加する処理を行います。
4. 物理とコリジョンの扱い
Unityの物理エンジンを使用してキャラクターやオブジェクトの動きを制御する方法を学びます。これには、キャラクターコントローラー、ナビメッシュエージェント、コライダーなど、Unityの物理コンポーネントを効果的に使用することが含まれます。
5. アニメーションとビジュアルフィードバック
アニメーターコンポーネントを使用してキャラクターのアニメーション状態を制御し、ゲームプレイに視覚的なリッチネスとフィードバックを提供する方法を学びます。
6. パフォーマンスと最適化
このコードの分析から、ゲームのパフォーマンスを監視し、必要に応じて最適化する技術も学ぶことができます。特に大規模なゲームや多くのアクティブなオブジェクトを含む場合に重要です。
このように、このコードの分析はUnityエンジニアとしてのさまざまなスキルを磨くための素晴らしい基盤となります。ゲーム開発の実践的なスキルだけでなく、問題解決やデバッグの技術も同時に向上させることができます。
分析の上での注意
このコードの分析を学ぶことのデメリットや潜在的な制約を理解することも重要です。以下に主なポイントを挙げます:
1. 時間と労力の投資
- 学習曲線が急であるため、初学者にとっては圧倒されるかもしれません。特に、UnityやC#に慣れていない場合、基本から応用まで幅広い知識を習得するのに時間がかかります。
- コードを理解し、実際に使いこなすためには多くの実践と試行錯誤が必要です。これには多くの時間が必要とされる場合があります。
2. 複雑性の理解
- コードが複雑であるため、全ての要素を完全に理解するまでには深い分析と多くの背景知識が必要です。特に、継承、イベント、物理エンジンの使い方など、複雑なトピックを理解するのは難しいかもしれません。
- 不適切なコード設計やパターンを学習してしまうリスクもあります。コードが良い実践例であるとは限らないため、学ぶ際には批判的な目も必要です。
3. 更新と保守性
- UnityやC#は頻繁に更新されるため、学んだ内容がすぐに古くなってしまう可能性があります。これにより、新しい機能や改善されたプラクティスを逃すかもしれません。
- 古いバージョンのUnityに依存する特定のコードやAPIを使用している場合、将来的に互換性の問題が生じる可能性があります。
4. 特化しすぎるリスク
- UnityとC#に特化し過ぎると、他のゲームエンジンやプログラミング言語に対する適応力が低下するかもしれません。技術的な多様性を持つことも、キャリアにおいては重要です。
- ゲーム開発のみならず、他のアプリケーション開発にも通用するスキルを身につけることが重要です。UnityやC#だけに焦点を当てることで、より広範な技術的視野を狭める可能性があります。
結論
これらのデメリットを理解した上で、UnityとC#の学習に臨むことが重要です。効果的に学び、技術を最大限に活用するためには、これらの課題を認識し、適切に対処する方法を考える必要があります。
活用されているジャンル
このようなシステムは、様々なゲームメカニクスや設計パターンが含まれているため、特にアクション、アドベンチャー、RPG(ロールプレイングゲーム)などのジャンルに適しています。これらのジャンルではプレイヤーや敵の挙動、アイテムの管理、ステータス変化、そしてイベント駆動型のインタラクションが一般的に重要とされます。以下に各ジャンルとコードの活用例を示します:
1. アクションゲーム
- キャラクターの動きと衝突検出:
PlayerController
やEnemyMove
クラスを通じて、プレイヤーと敵キャラクターの移動を制御。これにはUnityのCharacterController
やNavMeshAgent
が使用されています。 - 戦闘と攻撃システム:
MobAttack
クラスを通じて、敵との戦闘やプレイヤーによる攻撃の管理が行われます。これにはアニメーショントリガーやコライダーを使った衝突処理が含まれます。
2. アドベンチャーゲーム
- アイテムの収集と使用:
OwnedItemsData
クラスがアイテムの所有情報を管理し、プレイヤーがゲーム内でアイテムを収集し利用する機能を提供します。これは探索や謎解き要素が豊富なアドベンチャーゲームにおいて重要な機能です。 - イベント駆動のストーリーテリング:プレイヤーの行動や選択によって異なるゲームの展開が生まれることを、イベントハンドラを通じて実装できます。
3. ロールプレイングゲーム (RPG)
- キャラクターステータスと進化:
MobStatus
とそのサブクラス(PlayerStatus
,EnemyStatus
)は、キャラクターの生命力(HP)や状態(攻撃、死亡など)を管理します。これはRPGにおけるレベルアップやスキルの進化に関連する処理に直接利用可能です。 - クエストと進行管理:ゲーム内の進行状況やクエストの達成を管理するための基盤を提供します。例えば、特定のアイテムが必要なクエストで
OwnedItemsData
クラスがアイテムの所有状態をチェックすることが可能です。
総合的な応用
このコード群は、Unityのコンポーネントベースのアーキテクチャを活用しており、異なるゲーム要素(キャラクターの挙動、アイテム管理、イベント処理)をモジュール化しています。これにより、さまざまなジャンルのゲームに柔軟に適用しやすくなっています。また、コードの再利用性が高く、新しいゲームのプロトタイピングや開発を迅速に行うことが可能です。
ディスカッション
コメント一覧
まだ、コメントがありません