1. asa_s

    No comment

    asa_s
Changes in body
Source | HTML | Preview

TL; DR

ゲーム開発において、複数作る必要があり、さらに後からも追加することになる「スキル」や「コマンド」の実装に、GoFのデザインパターンの1つ「Factory Method」パターンを使うことで明快な管理をしよう、という趣旨の記事になります。

Factory Methodパターン

Factory Methodパターンは簡単に言うと「物(Instance)を作る工場(Factory)」を用意して、多様なインスタンス生成はFactoryに任せてしまう、という考え方です。

例えばゲーム開発の例でいうと、「Lightning」、「Heal」という2つのスキル(class)があったとして、利用者が各スキルをインスタンス化するのではなく、SkillFactoryというクラスに「Lightning, Healを作ってくれ」といった風にお願いするイメージです。

サンプルコード

では実際の上記の例をサンプルコードで見ていきたいと思います。
(Nossa様のコメントを参考に修正しました)

SkillFactory.cs
using System;

/// <summary>
/// Skillを管理し、作ってくれるFactoryクラス
/// </summary>
public class SkillFactory 
{
    // スキル一覧
    static readonly AbstractSkill[] skills = {
        new LightningSkill(),
        new HealSkill()
    };

    /// スキルのenum
    public enum SkillKind
    {
        Lightning,
        Heal
    }

    // SkillKindを引数に、それに応じたスキルを返す
    public AbstractSkill Create(SkillKind skillKind) 
    {
        return skills.SingleOrDefault(skill => skill.SkillKind == skillKind);
    }

}
SkillFactory.cs
using System;
using System.Linq;

/// <summary>
/// Skillを管理し、作ってくれるFactoryクラス
/// </summary>
public class SkillFactory 
{
    // スキル一覧
    static readonly AbstractSkill[] skills = {
        new LightningSkill(),
        new HealSkill()
    };

    /// スキルのenum
    public enum SkillKind
    {
        Lightning,
        Heal
    }

    // SkillKindを引数に、それに応じたスキルを返す
    public AbstractSkill Create(SkillKind skillKind) 
    {
        return skills.SingleOrDefault(skill => skill.SkillKind == skillKind);
    }

}

上記はスキルを管理しインスタンスを返してくれる最もキモとなるFactoryクラスです。
ポイントはスキルのインスタンス自体を管理していることと、LINQで渡されたSkillKindとスキル自体のSkillKindが一致しているものを1つ返している、というところです。
これらは、LINQ(SingleOrDefault)を使わず、switchでも対応可能です。

 

AbstractSkill.cs
/// <summary>
/// スキルの抽象クラス
/// </summary>
abstract public class AbstractSkill 
{
    // スキル種別の抽象プロパティ
    public abstract SkillFactory.SkillKind SkillKind { get; }

    // スキル実行の抽象メソッド
    public abstract void Play();
}

上記は全てのスキルの継承元となる抽象クラスです。
今回は簡潔に、スキル種別と実行メソッドをそれぞれAbstractで定義しています。
 

LightningSkill.cs
using UnityEngine;
/// <summary>
/// スキル「ライトニング」の具象クラス
/// </summary>
public class LightningSkill : AbstractSkill 
{
    // スキル種別
    public override SkillFactory.SkillKind SkillKind
    { 
        get {return SkillFactory.SkillKind.Lightning;}
    }

    // スキル「ライトニング」の実行
    public override void Play() {
        Debug.Log("Lightning!");
    }
}
HealSkill.cs
using UnityEngine;
/// <summary>
/// スキル「ヒール」の具象クラス
/// </summary>
public class HealSkill : AbstractSkill 
{
    // スキル種別
    public override SkillFactory.SkillKind SkillKind
    { 
        get {return SkillFactory.SkillKind.Heal;}
    }

    /// スキル「ヒール」の実行
    public override void Play() {
        Debug.Log("Heal!");
    }
}

上記は実際のスキルになります。
Playの中身は、サンプルコードでは違いがわかるようにUnityのコンソールに出力するだけ、となっています。実際の実行処理をここに書いていくことになります。

以上で準備は完了です。
では最後に、実行です。

プレイヤーはキーボードの「L」を押すことでスキル「ライトニング」を、「H」を押すことでスキル「ヒール」をセットし、「S」で実行する簡易サンプルコードです。

Player.cs
...

    // 選択中のスキル
    private SkillFactory.SkillKind selectedSkillKind;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.L)) {
            Debug.Log("Skill [Lightning] Selected!");
            this.selectedSkillKind = SkillFactory.SkillKind.Lightning;
        }

        if (Input.GetKeyDown(KeyCode.H)) {
            Debug.Log("Skill [Heal] Selected!");
            this.selectedSkillKind = SkillFactory.SkillKind.Heal;
        }

        if (Input.GetKeyDown(KeyCode.S)) {
            var skillFactory = new SkillFactory();
            var skill = skillFactory.Create(this.selectedSkillKind);
            skill.Play();
        }
    }

...

スクリーンショット 2018-09-26 23.09.52.png  実行結果

同じ「S」キーを押した処理でも、this.selectedSkillKindによって実行するスキルが変わっていることがわかります。

要点をまとめると、LightningやHealなどのスキルは全て抽象クラスのAbstractSkillを継承し、継承先のPlay()に実際の実行処理を書くことで、利用者(Player)側は何のスキルかを意識せず実行することができます。そのため新スキルを追加する際は、新スキルのクラスとFactory側の修正、「選択中のスキル」の管理で対応できます。

また、スキルの必要MPやダメージ量、スキル自体のType(攻撃、回復、バフ、デバフなど)のenumなどを抽象クラスで定義しておき実装を強制することで、安全に他の人も新スキルを追加していくことができます。

 

以上です。
何かご指摘やもっと良い方法等がありましたらコメントお願いします!