6.lua 数组是从1开始,这也是许多习惯吃第0碗饭的童鞋,感到不自在,有一种情况还是比较苦恼,比如:
t[0] = 1 t[1] = 2 print(#t) 长度为多少,动手试试
答案:1
LUA编程建议:
1 引用管理
1.1基本原则
C/C++内管管理的一个基本原则是,"谁分配,谁释放"。虽然C#和Lua语言本身都是基于引用的自动垃圾回收,这一原则对于引用的管理、资源的申请和释放同样适用。
正确管理引用的关键就是要满足"谁分配,谁释放"、"有分配,就有释放",做到以下几个方面在一个模块中(ViewComponet、MonoBehavior、LuaComponetContainer、自己写的功能模块等)成对出现:
-
- 通知的监听与反监听
- 资源Resource的Retain()与Release
- 定时器的注册于反注册
- UI事件的监听与反监听
1.2 ViewComponent
aounity框架中ViewComponet指明了对于UI系统这些逻辑应该写在什么地方,以方便清晰的管理引用以及检查。
-
- buildUI
在界面构建时执行。
这个函数里主要获取UI组件引用。
-
- destroyUI
在界面销毁时执行。
与buildUI中一一对应取消UI组件引用。
-
- bindEvents
在界面构建时执行。
在这个函数里面可以:
1) 监听一些界面显示、不显示时都需要监听的通知
2) 监听界面中UI组件的点击等事件
-
- unbindEvents
在界面销毁时执行。
此函数中应当与bindEvents中一一对应反注册监听。
-
- onEnter/onEnterFinished
在界面打开时执行。
在此函数中可以:
1)执行界面打开的一些逻辑处理
2)监听界面显示时才需要监听的通知
3)注册定时函数
-
- onExit/onExitFinished
在界面关闭时执行。
在此函数中应当与onEnter/onEnterFinished中一一对应反注册监听。
1.3 UI界面
UI界面占用的内存主要是:
-
- 对象本身占用的堆内存,很小
- 引用的本界面特有图集
- 引用的公共图集
- 大的背景图
UI界面引用的公共图集常驻内存不用考虑释放,用到的大的背景图采用动态加载的方式,在界面打开时加载,在界面关闭时释放。
下面主要讨论下UI界面对象以及特有图集的释放。这两方面的内存如果要释放,销毁UI界面对象,再执行一次堆内存和Unity资源的垃圾回收即可。对于UI系统开发者来说只需要配置:setting_view.lua中的对应界面的autoDestroyTime字段。
autoDestroyTime表示界面关闭后,多久自动销毁,每次重新打开会重置倒计时,界面打开中间不会发生销毁。
-
- 不配置或者配置0,表示常驻内存,不析构。
- 配置成负数,界面关闭后,立即销毁。
- 配置成正数,界面关闭后,对应时间的倒计时结束后销毁。
常用界面
对于常用界面(主界面上的一级按钮入口进入的、经常使用的等)可以不用配置销毁UI界面对象。只需要界面关闭时,清除动态加载的资源。
非常用界面
对于非常用界面,根据使用频率进行配置。
执行堆内存和Unity资源的垃圾回收时,需要注意UI界面配置的关闭后一定时间内销毁的销毁延时性。
1.4 GameObject
创建UnityEngine.Object子类的对象,销毁时一定要:
UnityEngine.GameObject.Destroy(o);o=nil
注意GameObject.Destroy并不是代码调用之后立即销毁对象的。真正销毁的执行时机,是在所有Update执行之后,绘制之前。因此挂在gameobject上的脚本对属性的引用、OnDetroy中释放的引用,也都不是立即执行的。如果想在销毁GameObject之后,想手动调用GC函数,应该做个延迟。
1.5 MonoBehavior
Lua组件
Aounity框架中提供了LuaComponentContainer来将lua代码绑定在gameobject上,实现像Monobehavior一样的功能。
当不需要此组件的时候,调用Remove 方法移除。更多的情况下,是不需要移除组件的,而是整个gameobject都需要销毁。LuaComponentContainer 中OnDestroy 中已经有清理添加到gameobject上的lua组件。因此只需要按销毁gameobject的方法销毁gameobject即可,不用再移除组件了。
C#组件
经常会需要继承MonoBehavior类,给Lua层提供注册、反注册回调,处理UI组件事件等。
会出问题的一个方面是,在lua中只注册了回调,忘记了反注册。首先写代码时要按照引用管理的基本原则来做到"有注册就要有反注册"外,可以在OnDestroy中做一次最后的清理。
OnDetroy
C#组件以及Lua组件,本身的OnDetroy方法都多提供了一个当gameobject销毁时,额外清理自己引用的外部资源的方法,建议每个组件都覆写此方法,以多一层防护。
2 Lua与C#交互
- 缓存常访问的C#对象的属性
go.transform => self._trs = go.transform
- 尽量只传最基本类型的参数如int、float、double,尽量不要传递bool、string、System.Object。
Chapter 7 - Improving Interop Performance | Microsoft Docs
- 函数类型是C# 结构体时,尽量使用结构体成员作为参数传递,C#函数中缓存结构体。
self._trs.position = pos => TransformUtil.SetPos(self._trs,x,y,z)
TransformUtil.cs: private static Vector3 tempVec3;
- 函数返回值是C#结构体时,使用多个out参数返回结构体成员。
public static void GetPos(this Transform trs,out float x,out float y,out float z)
TransformUtil.GetPos(self._trs,0,0,0)
- 实用方法尽量实现成静态类的静态方法
- 对象池缓存可复用的C#对象,避免频繁创建销毁
- 创建UnityEngine.Object子类的对象,销毁时一定要UnityEngine.GameObject.Destroy(o)以及o=nil。
- 尽量减少Lua与C#的交互,如果一段lua代码,多行都会与C#交互,采用这段逻辑用C#封装成一个函数整体给lua调用。
3 Lua编程Tips
- 尽量不要创建内部匿名function。外部函数多次调用时频繁的创建和释放funciton和upvalue。
- 字符串连接会产生新的拷贝,避免在循环内不断的连接字符串。
- 避免lua函数参数是表类型时,每次函数调用都构造一个table对象。注意不定参数被调用时,如果有不定参数在,每次都会定义一个table存放不定参数。
setWayPoint({x,y}) => setWayPoint(x,y)
foo(a,…)
- 注意Lua逻辑运算中的短路效应。我们经常用 a = a or "default",但如果缺省值是true时,a = a or true会将false也会变成true。
-
对于容量固定的table数组,可以在创建时就指定大小。
local t = {nil,nil,nil}
-
Table遍历过程中,可以将key赋为nil,但不要插入新的key。