アイテムやスキルのIDと固有のクラスを結び付けたい
記事の概要
ゲームにおけるスキルやアイテムなど、「いろいろな種類が存在していて、かつそれぞれが固有の動きをする」ものを実装する時のアイデア。
これが最適解とは全く思ってませんが、何かしらに使えるかもと思ってメモ。
備考
記事内ではUniRxを使用しています。
また、エフェクトとして以下のアセットを使用しています。
実装してみるもの
今回はスキルを例に実装を行ってみる。
これを実装するにあたって、下記のような設計で実装したい。
- 各種スキルのパラメータはScripatableObjectで管理。
- スキルごとに固有のパラメータが存在することも考慮したい。
- スキルごとに固有の制御クラスが存在しており、エフェクトの再生やダメージ処理などを行う。
- スキルごとの制御クラスはMonoBehaviourにしたくない。
- SkillManagerに渡すのはIdだけにしたい。
仮でボタンを押すとスキル発動という仕組みを作ってみる。
パラメータの実装 - ScripatableObject
(ScripatableObjectの説明は割愛します。)
スキルの情報やパラメータを管理するためのもの。
各スキル固有のパラメータ(画像1)を、SkillParameterHolder.asset(画像2)へ格納する。
各スキルのパラメータは SkillParameter_Base.cs を基底クラスとし、それぞれ固有のクラスとなっている。
この固有クラスが持つ GetSkillController を通して制御クラス(ISkillController)を取得することができる。
(ISkillControllerは後述。)
例として挙げるコードは SkillParameter_Fire.cs。
SkillParameter_Base.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* @ スキルパラメータSO 基底クラス
*
*/
public abstract class SkillParameter_Base : ScriptableObject
{
//! ID
[SerializeField]
protected int _id = 0;
public int Id => _id;
//! スキル名
[SerializeField]
protected string _skillName = "";
public string SkillName => _skillName;
//! エフェクト
[SerializeField]
protected GameObject _effectPrefab = null;
public GameObject EffectPrefab => _effectPrefab;
/*
* @ ISkillControllerを取得
*/
public abstract ISkillController GetSkillController();
}
SkillParameter_Fire.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* @ スキルパラメータSO 炎魔法
*
*/
[CreateAssetMenu(fileName = "SkillParameter_Fire", menuName = "ScriptableObjects/SkillParameters/SkillParameter_Fire")]
public class SkillParameter_Fire : SkillParameter_Base
{
//! 固有のパラメータ
[SerializeField]
protected float _uniqueParameter = 10f;
public float UniqueParameter => _uniqueParameter;
/*
* @ ISkillControllerを取得 SkillController_Fire
*/
public override ISkillController GetSkillController()
{
return new SkillController_Fire( this );
}
}
SkillParameterHolder はSkillParameter_Baseをリストとして持っているだけ。
GetParametrメソッドを通して、目的のスキルのパラメータを取得する。
SkillParameterHolder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* @ スキルパラメータSOをまとめる用のSO
*
*/
[CreateAssetMenu(fileName = "SkillParameterHolder", menuName = "ScriptableObjects/SkillParameterHolder")]
public class SkillParameterHolder : ScriptableObject
{
//! パラメータ群
[SerializeField]
protected List<SkillParameter_Base> _parameters = new List<SkillParameter_Base>();
/*
* @ IDからパラメータを取得
*/
public SkillParameter_Base GetParameter( int skillId )
{
var targetIndex = _parameters.FindIndex(x => x.Id == skillId);
if ( targetIndex < 0 ) return null;
return _parameters[targetIndex];
}
}
制御クラスの実装
スキルの挙動を実装するクラス。
スキルごとに固有の制御クラスが実装されており、それぞれが基底クラスの SkillController_Base とインタフェースである ISkillController を持っている。
SkillControllerはコンストラクタを通してパラメータを保持できるようにしている。
このパラメータはスキル固有のものにしたいので、SkillController_Base をジェネリッククラスにしている。
また、メインの処理は Main() 内に記述する。
このメソッドは SkillManager.cs から呼ばれる想定。
SkillController_Base.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* @ スキル制御 基底クラス
*
*/
public class SkillController_Base<P> : ISkillController
where P : SkillParameter_Base
{
//! パラメータ保持
protected P _parameter = null;
/*
* @ コンストラクタ
*/
public SkillController_Base( P parameter )
{
_parameter = parameter;
}
/*
* @ メインの処理
*/
public virtual void Main()
{
CreateEffect();
}
/*
* @ エフェクトの生成
*/
protected void CreateEffect()
{
GameObject.Instantiate( _parameter.EffectPrefab );
}
}
ISkillController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* @ スキル制御 インタフェース
*
*/
public interface ISkillController
{
/*
* @ メインの処理
*/
void Main();
}
各種スキルの固有クラスは以下のようになる。
SkillParameter_Fire で宣言した固有のパラメータにもアクセスが可能。
SkillController_Fire.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* @ スキル制御 炎魔法
*
*/
public class SkillController_Fire : SkillController_Base<SkillParameter_Fire>
{
/*
* @ コンストラクタ
*/
public SkillController_Fire(SkillParameter_Fire parameter) : base(parameter)
{
}
/*
* @ メインの処理
*/
public override void Main()
{
base.Main();
Debug.Log("Fire : " + _parameter.UniqueParameter);
}
}
Managerクラス
スキルの発動などを担うクラス。
今回はMonoBihaviourでボタンなどを持っているのが、本来はシングルトンでの運用を想定。
SkillActivateメソッドにスキルのIDだけを渡すだけのシンプルな実装になっている。
SkillManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UnityEngine.UI;
/*
*
* @ スキル関係の処理制御
* @ 本当はシングルトンにする方が良い
*
*/
public class SkillManager : MonoBehaviour
{
//! スキルデータ群
[SerializeField]
protected SkillParameterHolder _skillParameterHolder = null;
//! 炎魔法ボタン
[SerializeField]
protected Button _fireButton = null;
//! 雷魔法ボタン
[SerializeField]
protected Button _sparkButton = null;
//! 爆発魔法ボタン
[SerializeField]
protected Button _bombButton = null;
private void Start()
{
// ボタンでスキル発動
// ここら辺は適当
_fireButton.OnClickAsObservable().Subscribe( _ => SkillActivate(1) ).AddTo(this);
_sparkButton.OnClickAsObservable().Subscribe( _ => SkillActivate(2) ).AddTo(this);
_bombButton.OnClickAsObservable().Subscribe( _ => SkillActivate(3) ).AddTo(this);
}
/*
* @ スキル発動処理
*/
public void SkillActivate( int skillId )
{
// パラメータ取得
var parameter = _skillParameterHolder.GetParameter( skillId );
// コントローラ
var controller = parameter.GetSkillController();
// 発動
controller.Main();
}
}
おわりに
特に詳しい実装説明などはしていませんが、大まかにはソースコードにコメントを入れています。
きちんと使用するにはもっと増築が必要ですが、最低限何かしらに使えそうな形にはなったのではないかと。
一番の目的だった、「MangerにIDだけ渡せばスキルが発動できる」という部分は達成できました。
また、別案として「スキルの名称からクラス名を生成し、呼び出す」という方法も考えましたが、不都合が発生するのが目に見えていたので没にしました。
何かのお役に立てれば幸いです。
Discussion