游戏开发设计模式之原型模式 & unity3d JSON的使用(unity3d 示例实现)

命令模式:游戏开发设计模式之命令模式(unity3d 示例实现)

对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现)

实现原型模式

原型模式带来的好处就是,想要构建生成任意独特对象的生成类,只需要一个生成类和一个原型即可。
当我们有一个抽象的敌人Monster类就有很多继承它的各种各样的敌人,人类、动物、龙等等,如果我们想为每个敌人做一个生成器父类Spawner,也会有与monster对应数量的子类,也许就会这样:
 
这样就会产生类的数量变多,而且这些类的功能是重复的。
开始的spawner类可能是这样的:

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour {
  public GameObject createPerson(GameObject Person)
  {
      return Instantiate(Person);
  }
  public GameObject createAnimal(GameObject Animal)
  {
      return Instantiate(Animal);
  }
  public GameObject createDragon(GameObject Dragon)
  {
      return Instantiate(Dragon);
  }
}

上面的代码可见我们有重复的方法,而且随着敌人子类增多这种重复代码会越来越多。
我们可以视所有怪兽为一个原型,让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 

时间: 2024-08-09 06:21:44

游戏开发设计模式之原型模式 & unity3d JSON的使用(unity3d 示例实现)的相关文章

游戏开发设计模式之对象池模式(unity3d 示例实现)

前篇:游戏开发设计模式之命令模式(unity3d 示例实现) 博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 原理:从一个固定的池中重用对象,来提升性能和内存的使用,而不是一个一个的分配内存在释放它们.当你需要创造大量重复的对象,而且经常使用这些对象,你就要考虑使用对象池了,因为反复创建销毁就是一个内存反复分配与释放的过程,很容易产生内存碎片.在主机和移动端与PC相比内存稀缺,我们都希望游戏能够更加稳定,而不能有效的管理内

cocos2dx游戏开发——设计模式

一.二段设计模式 二段构建模式就是将内存空间的分配和初始化分开来完成,然后调用一个静态方法来返回这个对象. 调用Sprite::create()的时候内部先使用new来分配内存空间,然后调用init方法来初始化一些变量的设置.所以cocos2dx中的二段构建模式就是将new分配内存空间和init初始化内容分开来处理,而不是c++传统的做法在构造函数中初始化变量. Sprite* Sprite::create() { Sprite *sprite = new Sprite(); if (sprit

【设计模式】——原型模式

原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 下图是原型模式的结构图: 原型模型其实就是一个对象再创建另外一个可定制的对象,而且不需任何创建的细节,我们来看看基本的原型模式代码. //原型类 class Prototype { private: string id; public: Prototype(string id) { this->id=id; } string GetId() { return id; } virtual Protot

大话设计模式_原型模式(Java代码)

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 简单描述:即通过实现接口Cloneable重写方法clone(),使得创建新的拷贝对象不需要一个成员一个成员的重新复制,而且可以提高创建对象的效率 Java中要想实现拷贝使用clone()方法,类必须实现Cloneable接口,并且重写Object类中的clone()方法,调用父类的clone()方法即可实现浅复制 代码如下: WorkExperience类: 1 package com.longsheng.prototy

Unity3D游戏开发从零单排(五) - 导入CS模型到Unity3D

游戏动画基础 Animation组件 Animation组件是对于老的动画系统来说的. 老的动画形同对应的动画就是clip,每个运动都是一段单独的动画,使用Play()或CrossFade(),直接播放动画 或淡入淡出播放动画. animation.Play("name"); animation.CrossFade("name"); 下面的是它的几个属性 Animation:默认的动画片段: Aniamtions:包含的动画片段: Play Automaticall

大话设计模式之原型模式

原型模式 定义: 用原型实例制定创建对象的种类,并通过拷贝这些原型,创建新的对象. 实质: 就是从一个对象在创建另一个可定制的对象,而且不需要知道任何创建的细节. 核心: (1).实现Cloneable接口,可以使用此接口的类上使用clone方法. (2).重写Object类中的clone方法,因为所有类的父类是Object类,Object有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,将clone改为public类型. 结构图: 代

深入浅出设计模式 ------ Prototype(原型模式)之深度克隆

继上篇深入浅出设计模式 ------ Prototype(原型模式)的浅克隆实现, 本文进入Prototype(原型模式)的进阶篇----深度克隆. 深度克隆 ---- 序列化方式实现 把对象写到流里的过程是序列化(Serilization)过程,而把对象从流中读出来的过程则叫做反序列化(Deserialization).写在流里的是对象的一个克隆(新的, 独立的), 而原对象仍存在于JVM内存模型里.因此, 以下代码采用序列化方式实现深度克隆. 第一步: 将上篇的代码做些许改动, 加入对象引用

C#设计模式(6)——原型模式(Prototype Pattern)

一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在内存中分配了多个一样的类实例对象,然后如果采用工厂模式来创建这样的系统的话,随着产品类的不断增加,导致子类的数量不断增多,反而增加了系统复杂程度,所以在这里使用工厂模式来封装类创建过程并不合适,然而原型模式可以很好地解决这个问题,因为每个类实例都是相同的,当我们需要多个相同的类实例时,没必要每次都使

创建型设计模式 之 原型模式

同为创建型模式的原型模式与单例模式是密不可分的,这也是最常用的设计模式之一. 原型模式是一种非常简单的设计模式.这里除了基本介绍和演示,还详细介绍了Java中原型模式的本质. 一.介绍 同样,先来看一下<研磨设计模式>的定义——用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 原型模式的本质——克隆生成对象. 那么原型模式是什么意思呢?说白了就是克隆自身.我们知道Java中没有引用这个概念,Java用变量名代表引用.像 Apple a = new Apple();我们知道,想要操