こんにちは!河条です!
今回はUnityとC#でFlyweightというプログラム設計について学びましょう!
※事前知識:C#の基本構文、オブジェクト指向の知識
Flyweightパターンって?🤔
余分なオブジェクトの生成を防止、効率化するのに使えます!
例えばモンスターの絵柄は全部同じなのに、10体生成した数だけ同じように絵を生成してたら処理は重いしメモリ使うしであまりよくないですよね!
このモンスターのモデルデータやテクスチャデータを共有しよう!というのがFlyweightパターンです!
ひとまずどれくらい節約されるか結果から🤔
用途としてはあまり適切ではないですが、スライムそのものを10体普通に生成するパターンとFlyweight設計で生成したパターンでメモリの使用量を見てみましょ(わかりやすさ重視で)
まずは10体普通に生成するメイン処理!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/// <summary> /// ここで使っているスライム生成はinstantiate関数に変えても大丈夫だよ^o^ /// </summary> public class FlyweightPattern : MonoBehaviour { // Use this for initialization void Start () { long before_memory = System.GC.GetTotalMemory (false); Debug.Log ("生成前メモリ" + before_memory); for(int x=0; x<=9; ++x) { //for文を使ってスライムを10体生成 instantiate関数でも良い SlimeFlyweight normalslime = new Slime(); } long after_memory = System.GC.GetTotalMemory (false); Debug.Log ("生成後使用メモリ" + after_memory); long use_memory = after_memory - before_memory; Debug.Log ("使用メモリ" + use_memory); } } |
結果!
次はFlyWeight設計で10体生成したパターンのメイン処理!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/// <summary> /// ここで使っているスライム生成はinstantiate関数に変えても大丈夫だよ^o^ /// </summary> public class FlyweightPattern : MonoBehaviour { // Use this for initialization void Start () { long before_memory = System.GC.GetTotalMemory (false); Debug.Log ("生成前メモリ" + before_memory); //スライム生成するFactory SlimeFactory factory = new SlimeFactory (); for(int x=0; x<=9; ++x) { //Flyweight設計での10体スライム生成 SlimeFlyweight normalslime = factory.GetFlyweight ("NormalSlime"); } long after_memory = System.GC.GetTotalMemory (false); Debug.Log ("生成後使用メモリ" + after_memory); long use_memory = after_memory - before_memory; Debug.Log ("使用メモリ" + use_memory); } } |
結果!
普通の生成だとメモリ20Kバイト使っているのに対してFlyweight設計だと8Kバイトですね!
1つのオブジェクトのメモリ使用量と、生成数によってはもっともっと大きな効果が出そうです!
上のメイン処理って結局裏で何やっているの🤔
最低限必要なのは以下の2つです!
スライムそのもののクラス
スライムを生成するFactory(工場)
実際のコードを見てみましょう!
ただメイン処理では使ってないですが、Factoryクラスの利便性も紹介するために、スライムだけでなくキングスライムやメタルスライムのクラスも作ります!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// <summary> /// The 'Flyweight' interface class /// </summary> public interface SlimeFlyweight { void Apper(); } /// <summary> /// The 'Slime' class /// </summary> class Slime : SlimeFlyweight { //無駄にメモリ使わせる int[] a= new int[500]; public void Apper() { Debug.Log("スライムが現れた! "); } } /// <summary> /// The 'MetalSlime' class /// </summary> class MetalSlime : SlimeFlyweight { //無駄にメモリ使わせる int[] a= new int[500]; public void Apper() { Debug.Log("メタルスライムが現れた! "); } } /// <summary> /// The 'KingSlime' class /// </summary> class KingSlime : SlimeFlyweight { //無駄にメモリ使わせる int[] a= new int[500]; public void Apper() { Debug.Log("キングスライムが現れた! "); } } |
ここは特に説明いらないですかね🤔
それぞれのスライムのクラスを作ってインターフェースを用意させてます
肝は次のスライム工場です
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/// <summary> /// The 'SlimeFactory' class /// </summary> class SlimeFactory { private Dictionary<string, SlimeFlyweight> slimes = new Dictionary<string, SlimeFlyweight>(); // Constructor public SlimeFactory() { //slimesディクショナリーに共有化させたいオブジェクトを追加 instantiate関数でも良い slimes.Add("NormalSlime", new Slime()); slimes.Add("MetalSlime", new MetalSlime()); slimes.Add("KingSlime", new KingSlime()); } /// <summary> /// SlimeFactoryからオブジェクトを取得 /// </summary> /// <returns>指定したスライムをこのクラスから返す</returns> /// <param name="スライムの名前">slimename.</param> public SlimeFlyweight GetFlyweight(string slimename) { return ((SlimeFlyweight)slimes[slimename]); } } |
解説フェイズ🤔
やっていることは単純に3つです
1、工場クラスで共有化させたいオブジェクト(今回だとスライム、メタルスライム、キングスライム)をDictionaryに登録
2、工場クラスで生成されたオブジェクトを呼び出し側に渡す
3、呼び出し側は工場クラスを介してオブジェクトを操作する
オブジェクトの共有化と聞いて難しそうな処理してそうな感じでしたが、案外クラスもロジックも少ないですね!
実際にFlyweightパターン使うなら冒頭で書いた通りスライムオブジェクトのグラフィック等のが適切ですが(^o^)
Flyweightパターンは中規模以上のゲーム制作なら必ず必要な場面あると思います!ぜひ使ってみましょー!^o^
ちなみに他にもObjectPoolというオブジェクトの生成、破棄のコストを下げる設計もあるのですが、これは長くなりそうなのでまた。。。