用unity3d和虚幻4哪个好内的 Network组件创建游戏后在游戏平台的虚拟局域网内无法联机怎么办

&figure&&img src=&https://pic1.zhimg.com/v2-3941eccfa098e495f7277_b.jpg& data-rawwidth=&1291& data-rawheight=&1029& class=&origin_image zh-lightbox-thumb& width=&1291& data-original=&https://pic1.zhimg.com/v2-3941eccfa098e495f7277_r.jpg&&&/figure&&p&前两周分享了资源配置与资源管理,今天分享一种特殊的资源脚本数据。在Unity项目中,我们通常使用C#编写脚本,C#是一门非常方便的语言可以帮助我们快速开发。不过也有一些要点需要关注,影响内存与性能。&/p&&h2&&b&字符串String&/b&&/h2&&p&首先要关注String,String没有看起来那么简单,什么是String呢?&/p&&ul&&li&String是一个UTF-16编码的文本&/li&&li&String是一个引用类型&/li&&li&String是不可变的&/li&&/ul&&p&在C#里面,字符串是一个引用类型而不是一个值类型,即使看起来像是持有一个值类型对象并可以方便的修改,这里修改字符串是创建一个新的字符串。通常建议使用StringBuilder来拼接字符串,下面看看不同行为的拼接字符串带来的性能差异吧。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class MonoTest : MonoBehaviour {
const int SIZE = 1024;
void Update () {
_UpdateStringAppend();
_UpdateStringFormat();
_UpdateStringBuild();
string _UpdateStringAppend() {
string str = string.E
for (int i = 0; i & SIZE; ++i) {
string _UpdateStringFormat() {
string str = string.E
for (int i = 0; i & SIZE; ++i) {
str += string.Format(&{0}&, i);
string _UpdateStringBuild() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i & SIZE; ++i) {
sb.Append(i);
return sb.ToString();
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-5ed19eb7fda80ee48e765_b.png& data-rawwidth=&386& data-rawheight=&227& class=&content_image& width=&386&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&观察数据可以发现StringBuilder在性能上和GC上都有极大的提升,每次创建一个新的字符串,字符串长度从0增长到n,这是一个O(n^2)的操作。而StringBuilder则只会在长度不够的时候重新申请并赋值,如果内部的长度是按2递增的话,这里的复杂度是O(nlogn)。如果设定一个足够大的预初始值,那这里的复杂度则可以降低到O(n)。&/p&&p&这里的内存申请的量级和运算复杂度也是一样的,关注大小与分配次数。过多的分配次数会导致堆碎片变多,过多的内存分配则会导致触发内存清理GC,这是额外的无必要的开销。所以推荐尽可能的使用StringBuilder来优化这个操作,同时StringBuilder本身也会在内部申请内存,复用StringBuilder能进一步优化内存。&/p&&p&再来看看之前讨论里面被忽略的Format拼接,Format表现最差有点出人意外又在情理之中。我们在平时被建议使用Format来拼接字符串,但在有些情况下Format的表现非常差。这里就是一个不恰当的Format使用案例,这是一个冗余的Format操作,同时还多了一次int转object的GC。如果还难以理解,则可以看看下面的Format操作得到了一个更差的结果。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&string _UpdateStringFormatEx() {
string str = string.E
for (int i = 0; i & SIZE; ++i) {
str = string.Format(&{0}{1}&, str, i);
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-ae79aa3e673f0b0f96cddca_b.png& data-rawwidth=&417& data-rawheight=&288& class=&content_image& width=&417&&&/figure&&p&&br&&/p&&p&其实一个正确的Format用法是下面这样的&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&string str = string.Format(&{0}{1}....{n}&, 0, 1, ..., n);
&/code&&/pre&&/div&&p&通过把多次拼接操作合并成一次来达到减少GC提高效率,实际Format的内部使用了StringBuilder来拼接字符串。N次使用StringBuilder来拼接字符串的性能与1次的操作性能有较大差异,这是平时使用中也需要注意的。&/p&&h2&&b&优化字符串数量&/b&&/h2&&p&字符串是不可变,每次修改字符串都会生成一个新的字符串,那创建的字符串呢?尽管实验得到,每次创建字符串都会得到一个新串,即使已经存在一个相同的字符串。这里有一篇顾露分享的&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&《Unity 游戏的 string interning 优化》&/a&已经做了这块内容详细描述。这里通过string.Intern来减少字符串数量达到优化内存的效果,同时让我发现了项目中存在着大量的字符串使用。如何更进一步的减少字符串数量是个有趣的问题。&/p&&p&通过顾露的自制工具&a href=&http://link.zhihu.com/?target=https%3A//github.com/PerfAssist/PA_ResourceTracker& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&PA_ResourceTracker&/a&采集的数据,分析数据发现字符串数据里面存在较多的资源加载路径。这些路径数据非常的长,而且数量也非常的多。字符串路径的作用是标识资源,考虑使用Hash来标识资源也可以做到相同的事情。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Resources.Load(string path, Type type);
Resources.Load(ulong pathHash, Type type);
Resources.PathToHash(string path);
&/code&&/pre&&/div&&p&在资源管理上实现两个新增的接口,支持按Hash加载资源,然后提供一个字符串路径转Hash的接口,来实现这一目标。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Template
public int id = -1;
public class Template
public int id = -1;
public ulong pathH
&/code&&/pre&&/div&&p&然后替换结构体里面的变量为Hash,在第一次得到这个字符串后立刻调用Resources.PathToHash计算Hash值并存储。&/p&&p&计算路径的Hash还需要考虑路径的大小写、斜杠与放斜杠。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public ulong PathToHash(string str) {
ulong hashCode = 0;
for (int i = 0; i & str.L ++i) {
char ch = Char.ToUpperInvariant(str[i]);
if (str[i] == '\\')
{ ch = '/'; }
hashCode = (hashCode && 7) + (hashCode && 1) + hashCode +
return hashC
&/code&&/pre&&/div&&p&使用ulong降低Hash的冲突,由于存在冲突的可能,这里在日常构建的时候对所有的资源路径计算Hash判断是否有冲突。这里路径Hash不仅减少了对象数量,也减少了一些字符串修改操作导致的GC。下面举个降低的例子,得到唯一字符串路径。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public string GetUniString(string str) {
return str.Replace('\\', '/').ToUpperInvariant();
&/code&&/pre&&/div&&p&这实在是一个低效的行为,所以即使你不需要缩减字符串个数,还是强烈推荐使用Hash来做唯一标识符。&/p&&p&由于Unity提供的Resources接口需要使用路径字符串来加载资源,所以之前说了那么多还没有解释为什么可以减少字符串对象这个问题。这里我们项目能使用主要是由于使用了AssetBundle。只需要先存Hash对应的AssetBundle ID然后加载这个AssetBundle的时候加载Hash对应Name即可。AssetBundle支持直接使用Name加载,也可以使用Asset Path加载。这里的AssetPath是相对于Assets目录的路径与Resources的相对于Resources目录还是有差异的,所以使用Name来加载。AssetBundle本身就有一个接口AssetBundle.GetAllAssetNames()获取所有资源路径。不过这里会包含被依赖的所有资源路径,所以一般自己存这个数据。&/p&&p&细心的人也注意到了上面提到的AssetBundle ID,由于AssetBundle打包是可以完全控制的。所以给AssetBundle命名一个数字ID,也是有效的减少字符串数量的方法。这对使用AssetBundle打包加载资源的项目是一个不错的参考。我们实现自己的AssetBundleManifest维护AssetBundle之间的依赖关系。&/p&&p&Unity的Animator类提供了StringToHash接口来帮助消除字符串,同时配套提供两套接口可以调用,和这里消除字符串路径的思路是一致的。相信还有其他地方也可以通过这个思路来消除字符串优化性能。&/p&&p&最后这里做字符串转路径这个实现是由于游戏在开始的时候就加载了大部分配置表,表现里面有着大量路径字符串。在工具里面发现路径字符串的比重大概在20%,所以来做这项工作优化对象数量。带来了不错的性能收益,不过如果出错只能看到hash而不能看到实际字符串。不过可以通过区分DEBUG与RELEASE版本来决定是否保留这些字符串。&/p&&h2&&b&优化字符串比较&/b&&/h2&&p&默认的字符串比较操作是非常低效的,&a href=&http://link.zhihu.com/?target=https%3A//docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Best Practices for Using Strings in .NET》&/a&这篇文章讲了这方面的大部分细节。这里主要展示一些实践测试数据,让我们对性能有一个认识。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&StringBuilder sBuilder = new StringBuilder();
System.Random random = new System.Random();
for (int i = 0; i & 100; ++i)
sBuilder.Append((char)(random.Next() % 256));
string str = sBuilder.ToString();
string preStr = str.Substring(0, 16);
string lastStr = str.Substring(str.Length - 16, 16);
int cnt = 0;
for (int i = 0; i & 100 * 1024; ++i)
if (str.StartsWith(preStr)) ++
if (str.EndsWith(lastStr)) ++
&/code&&/pre&&/div&&p&测试结果&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-74cf2ddf2bdaaeb5326a_b.png& data-rawwidth=&594& data-rawheight=&389& class=&origin_image zh-lightbox-thumb& width=&594& data-original=&https://pic3.zhimg.com/v2-74cf2ddf2bdaaeb5326a_r.jpg&&&/figure&&p&&br&&/p&&p&字符串比较接口默认行为&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-47b68fcc3ce64e_b.png& data-rawwidth=&531& data-rawheight=&451& class=&origin_image zh-lightbox-thumb& width=&531& data-original=&https://pic1.zhimg.com/v2-47b68fcc3ce64e_r.jpg&&&/figure&&p&&br&&/p&&p&正常情况下使用Ordinal比较即可,自己实现Ordinal行为的比较还可以提高10倍的性能。&/p&&h2&&b&从容器谈Boxing&/b&&/h2&&p&泛型容器内部实现会调用一些System.Object接口,如果我们不实现对应的泛型接口,在调用接口的时候就会找到基类Object的接口。而由于Struct是一个值类型,value type转class type会触发内存分配,定义这种行为为Boxing。&a href=&http://link.zhihu.com/?target=http%3A//www.somasim.com/blog/2015/08/c-performance-tips-for-unity-part-2-structs-and-enums/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《c-performance-tips-for-unity-part-2-structs-and-enums》&/a&这篇文章已经对这块做了详细描述与举例。我自己也做了一些数据测试,分享给大家做参考。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public struct SmallStruct
// 2 int fields. Total size: 2 * 4B + 16B = 24B
public int a,
public struct LargeStruct
// 20 int fields. Total size: 20 * 4B + 16B = 96B
public int a, b,
// Dictionary&Struct, bool& dict
// 1024 calls dict. ContainsKey
&/code&&/pre&&/div&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-cc6ed5d711a5ff7a5beab0_b.png& data-rawwidth=&356& data-rawheight=&177& class=&content_image& width=&356&&&/figure&&p&&br&&/p&&p&实现了不同接口之后&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-d0423ebbed_b.png& data-rawwidth=&530& data-rawheight=&282& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-d0423ebbed_r.jpg&&&/figure&&p&&br&&/p&&p&观察发现Dictionary内部使用 EqualityComparer&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public abstract class EqualityComparer&T&
protected EqualityComparer();
public static EqualityComparer&T& Default { }
public abstract bool Equals(T x, T y);
public abstract int GetHashCode(T obj);
&/code&&/pre&&/div&&p&如果没有实现还GetHashCode触发一次boxing,而Equals则触发两次。实现IEquatable泛型接口,以及override int GetHashCode则可避免触发GC。非泛型的HashTable实现和泛型Dictionary基本一致,推荐使用Dictionary泛型版本,提高性能。&/p&&h2&&b&其他Tips&/b&&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&void DispatchEvent(string str, params object[] data);
static object[] _default = new object[] {};
void DispatchEvent(string str)
_DispatchEvent(str, _default);
void _DispatchEvent(string str, object[] data);
&/code&&/pre&&/div&&p&params object每次调用会申请一个object数组,对于无参数的行为,实现一个默认接口减少GC。&/p&&p&一般情况下使用Profile Windows排查不必要的GC Alloc。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-56ce3db9fd32ecf380c4b6e_b.png& data-rawwidth=&855& data-rawheight=&304& class=&origin_image zh-lightbox-thumb& width=&855& data-original=&https://pic3.zhimg.com/v2-56ce3db9fd32ecf380c4b6e_r.jpg&&&/figure&&p&&br&&/p&&p&这个工具能帮助我们定位发生GC Alloc行为的代码,通常第一步优化那些每帧都存在的GC,之后优化那些峰值很高的GC。优化GC能带来什么好处呢,假设当前使用了30M内存,申请了50M内存。这里有20M的空间可以用于日常的GC Alloc。假设我们每帧的GC Alloc=100K,则20 * 1024 / 100 = 204帧。如果每帧的执行时间为33ms(30帧),则6.76S触发一次GC.Collect()。这个函数开销在100ms以上,当前帧的开销从33ms变成133ms,这会有明显的卡顿感。更多的GC优化可以参考&a href=&http://link.zhihu.com/?target=https%3A//unity3d.com/cn/learn/tutorials/temas/performance-optimization/optimizing-garbage-collection-unity-games%3Fplaylist%3D44069& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Structing out code to minimize the impact of garbage collection》&/a&。&/p&&h2&&b&从Struct再谈优化对象数量&/b&&/h2&&p&从Rich Geldreich的《Lessons Learned While Fixing Memory Leaks in our First Unity Title》了解到对象数量过大造成额外的内存使用。这里再次谈对象数量优化,优化内存使用。&/p&&blockquote&The Boehm collector grows its OS memory allocation so it has enough internal heap headroom to avoid collecting too frequently. You must factor this headroom into account when budgeting your C# memory, i.e. if your budget calls for 25MB of C# memory then the actual amount of memory consumed at the OS level will be significantly larger (approximately 40-50MB in our experience).&/blockquote&&p&这里主要讨论配置表,配置表一般是一种Key-Value结构,同时在运行时我们不需要修改内存,最后配置表的总量和数量会非常多。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Dicitonary&TKey, TValue& {
private int[] m_
private int[] m_entryN
private int[] m_entryH
private TKey[] m_entryK
private TValue[] m_entryV
public class PlayerTemplate {
public ulong pathH
/* ... more data */
} // assume size = 128B
Dictionary&int, PlayerTemplate&
&/code&&/pre&&/div&&p&一般使用Dictionary存储配置表数据,上面定义的配置表数据类型为class,则可以得到下面的数据。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Set PlayerTemplate Count = 5000;
// 第一个大于Count * 2的素数
Dictionary ArraySize = 10103;
ObjectCount = 5000 + 5 + 1 = 5006;
MemorySize = 5000 * 128B + 10103 * 24 = 882472B = 861.8KB
&/code&&/pre&&/div&&p&之后我们把class改struct&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public struct PlayerTemplate {/* … */}
ObjectCount = 5 + 1 = 6;
MemorySize = 10103 * 128B + 10103 * 16 = 1454832B = 1420.7KB
&/code&&/pre&&/div&&p&对象数量减少后的代价是内存使用的增长,下面来看怎么优化内存使用。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public interface ITableType&TKey, TValue& {
TKey GetKey();
public class TableOrderList&Tkey, TValue& {
private bool m_
private TValue[] m_
private int m_
public struct PlayerTemplate : ITableType&int, PlayerTemplate& {
public int GetKey() {
public ulong pathH
/* ... more data */
&/code&&/pre&&/div&&p&自定义容器与接口实现线性内存空间存储数据。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public int LowerBounder(TKey key) {
int low = 0, high = m_
while (low & high) {
int mid = (low + high) && 1;
if (m_list[mid].GetKey().CompareTo(key) & 0) {
low = mid + 1;
&/code&&/pre&&/div&&p&通过二分查找数据,再数据加载结束后进行一次排序。最后的数据对比如下。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-850d5bab5e6bde2d18655_b.png& data-rawwidth=&643& data-rawheight=&235& class=&origin_image zh-lightbox-thumb& width=&643& data-original=&https://pic2.zhimg.com/v2-850d5bab5e6bde2d18655_r.jpg&&&/figure&&p&&br&&/p&&p&新实现的容器再对象数量与内存使用上都有着较大优势,由于一般游戏很难有超过1W以上的数据量,这里O(logn)与O(1)的差距较小可以接受,而且一般这里也不是性能瓶颈。&/p&&p&Struct只能整存整取,Class则可以简易的修改成员变量。但是对于只读的数据来说,使用Struct来存储数据有极大的优势。更多Struct与Class的讨论可以参考&a href=&http://link.zhihu.com/?target=http%3A//stackoverflow.com/questions/13049/whats-the-difference-between-struct-and-class-in-net& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《What's the difference between struct and class in .NET》&/a&。&/p&&p&[完 Carber ]&/p&&ul&&li&本文首发于&a href=&http://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s/bYZqOZD0WDVK_o8W6ygG5A& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&西山居技术中心&/a&&/li&&/ul&
前两周分享了资源配置与资源管理,今天分享一种特殊的资源脚本数据。在Unity项目中,我们通常使用C#编写脚本,C#是一门非常方便的语言可以帮助我们快速开发。不过也有一些要点需要关注,影响内存与性能。字符串String首先要关注String,String没有看起来那…
&figure&&img src=&https://pic1.zhimg.com/v2-c3cd6b6c9f6_b.jpg& data-rawwidth=&965& data-rawheight=&291& class=&origin_image zh-lightbox-thumb& width=&965& data-original=&https://pic1.zhimg.com/v2-c3cd6b6c9f6_r.jpg&&&/figure&&h2&&b&0. 照旧的碎碎念&/b&&/h2&&p&转眼间已经三月了,2月份的博客因为过年的懒惰和开年之后的忙碌而没有写……第二个月就打破了去年总结时对于2018年的愿望,真是羞耻呢……&/p&&p&年后在准备新的测试版本,断断续续做了一些优化,更多的精力放在团队的绩效评估、沟通这样偏管理的事物上,说实话技术上可以聊的东西不多。近期看到UWA群里和问答上聊Lua的使用之类的话题比较多,也在看ET这套完全基于C#进行游戏开发的框架中提到——&/p&&blockquote&“在发布的时候,定义预编译指令ILRuntime就可以无缝切换成使用ILRuntime加载热更新动态库。这样开发起来及其方便,再也不用使用狗屎lua了。”&/blockquote&&p&Lua是门小而精的语言,它的确很多地方像狗屎一样……比如只提供table这样一种数据结构,而且基于数组域和哈希域的封装让#这样的操作符号可以坑死不少新手甚至老司机,一个哈希表要取长度还要自己封装一个遍历函数等等诸多不便的地方。&/p&&p&我们项目深度使用了Lua,原因其实在1年多前的一篇文章里已经有聊过——&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&《Unity手游开发札记——Lua语言集成》&/a&,有兴趣的朋友可以再去看看。那篇文章也聊了最初对于一些框架上的改造,而今天这篇文章我想聊聊我们团队是如何使用Lua来开发大型游戏的。一方面让大家看看我们是如何把Lua这个“狗屎”,捏成巧克力的形状甚至做出一点点巧克力的味道;另外一方面,也想为纠结是否使用Lua来做Unity的代码更新方案的朋友提供一些做决策的参考。&/p&&h2&&b&1. 我的观点&/b&&/h2&&p&在聊一些更加具体的经验之前,我想先把我自己的观点抛出来,这也是我花时间写这篇文章最想表达的两点内容:&/p&&ol&&li&&b&使用Lua这样的脚本语言,目的不仅仅在于让代码可以被Patch更新,而且让游戏逻辑可以被Hotfix更新&/b&。&/li&&li&&b&使用Lua这样的脚本语言,调试bug的效率并不低,甚至可能比C#这样的静态语言还要高&/b&。&/li&&/ol&&p&先聊下第一点,我看很多朋友在聊的时候不断提到客户端的&b&热更新&/b&,可能每个人或者公司有自己不同的叫法,在我的观点里,通过在游戏启动的时候下载新的资源文件替换之前的文件,让游戏不需要重新安装就可以更新内容的方式叫做“&b&Patch更新&/b&”,而不是&b&热更新(Hotfix)&/b&。&/p&&p&在我的理解中,热更新(Hotfix)的概念从服务端来讲,是指不停止服务的情况下进行的更新,此时如果玩家正在进行游戏,玩家是无感知的,最多感觉到一点顿卡之类的。而对于客户端来说,玩家正在进行游戏,这时候如果需要玩家退出到登陆界面重新下载Patch内容再进入游戏,打断了玩家的游戏体验,根本就不能称之为“热”更新,虽不至于是冷更,最多是“温”更新……&/p&&p&脚本语言让游戏逻辑和数据可以做到玩家无感知的情况下进行错误的修复,比如有一个trace导致了玩家某个系统的界面打开后内容显示错误,Hotfix应用之后,玩家下次打开这个界面的时候,trace就已经被修复了,内容显示正确,而玩家完全没有任何更新的感知,这种才能叫做真正的&b&客户端热更新&/b&。&/p&&p&第二点,有些朋友认为脚本语言只能通过打log进行调试,是一件非常痛苦的事情。首先,Python和Lua这样的脚本语言都有各自的调试工具,可能没有那么便利,但基本功能是够用的;其次,在移动网络游戏的开发中,有网络因素、异步逻辑、设备上运行等存在的情况下,有些bug是很难单步调试来进行重现和分析的,这种情况下log调试必不可少,而且我认为通过分析代码逻辑精准地添加log快速定位问题并修复问题的能力,是每一个程序员应该掌握的基本技巧;最后,结合动态语言的reload功能,即使是使用log调试,也有很高效的方法,在加上内存查看工具,可以做到很高效的bug定位和修复。&/p&&p&这里只是先阐述一下我个人的观点,下面我将根据实际的项目经验来聊聊我们使用Lua的一些方面。&/p&&h2&&b&2. 让Lua代码更好写&/b&&/h2&&p&Lua自身提供的功能很精简,精简也意味着它在很多方面会有些“残疾”……这会导致团队的开发效率比较低,因此必须通过一些基础内容的构建来让团队更好地使用Lua语言。需要注意的是,天下没有免费的午餐,&b&更快的开发效率&/b&有很多时候意味着&b&更慢的运行效率&/b&。&/p&&h2&2.1 全局变量访问控制&/h2&&p&Lua的设计中有一个特点就是:&/p&&blockquote&当你不在变量前使用local关键字的时候,这个变量会被放在_G这个全局表中。&/blockquote&&p&我在最初学习Lua的时候也很难理解这个设计,这和之前我使用的编程语言中作用域的概念是相违背的,但是当你理解函数的env概念之后,就很容易理解为什么在Lua语言中,这样的设计反而是最为合理和自洽的。&/p&&p&对于Lua语言自身来说,这种合理和自洽是美的,但是它会给使用的人带来困惑和难以排查的bug,因为你非常可能因为遗漏的local声明,导致污染了_G,甚至修改到了了你不想修改的变量,或者你的某个变量被别处的代码不小心修改了。因此在我们的工程中,去掉了Lua的这一特性,当期望使用一个局部变量但是没有写local变量的时候,使用error报出错误,所有的全局变量必须&b&显示地进行声明&/b&。&/p&&p&实现方法很简单,重写_G的__index方法和__newindex方法:&/p&&div class=&highlight&&&pre&&code class=&language-lua&&&span&&/span&&span class=&c1&&-- Global.lua&/span&
&span class=&c1&&-- 辅助记录全局变量的名称是否被使用过&/span&
&span class=&kd&&local&/span& &span class=&n&&_GlobalNames&/span& &span class=&o&&=&/span& &span class=&p&&{&/span& &span class=&p&&}&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__innerDeclare&/span&&span class=&p&&(&/span&&span class=&n&&name&/span&&span class=&p&&,&/span& &span class=&n&&defaultValue&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&nb&&rawget&/span&&span class=&p&&(&/span&&span class=&nb&&_G&/span&&span class=&p&&,&/span& &span class=&n&&name&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&nb&&rawset&/span&&span class=&p&&(&/span&&span class=&nb&&_G&/span&&span class=&p&&,&/span& &span class=&n&&name&/span&&span class=&p&&,&/span& &span class=&n&&defaultValue&/span& &span class=&ow&&or&/span& &span class=&kc&&false&/span&&span class=&p&&)&/span&
&span class=&k&&else&/span&
&span class=&nb&&print&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&[Warning] The global variable &&/span& &span class=&o&&..&/span& &span class=&n&&name&/span& &span class=&o&&..&/span& &span class=&s2&&&&/span&&span class=&s&& is already declared!&&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&n&&_GlobalNames&/span&&span class=&p&&[&/span&&span class=&n&&name&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&kc&&true&/span&
&span class=&k&&return&/span& &span class=&nb&&_G&/span&&span class=&p&&[&/span&&span class=&n&&name&/span&&span class=&p&&]&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__innerDeclareIndex&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&_GlobalNames&/span&&span class=&p&&[&/span&&span class=&n&&key&/span&&span class=&p&&]&/span& &span class=&k&&then&/span&
&span class=&nb&&error&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&Attempt to access an undeclared global variable : &&/span& &span class=&o&&..&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&return&/span& &span class=&kc&&nil&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__innerDeclareNewindex&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&n&&value&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&_GlobalNames&/span&&span class=&p&&[&/span&&span class=&n&&key&/span&&span class=&p&&]&/span& &span class=&k&&then&/span&
&span class=&nb&&error&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&Attempt to write an undeclared global variable : &&/span& &span class=&o&&..&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&)&/span&
&span class=&k&&else&/span&
&span class=&nb&&rawset&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&n&&value&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__GLDeclare&/span&&span class=&p&&(&/span&&span class=&n&&name&/span&&span class=&p&&,&/span& &span class=&n&&defaultValue&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&ok&/span&&span class=&p&&,&/span& &span class=&n&&ret&/span& &span class=&o&&=&/span& &span class=&nb&&pcall&/span&&span class=&p&&(&/span&&span class=&n&&__innerDeclare&/span&&span class=&p&&,&/span& &span class=&n&&name&/span&&span class=&p&&,&/span& &span class=&n&&defaultValue&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&ok&/span& &span class=&k&&then&/span&
&span class=&c1&&--
LogError(debug.traceback(res, 2))&/span&
&span class=&k&&return&/span& &span class=&kc&&nil&/span&
&span class=&k&&else&/span&
&span class=&k&&return&/span& &span class=&n&&ret&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__isGLDeclared&/span&&span class=&p&&(&/span&&span class=&n&&name&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&n&&_GlobalNames&/span&&span class=&p&&[&/span&&span class=&n&&name&/span&&span class=&p&&]&/span& &span class=&ow&&or&/span& &span class=&nb&&rawget&/span&&span class=&p&&(&/span&&span class=&nb&&_G&/span&&span class=&p&&,&/span& &span class=&n&&name&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&k&&return&/span& &span class=&kc&&true&/span&
&span class=&k&&else&/span&
&span class=&k&&return&/span& &span class=&kc&&false&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- Set &GLDeclare& into global.&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&__isGLDeclared&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&GLDeclare&&/span&&span class=&p&&))&/span& &span class=&ow&&or&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&GLDeclare&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&n&&__GLDeclare&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&GLDeclare&&/span&&span class=&p&&,&/span& &span class=&n&&__GLDeclare&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- Set &IsGLDeclared& into global.&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&__isGLDeclared&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&IsGLDeclared&&/span&&span class=&p&&))&/span& &span class=&ow&&or&/span&&span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&IsGLDeclared&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&n&&__GLDeclare&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&IsGLDeclared&&/span&&span class=&p&&,&/span& &span class=&n&&__isGLDeclared&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&nb&&setmetatable&/span&&span class=&p&&(&/span&&span class=&nb&&_G&/span&&span class=&p&&,&/span&
&span class=&p&&{&/span&
&span class=&n&&__index&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&ok&/span&&span class=&p&&,&/span& &span class=&n&&res&/span& &span class=&o&&=&/span& &span class=&nb&&pcall&/span&&span class=&p&&(&/span&&span class=&n&&__innerDeclareIndex&/span&&span class=&p&&,&/span& &span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&ok&/span& &span class=&k&&then&/span&
&span class=&n&&logerror&/span&&span class=&p&&(&/span&&span class=&nb&&debug.traceback&/span&&span class=&p&&(&/span&&span class=&n&&res&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&))&/span&
&span class=&k&&end&/span&
&span class=&k&&return&/span& &span class=&kc&&nil&/span&
&span class=&k&&end&/span&&span class=&p&&,&/span&
&span class=&n&&__newindex&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&n&&value&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&ok&/span&&span class=&p&&,&/span& &span class=&n&&res&/span& &span class=&o&&=&/span& &span class=&nb&&pcall&/span&&span class=&p&&(&/span&&span class=&n&&__innerDeclareNewindex&/span&&span class=&p&&,&/span& &span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&n&&value&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&ok&/span& &span class=&k&&then&/span&
&span class=&n&&logerror&/span&&span class=&p&&(&/span&&span class=&nb&&debug.traceback&/span&&span class=&p&&(&/span&&span class=&n&&res&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&))&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&p&&}&/span& &span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&__GLDeclare&/span&
&/code&&/pre&&/div&&p&我相信这种强制报错的设定可以帮助很多刚刚上手Lua的朋友避免一些错误。上述的代码也是参考网上的开源工程,需要用的朋友可以直接拿去。&/p&&h2&2.2 Class的设计&/h2&&p&虽然面向对象的设计在很多帖子的讨论中已经过时的,面向切面编程等等新概念不断被提出,但是对于一个需要团队协作的游戏项目来说,面向对象的设计依然是目前最为常用的逻辑实现方式。Lua自身没有Class的概念,提供了metatable来做继承,但很弱。我们在项目最初的时候就构建了Class的机制,来方便代码的编写。虽然和原生支持Class的Python和C#这样的语言相比易用性和功能上还都有差距,但是基本够用了。&/p&&p&直接提供核心代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-lua&&&span&&/span&&span class=&c1&&-- Class.lua&/span&
&span class=&c1&&-- 类定义,不支持多重继承&/span&
&span class=&kd&&local&/span& &span class=&n&&GLDeclare&/span& &span class=&o&&=&/span& &span class=&nb&&require&/span& &span class=&s2&&&&/span&&span class=&s&&Framework/Global&&/span&
&span class=&c1&&-- 所有定义过的类列表,key为类的类型名称,value为对应的虚表&/span&
&span class=&kd&&local&/span& &span class=&n&&__ClassTypeList&/span& &span class=&o&&=&/span& &span class=&p&&{&/span& &span class=&p&&}&/span&
&span class=&c1&&-- 类的继承关系数据,用于处理Hotfix等逻辑。&/span&
&span class=&c1&&-- 数据形式:key为ClassType,value为继承自它的子类列表。&/span&
&span class=&kd&&local&/span& &span class=&n&&__InheritRelationship&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__createSingletonClass&/span&&span class=&p&&(&/span&&span class=&n&&cls&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&_instance&/span& &span class=&o&&==&/span& &span class=&kc&&nil&/span& &span class=&k&&then&/span&
&span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&_instance&/span& &span class=&o&&=&/span& &span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&new&/span&&span class=&p&&(&/span&&span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&return&/span& &span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&_instance&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&n&&TypeNames&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&c1&&-- 参数含义为:&/span&
&span class=&c1&&-- typeName: 字符串形式的类型名称&/span&
&span class=&c1&&-- superType: 父类的类型,可以为nil&/span&
&span class=&c1&&-- isSingleton: 是否是单例模式的类&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__Class&/span&&span class=&p&&(&/span&&span class=&n&&typeName&/span&&span class=&p&&,&/span& &span class=&n&&superType&/span&&span class=&p&&,&/span& &span class=&n&&isSingleton&/span&&span class=&p&&)&/span&
&span class=&c1&&-- 该table为类定义对应的表&/span&
&span class=&kd&&local&/span& &span class=&n&&classType&/span& &span class=&o&&=&/span& &span class=&p&&{&/span& &span class=&n&&__IsClass&/span& &span class=&o&&=&/span& &span class=&kc&&true&/span& &span class=&p&&}&/span&
&span class=&c1&&-- 类型名称&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&typeName&/span& &span class=&o&&=&/span& &span class=&n&&typeName&/span&
&span class=&k&&if&/span& &span class=&n&&TypeNames&/span&&span class=&p&&[&/span&&span class=&n&&typeName&/span&&span class=&p&&]&/span& &span class=&o&&~=&/span& &span class=&kc&&nil&/span& &span class=&k&&then&/span&
&span class=&n&&logerror&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&The class name is used already!!!&&/span& &span class=&o&&..&/span& &span class=&n&&typeName&/span&&span class=&p&&)&/span&
&span class=&k&&else&/span&
&span class=&n&&TypeNames&/span&&span class=&p&&[&/span&&span class=&n&&typeName&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&classType&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 父类类型&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&superType&/span& &span class=&o&&=&/span& &span class=&n&&superType&/span&
&span class=&c1&&-- 在Class身上记录继承关系&/span&
&span class=&c1&&-- Todo:在修改了继承关系的情况下,Reload和Hotfix可能会存在问题&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&_inheritsCount&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&
&span class=&k&&if&/span& &span class=&n&&superType&/span& &span class=&o&&~=&/span& &span class=&kc&&nil&/span& &span class=&k&&then&/span&
&span class=&kd&&local&/span& &span class=&n&&cache&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&kd&&local&/span& &span class=&n&&counter&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&
&span class=&kd&&local&/span& &span class=&n&&curClass&/span& &span class=&o&&=&/span& &span class=&n&&superType&/span&
&span class=&k&&while&/span& &span class=&n&&curClass&/span& &span class=&k&&do&/span&
&span class=&n&&cache&/span&&span class=&p&&[&/span&&span class=&n&&counter&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&curClass&/span&
&span class=&n&&counter&/span& &span class=&o&&=&/span& &span class=&n&&counter&/span& &span class=&o&&+&/span& &span class=&mi&&1&/span&
&span class=&n&&curClass&/span& &span class=&o&&=&/span& &span class=&n&&curClass&/span&&span class=&p&&.&/span&&span class=&n&&superType&/span&
&span class=&k&&end&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&_classInherits&/span& &span class=&o&&=&/span& &span class=&n&&cache&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&_inheritsCount&/span& &span class=&o&&=&/span& &span class=&n&&counter&/span&
&span class=&k&&end&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&_IsSingleton&/span& &span class=&o&&=&/span& &span class=&n&&isSingleton&/span& &span class=&ow&&or&/span& &span class=&kc&&false&/span&
&span class=&c1&&-- 记录类的继承关系&/span&
&span class=&k&&if&/span& &span class=&n&&superType&/span& &span class=&k&&then&/span&
&span class=&k&&if&/span& &span class=&n&&__InheritRelationship&/span&&span class=&p&&[&/span&&span class=&n&&superType&/span&&span class=&p&&]&/span& &span class=&o&&==&/span& &span class=&kc&&nil&/span& &span class=&k&&then&/span&
&span class=&n&&__InheritRelationship&/span&&span class=&p&&[&/span&&span class=&n&&superType&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&k&&end&/span&
&span class=&nb&&table.insert&/span&&span class=&p&&(&/span&&span class=&n&&__InheritRelationship&/span&&span class=&p&&[&/span&&span class=&n&&superType&/span&&span class=&p&&],&/span& &span class=&n&&classType&/span&&span class=&p&&)&/span&
&span class=&k&&else&/span&
&span class=&n&&__InheritRelationship&/span&&span class=&p&&[&/span&&span class=&n&&classType&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&p&&{}&/span&
&span class=&k&&end&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&ctor&/span& &span class=&o&&=&/span& &span class=&kc&&false&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&dtor&/span& &span class=&o&&=&/span& &span class=&kc&&false&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&objToString&/span&&span class=&p&&(&/span&&span class=&n&&self&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&ow&&not&/span& &span class=&n&&self&/span&&span class=&p&&.&/span&&span class=&n&&__instanceName&/span& &span class=&k&&then&/span&
&span class=&kd&&local&/span& &span class=&n&&str&/span& &span class=&o&&=&/span& &span class=&nb&&tostring&/span&&span class=&p&&(&/span&&span class=&n&&self&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&_&/span&&span class=&p&&,&/span& &span class=&n&&_&/span&&span class=&p&&,&/span& &span class=&n&&addr&/span& &span class=&o&&=&/span& &span class=&nb&&string.find&/span&&span class=&p&&(&/span&&span class=&n&&str&/span&&span class=&p&&,&/span& &span class=&s2&&&&/span&&span class=&s&&table%s*:%s*(0?[xX]?%x+)&&/span&&span class=&p&&)&/span&
&span class=&n&&self&/span&&span class=&p&&.&/span&&span class=&n&&__instanceName&/span& &span class=&o&&=&/span&
&span class=&nb&&string.format&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&Class %s : %s&&/span&&span class=&p&&,&/span& &span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&typeName&/span&&span class=&p&&,&/span& &span class=&n&&addr&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&return&/span& &span class=&n&&self&/span&&span class=&p&&.&/span&&span class=&n&&__instanceName&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&objGetClass&/span&&span class=&p&&(&/span&&span class=&n&&self&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&classType&/span&
&span class=&k&&end&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&objGetType&/span&&span class=&p&&(&/span&&span class=&n&&self&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&typeName&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 创建对象的方法。&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&new&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&c1&&-- 该table为对象对应的表&/span&
&span class=&kd&&local&/span& &span class=&n&&obj&/span& &span class=&o&&=&/span& &span class=&p&&{&/span& &span class=&p&&}&/span&
&span class=&c1&&-- 对象的toString方法,输出结果为类型名称 内存地址。&/span&
&span class=&n&&obj&/span&&span class=&p&&.&/span&&span class=&n&&toString&/span& &span class=&o&&=&/span& &span class=&n&&objToString&/span&
&span class=&c1&&-- 获取类&/span&
&span class=&n&&obj&/span&&span class=&p&&.&/span&&span class=&n&&getClass&/span& &span class=&o&&=&/span& &span class=&n&&objGetClass&/span&
&span class=&c1&&-- 获取类型名称的方法。&/span&
&span class=&n&&obj&/span&&span class=&p&&.&/span&&span class=&n&&getType&/span& &span class=&o&&=&/span& &span class=&n&&objGetType&/span&
&span class=&c1&&-- 递归的构造过程&/span&
&span class=&kd&&local&/span& &span class=&n&&createObj&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&class&/span&&span class=&p&&,&/span& &span class=&n&&object&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&c1&&-- 优化递归过程中的函数调用&/span&
&span class=&k&&if&/span& &span class=&n&&class&/span&&span class=&p&&.&/span&&span class=&n&&superType&/span& &span class=&o&&~=&/span& &span class=&kc&&nil&/span& &span class=&k&&then&/span&
&span class=&k&&for&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&n&&class&/span&&span class=&p&&.&/span&&span class=&n&&_inheritsCount&/span&&span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span& &span class=&k&&do&/span&
&span class=&kd&&local&/span& &span class=&n&&curClass&/span& &span class=&o&&=&/span& &span class=&n&&class&/span&&span class=&p&&.&/span&&span class=&n&&_classInherits&/span&&span class=&p&&[&/span&&span class=&n&&i&/span&&span class=&p&&]&/span&
&span class=&k&&if&/span& &span class=&n&&curClass&/span&&span class=&p&&.&/span&&span class=&n&&ctor&/span& &span class=&k&&then&/span&
&span class=&n&&curClass&/span&&span class=&p&&.&/span&&span class=&n&&ctor&/span&&span class=&p&&(&/span&&span class=&n&&object&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&n&&class&/span&&span class=&p&&.&/span&&span class=&n&&ctor&/span& &span class=&k&&then&/span&
&span class=&n&&class&/span&&span class=&p&&.&/span&&span class=&n&&ctor&/span&&span class=&p&&(&/span&&span class=&n&&object&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 设置对象表的metatable为虚表的索引内容&/span&
&span class=&nb&&setmetatable&/span&&span class=&p&&(&/span&&span class=&n&&obj&/span&&span class=&p&&,&/span& &span class=&p&&{&/span& &span class=&n&&__index&/span& &span class=&o&&=&/span& &span class=&n&&__ClassTypeList&/span&&span class=&p&&[&/span&&span class=&n&&classType&/span&&span class=&p&&]})&/span&
&span class=&c1&&-- 构造对象&/span&
&span class=&n&&createObj&/span&&span class=&p&&(&/span&&span class=&n&&classType&/span&&span class=&p&&,&/span& &span class=&n&&obj&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&obj&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 类的toString方法。&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&toString&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&self&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&self&/span&&span class=&p&&.&/span&&span class=&n&&typeName&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&_IsSingleton&/span& &span class=&k&&then&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&GetInstance&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&__createSingletonClass&/span&&span class=&p&&(&/span&&span class=&n&&classType&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&n&&superType&/span& &span class=&k&&then&/span&
&span class=&c1&&-- 有父类存在时,设置类身上的super属性&/span&
&span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&super&/span& &span class=&o&&=&/span& &span class=&nb&&setmetatable&/span&&span class=&p&&(&/span& &span class=&p&&{&/span& &span class=&p&&},&/span&
&span class=&p&&{&/span&
&span class=&n&&__index&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&func&/span& &span class=&o&&=&/span& &span class=&n&&__ClassTypeList&/span&&span class=&p&&[&/span&&span class=&n&&superType&/span&&span class=&p&&][&/span&&span class=&n&&key&/span&&span class=&p&&]&/span&
&span class=&k&&if&/span& &span class=&s2&&&&/span&&span class=&s&&function&&/span& &span class=&o&&==&/span& &span class=&nb&&type&/span&&span class=&p&&(&/span&&span class=&n&&func&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&c1&&-- 缓存查找结果&/span&
&span class=&c1&&-- Todo,要考虑reload的影响&/span&
&span class=&n&&tbl&/span&&span class=&p&&[&/span&&span class=&n&&key&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&func&/span&
&span class=&k&&return&/span& &span class=&n&&func&/span&
&span class=&k&&else&/span&
&span class=&nb&&error&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&Accessing super class field are not allowed!&&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&p&&}&/span& &span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 虚表对象。&/span&
&span class=&kd&&local&/span& &span class=&n&&vtbl&/span& &span class=&o&&=&/span& &span class=&p&&{&/span& &span class=&p&&}&/span&
&span class=&n&&__ClassTypeList&/span&&span class=&p&&[&/span&&span class=&n&&classType&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&vtbl&/span&
&span class=&c1&&-- 类的metatable设置,属性写入虚表,&/span&
&span class=&nb&&setmetatable&/span&&span class=&p&&(&/span&&span class=&n&&classType&/span&&span class=&p&&,&/span&
&span class=&p&&{&/span&
&span class=&n&&__index&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&vtbl&/span&&span class=&p&&[&/span&&span class=&n&&key&/span&&span class=&p&&]&/span&
&span class=&k&&end&/span&&span class=&p&&,&/span&
&span class=&n&&__newindex&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&,&/span& &span class=&n&&value&/span&&span class=&p&&)&/span&
&span class=&n&&vtbl&/span&&span class=&p&&[&/span&&span class=&n&&key&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&value&/span&
&span class=&k&&end&/span&&span class=&p&&,&/span&
&span class=&c1&&-- 让类可以通过调用的方式构造。&/span&
&span class=&n&&__call&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&self&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&c1&&-- 处理单例的模式&/span&
&span class=&k&&if&/span& &span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&_IsSingleton&/span& &span class=&o&&==&/span& &span class=&kc&&true&/span& &span class=&k&&then&/span&
&span class=&k&&return&/span& &span class=&n&&__createSingletonClass&/span&&span class=&p&&(&/span&&span class=&n&&classType&/span&&span class=&p&&,&/span& &span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&else&/span&
&span class=&k&&return&/span& &span class=&n&&classType&/span&&span class=&p&&.&/span&&span class=&n&&new&/span&&span class=&p&&(&/span&&span class=&o&&...&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&p&&}&/span& &span class=&p&&)&/span&
&span class=&c1&&-- 如果有父类存在,则设置虚表的metatable,属性从父类身上取&/span&
&span class=&c1&&-- 注意,此处实现了多层父类递归调用检索的功能,因为取到的父类也是一个修改过metatable的对象。&/span&
&span class=&k&&if&/span& &span class=&n&&superType&/span& &span class=&k&&then&/span&
&span class=&nb&&setmetatable&/span&&span class=&p&&(&/span&&span class=&n&&vtbl&/span&&span class=&p&&,&/span&
&span class=&p&&{&/span&
&span class=&n&&__index&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&(&/span&&span class=&n&&tbl&/span&&span class=&p&&,&/span& &span class=&n&&key&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&ret&/span& &span class=&o&&=&/span& &span class=&n&&__ClassTypeList&/span&&span class=&p&&[&/span&&span class=&n&&superType&/span&&span class=&p&&][&/span&&span class=&n&&key&/span&&span class=&p&&]&/span&
&span class=&c1&&-- Todo 缓存提高了效率,但是要考虑reload时的处理。&/span&
&span class=&n&&vtbl&/span&&span class=&p&&[&/span&&span class=&n&&key&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&ret&/span&
&span class=&k&&return&/span& &span class=&n&&ret&/span&
&span class=&k&&end&/span&
&span class=&p&&}&/span& &span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&return&/span& &span class=&n&&classType&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 判断一个类是否是另外一个类的子类&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__isSubClassOf&/span&&span class=&p&&(&/span&&span class=&n&&cls&/span&&span class=&p&&,&/span& &span class=&n&&otherCls&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&nb&&type&/span&&span class=&p&&(&/span&&span class=&n&&otherCls&/span&&span class=&p&&)&/span& &span class=&o&&==&/span& &span class=&s2&&&&/span&&span class=&s&&table&&/span& &span class=&ow&&and&/span&
&span class=&nb&&type&/span&&span class=&p&&(&/span&&span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&superType&/span&&span class=&p&&)&/span& &span class=&o&&==&/span& &span class=&s2&&&&/span&&span class=&s&&table&&/span& &span class=&ow&&and&/span&
&span class=&p&&(&/span& &span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&superType&/span& &span class=&o&&==&/span& &span class=&n&&otherCls&/span& &span class=&ow&&or&/span& &span class=&n&&__isSubClassOf&/span&&span class=&p&&(&/span&&span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&superType&/span&&span class=&p&&,&/span& &span class=&n&&otherCls&/span&&span class=&p&&)&/span& &span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&IsGLDeclared&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&isSubClassOf&&/span&&span class=&p&&))&/span& &span class=&ow&&or&/span&&span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&isSubClassOf&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&n&&GLDeclare&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&isSubClassOf&&/span&&span class=&p&&,&/span& &span class=&n&&__isSubClassOf&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&c1&&-- 判断一个对象是否是一个类的实例(包含子类)&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__isInstanceOf&/span&&span class=&p&&(&/span&&span class=&n&&obj&/span&&span class=&p&&,&/span& &span class=&n&&cls&/span&&span class=&p&&)&/span&
&span class=&kd&&local&/span& &span class=&n&&objClass&/span& &span class=&o&&=&/span& &span class=&n&&obj&/span&&span class=&p&&:&/span&&span class=&n&&getClass&/span&&span class=&p&&()&/span&
&span class=&k&&return&/span& &span class=&n&&objClass&/span& &span class=&o&&~=&/span& &span class=&kc&&nil&/span& &span class=&ow&&and&/span& &span class=&nb&&type&/span&&span class=&p&&(&/span&&span class=&n&&cls&/span&&span class=&p&&)&/span& &span class=&o&&==&/span& &span class=&s1&&'&/span&&span class=&s&&table'&/span& &span class=&ow&&and&/span& &span class=&p&&(&/span&&span class=&n&&cls&/span& &span class=&o&&==&/span& &span class=&n&&objClass&/span& &span class=&ow&&or&/span& &span class=&n&&__isSubClassOf&/span&&span class=&p&&(&/span&&span class=&n&&objClass&/span&&span class=&p&&,&/span& &span class=&n&&cls&/span&&span class=&p&&)&/span& &span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&IsGLDeclared&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&isInstanceOf&&/span&&span class=&p&&))&/span& &span class=&ow&&or&/span&&span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&isInstanceOf&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&n&&GLDeclare&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&isInstanceOf&&/span&&span class=&p&&,&/span& &span class=&n&&__isInstanceOf&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&IsGLDeclared&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&Class&&/span&&span class=&p&&))&/span& &span class=&ow&&or&/span&&span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&Class&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&n&&GLDeclare&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&Class&&/span&&span class=&p&&,&/span& &span class=&n&&__Class&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&span class=&k&&return&/span& &span class=&n&&__Class&/span&
&/code&&/pre&&/div&&p&这个Lua的Class实现也有参考网上的开源代码,做了一些自己的改进,主要功能有:&/p&&ol&&li&只支持单继承;&/li&&li&原生支持单例,但注意,对于不需要继承的单例,比如一些常用的Manager,其实不推荐使用Class的方式,而是直接使用Lua的Table的形式来做效率更高;&/li&&li&支持super来调用父类的方法,但是调用的时候必须使用 &code&ClassName.super(self, ...)&/code& 这样的方式来显示地把self传递给父类,否则父类拿到的self会是错误的对象;&/li&&li&支持构造函数ctor,但是这在某些想自动控制构造的情况下也是一把双刃剑……&/li&&/ol&&p&对于多重集成没有提供原生支持,本来是可以的,但是多重集成有自身的问题,我们提供了一种基于Mixin 的思路来处理,类似于Interface,核心目标功能是合并一些函数到一个Class中,提供一些大类的模块拆分,避免出现一个几千甚至上万行代码的类文件。(之前端游项目中,几万行的py文件都有遇到……当时eclipse这样的IDE打开这样的py文件都要好久……)&/p&&div class=&highlight&&&pre&&code class=&language-lua&&&span&&/span&&span class=&c1&&-- 将一个table中所有的属性和方法合并到一个class中,用于处理一个类比较大的设计&/span&
&span class=&c1&&-- 注意,合并的方法的reload需要单独处理&/span&
&span class=&kd&&local&/span& &span class=&k&&function&/span& &span class=&nf&&__MixinClass&/span&&span class=&p&&(&/span&&span class=&n&&cls&/span&&span class=&p&&,&/span& &span class=&n&&mixin&/span&&span class=&p&&)&/span&
&span class=&nb&&assert&/span&&span class=&p&&(&/span&&span class=&nb&&type&/span&&span class=&p&&(&/span&&span class=&n&&mixin&/span&&span class=&p&&)&/span& &span class=&o&&==&/span& &span class=&s1&&'&/span&&span class=&s&&table'&/span&&span class=&p&&,&/span& &span class=&s2&&&&/span&&span class=&s&&mixin must be a table&&/span&&span class=&p&&)&/span&
&span class=&k&&for&/span& &span class=&n&&name&/span&&span class=&p&&,&/span& &span class=&n&&attr&/span& &span class=&k&&in&/span& &span class=&nb&&pairs&/span&&span class=&p&&(&/span&&span class=&n&&mixin&/span&&span class=&p&&)&/span& &span class=&k&&do&/span&
&span class=&k&&if&/span& &span class=&n&&cls&/span&&span class=&p&&[&/span&&span class=&n&&name&/span&&span class=&p&&]&/span& &span class=&o&&==&/span& &span class=&kc&&nil&/span& &span class=&k&&then&/span&
&span class=&n&&cls&/span&&span class=&p&&[&/span&&span class=&n&&name&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&n&&attr&/span&
&span class=&k&&else&/span&
&span class=&c1&&-- 属性名称相同不覆盖而是给出警告。&/span&
&span class=&nb&&print&/span& &span class=&p&&(&/span&&span class=&nb&&string.format&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&[WARNING] The attribute name %s is already in the Class %s!&&/span&&span class=&p&&,&/span& &span class=&n&&name&/span&&span class=&p&&,&/span& &span class=&n&&cls&/span&&span class=&p&&.&/span&&span class=&n&&toString&/span&&span class=&p&&()))&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&k&&end&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&IsGLDeclared&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&MixinClass&&/span&&span class=&p&&))&/span& &span class=&ow&&or&/span&&span class=&p&&(&/span&&span class=&ow&&not&/span& &span class=&n&&MixinClass&/span&&span class=&p&&)&/span& &span class=&k&&then&/span&
&span class=&n&&GLDeclare&/span&&span class=&p&&(&/span&&span class=&s2&&&&/span&&span class=&s&&MixinClass&&/span&&span class=&p&&,&/span& &span class=&n&&__MixinClass&/span&&span class=&p&&)&/span&
&span class=&k&&end&/span&
&/code&&/pre&&/div&&h2&2.3 常用函数库的补充&/h2&&p&这一部分是自己来弥补Lua语言函数库不丰富的问题,当然也要看项目需求,我们引入的主要有:&/p&&ol&&li&table相关的一些操作函数,包括长度获取、dump为字符串、深浅拷贝、深度对比、根据值获得索引等等;&/li&&li&json库;&/li&&li&int64库(用的是Lua 5.1);&/li&&li&bit操作库;&/li&&li&Lua socket库;&/li&&li&……&/li&&/ol&&p&这部分跟项目具体需求相关,就不一一列举和给出代码了。&/p&&h2&2.4 IDE&/h2&&p&IDE的部分也只说几句,我们团队目前用的比较多的是Sublime Text 3和VS Code,最初我个人还在使用VS+插件的形式,后来也转向了VS Code阵营。&/p&&p&个人体验VS Code还是比较不错的,加上一些自动补全和基于LuaChecker的语法检查插件,基本能够保证避免开发中一些很蠢的bug。&/p&&p&如果需要,可以自己导出一下Unity的接口为一个Lua的文件,提升自动补全的体验,比如我们最初导出的一份U3DAPI.lua的部分内容截取示例如下:&/p&&div class=&highlight&&&pre&&code class=&language-lua&&&span&&/span&&span class=&c1&&--- &summary&&/span&
&span class=&c1&&--- 全名:UnityEngine.Camera.depthTextureMode [读写] &/span&
&span class=&c1&&--- 返回值 : DepthTextureMode&/span&
&span class=&c1&&--- &/summary&&/span&
&span class=&c1&&--- &returns type=&DepthTextureMode&&&/returns&&/span&
&span class=&n&&Camera&/span&&span class=&p&&.&/span&&span class=&n&&depthTextureMode&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&()&/span& &span class=&k&&end&/span&
&span class=&c1&&--- &summary&&/span&
&span class=&c1&&--- 全名:UnityEngine.Camera.clearStencilAfterLightingPass [读写] &/span&
&span class=&c1&&--- 返回值 : Boolean&/span&
&span class=&c1&&--- &/summary&&/span&
&span class=&c1&&--- &returns type=&Boolean&&&/returns&&/span&
&span class=&n&&Camera&/span&&span class=&p&&.&/span&&span class=&n&&clearStencilAfterLightingPass&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&()&/span& &span class=&k&&end&/span&
&span class=&c1&&--- &summary&&/span&
&span class=&c1&&--- 全名:UnityEngine.Camera.commandBufferCount [读写] &/span&
&span class=&c1&&--- 返回值 : Int32&/span&
&span class=&c1&&--- &/summary&&/span&
&span class=&c1&&--- &returns type=&Int32&&&/returns&&/span&
&span class=&n&&Camera&/span&&span class=&p&&.&/span&&span class=&n&&commandBufferCount&/span& &span class=&o&&=&/span& &span class=&k&&function&/span&&span class=&p&&()&/span& &span class=&k&&end&/span&
&/code&&/pre&&/div&&h2&2.5 培训和分享&/h2&&p&我们团队的同学大都有多年使用Python的经验,但是对于Lua还是需要上手时间,所以在最初的时候就组织了程序内部的Lua培训和分享,把比如对于table和string使用的坑、元表、Lua的GC基本原理、错误处理等等方面在团队内部进行了统一的学习和讨论,整体的收获还是比较大的。在开发过程中发现的代码上的问题,也及时在群内进行讨论,这些都逐步提高了整个团队使用Lua进行游戏开发的能力和效率。&/p&&h2&2.6 小结&/h2&&p&Lua语言自身的确是有很多易用性上的问题,前文提到的库不够丰富之类的,通过在项目初期添加一些基础的结构和库,再加上一些提前规避错误的强制手段,可以一定程度上改善易用性的问题。然而,即使到现在,使用Lua有一年多的时候,我们团队中还是偶尔有同学出现.和:用错导致bug的现象。用好一门语言总是需要一个不断踩坑不断成长的过程,C#也好,Python也好,Lua也好,都需要不断地学习和改进,希望我们的一些经验和教训可以帮助刚刚上手Lua的团队提前规避一些坑,也期望更多已经熟练使用Lua的团队可以分享你们经验和方法~&/p&&p&总是,Lua这门小而精的语言,在提供了脚本语言中几乎最快的运行效率的同时,也有着开发效率方面的各种问题,这些问题需要整个团队的力量去弥补和改进。 我相信,经过积淀的团队,在使用Lua进行大型游戏的开发时,可以达到不差于任何其他语言的开发速度。&/p&&p&[未完待续]&/p&&p&&br&&/p&&p&日凌晨于杭州家中&/p&
0. 照旧的碎碎念转眼间已经三月了,2月份的博客因为过年的懒惰和开年之后的忙碌而没有写……第二个月就打破了去年总结时对于2018年的愿望,真是羞耻呢……年后在准备新的测试版本,断断续续做了一些优化,更多的精力放在团队的绩效评估、沟通这样偏管理的事…
在Unity开发者大会上听了一个介绍对Unity脚本做静态分析的演讲,感觉非常有用,于是做了一些尝试。大概就是提供了一个基于微软Roslyn插件UnityEngineAnalyzer可以对Unity的代码做静态分析,找到一些代码隐患和一些可能存在的性能问题。插件可以单独运行,也可以导入VS做为一个插件,它取代部分人工的ProjectReview,并可集成到CI系统(持续集成)做自动化检测。&/p&&p&&br&&/p&&p&&b&什么是静态代码分析&/b&&/p&&p&
简而言之就是指在不运行代码的情况下,通过词法分析,语法分析等技术对代码做扫描和解析,验证代码是否满足规范,是否有隐患,是否可靠可维护等。例如比较著名的Lint可以检测出可能的空指针,拼写错误,除0等等。VS,还有VS的插件Resharper也带有静态代码分析的功能。和一般的插件不同,UnityEngineAnalyzer可以分析一些Unity特有的一些性能和问题隐患,也支持自定义添加规则。 &/p&&p&&br&&/p&&p&&b&什么是Roslyn&/b&&/p&&p&
Roslyn 是微软公司开源的 .NET 编译器。编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API。这是一个更为开放式的编译器,与以往不透明的编译过程不同,开发者可以在编译过程中访问和分析编译数据。根据提供的API可以进行大量用户自定义的扩展。通过使用Roslyn提供的API,在您键入代码时,甚至在您完成一行之前,它们就能生成警告,不需要等到生成代码时才找出您所犯的错误。分析器还可以通过新的 Visual Studio 灯泡图标提示来显现自动代码修复方案,让您立即清理代码。&/p&&p&&br&&/p&&p&&b&为什么需要静态代码分析&/b&&/p&&ul&&li&提早发现代码中的BUG,避免将BUG带到生产环境&/li&&li&极大的提高软件质量,以及可维护性&/li&&li&统一代码规范、提高可读性,减少新加入成员的熟悉时间&/li&&li&加速个人和团队的成长,知识和经验的积累&/li&&li&节约有限的开发时间&/li&&li&避免人工检查的遗漏,不可能记住所有优化点也不能保证不遗漏&/li&&li&可以内建到CI系统(持续集成)&/li&&/ul&&p&
规范的项目一般会有代码审核,而代码审核分为人工审查和工具审查。人工审查方面目前每天会有代码Review,每个版本会做ProjectReview。但是人都会有疏漏,使用自动化的工具进行补充可以避免人工检查的漏洞和节省大量时间。&/p&&p&&br&&/p&&p&&b&UnityEngineAnalyzer的优点&/b&&/p&&ul&&li&可以发现Unity特有的一些潜在问题,例如代码规范,性能问题&/li&&li&可以作为VS插件使用,也可以作为独立的程序单独执行&/li&&li&可以通过ComandLine执行&/li&&li&可以生成HTML报告&/li&&li&多平台支持&/li&&/ul&&p&
以下是他们官网的介绍:UnityEngineAnalyzer分析器是一套基于Roslyn分析程序,旨在检测Unity3D C#代码中的常见问题。Unity3D让我们很容易做跨平台游戏,但是有一些隐藏的规则例如性能和AOT等,会影响到程序的体验和测试等。我们希望这些问题能在编译之前被发现。&/p&&p&&br&&/p&&p&&b&UnityEngineAnalyzer的检查项&/b&&/p&&ul&&li&不允许直接Tag的比较,使用CompareTag代替直接比较减少GC;&/li&&li&不允许在Update中调用Find;&/li&&li&不允许有空的MonoBehaviour方法,例如Update,FixedUpdate,LateUpdate等;&/li&&li&不允许使用OnGUI,OnGUI会导致GC;&/li&&li&不允许使用Coroutine避免产生GC,这个目前使用的5.5.1版本已经修复不需要了;&/li&&li&不允许使用Foreach避免产生GC,这个目前使用的5.5.1版本已经修复不需要了;&/li&&li&string方法&/li&&li&Remoting(AOT),EmitCalls(AOT),GetType(AOT)&/li&&li&对IL2CPP,使用去虚拟化可以提升部分虚函数调用的开销 &a href=&http://link.zhihu.com/?target=http%3A//gad.qq.com/article/detail/7168288& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&gad.qq.com/article/deta&/span&&span class=&invisible&&il/7168288&/span&&span class=&ellipsis&&&/span&&/a&&/li&&li&…….&/li&&/ul&&p&例子如下:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-e1baa4bb4f37faf9192ddd_b.png& data-rawwidth=&608& data-rawheight=&704& class=&origin_image zh-lightbox-thumb& width=&608& data-original=&https://pic1.zhimg.com/v2-e1baa4bb4f37faf9192ddd_r.jpg&&&/figure&&p&&br&&/p&&p&&b&UnityEngineAnalyzer的使用&/b&&/p&&ul&&li&&b&命令行模式&/b&&/li&&/ul&&p&1. 到 &a href=&http://link.zhihu.com/?target=https%3A//github.com/vad710/UnityEngineAnalyzer& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/vad710/Unity&/span&&span class=&invisible&&EngineAnalyzer&/span&&span class=&ellipsis&&&/span&&/a& 下载最新版本,然后解压缩,编译。UnityEngineAnalyzer.CLI\bin目录下得到可执行文件UnityEngineAnalyzer.CLI.exe。&/p&&p&2. 打开命令提示符或Powershell窗口&/p&&p&3. 运行UnityEngineAnalyzer.CLI.exe
&project path&。例如:&
UnityEngineAnalyzer.CLI.exe C:\Code\MyGame.CSharp.csproj&/p&&p&4. 观察分析结果&/p&&p&5. 在项目文件相同位置,会生成report.json和UnityReport.html报告&/p&&figure&&img src=&https://pic1.zhimg.com/v2-3d4e610a503d8e1c16ffd98_b.png& data-rawwidth=&1100& data-rawheight=&658& class=&origin_image zh-lightbox-thumb& width=&1100& data-original=&https://pic1.zhimg.com/v2-3d4e610a503d8e1c16ffd98_r.jpg&&&/figure&&p&
上图是我们项目的分析报告。需要注意的是html需要用FireFox浏览器打开,json可以直接浏览。原始工程的exe在执行的时候会报一些异常需要修改一下代码,主力工程不需要。命令行模式设置可以通过选择项目-〉右键-〉属性;调试-〉命令行参数。然后在命令行参数里面输入命令行参数调试运行。&/p&&p&&br&&/p&&ul&&li&&b&NuGet打包&/b

我要回帖

更多关于 unity3d和虚幻4哪个好 的文章

 

随机推荐