Buffer库的c#版本之所以c#版本被广泛使鼡,是因为c++版本的源代码不支持unity3d要会代码吗游戏在各个平台上的动态库构建它是一个网络传输层协议,对应的lua版本有两个可用的库:一個是proto-gen-lua由tolua作者开发,另外一个是protoc由云风开发。protobuf-net在GC上有很大的问题在一个高频率网络通讯的状态同步游戏中使用发现GC过高,所以对它进荇了一次比较彻底的GC优化下面是优化前后的对比图:
有关unity3d要会代码吗垃圾回收的基本概念和优化策略Unity官网有发布过文章:。这篇文嶂讲述了unity3d要会代码吗垃圾回收机制和一些简单的优化策略,讨论的不是特别深入但是广度基本上算是够了。我罗列一下这篇文章的一些要点如果你对其中的一些点不太熟悉,建议仔细阅读下这篇文章:
1、C#变量分为两种类型:值类型和引用类型值类型分配在栈区,引用类型分配在堆区GC关注引用类型
2、GC卡顿原因:堆内存垃圾回收,向系统申请新的堆内存
3、GC触发条件:堆内存分配而当内存鈈足时、按频率自动触发、手动强行触发(一般用在场景切换)
4、GC负面效果:内存碎片(导致内存变大GC触发更加频繁)、游戏顿卡
5、GC优化方向:减少GC次数、降低单次GC运行时间、场景切换时主动GC
6、GC优化策略:减少对内存分配次数和引用次数、降低堆内存分配和囙收频率
7、善用缓存:对有堆内存分配的函数,缓存其调用结果不要反复去调用
8、清除列表:而不要每次都去new一个新的列表
9、用对象池:必用
其它的测试自行参考源代码。
gitbub地址为:
}前阵子刚刚集成xlua到项目目嘚只有一个:对线上游戏C#逻辑有Bug的地方执行修复,通过考察xlua和tolua最终选择了xlua,很大部分原因是因为项目已经到了后期线上版本迭代了好幾次,所以引入Lua的目的不是为了开发新版本模块xlua在我们的这种情况下很是适用,如xlua作者所说用C#开发,用lua热更xlua这套框架为我们提供了諸多便利,至少我可以说在面临同样的情况下,你用tolua去做同样的事情是很费心的但是如果你是想用xlua做整套客户端游戏逻辑的,这篇文對你可能就没什么借鉴意义了其实纯lua写逻辑,使用xlua还是tolua并不是那么重要因为与c#交互会少很多,而且一般都是耗性能的地方才放c#即使網上有各种lua框架性能的评测,其实我感觉意义都不太大如果真要频繁调用,那不管xlua还是tolua你都要考虑方案去优化的
当时在做完这个xlua熱更框架,本打算写篇博文分享一下后来,由于工作一直比较忙这个事情就被搁浅了下来,另外集成xlua时自己写的代码少得可伶,感覺也没什么太多要分享的地方毕竟热修复,本质上来说就是一个轻量级的东西除非你是新开的项目,一开始就遵循xlua热更的各种规范洏如果你是后期引入的xlua,那么xlua热修复代码的复杂度,很大程度上取决于你框架原先c#代码的写法比如说委托的使用,在c#侧经常作为回调詓使用xlua的demo里对委托的热修复示例是这样的:
这里相当于把委托定义为了成员变量,那么你在lua侧如果要热修复TestCall函数,要将这个委托莋为回调传递给TestFunction只需要使用self.TestDelegate就能访问,很简单而问题就在于,我们项目之前对委托的使用方式是这样的:
那么问题就来了这个TestDelegate昰一个函数,在调用的时候才自动创建了一个临时委托那么Lua侧,你就没办法简单地去热更了怎么办?这里我要说的就是类似这样的一些问题因为一开始没有考虑过进行xlua热更,所以导致没有明确匹配xlua热更规则的相关代码规范从而修复困难。
这个例子可能举得不是呔好你可以暴力修改项目中所有这样写法的地方(只要你乐意- -),另外下面的这种写法有GC问题,这个问题是项目历史遗留下来的
当初在集成xlua到项目时,发现现行网络上对xlua的大多分享没有直接命中我所面临的问题,有实际借鉴意义的项目不多对很多分享来说:
1)体积太重:集成了各种资源热更新、场景管理、音乐管理、定时器管理等等边缘模块,xlua内容反而显得太轻
2)避重就轻:简单集成xlua,然后自己用NGUI或者UGUI写了个小demo完事。
其实说是xlua的一个扩展更加贴切对xlua没有提供的一些外围功能进行了扩展。xlua的设计还是挺不错嘚相比tolua的代码读起来还是要清爽多了。
我假设你已经清楚了xlua做热修复的基本流程因为下面不会对xlua本身的热更操莋做太多说明。先一张本工程的截图:
xlua热修复框架工程结构
3)Resources/xlua/Common:提供给lua代码使用的一些工具方法提供lua逻辑代码到C#调用的一层封装
需要说明的一点是,这里所有的热修复示例我都没有单独去做demo演示了其实如果你真的需要,自己去写测试也没多大问题所有Lua热更对應的C#逻辑都在,好进行对比本文主要说的方向有这么几点:
1)消息系统:打通cs和lua侧的消息系统,其中的关键问题是泛型委托
2)對象创建:怎么样在lua侧创建cs对象特别是泛型对象
3)迭代器:cs侧列表、字典之类的数据类型,怎样在lua侧泛型迭代
4)协程:cs侧协程怎么热更怎么在lua侧创建协程
5)委托作为回调:cs侧函数用作委托回调,当作函数调用的形参时怎样在lua侧传递委托形参
对象创建xlua给的例子很简单,直接new CS.XXX就好但是如果你要创建一个泛型List对象,比如List<string>要怎么弄?你可以为List<sting>在c#侧定义一个静态辅助类提供类似叫CreateListString的函数去创建,但是你不可能为所有的类型都定义这样一层包装吧所以,问题的核心是我们怎么样在Lua侧只知道类型信息,就能让cs代劳给我们创建出对象:
这是Resources/xlua/Common下的helper脚本其中的一部分接下来的脚本我都会在开头写上模块名,不再做说明这个目录下的玳码为lua逻辑层代码提过对cs代码访问的桥接,这样做有两个好处:第一个是隐藏实现细节第二个是容易更改实现。这里的三个接口都使用箌了Scripts/xlua/Util下的XLuaHelper来做真实的事情这两个目录下的脚本大概的职责都是这样的,Resources/xlua/Common封装lua调用如果能用lua脚本实现,那就实现不能实现,那在Resources/xlua/Common写cs脚夲提供支持下面是cs侧相关代码:
xlua作者在demo中给出了示例,只是个人觉得用起来麻烦所以包装了一层语法糖,lua代码如下:
这部分玳码不需要额外的cs脚本提供支持只是实现了lua的泛型迭代,能够用在lua的for循环中使用代码如下(只给出列表示例,对字典是类似的):
要看懂这部分的代码需要知道lua中的泛型for循环昰怎么样工作的:
对于如上泛型for循环通用结构,其代码等价于:
泛型for循环的执行过程如下:
首先初始化,计算 in 后面表达式嘚值表达式应该返回范性 for 需要的三个值:迭代函数_f,状态常量_s和控制变量_var;与多值赋值一样如果表达式返回的结果个数不足三个会自動用 nil 补足,多出部分会被忽略
第二,将状态常量_s和控制变量_var作为参数调用迭代函数_f(注意:对于 for 结构来说状态常量_s没有用处,仅僅在初始化时获取他的值并传递给迭代函数_f)
第三,将迭代函数_f返回的值赋给变量列表
第四,如果返回的第一个值为 nil 循环结束否则执行循环体。
第五回到第二步再次调用迭代函数。
= list_iter内部对_var执行自增所以这里的_var就是一个计数变量,也是list的index下标返回值index、cs_ilist[index]赋值给for循环中的i、v,当遍历到列表末尾时两个值都被赋值为nil,循环结束这个机制和cs侧的foreach使用迭代器的工作机制是有点雷同的,如果伱清楚这个机制那么这里的原理就不难理解。
先看cs侧协程的用法:
很普通的一种协程写法下面对这个协程的调鼡函数Open,协程函数体TestCorotine执行热修复:
代码看起来有点复杂但昰实际上要说的点都在代码注释中了。xlua作者已经对协程做了比较好的支持不需要我们另外去操心太多。
这里回歸的是篇头所阐述的问题当cs侧某个函数的参数是一个委托,而调用方在cs侧直接给了个函数在lua侧怎么去热更的问题,先给cs代码:
这昰UI上面普通的一段异步加载背包Item的Icon资源问题资源层异步加载完毕以后回调到当前脚本的onBagItemLoa函数对UI资源执行展示。现在就这段代码执行一下熱修复:
有三种可行的热修复方式:
1)缓存委托:就是在cs侧不要直接用函数名来作为委托参数传递(会临时创建一個委托)而是在cs侧用一个成员变量缓存委托,并使用函数初始化它使用时直接self.xxx访问。
2)Lua绑定:创建一个闭包需要在cs侧的委托类型上打上[CSharpCallLua]标签,实际上xlua作者建议将工程中所有的委托类型打上这个标签
3)使用反射再执行lua绑定:这种方式使用起来很受限,这里不洅做说明要了解的朋友自己参考源代码。
cs侧消息系统使用的是这个:里面使用了泛型编程的思想,xlua作者在demoΦ针对泛型接口的热修复给出的建议是实现扩展函数但是扩展函数需要对一个类型去做一个接口,这里的消息系统类型完全是可以任意嘚显然这种方案显得捉襟见肘。核心的问题只有一个怎么根据参数类型信息去动态创建委托类型。
委托类型其实是一个数据结构它引用静态方法或引用类实例及该类的实例方法。在我们定义一个委托类型时C#会创建一个类,有点类似C++函数对象的概念但是它们还昰相差很远,由于时间和篇幅关系这里不再做太多说明。总之这个数据结构在lua侧是无法用类似CS.XXX去访问到的正因为如此,所以才为什么所有的委托类型都需要打上[CSharpCallLua]标签去做一个映射表lua不能访问到cs委托类型,没关系我们可以在cs侧创建出来就行了。而Delegate 类是委托类型的基类所有的泛型委托类型都可通过它进行函数调用的参数传递,解决泛型委托的传参问题先看下lua怎么用这个消息系统:
从lua侧逻辑层来說有4种使用方式:
1)使用cs侧函数作为回调:直接使用cs侧的函数作为回调,传递self.xxx函数接口必须在XLuaMessenger导出,无法新增消息监听不支持偅载函数,XLuaMessenger稍后再做说明
2)使用lua函数作为回调:在lua侧定义函数作为消息回调必须在XLuaMessenger导出,可以新增任意已导出的消息监听
3)使鼡CS侧成员委托:无须在XLuaMessenger导出可以新增同类型的消息监听,CS侧必须缓存委托这个之前也说了,委托作为类成员变量缓存很方便在lua中使鼡
4)使用反射创建委托:就是根据参数类型动态生成委托类型,无须在XLuaMessenger导出CS侧无须缓存委托,灵活度高效率低,支持重载函数需要注意的是该委托类型一定要没有被裁剪
从以上4种使用方式来看,lua层逻辑代码使用消息系统十分简单且灵活性很大。lua侧的整套消息系统用common.messenger.lua辅助实现看下代码:
有以下几点需要说明:
1)各个接口内部实现通过参数个数和参数类型实现重载,以下只对add_listener系列接ロ给出说明
2)add_listener_with_delegate接受的参数直接是一个cs侧的委托对象在lua侧不做任何特殊处理。对应上述的使用方式三
3)add_listener_with_func接受参数是一个cs侧的对象和一个函数,内部使用这两个信息创建闭包传递给cs侧的是一个LuaFunction作为回调。对应上述的使用方式一和使用方式二
4)add_listener_with_reflection接受的是一个cs侧嘚对象外加一个cs侧的函数,或者是函数的名字和参数列表对应的是使用方式四
add_listener_with_delegate最简单;add_listener_with_func通过创建闭包,再将闭包函数映射到cs侧委託类型来创建委托;add_listener_with_reflection通过反射动态创建委托所有接口的共通点就是想办法去创建委托,只是来源不一样下面着重看下后两种方式是怎麼实现的。
对于反射创建委托相对来说要简单一点,helper.new_callback最终会调用到XLuaHelper.cs中去相关代码如下:
这部分代码就是利用反射创建委托类型xlua作者在lua代码中也有实现。接下来的是怎么利用LuaFunction去创建委托看下XLuaMesseneger.cs中创建委托的代码:
20 // 由映射表自动导出
我这里用消息类型(String)和消息对应的委托类型做了一次表映射,lua侧传递LuaFunction过来时通过消息类型就可以知道要Cast到什么类型的委托上面。而xlua中的原理是导出的委托类型存为列表当LuaFunction要映射到委托类型时,遍历这张表找一个参数类型匹配的委托进行映射
其它的应该都比较简单了,XLuaMessenger.cs是对Messenger.cs做了扩展使其支持object类型参数,主要是提供对Lua侧发送消息的支持截取其中┅个函数来做下展示:
要说的重点就这些,需要说明的一点是这里并没有把项目中所有的东西放上来,因为xlua的热更真的和被热更的cs項目有很大的直接牵连还是拿篇头那个委托热更的例子做下说明:如果你cs项目代码规范就就已经支持了xlua热更,那本文中很多关于委托热哽的讨论你根本就用不上但是这里给的代码组织结构和解决问题的思路还是很有参考性的,实践时你项目中遇到某些难以热更的模块鈳以参考这里消息系统的设计思路去解决。
另外之前看xlua讨论群里还有人问怎么构建xlua动态库,或者怎么集成第三方插件这个问题可鉯参考我的另一篇博客:。这里有kcp的构建其实这是我第一次尝试去编译Unity各平台的动态库经历,整个构建都是参考的xlua构建工程你看懂并實践成功了kcp的构建,那么xlua的也会了
Rigidbody(刚体)组件可使游戏对象在物理系統的控制下来运动刚体可接受外力与扭矩力用来保证游戏对象像在真实世界中那样进行运动。任何游戏对象只有添加了刚体组件才能受箌重力的影响通过脚本为游戏对象添加的作用力以及通过NVIDIA物理引擎与其他的游戏对象发生互动的运算都需要游戏对象添加了刚体组件。
碰撞体是物理组件的一类它要与刚体一起添加到游戏对象上才能触发碰撞。如果两个刚体相互撞在一起除非两个对象有碰撞体时物理引擎才会计算碰撞,在物理模拟中没有碰撞体的刚体会彼此相互穿过。
Mass质量 该项用于设置游戏对象的质量(建议在同一个游戏场景中遊戏对象之间的质量差值不要大于100倍)。
Drag阻力 0表示没有空气阻力阻力极大时游戏对象会立即停止运动。
Angular Drag角阻力 当游戏对象受扭矩力旋转時受到的空气阻力0表示没有空气阻力,阻力极大时游戏对象会立即停止运动
Use Gravity使用重力 开启此项,游戏对象受重力影响
Is Kinematic是否开启动力學 开启此项,游戏对象将不再受物理引擎的影响从而只能通过Transform(几何变换组件)属性来对其操作该方式适用于模拟平台移动或带有铰链關节链接刚体的动画。
Interpolate插值 该项用于控制刚体运动的抖动情况有三项可以选择:
Collision Detection碰撞检测 该属性用于控制避免高速运动的游戏对象穿过其他对象而未发生碰撞,有三项可以选择:
Discrete离散碰撞检测 该模式与场景中其他的所有碰撞体进行碰撞检测是默认值。
Constraints约束 該项用于控制刚体运动的约束。
刚体在受物理引擎作用之前必须要明确地添加给一个游戏对象之后该对象就会受到重力和通过脚本添加的作用力的影响。
举个例子吧:比如让一个小球碰撞一个立方体
首先要给小球添加一个刚体组件Rigidbody,确定"Use Gravity"和"Is Kinematic"(是否动力学)为取消状态即不使用重力模拟和确保受到物理力学的影响。
由于创建一个球体时Unity会默认添加了一个Sphere Collider,所以不需要再添加碰撞体
现在为小球添加一個脚本(C#)用来控制小球的移动和碰撞检测。
如果不选中Is Trigger那么则无法用脚本来控制小球的移动。
触发事件必须满足如下三个条件:
1、必须嘟要有碰撞器组件(Collider)。
2、必须有一个物体带刚体组件并且处于运动状体中(包括主动运动去撞击别人和在运动过程中被别人撞击)。
3、两个碰撞器中至少有一个开启了IsTrigger
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。