🍜

アイテムやスキルのIDと固有のクラスを結び付けたい

2024/10/05に公開

記事の概要

ゲームにおけるスキルやアイテムなど、「いろいろな種類が存在していて、かつそれぞれが固有の動きをする」ものを実装する時のアイデア。
これが最適解とは全く思ってませんが、何かしらに使えるかもと思ってメモ。

備考

記事内ではUniRxを使用しています。

また、エフェクトとして以下のアセットを使用しています。
https://assetstore.unity.com/packages/vfx/particles/spells/ky-magic-effects-free-21927?locale=ja-JP&srsltid=AfmBOoqPOanxQbiCISxxnmUtoicipWKLReU58BbDnlCyBboG_3m1tT2L

実装してみるもの

今回はスキルを例に実装を行ってみる。
これを実装するにあたって、下記のような設計で実装したい。

  • 各種スキルのパラメータはScripatableObjectで管理。
  • スキルごとに固有のパラメータが存在することも考慮したい。
  • スキルごとに固有の制御クラスが存在しており、エフェクトの再生やダメージ処理などを行う。
  • スキルごとの制御クラスはMonoBehaviourにしたくない。
  • SkillManagerに渡すのはIdだけにしたい。

仮でボタンを押すとスキル発動という仕組みを作ってみる。

パラメータの実装 - ScripatableObject

(ScripatableObjectの説明は割愛します。)
スキルの情報やパラメータを管理するためのもの。
各スキル固有のパラメータ(画像1)を、SkillParameterHolder.asset(画像2)へ格納する。

各スキルのパラメータは SkillParameter_Base.cs を基底クラスとし、それぞれ固有のクラスとなっている。
この固有クラスが持つ GetSkillController を通して制御クラス(ISkillController)を取得することができる。
(ISkillControllerは後述。)
例として挙げるコードは SkillParameter_Fire.cs。

SkillParameter_Base.cs
SkillParameter_Base
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
SkillParameter_Fire
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
SkillParameterHolder
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
SkillController_Base
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
ISkillController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/*
 * 
 *  @ スキル制御 インタフェース
 *
 */
public interface ISkillController
{
    /*
     * @ メインの処理
     */
    void Main();
}

各種スキルの固有クラスは以下のようになる。
SkillParameter_Fire で宣言した固有のパラメータにもアクセスが可能。

SkillController_Fire.cs
SkillController_Fire
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
SkillManager
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