• 游戏开发设计模式之原型模式 & 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 

  • 相关阅读:
    Docker——搭建SFTP
    PicGo——利用PicGo和GitHub搭建免费图床提供给Typecho使用
    GitHub——如何生成Personal access tokens
    leetcode——两数相加【二】
    每天一道面试题——请实现add(1,2)(3)【二】
    leetcode——两数之和【一】
    每天一道面试题——JavaScript的this指向【一】
    PHP——安装ThinkPHP框架报错
    项目代码 if/else 过多,引起程序猿口吐莲花
    JDK9-JDK14 相关新特性说明及使用
  • 原文地址:https://www.cnblogs.com/zhanlang96/p/4890852.html
Copyright © 2020-2023  润新知