はじめに
様々な言語で「デザインパターン」の本が世の中にありますが、筆者個人の経験では
いまいちピンとこない例
いまいちピンとこないコード
で説明されてることが多く、
結局これっていつ使うの?
という疑問に答えるには仕事仲間等との議論をしないと
辿り着けないことが多々ありました。
そこで特に「ゲーム開発ではどう使うか?」にフォーカスを当てて、実践的な例を交えて
デザインパターンの説明の需要があると思い記事を作りました。
デザインパターンを学ぶ理由
デザインパターンを学ぶ理由としては
- 車輪の再発明の防止
- 長文で読みにくいコード(可読性の低いコード)を減らす
- コードを疎結合にして変更に強くなる(変更時のコスト・変更箇所を減らす)
- モジュールとして使いまわせるように、コードの再利用性を高める
といった効果を期待できます。
対象読者
Unity 全くの初心者(インストールしただけで触ったことがないような方)はお断りです。
最低限以下のことは理解・経験を積んでおくことが必須になります。
- MonoBehaviour 継承クラスでコードを書いたことがある
- C# のピュアクラスを用いた自作クラスを作ったことがある
- クラスの継承という概念は知っている
そのため、脱・初心者
中級者へのステップアップ
として デザインパターンを学ぶ
のが良いと思います。
デザパタ記事リンク
生成系
構造系
様態・ふるまい系
- Chain of Responsibility パターン
- Command パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン(本記事)
- Observer パターン
- State パターン
- Strategy パターン
- TemplateMethod パターン
- Visitor パターン
Memento パターンについて
Memento と聞くと memento mori(死を想え)
を想起する人は立派な 厨二病 ゲーマー ・読書家・芸術肌の持ち主だと思います。この Memento
ですが、英語で 形見
の意味を持つ単語です。
つまり、 memento パターンは 形見の表現
ということになります。
より具体的な話で言うと あるインスタンスのスナップショットの取得
及び スナップショットからインスタンスの状態再現
を行うデザインパターンです。
Web系の話でいえば、データベースはバックアップをとるために、その時のスナップショットを撮っておき、復元するときにそのスナップショットを利用するという活用法があります。
ゲームにおける Mementoパターン
mementoパターンを実現するためには インスタンスのSnapshot
を パラメータクラスで表現
することが必要になります。
パラメータクラスのmementoパターン
インスタンスのスナップショットを撮る前に、簡単な事例としてパラメータクラスのメメントパターンを考えてみましょう。
本記事ではパラメータクラスとは以下のようなクラスのことを指します。
public class BaseCharaStatus{
public string Name;
public int MaxHP;
public int HP;
public int ATK;
public int DEF;
}
つまりロジックを持たず、純粋なゲーム内パラメータ定義クラスです。
ここではUnity特有のMonoBehaviour継承クラスではなくC#ピュアクラスであることが重要です。
このクラスのmementoパターンは単純で、クローンを作ることと、復元はコピーコンストラクタを用意するだけです。
ここで勘が良い人は 前に別のデザパタで似たような話をしたな?
と思うかもしれません。
まさにやっていることは prototypeパターン と同じです。
しかし、mementoパターンとprototypeパターンが大きく異なるのは次に紹介するインスタンスのmementoパターンです。
インスタンスのmementoパターン
インスタンス、ここではMonoBehaviour 継承クラスのコンポーネントがついた Instantiate()で生成されたObject
のことを指します。
MonoBehaviour 継承クラスのObject についてもう少し具体例を考えましょう。
using System;
using UnityEngine;
[Flags]
public enum BadStatus
{
Undefined = 0,
Sleep, // 睡眠
Paralyze, // 麻痺
Freeze, // 凍結
Confuse, // 混乱
Burn, // 火傷
Poison, // 毒
}
public class BaseCharaStatus{
public string Name;
public int MaxHP;
public int HP;
public int ATK;
public int DEF;
}
public class CharaObject : MonoBehaviour
{
public BaseCharaStatus myParameter = null;
public BadStatus MyBadStatus { get; protected set; }
}
CharaObject クラスは基本パラメータの BaseCharaStatus
と状態異常の BadStatus
と座標として Transform.position
を持っています。
このCharaObjectの状態を一時保存+復元をするためにはこれらのパラメータをまとめたCharaMemento クラスが必要になります。
public class CharaMemento
{
public BaseCharaStatus Status;
public BadStatus BadStatus;
public Vector3 Position;
}
また、このMementoクラスを用いて一時保存・復元処理を書くと以下のようになります。
public class CharaObject : MonoBehaviour
{
public BaseCharaStatus myParameter = null;
public BadStatus MyBadStatus { get; protected set; }
public CharaMemento createMemento()
{
var memento = new CharaMemento();
memento.Status = myParameter;
memento.BadStatus = MyBadStatus;
memento.Position = transform.position;
return memento;
}
public void SetMemento(CharaMemento memento)
{
myParameter = memento.Status;
MyBadStatus = memento.BadStatus;
transform.position = memento.Position;
}
}
このようにパラメータだけ切り出しておくと、元々のインスタンスを保存するより軽量にデータを保存でき、また復元する時にもパラメータをセットするだけで状態の復元が可能です。
この機能を利用した例としてはマルチプレイゲームにおいて、 通信のラグ
等でプレイヤー間の情報にズレが生じる場合、情報の同期として 親プレイヤーが持っているパラメータで全員上書きする
というやり方があります。
この時各プレイヤーのパラメータを上記のようにMementoパターンで別クラスにパラメータ出力をしておけば、その出力されたクラスを全員にBroadcastするだけで同期を行えるようになります。
また、オートセーブやクイックセーブなどの一時的なパラメータの保持もmementoパターン有効に働く例です。
実際のセーブデータのファイルI/Oをしなくてもメモリ上に一時的にmementoパターンで保存しておくことで、再現がすぐにしやすくなったりします。
まとめ
mementoパターンはスナップショット撮影+復元を行うためのデザインパターンです。
スナップショットだけならprototypeパターンもありますが、インスタンスのスナップショットはClone だけでは表現できないため、そこが大きな違いとなります。
マルチプレイゲームでは強制同期等で必須の技術となりますので、ぜひ覚えておくとよいでしょう