游戲開發設計模式之原型模式 & unity3d JSON的使用(unity3d 示例實現)


命令模式:游戲開發設計模式之命令模式(unity3d 示例實現)

對象池模式:游戲開發設計模式之對象池模式(unity3d 示例實現)

 

 

實現原型模式

原型模式帶來的好處就是,想要構建生成任意獨特對象的生成類,只需要一個生成類和一個原型即可。
當我們有一個抽象的敵人Monster類就有很多繼承它的各種各樣的敵人,人類、動物、龍等等,如果我們想為每個敵人做一個生成器父類Spawner,也會有與monster對應數量的子類,也許就會這樣:
 
這樣就會產生類的數量變多,而且這些類的功能是重復的。
開始的spawner類可能是這樣的:

 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class Spawner : MonoBehaviour {
 5   public GameObject createPerson(GameObject Person)
 6   {
 7       return Instantiate(Person);
 8   }
 9   public GameObject createAnimal(GameObject Animal)
10   {
11       return Instantiate(Animal);
12   }
13   public GameObject createDragon(GameObject Dragon)
14   {
15       return Instantiate(Dragon);
16   }
17 }

 



上面的代碼可見我們有重復的方法,而且隨着敵人子類增多這種重復代碼會越來越多。
我們可以視所有怪獸為一個原型,讓Spawner類只生成這個原型,通過改變這個原型來生產不同的怪獸。
再進一步,我們可以讓這個原型有一個生成自己的方法,就不需要在Spawner類中new了只需要在Spawner類調用原型的方法就可以,我們做一個monster生成(克隆)自己的方法clone()。

using UnityEngine;
using System.Collections;

public class Monster : MonoBehaviour {
    public string MonsterName;
    public int attack;
    public int defense;
    public string weapon;

    // Use this for initialization
 /*   virtual public Monster clone()
    {
        return this;
    }*/
  public  GameObject clone()
    {
        return Instantiate(this.gameObject) as GameObject;
    }
}

 



這里存在一個深復制和淺復制的問題,C#數據類型大體分為值類型(valuetype)與引用類型 (referencetype)。對於值類型數據,復制的時候直接將數據復制給另外的變量, 而對於引用型變量而言,復制時,其實只是復制了其引用。復制引用的方式叫淺復制,而逐一復制被復制對象的數據成員的方式稱為深復制。
unity的Instantiate就是深復制GameObject
如果你想淺復制clone函數為:

  virtual public Monster clone()
    {
        return this;
    }

 



淺復制返回它本身的引用,如果你想深復制,就Instantiate一個新的:

  public  GameObject clone()
    {
        return Instantiate(this.gameObject) as GameObject;
    }

 



再看看子類

using UnityEngine;
using System.Collections;

public class AnimalMonster : Monster
{
    public AnimalMonster()
    {
        MonsterName = "Animal";
        attack = 8;
        defense = 15;
        weapon = "tooth";
    }

}

 



這樣每個敵人子類都有一個clone方法,就不需要每個都配一個Spawner類了,一個Spawner就可以。

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour {
    Monster prototype;
    // Use this for initialization
   public void setPrototype(Monster _prototype)
    {
        this.prototype = _prototype;
    }
    
  public  GameObject createMonster()
    {
        return prototype.clone();
    }
}

 



 
創建他們的方法也很簡單,設置原型,create。

            spawner.setPrototype(People);
            spawner.createMonster();
            spawner.setPrototype(Animal);
            spawner.createMonster();

 



克隆出的屬性值都和原型相同,如果當前怪獸處在某種狀態,比如,中毒、虛弱、灼燒,也可以被復制下來。
再進一步,我們可以通過構造函數實例化不同的spawner對象來代替不同的spawner子類,使spawner實體專一化的生成某種怪獸
u

sing UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour {
    Monster prototype;
    // Use this for initialization
   public Prototype(Monster _prototype)
    {
        this.prototype = _prototype;
    }
    
  public  GameObject createMonster()
    {
        return prototype.clone();
    }
}

Spawner PersonSpawner = new Spawner(People);
Spawner AnimalSpawner = new Spawner(Animal);
PersonSpawner.createMonster();
AnimalSpawner.createMonster();

 




利用泛型類實現原型模式

再進一步,我們可以建立一個SpawnerFor泛型類來更加專一的生成某種怪獸,SpawnerFor泛型類繼承自Spawner類

using UnityEngine;
using System.Collections;

public class SpawnerFor<T> : Spawner
    where T : Monster
{
    T prototype;
}

    Spawner s1;
    Spawner s2;
    void Start()
    {
     s1 = new SpawnerFor<PersonMonster>();
     s2 = new SpawnerFor<AnimalMonster>();
     s1.setPrototype(People);
     s2.setPrototype(Animal);
s1.createMonster();
     s2.createMonster();
}

 



如果不是返回gameobject,就要寫一個這樣的方法:

  public GameObject createMonster()
     {
         return new T();
     }

 





關於First Class type

最好的辦法是把類型當做參數付給了生成類,這種類型叫做First Class類型,這樣把要生成的怪獸的類型作為參數付給Spawner,Spawner就知道原型是要生成這種類型的參數了。
我們把類型分為三類:
First Class。該類型可以作為函數的參數和返回值,也可以賦給變量。
Second Class。該類型可以作為函數的參數,但不能從函數返回,也不能賦給變量。
Third Class。該類型作為函數參數也不行
也就是說First Class type可以把類型看作是對象來賦值、返回值等等。
但是在C++和C#中類的類型都不是First Class type,所以需要進行一些操作,像上面的方法那樣。

實現結果



對原型進行數據建模

我們通過數據建模data modeling把代碼變成實在的數據。
通 過這種方法我們不需要Monster的子類了,因為Monster里的屬性都是相同的,我們只需要一個Monster類和一個存了各種敵人的數據的文件即 可。這種方式的好處在數據量大的游戲中尤為明顯,省去大量代碼。我們首先需要把每個怪獸的屬性都儲存起來,然后能提供給Spawner讀取,這就是序列化 與反序列化。
也通過序列化和反序列化對原型進行數據建模。使用JSON,XML等等都可以,此處博主用JSON實現
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。它基於ECMAScript的一個子集。易於人閱讀和編寫,同時也易於機器解析和生成(網絡傳輸速率)。
JSON 有簡易的語法,XML有范的標簽形式,JSON和XML有一個很大的區別在於有效數據率。JSON作為數據包格式傳輸的時候具有更高的效率,這是因為 JSON不像XML那樣需要有嚴格的閉合標簽,這就讓有效數據量與總數據包比大大提升,從而減少同等數據流量的情況下,網絡的傳輸壓力

具體可以查看寫得很全很詳細

我們可以在代碼中生成JSON,也可以自己在txt中編寫,大概的格式是這樣的

       {
            "MonsterName": "Person",
            "attack": 10,
            "defense": 10,
            "weapon": "Sword"
        }

 



上面是本文的例子,但是如果敵人類是這樣的:
  

    {
            "MonsterName": "dwarf saber",
            "HP": 10,
            "characteristic": "DEF up",
            "weapon": "sword",
"attacks": ["hack","chop"]
        }
       {
            "MonsterName": "dwarf archer",
            "HP": 10,
            "characteristic": "DEF up",
            "weapon": "bow",
"attacks": ["shoot","sight"]

        }
       {
            "MonsterName": "dwarf caster",
            "HP": 10,
            "characteristic": "DEF up",
            "weapon": "wand",
"magic": ["fire ball","ice storm"]

        }

 

好吧,雖然在矮人中caster這個職階並不常見(武器想寫破盡萬法之符來着,但是fate里沒有矮人英雄啊。。矮人還是多出現在歐洲風游戲里= =;)

可 以明顯發現里面的HP,characteristic屬性是一樣的,因為都是矮人,矮人本身的特性都是一樣的,這里我們就出現了重復,解決方法就是使用享 元模式,把相同的單拿出來,或者放在最普通的“原怪獸”或者是一個比較簡單的怪獸里,再在其他怪獸中加一個這個原模型方便取值,這里博主把它放在矮人平民 中。享元模式可以節省大量空間和讀取時間:
   

   {
            "MonsterName": "dwarf civilian",
            "HP": 10,
            "characteristic": "DEF up",
        }
       {
            "MonsterName": "dwarf saber",
            "prototype": "dwarf civilian",
            "weapon": "sword",
"attacks": ["hack","chop"]
        }
       {
            "MonsterName": "dwarf archer",
            "prototype": "dwarf civilian",
            "weapon": "bow",
"attacks": ["shoot","sight"]

        }
       {
            "MonsterName": "dwarf caster",
            "prototype": "dwarf civilian",
            "weapon": "wand",
"magic": ["fire ball","ice storm"]
        }

 


也可以實現道具和武器的多元化,比如一把“火焰劍”就可以視為一把長劍,和附加傷害,還有一個好聽的名字:

       {
            "Name": "FireBlaze",
            "prototype": "long sword",
            " additionalDamage ": 10
        }

 



長劍中就包含了劍類所有的基本屬性,比如基礎傷害,武器相克效果,等等等。。這就是細微的改變帶來豐富的變化!

接下來要說JSON在unity中的代碼實現,
分為在txt中寫入數據叫做序列化,和讀取數據叫做反序列化,
首先我們需要LitJson.dll(文章最后github鏈接中含有本文測試所有游戲代碼和LitJson.dll)
在類前面需要:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LitJson;
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
#endif

 




在.net中的解析方式,在unity中用最后一種

 

主要類

命名空間

DataContractJsonSerializer

System.Runtime.Serialization.Json

JavaScriptSerializer

System.Web.Script.Serialization

JsonArrayJsonObjectJsonValue

System.Json

JsonConvertJArrayJObjectJValueJProperty

Newtonsoft.Json

先看寫入文件方法:

void WriteJsonToFile(string path, string fileName)
    {
        System.Text.StringBuilder strB = new System.Text.StringBuilder();
        JsonWriter jsWrite = new JsonWriter(strB);
        jsWrite.WriteObjectStart();
        jsWrite.WritePropertyName("Monster");//Monster為對象數組名
 
        jsWrite.WriteArrayStart();//對象數組

        jsWrite.WriteObjectStart();
        jsWrite.WritePropertyName("MonsterName");
        jsWrite.Write("Person");
        jsWrite.WritePropertyName("attack");
        jsWrite.Write(10);
        jsWrite.WritePropertyName("defense");
        jsWrite.Write(10);
        jsWrite.WritePropertyName("weapon");
        jsWrite.Write("Sword");
        jsWrite.WriteObjectEnd();
        jsWrite.WriteObjectStart();
        jsWrite.WritePropertyName("MonsterName");
        jsWrite.Write("Animal");
        jsWrite.WritePropertyName("attack");
        jsWrite.Write(8);
        jsWrite.WritePropertyName("defense");
        jsWrite.Write(15);
        jsWrite.WritePropertyName("weapon");
        jsWrite.Write("tooth");
        jsWrite.WriteObjectEnd();
        jsWrite.WriteObjectStart();
        jsWrite.WritePropertyName("MonsterName");
        jsWrite.Write("Dragon");
        jsWrite.WritePropertyName("attack");
        jsWrite.Write(100);
        jsWrite.WritePropertyName("defense");
        jsWrite.Write(200);
        jsWrite.WritePropertyName("weapon");
        jsWrite.Write("fire breath");
        jsWrite.WriteObjectEnd();

        jsWrite.WriteArrayEnd();
        jsWrite.WriteObjectEnd();
        Debug.Log(strB);
        //創建文件目錄
        DirectoryInfo dir = new DirectoryInfo(path);
        if (dir.Exists)
        {
            Debug.Log("This file is already exists");
        }
        else
        {
            Directory.CreateDirectory(path);
            Debug.Log("CreateFile");
#if UNITY_EDITOR
            AssetDatabase.Refresh();
#endif
        }
        //把json數據寫到txt里
        StreamWriter sw;
        if (File.Exists(fileName))
        {
            //如果文件存在,那么就向文件繼續附加(為了下次寫內容不會覆蓋上次的內容)
            sw = File.AppendText(fileName);
            Debug.Log("appendText");
        }
        else
        {
            //如果文件不存在則創建文件
            sw = File.CreateText(fileName);
            Debug.Log("createText");
        }
        sw.WriteLine(strB);
        sw.Close();
#if UNITY_EDITOR
        AssetDatabase.Refresh();
#endif

    }

 




然后是讀出,讀出方法我們放在Spawner類里

Monster ReadJsonFromTXT(string name)
    {
        //解析json
        Monster monster = new Monster();
        JsonData jd = JsonMapper.ToObject(txt.text);
        print(jd.IsArray);
        JsonData monsterData = jd["Monster"];
        print(monsterData.IsArray);
        //打印一下數組
        for (int i = 0; i < monsterData.Count; i++)
        {
            if (name == monsterData[i]["MonsterName"].ToString())
            {
                monster.MonsterName = monsterData[i]["MonsterName"].ToString();
                monster.attack = int.Parse(monsterData[i]["attack"].ToString());
                monster.defense = int.Parse(monsterData[i]["defense"].ToString());
                monster.weapon = monsterData[i]["weapon"].ToString();
            }
        }

        return monster;
    }

 

 


寫好的JSON可以在這個網站中測試http://www.bejson.com/,這是博主生成的JSON
 

實現結果

JSON測試結果,成功生成Monster
 


總結

基本的好處就是對象可以深復制自己,可以很方便有無差錯的生成實體,並且把本來大量的類和與之對應的生成類(而且還會隨着擴充增加!),縮小成一個原型類, 一個生成類,一個數據文件,減少了大量重復的,甚至不重復的代碼量!數據文件可以根據實際情況選擇xml或者是JSON或者是別的。
進一步考慮,玩家們都喜歡豐富的游戲,像這樣可以對數據進行微小的改動就會產生很多變化,代碼量花費很少,還能產生豐富的游戲世界,何樂而不為?

測試用全部代碼及dll文件已共享至GitHub

命令模式:游戲開發設計模式之命令模式(unity3d 示例實現)

對象池模式:游戲開發設計模式之對象池模式(unity3d 示例實現)

博主近期渲染:最近用unity5弄的一些渲染

 

                              ---- by wolf96 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM