刨根问底U3D---如何退出Play模式后保留数据更改
实际中遇到的需求
在做一款对抗类游戏,目前正在调整游戏的平衡性 所以就产生了一个需求 希望可以在Play模式时候对数据源做的更改可以在退出时候被保存下来。
举个Case, 比如 有一个炮塔 可以发射子弹, 然后有一组敌人 去攻击这个炮塔.
首先点击Play按钮 开始执行游戏逻辑
接着在Inspector中调整相应的数值,比如 射速啊,血量啊,敌人移动速度等等.
按原始的做法 就是调整以后 把所有的数据记录下来(截图或者写纸上)
点Stop进入编辑模式,所有数据回滚原始的 然后再一个一个把刚才改过的再重新填上去
一次两次还好,不过平衡性这东西 必然要经常调整 每次这样调整 非常影响工作效率,那有没有办法 可以让U3D在退出Play模式后保留对应的修改呢?
就此我还提了一个问题http://ask.unitymanual.com/question/37917 不过也没得到比较满意的答案 :(
只好自己瞎折腾了..
实现这个Case的思路
其实思路很简单, 因为 MonoBehaviour 有 void OnApplicationQuit (){} 函数 所以 在该函数内部 序列化相应的Class 然后保存到本地
当下次Awake 时候在反序列化 即可
也是就是
Awake
- 1·从本地Load
- 2·反序列化
OnApplicationQuit
- 3·序列化
- 4·保存到本地
想法很简单 不过却遇到了不少问题..
LitJson 无法序列化float类型
LitJson 用的人应该很多吧,反正我自己一直在用,序列化首先就想到它了,不过JsonMapper.ToJson (this); 直接报错
JsonException: Max allowed object depth reached while trying to export from type System.Single
问答里面一个哥们儿说他那里没问题,我就Google了一下 发现也有几个人遇到了我同样的问题,并且我把所有float都去了 一切就都ok了
网上帖子里面是建议用doulbe然后再自己转换,想想实在太麻烦. 后来又在GoogleCode搜到一个自己改过的LitJson不过也觉得不好.
最后还是在GitHub上发现一个很NB的库
Full Serializer https://github.com/jacobdufault/fullserializer
代码结构很清晰 并且使用起来也很简单. 试了一下float的问题 完美Fix了. 这样第一个问题就算搞定了
MonoBehaviour 无法被New 出来
序列化的问题是解决了,不过反序列化时候就又有问题了.. 写好反序列化代码 然后运行 直接报Warning
You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
写的很清楚了, MonoBehaviour 不能用new,只能AddComponent, 并且直接序列化MonoBehaviour到JSON以后里面有很多乱七八糟的东西,比如gameobjct,transforme等等.. 所以看来这条路是走不通了.
嵌套类或Dictionary<string,object>
因为是希望Play时候可以在Inspector中编辑,但是同时又不能是MonoBehaviour(否则无法序列化) 所以想到的两个解决方法 一个就是用单写一个Vo类,然后在MonoBehaviour里面写get/set 方法
Ex: public class MetaManger:MonoBehaviour { class MetaVo { public float speed; } private MetaVo mVo; public float Speed { get{return mVo.speed;} set{mVo.speed = value;} } }
这样做应该是可以的(抱歉我没有尝试),不过问题就是 每次新加入一个属性时候要相应的写get/set方法 很麻烦.
第二个方法就是 使用反射,在序列化时候把所有Property Add 到 一个Dictionary<string,object>里面 然后反序列时候再 反射还原。
理论上这个应该是可行的,不过反射弄起来很费劲 尤其是如果出现嵌套Array,枚举啊 什么这种不太普通类型时候.. 应该是个坑 果断放弃了这个思路
那有没有简单可行方法 来实现这个需求呢? 答案是有的(折腾了一天啊...), 不过在这之前 还是先了解一些知识(开始刨根问底..)
为什么U3D 在退出编辑时候无法保存数据?
Google了很多不过没有及时保存 已经找不到了 不过大体的意思就是
Unity会在Play时候 把当前所有所有的数据 序列化一份,然后在退出的时候再全部反序列化回去 就相当于RollBack了 所以 所有的改变自然都回去了.
[System.Serializable] 标签
这个也是我Google时候查到的,
在之前Case中 我也尝试过不对MetaVo的每个属性写get/set方法 就是在MetaManger中 直接public MetaVo vo; 这样Inspector中 无法看到
MetaVo中的内部属性,只能看到一个Vo,但是 如果对MetaVo加上了[System.Serializable]标签 则可以在Inspector中查到了
Google了一下 大部分人都是在复制粘贴 就说加上以后Inspector中就可以显示了. Ok 可是为什么呢?
首先来看下官网这篇文章 http://docs.unity3d.com/Manual/script-Serialization.html(这篇文章写的很好,如果没读过的朋友建议仔细看看)
Inspector window. The inspector window doesn’t talk to the C# api to figure out what the values of the properties of whatever it is inspecting is. It asks the object to serialize itself, and then displays the serialized data.
个人理解的inspector组件的工作流程(未被证实,但是可以解释通)
我们先假设inspector是用js写的,脚本代码是用C#写的,底层U3D是C++写的. 序列化的方式采用JSON 那假象的交互流程就是
首先点击Play按钮
Unity通过momo 把C# 中的代码都序列化成JSON 保存在一块内存里面
inspector中读取这块内存中的JSON,然后反序列化成js中可识别的数据类型 接着显示在面板上面
用户在inspector中修改相应数据,inspector把修改的数据再序列化成JSON 放回那块内存里面
底层U3D代码 反序列化那个json到C++中可识别的数据类型,然后再根据相应的数值 操作场景上面的对象渲染
这个整个流程肯定是YY的 不过里面的逻辑我觉得应该是对的 就像文档中那句说的
Serialization of “things” is at the very core of Unity. Many of our features build ontop of the serialization system:
那整个又和[System.Serializable]标签有什么关系呢?
其实这个是因为,Unity只能序列化MonoBehavior和后面要提到的ScriptableObject 对于其他类型 他不知道这是什么东西 所以自然无法序列化了,无法序列化 自然也就无法在inspector中显示出来了
http://www.cnblogs.com/oldman/articles/2409523.html 这篇也提到了我说的这个问题
有时候我们会自定义一些单独的class/struct, 由于这些类并没有从 MonoBehavior 派生所以默认并不被Unity3D识别为可以Serialize的结构。自然也就不会在Inspector中显示。我们可以通过添加 [System.Serializable]这个Attribute使Unity3D检测并注册这些类为可Serialize的类型。
当然U3D 自己的序列化中还有很多需要注意的,比如什么类型无法序列化,包括对于复杂类型如何写自己的序列化Callback函数. 我就不一一说了
http://docs.unity3d.com/Manual/script-Serialization.html 这篇文章已经说的很全了.
第二个要说的就是ScriptableObject
Unity对于这种Vo数据类型的存储及序列化有着自己的解决方案 就是 ScriptableObject
http://docs.unity3d.com/Manual/class-ScriptableObject.html
ScriptableObject is a class that allows you to store large quantities of shared data independent from script instances.
具体如何使用 可以参考
http://ivanozanchetta.com/gamedev/unity3d/unity-serialization-behind-scriptableobject/
http://maluoi.blogspot.com/2014_05_01_archive.html
http://godstamps.blogspot.com/2012/02/unity-3d-scriptableobject-assetbundle.html
思路就是 首先通过编辑器先产生一个ScriptableObject类型的asset文件,然后在对应的把GameObject和这个文件Link起来. 具体的思路在解决方案二中有提及
有一点需要仔细理解一下 就是文档中提到的那个4MB 和 40MB 的例子 我个人认为 这个应该是ScriptableObject的核心。
ok废话就到这里 说两种我已经试验成功的解决方案
解决方案一
使用 [System.Serializable]标签 + Full Serializer。 这个方案应该很好理解 还是之前Case中的四步
Full Serializer解决的是
无法序列化float类型的问题,[System.Serializable]标签 解决的是直接public MetaVo vo 无法在inspector中显示的问题
public class MetaManger:MonoBehaviour { [System.Serializable] class MetaVo { public float speed; } public MetaVo vo; }
(抱歉 代码没有试 感觉应该可以,因为我实际的代码 要涉及到单例 以及个个Serializable的类嵌套 比较复杂 无法直接贴上来)
只要把MetaVo 前面加上[System.Serializable]Tag 即可 目前实际项目中 使用一切都Ok ,其中包括
1`对于 [System.Serializable]内部嵌套另一个 [System.Serializable] 的 类, inspector中显示OK 并且有折叠 编辑起来很舒服
Full Serializer序列化和反序列化也Ok 毕竟这只是普通类而已..
2`类中包含有枚举类型,inspector显示ok ,Full Serializer 序列化也ok 并且序列化后的JSON中枚举类型被转化为String,这样即使以后枚举中有增删改 照样可以反序列化回来 不知道LitJSON是不是 没有试过
解决方法二
第一种方案 其实是挺正统的一种方法,就是 做一个单例的MetaManger 然后所有的实例对象 比如炮塔啊 子弹啊 敌人啊 当需要相应数据时候 向MetaManger中去要.
不过 用ScriptableObject的特性 可以用另外一种方式 来做.
把刚才的例子变得复杂一些, 炮塔变成 红黄两种, 炮塔属性相同 都只有一个speed,和damage项需要配置 此时可以
1· 写MetaClass
public TowerMeta:ScriptableObject { public float speed; public float damage; }
2· 执行命令生成两个asset 一个交 RedTowerMeta.asset, 一个叫 YellowTowerMeta.asset
3· 写TowerRender类
public TowerRender:MonoBehaviour { public TowerMeta meta; ... }
4· 在场景上建立 红黄两个塔实例,然后分别对应拖入RedTowerMeta.asset和YellowTowerMeta.asset并存成两个Prefab
5· 用代码或者直接拖拽都行,在场景上 建立10个红塔 10个黄塔
6· 点击Play按钮 然后 点击 Hierarchy中的RedTowerMeta.asset或者YellowTowerMeta.asset 此时 Inspector中 应该可以显示出来对应的数值的, 并且可以直接更改 即使退出Play模式以后不发生回滚
这里其实就是用了ScriptableObject得特性, 即使产生了10个红塔 10个黄塔 但是他们引用的Meta(ScriptableObject) 是同一份.
方案一和方案二的优劣性
方案一 最终生成的是JSON,优势是明文可读 项目大了以后 可以单独写编辑器解析对应JSON 进行编辑适配。劣势 就是JSON体积大 体积大 需要自己压缩.
方案二 优势是生成的asset是被压缩过的体积小,并且和Unity配合很紧密 一切托托拽拽即可 也不用管初始化 每个类Start()以后直接取用就好
劣势 就是一个是要写命令产生对应的asset 第二个就是 如果数据格式发生变更 比如 TowerMeta中加入新的属性 int cost ,那之前的asset就不能用了
因人而宜吧,我个人是比较倾向于 [System.Serializable] + Full Serializer 操作起来很方便 :)
Ok 就叨唠这么多好了
Thanks
Best
Eran
刨根问底U3D---如何退出Play模式后保留数据更改的更多相关文章
- 4、flask之分页插件的使用、添加后保留原url搜索条件、单例模式
本篇导航: flask实现分页 添加后保留原url搜索条件 单例模式 一.flask实现分页 1.django项目中写过的分页组件 from urllib.parse import urlencode ...
- flask之分页插件的使用、添加后保留原url搜索条件、单例模式
本篇导航: flask实现分页 添加后保留原url搜索条件 单例模式 一.flask实现分页 1.django项目中写过的分页组件 from urllib.parse import urlencode ...
- Linux中退出编辑模式的命令
vim 有三种模式,注意:这三种模式有很多不同的叫法,我这里是按照鸟哥的linux书中的叫法. 一般指令模式.编辑模式.指令列命令模式 1.vim 文件名 进入一般模式: 2.按 i 进行编 ...
- 退出recoveyr模式的iOS设备
大致分析了一下几款工具,大概流程是: 使用AMDRestoreRegisterForDeviceNotifications来监听设备的连接. 监听设备连接的回调函数中获取设备的句柄. 调用AMReco ...
- Wind7外接显示器选择拓展模式后,鼠标只能往右移动才能切换到外接显示器上,不能修改切换方向
win7外接显示器选择拓展模式后,为什么鼠标只能往右移动才能切换到外接显示器上,不能修改切换方向 打开控制面板-->显示 其他不变的情况下,鼠标拖动上面的两个显示器图标,拉出你希望的方向即可.
- C# 版本的 计时器类:精确到微秒 秒后保留一位小数 支持年月日时分秒带单位的输出
class TimeCount { // 临时变量,存放当前类能表示的最大年份值 ; /// <summary> /// 获取毫秒能表示的最大年份数 /// </summary> ...
- struts2 jsp表单提交后保留表单中输入框中的值 下拉框select与input
原文地址:struts2 jsp表单提交后保留表单中输入框中的值 下拉框select与input jsp页面 1 function dosearch() {2 if ($(&q ...
- 编辑后保留原URl搜索条件
首先需要知道的一个知识点: 1.request.GET是一个QueryDict类型的,要想取出?后面的结构就用request.GET.urlencode() 2.request.GET默认是不可修改的 ...
- MCU ADC 进入 PD 模式后出现错误的值?
MCU ADC 进入 PD 模式后出现错误的值? 在调试一款 MCU,最开始问题是无法读到 ADC 的值,应该是读到的值是异常高. 怀疑问题 可能是主频太低,为了降低功耗,这个 MCU 主频被我降了很 ...
随机推荐
- URAL 1117. Hierarchy(DP)
题目链接 这破题,根本看不懂题意啊...题意:一棵中序遍历是1 2 3 4 5...的满二叉树,从a a+1 a+2 a+3 b,总共多少步.x到y的距离为中间有多少个点.a > b没注意2Y. ...
- HDU 4597 Play Game
题目链接 什么都不想说,最近状态暴跌.. #include <cstdio> #include <cstring> #include <iostream> usin ...
- BZOJ3835: [Poi2014]Supercomputer
Description Byteasar has designed a supercomputer of novel architecture. It may comprise of many (id ...
- 硬盘分区工具gparted使用
一.介绍 GParted是一款linux下的功能非常强大的分区工具,和windows下的‘分区魔术师’类似,操作和显示上也很相似.GParted可以方便的创建.删除分区,也可以调整分区的大小和移动分区 ...
- Spring MVC中处理静态资源的多种方法
处理静态资源,我想这可能是框架搭建完成之后Web开发的”头等大事“了. 因为一个网站的显示肯定会依赖各种资源:脚本.图片等,那么问题来了,如何在页面中请求这些静态资源呢? 还记得Spring MVC中 ...
- 二 、打开地图《苹果iOS实例编程入门教程》
该app为应用的功能为给你的iPhone打开google地图有效地址连接 现版本 SDK 8.4 Xcode 运行Xcode 选择 Create a new Xcode project ->Si ...
- 坐标系统与投影变换及在ArcGIS桌面产品中的应用
坐标系统与投影变换及在ArcGIS桌面产品中的应用 1.地球椭球体(Ellipsoid) 2.大地基准面(Geodetic datum) 3.投影坐标系统(Projected Coordinate S ...
- php归档函数(按时间)实现
今日开发本站需要用到按时间归档文章的功能,即按文档发布时间将文章文类,以实现检索和统计功能,于是自己写了一个, 现分享给大家,相信大家工作和学习中有可能会用到,实现原理很简单,即取出文章发布时间戳的年 ...
- 可能碰到的iOS笔试面试题(4)--C语言
可能碰到的iOS笔试面试题(4)--C语言 可能碰到的iOS笔试面试题(4)--C语言 C语言,开发的基础功底,iOS很多高级应用都要和C语言打交道,所以,C语言在iOS开发中的重要性,你懂的.里面的 ...
- ASPCMS标签教程
导航栏调用{aspcms:navlist type=0} <a href="[navlist:link]">[navlist:name]</a>{/a ...