命令模式:游戏开发设计模式之命令模式(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 = ;
defense = ;
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": ,
"defense": ,
"weapon": "Sword"
}

上面是本文的例子,但是如果敌人类是这样的:

    {
"MonsterName": "dwarf saber",
"HP": ,
"characteristic": "DEF up",
"weapon": "sword",
"attacks": ["hack","chop"]
}
{
"MonsterName": "dwarf archer",
"HP": ,
"characteristic": "DEF up",
"weapon": "bow",
"attacks": ["shoot","sight"] }
{
"MonsterName": "dwarf caster",
"HP": ,
"characteristic": "DEF up",
"weapon": "wand",
"magic": ["fire ball","ice storm"] }

好吧,虽然在矮人中caster这个职阶并不常见(武器想写破尽万法之符来着,但是fate里没有矮人英雄啊。。矮人还是多出现在欧洲风游戏里= =;)


以明显发现里面的HP,characteristic属性是一样的,因为都是矮人,矮人本身的特性都是一样的,这里我们就出现了重复,解决方法就是使用享
元模式,把相同的单拿出来,或者放在最普通的“原怪兽”或者是一个比较简单的怪兽里,再在其他怪兽中加一个这个原模型方便取值,这里博主把它放在矮人平民
中。享元模式可以节省大量空间和读取时间:

   {
"MonsterName": "dwarf civilian",
"HP": ,
"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 ":
}

长剑中就包含了剑类所有的基本属性,比如基础伤害,武器相克效果,等等等。。这就是细微的改变带来丰富的变化!

接下来要说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();
jsWrite.WritePropertyName("defense");
jsWrite.Write();
jsWrite.WritePropertyName("weapon");
jsWrite.Write("Sword");
jsWrite.WriteObjectEnd();
jsWrite.WriteObjectStart();
jsWrite.WritePropertyName("MonsterName");
jsWrite.Write("Animal");
jsWrite.WritePropertyName("attack");
jsWrite.Write();
jsWrite.WritePropertyName("defense");
jsWrite.Write();
jsWrite.WritePropertyName("weapon");
jsWrite.Write("tooth");
jsWrite.WriteObjectEnd();
jsWrite.WriteObjectStart();
jsWrite.WritePropertyName("MonsterName");
jsWrite.Write("Dragon");
jsWrite.WritePropertyName("attack");
jsWrite.Write();
jsWrite.WritePropertyName("defense");
jsWrite.Write();
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 = ; 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 

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

  1. 游戏开发设计模式之状态模式 & 有限状态机 & c#委托事件(unity3d 示例实现)

    命令模式:游戏开发设计模式之命令模式(unity3d 示例实现) 对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现) 原型模式:游戏开发设计模式之原型模式 & unity3d ...

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

    博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 打 算写设计模式的目的就是,首先自己可以理清思路,还有就是国内的设计模式资料很 ...

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

    前篇:游戏开发设计模式之命令模式(unity3d 示例实现) 博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 原理:从一个固定 ...

  4. C# Json反序列化 C# 实现表单的自动化测试<通过程序控制一个网页> 验证码处理类:UnCodebase.cs + BauDuAi 读取验证码的值(并非好的解决方案) 大话设计模式:原型模式 C# 深浅复制 MemberwiseClone

    C# Json反序列化   Json反序列化有两种方式[本人],一种是生成实体的,方便处理大量数据,复杂度稍高,一种是用匿名类写,方便读取数据,较为简单. 使用了Newtonsoft.Json,可以自 ...

  5. 设计模式_11_原型模式(prototype)深拷贝、浅拷贝

    设计模式_11_原型模式(prototype) 浅拷贝: package designPatternOf23; /** * 定义:用原型实例,指定创建对象的种类,并通过拷贝这些原型创建新的对象 * P ...

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

    一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...

  7. 乐在其中设计模式(C#) - 原型模式(Prototype Pattern)

    原文:乐在其中设计模式(C#) - 原型模式(Prototype Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:weba ...

  8. C#设计模式之六原型模式(Prototype)【创建型】

    一.引言 在开始今天的文章之前先说明一点,欢迎大家来指正.很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存.我认为这是不对的,因为拷贝出来的每一个对 ...

  9. C#设计模式之五原型模式(Prototype Pattern)【创建型】

    一.引言 在开始今天的文章之前先说明一点,欢迎大家来指正.很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存.我认为这是不对的,因为拷贝出来的每一个对 ...

随机推荐

  1. Azure cache 的配置与应用

    最近公司的项目要是用cloud Service 所以研究了下 Azure cache 的配置与使用. 首先创建项目 第二步 配置 cache worker role (1) 点击 cache work ...

  2. 从零开始 WIN8.1 下Android 开发环境搭建

    一.JDK安装 当前最新版本是JDK8.0 地址http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-21331 ...

  3. [设计模式]解释器(Interpreter)之大胆向MM示爱吧

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 “我刚写了个小程序,需要你来参与下.”我把MM叫到我的电脑旁,“来把下面这条命令打进去,这是个练习打(Pian)符(ni)号(de)的 ...

  4. 2013年12月26日 星期四 doxygen入门--很好

    body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...

  5. 常用sql笔记

    Student(S#,Sname,Sage,Ssex) 学生表Course(C#,Cname,T#) 课程表SC(S#,C#,score) 成绩表Teacher(T#,Tname) 教师表问题:1.查 ...

  6. 支付宝接口使用文档说明 支付宝异步通知(notify_url)与return_url.

    支付宝接口使用文档说明 支付宝异步通知(notify_url)与return_url. 现支付宝的通知有两类. A服务器通知,对应的参数为notify_url,支付宝通知使用POST方式 B页面跳转通 ...

  7. #Leet Code# Same Tree

    语言:Python 描述:使用递归实现 # Definition for a binary tree node # class TreeNode: # def __init__(self, x): # ...

  8. 为何要fork()两次来避免产生僵尸进程??

    最近安装书上说的,开始搞多进程了..看到了一个好帖子,学习学习 http://blog.sina.com.cn/s/blog_9f1496990100y420.html 首先我们要明白,为什么要避免僵 ...

  9. 转: QtCreator调试程序时GDB崩溃

    这个情况出现在QtCreator的2.5版以上,是由于新版QtCreator至少需要7.2 IIRC版的GDB.可以到:http://builds.qt-project.org/job/gdb-win ...

  10. HTML文档模式与盒模型

    HTML文档根据文档顶部的doctype声明来决定渲染模式,有标准模式(Standards Mode)与怪异模式(Quirks mode,或叫做混杂模式)两种模式. IE5及以前默认总是表现为怪异模式 ...