美高梅4688.comUnity手游开发札记——Lua语言集成

0. 前言

2016年8月自网易“毕业”,在新的局开始新的干活,这间的波折和故事暂时不领取,等随后来日之当儿更另起文章总结回顾。全新的手游类型从零开始,使用Unity引擎开发同悠悠手游类型,本系列札记主要对开发进程被重要的片进行记录与小结,一方面有利于自己下回顾,也冀望可以于撞类似问题之情侣有唤起和启迪。当然,自己于Unity引擎和Lua语言方面都是新手,更加希望抛砖引玉,针对遇到的题目开展更广大的座谈以及再次多生牛之提点。
切切实实到本篇文章的主题,主要是Lua语言和Unity引擎的购并。这是自我多年来一个月左右的工夫一直以尝解决的题目,本文主要记录方案的挑选与对比,以及针对选择的方案自己开展的有的改造暨思维。

0. 例行的啰嗦

落得篇关于技术之章曾是10月之之作业了,上个月形容了一半篇有关创业之稿子,写到新兴以为无趣,就歇了——谁会甘愿看一个名不见经传小卒的庸俗感慨吧?又是贴近月底了,其实只是写的内容未掉,只是岁月未多,就选最近费了盖一半独礼拜时间下手的之优化来聊聊Fast
Shadow Receiver这个插件的施用吧。

1. 为何而集成Lua语言

起句从知乎开始提高起的名言叫做——“先问是未是,再提问怎么”,类似地,在做一个艺方案的时刻,“先咨询为何,再考虑怎么做”。那我们第一独问题就是只要缓解者路“为什么要集成Lua语言”?
当网易内部,一向遵守的风俗是逻辑用脚本来做,比如Python、Lua等,好处主要出如下几沾:

  • 以脚本语言的动态特性,客户端好做Hotfix,服务端可以举行Refresh,无论以运营或出要就无异风味还不行有因此;
  • 脚本语言运行在虚拟机中,它将玩过程将挂的票房价值相比C/C++等静态语言要没有;
  • 脚本语言相对好读书一些,对于新手来说上手难度比逊色,比如Python,当然要通呢用时间与经验的积累;

自还时有发生任何的助益,对应的败笔就是在于运行效率比C/C++低不少,相对于静态语言在编译器有完备的语法检查,动态语言更便于有片运转时的不当,调试难度相对非常有。
比方于Unity引擎,因为其已选择了C#作为对应的脚本语言,因此更集成一门Lua语言显得有点多余。核心的来头还是当IOS设备及以以了IL2CPP,无法兑现像Android上面那样直接调换DLL的办法来开展更新,这招游戏逻辑如果起谬误,不但无法Hotfix修复,甚至连Patch都不可知修补,只能更提包。虽然APP
Store现在对用的按速度就变快,
但是还是要2-3天以上的年月,这对于急需快速反应的生意娱乐吧是无法耐受的。
眼下打探及之标准常用的做法要有如下几栽:

  • 纯C#支付的方法,比如腾讯这种大厂,某些工作室的做法就是是了使用C#来进展支付,尽量做到功能逻辑可配置,这样出现一些重点题材可透过创新数据的道把逻辑暂时关闭掉。逻辑的翻新安卓应用替换DLL的措施,IOS使用更提包的艺术。对外测试为安卓为主,并且大厂有比好的QA团队进行质量担保,因此得以成功IOS最终上线的人品及bug都是对立少之。
  • C#开基本逻辑,Lua做UI和动玩法齐施行效率低,需求变动较充分的一对。这是眼前了解及的组成部分创业团以得比较多之做法,在效率和可更新性之间的一个折中。
  • 因为Lua为主的法子。也了解及有些局的团组织,包括网易内部的一对类型,使用逻辑都归因于Lua语言来写的法门展开付出。从网易之前的经验来拘禁,逻辑下纯脚本的方并无会见发出尽非常问题。

咱们如果支付的成品是同缓商业娱乐,对于出现问题迅速响应的要求相对明显,因此在Unity中行使Lua语言是不可或缺的,至于多深范围地行使它,初步是计划大部分功力还是用Lua语言来开,并制定各级隔一段时间周期进行性能测试和评估的章程来保证性能好满足要求。

1. 起因

至于Unity中之动态阴影,已经起深多帖子聊了这个话题了,比如就首《Unity移动端动态阴影总结》,还有钱康来博客里之立即首《利用Projector实现动态阴影》和《Planar
Shadow》等等。
凭最简便的因Planar投影的方案或有点“老式”一些之Projector的方案,乃至目前比主流的ShadowMap的方案其实还各起上下与相应之动场景,它们中间的原理与距离不是本文的严重性,有趣味之同学为堪十分容易地找到有关的舆论或博客来拘禁。

我们种针对不要还过去轮子的想法,一直坚称下Unity原生的ShadowMap的方案来举行动态阴影。而且UWA也召开了有动态阴影方案效率的相比,自己的车轱辘能做得比出源码的合法好的连无多,更何况我们这种地表来起伏,高配用支持多角色动态阴影的“大型”MMORPG游戏,ShadowMap已经是无与伦比可的方案了。

而!人生总会有然而,否则就顶平淡无味了无是?……

约莫1单多月前,我发现了这题目——《Unity中静态合批与Shadowmap的宏设置冲突问题》,简单的话,静态合批首先对气象物体进行了排序,保证结果对,但是当引入了动态阴影下,会失去修改物体接受阴影的特大(这为是一律栽优化,因为发采样和阴影计算的耗费,所以关闭掉宏着色器的频率又强),导致本排序好之物体无法正常进行合批,因为着色器的宏不一样了,从而致使前面静态合批之后理论及可以形成格外没有之Batch数值增加了成千上万,使得场面渲染的频率大幅下降。

此问题在惦记知道原因之后,在还想只要动用Unity的ShadowMap的前提下感到是绝非呀特别简单的优化方案的,于是就临时搁置下来,直到上周之时节针对游戏各个职能对于帧率的影响于真机上召开了一个定量的测试之后,才意识问题颇为较想象着的严重……

美高梅4688.com 1

次第职能对于帧率影响之定量测试结果

方的测试是以中配机型小米Max2臻展开的,可以见到阴影的开关与否导致一帧的时间耗来9.5ms左右之歧异,是富有机能被影响最为老之!而ShadowMap自身渲染消耗不应有发这般大之反差才对,观察了下Batch数量的别,单纯场景的Batch数量大致会起25增加至150左右,这出接触过发我们前面制定的图专业了。

于中配效果下,我们惟有主角自己打开了动态阴影,因此最初的一个想方设法就是引入另外一模仿阴影绘制方案,比如Dynamic
Shadow
Projector,来专门对主角进行阴影的绘图。虽然本人个人死无喜以采用有限学技术方案,但眼前羁押起就如同是以无跌效果的前提下唯一的选择了。

2. 怎样集成Lua语言

当支配要利用Lua语言之后,要面临的题目即是怎么样以Unity中去拼它。可挑选的方案有成百上千,各种方案的实现原理也不尽相同,早期有各种以C#言语中贯彻Lua虚拟机的,也发出使反射动态查找脚本的,但是目前比较主流的少种方案是ToLua#和SLua立即简单栽方案。

2. Dynamic Shadow Projector插件

This simple Unity asset provides a few components to render a shadow
onto a render texture so that the render texture can be used with Blob
Shadow Projector. Blob Shadow Projector is usually used for dropping a
round blurry shadow which is not suitable for a skinned mesh object.
This asset enables a projector to drop a dynamic shadow which is
perfect for skinned mesh objects.

Dynamic Shadow
Projector插件的原理比较简单,将角色的黑影绘制到平张rt上,然后用Unity的Projector组件将随即张rt作绘制输入,再绘制一任何接受阴影的体。阴影的rt是每帧更新的,也就算完事了可叫带有动画的角色阴影是实时变化的。

试用了瞬间,还是比较简单易上手的,几个零部件是安装后就可以看到成效了,由于是指向单个角色的,因此用比较粗的rt就得得比shadowmap更加精致的功效,但是一旦想让一个projector处理多只角色,一旦扩展projector的限制,阴影效果质量的暴跌就比shadowmap的办法还要厉害。

美高梅4688.com 2

128*128的rt只投影一个Cube的场面下rt的使用率和影子质量

美高梅4688.com 3

512*512的rt投影三个Cube的景象下rt的使用率和影子质量

上面两布置图分别叫闹了效仿下一个Projector针对单个角色进行投影和多独角色进行投影的效能指向比图,在脚的那么张图中,三单Cube的相距相隔并无多,但是就算用了512*512的rt,明显得见到那个阴影已经起矣锯齿感,距离还充分之时光锯齿更加严重。
那我胡纠结于得想如果使用一个Projector来开展多只角色的动态阴影绘制呢?因为于各级一个Projector来说,绘制阴影的时光都得将接受阴影的模型完全重回一不折不扣,从底下抓帧的截图可以看看,三个Cube分别采用三个例外的Projector,地表平面需要绘制三全勤。这其实就算是Projector的方不太适合运动装备及基本上个物体都用进行动态阴影绘制的由来。

美高梅4688.com 4

多独Projector的时接到阴影的地表绘制抓帧截图

咱的地表利用了Terrain制作,转为Mesh之后的三角数量一般以老大几千之品位,多举绘制对于整体面数的加还是不行惊人的,虽然在我们的中配下独自来主角接受动态阴影,只需要多同等满地表模型的绘图,拿一样糟Draw
Call和几千迎之耗费换取100+次Batch的抽,理论及曾经够用划算了,但是我还生头未极端情愿,于是想尝尝下Dynamic
Shadow Projector推荐配合“服用”的Fast Shadow Receiver插件。

2.1 性能比测试

马上有限个方案的规律都相似,基于LUAInterface,在出时用C#的接口导出为Lua的本,通过LuaState的堆栈结构来进行有限种语言中方法调用。这有限只开源项目对性能比在网上自了众总人口水仗,到底何许人也还了不起生不便公允地评论,因为作为一个中间件性质的开源项目,除了性能外还有生态圈、易用性等各个方面的题目需勘查。网上发诸多对待的帖子可以协调查找一下,这里不进行详述了,以免引起论战。
以就等同有些我们最终摘取了ToLua#,原因我是投机在安卓设备上展开测试了结果。钱康来前段时间发了一个帖子来比几款款Unity中Lua集成方案的属性,Unity常见lua解决方案性能于,这首文章也罢整治投稿至了UWA博客面临,我要好根据测试用例在锤子T2达开展了简便易行的性测试,结论和这首博客中的基本一致,未整理的数如下表所示。

框架 test1 test2 test3 test4 test5 test6
SLua 755.004 623.619 34.126 6812.41 1648.68 0.6352
ToLua# 634 871.2 297.8 3056.2 1139.4 1.206

数据的单位是毫秒,测试是进行五坏测试的平均值,使用锤子T2开展。这次测试并无审慎,只是为着亲自说明一下两者之间的性能差异究竟是呀体统的。每一个测试用例的代码可以参见前文提到文章,这里就简简单单进行说明:

  • test1是简单的特性操作;
  • test2和test3凡是向量的操作;
  • test4是GameObject的创建;
  • test5凡开创GameObject并开展有特性操作;
  • test6凡是针对四元数进行操作。

3. Fast Shadow Receiver的试用

Fast Shadow
Receiver插件是老长远前我哪怕关心过的一个插件,钱康来当外的博客里呢出涉及。我直接维系一个敬畏的心情,一凡是为自更上吧ShadowMap没有经受阴影方需要重绘的问题,只是庞大的更动,效率应十分高的(没悟出影响了Static
Batching);二是对于运行时对mesh进行强力重建一直心心存疑虑,担心其对CPU和内存的附加压力。

置了插件,将该引入我好本地的型工程,玩了玩Demo之后,尝试将那同Dynamic
Shadow
Projector结合并使用。和AssetStore上对此这插件的褒贬一样,这个插件的文档的确有点别扭,大约打了三四只钟头之岁月才正式在玩乐中走通整个工艺流程,过程未详述了,几单稍坑记录转:

  1. 恐怕是法定给吐槽文档太难读,所以开了同等拟Wizard,一步步平移教君怎么布局,然而我按照步骤做截止之后并无沾正确的结果,反而以Wizard隐藏了默默的组成部分装置步骤导致自己一筹莫展正确理解过程,从而难以排查原因。而且Wizard是对特定的求,未必是自己要好想如果的功用。最终自要遵循Demo工程里之组件逐个比配置实现的职能。
  2. LayerMask设定得小心,为了优化效率,Projector组件上生Igore
    Layers的设定,在Draw Target Object上,也起Layer
    Mask的设定用于标识要绘制的节点下怎样Layer会被绘制,最终的ShadowReceiver组件也会属于有一个Layer,比如默认的Default。这几乎单Layer如果设定有问题,会导致最终没影子被绘制出。我为此地的差多消费了1单小时的工夫调试各种参数,如果您于采取中遇到了竟然的题材,可以拿温馨设置的各种Layer梳理一全方位,保证逻辑上之没错。我随即的题材有是管ShadowReceiver所在的GameObject归入到了Default
    Layer,而Projector又Igore掉了Default Layer,导致结果未得法。
  3. Fast Shadow
    Receiver的插件制作者估计没受过中华绘画的洗礼,除了文档晦涩之外,代码中于容错的配合考虑得为未周全……我们场景中出几千单物体,在初测试的早晚没有花心思标记所有的地表接受阴影的物体,索性将具有物体都进展标注,结果MeshTree的变一直有问题,查了下是坐我们场景被在一个Mesh对象也miss状态的GameObject导致的,做一下匹配就好了,当然从上啊要画去修补掉mesh
    miss的问题。

一言以蔽之,经过同密密麻麻的品尝,最终于咱们团结一心之工外使用规范的图画资源跑通了上上下下工艺流程,也对Fast
Shadow Receiver的法则来了更要命的领悟:它采用Mesh
Tree这样一个累自Scriptable的切近在相距线等来预测算并储存需要经受阴影的地表网格信息,并且提供BinaryMeshTree、OctMeshTree和TerrainMeshTree三种类型来回答不同的场景。运行时,它提供MeshShadowReceiver这样的组件,根据Projector的设定实时计算出来接受阴影的地方需要盖的那些面片,生成一个初的网格作阴影接收者的网格对象进行渲染,从而就好将本几千直面之模型就需要几十单照虽可绘制出来,因为毕竟需要绘制动态阴影的才生镜头前之一些区域。

美高梅4688.com 5

Fast Shadow Receiver的Demo中的以身作则截图

2.2 性能差异的或原因有

个人感觉ToLua#以性能操作方面性能比好,而Vector的向量操作,因为可能会见来Lua层的优化,即于Lua层完全落实了对应之操作,因此用针对源码进行详尽的自查自纠。至于性能差异的原由,我没于Lua虚拟机的兑现有分析,只是查看两种植生成Warp后的接口进行一个概括的猜测。
慎选和一个接口进行对比,UnityEngine.Animator的GetFloat接口,ToLua#的贯彻如下:

    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int GetFloat(IntPtr L)
    {
        try
        {
            int count = LuaDLL.lua_gettop(L);

            if (count == 2 && TypeChecker.CheckTypes(L, 1, typeof(UnityEngine.Animator), typeof(int)))
            {
                UnityEngine.Animator obj = (UnityEngine.Animator)ToLua.ToObject(L, 1);
                int arg0 = (int)LuaDLL.lua_tonumber(L, 2);
                float o = obj.GetFloat(arg0);
                LuaDLL.lua_pushnumber(L, o);
                return 1;
            }
            //此处省略另一个重载接口
            else
            {
                return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.Animator.GetFloat");
            }
        }
        catch(Exception e)
        {
            return LuaDLL.toluaL_exception(L, e);
        }
    }

SLua生成的代码如下:

    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static public int GetFloat(IntPtr l) {
        try {
            int argc = LuaDLL.lua_gettop(l);
            if(matchType(l,argc,2,typeof(int))){
                UnityEngine.Animator self=(UnityEngine.Animator)checkSelf(l);
                System.Int32 a1;
                checkType(l,2,out a1);
                var ret=self.GetFloat(a1);
                pushValue(l,true);
                pushValue(l,ret);
                return 2;
            }
            //此处省略另一个重载接口
            pushValue(l,false);
            LuaDLL.lua_pushstring(l,"No matched override function to call");
            return 2;
        }
        catch(Exception e) {
            return error(l,e);
        }
    }

咱俩注意到,这等同部数单独待一个返回值的,但是SLua往栈里pushValue了片只价,然后回来2,第一独价值是一个bool值,它应有是用来标识函数调用是否中标。在不打听其他地方是否来性能差别的动静下,这里应该是ToLus#以及SLua在简练的接口调用上的特性差别之因之一。SLua使用一个单身的价来表示函数运行结果,这对错误可以展开再好的拍卖,但是多生之压栈和出栈操作发生格外的特性消耗。

4. 和ShadowMap的整合及集成

当早期的设想中凡对准单身的主角使用Projector方式的动态阴影,然后据此Fast
Shadow Receiver进行优化,在Demo中看到Fast Shadow
Receiver支持ShadowMap的方案时为未曾多想。后来以跟共事谈论这个题材的下聊至Projector的动态阴影方案与ShadowMap的动态阴影方案的上下,被咨询到个别种植方案是休是产生或做一个组成,然后想起了当Demo中看到了用Fast
Shadow
Receiver来优化ShadowMap的例子。正好为以纠结我们抽离式的杀中在中配置下之意义,如果下Projector,需要多几乎张rt的绘图是否合算,那要得以用Fast
Shadow Receiver结合之前的Shadow
Map方案,对于眼前组织的更动是绝小的,也不用引入第二仿照动态阴影的生方案,只相当给用新的插件在中配下解决景静态合批的题材,这似乎是那个精彩之一个方案。

本着这思路,学习了瞬间Fast Shadow
Receiver中有关ShadowMap的例证,看上去也非常简单。在明亮了规律的景象下,只是于观内的外Render组件的Receive
Shadow属性都更改为false,然后就让Fast Shadow
Receiver生成的那样一个面片读取生成的ShadowMap进行阴影的绘图即可,这样额外多1只Draw
Call和几十只当之渲染消耗,就好成功与事先相似之功力,中高配置的切换逻辑吗愈加简明。

咱事先来拘禁一下末尾通过修改敲得下来的制作步骤,然后重新聊一些中间的筹划细节。

  1. 统一用气象被的Mesh相关的机件放置到同一个GameObject下。即时无异于长条本没同长硬性的规定,完全看场编同学自觉,其实整理下Unity中的Hierarchy面板也会见尤其彻底清爽;

    美高梅4688.com 6
    场景Mesh统一放入ArtRoot根节点下

  2. 标志接受阴影的体。即同步是一个聊琐碎之做事,需要美术标记出来怎样物体是吸纳阴影的,BinaryMeshTree是依据这些号出来的物体来开展网格的先期处理的。标记的物体过少会油然而生该接受阴影的体没有影子效果,而过多见面招BinaryMeshTree的数内容了多,加载变慢、检索速度下跌,内存占用呢会过多。由于我们目前仅仅在遭受配下使用,所以对这有些就要求地表和表现明显的体加入到号中。Fast
    Shadow
    Receiver只支持Layer和RenderType的过滤方式,在咱们场景中约略物体就被标记了了别样发出逻辑意义之Layer,因此我对这点进行了改造,增加了Tag的过滤,和Mask
    Layer取或的不二法门来展开处理,并且也画提供了造福的快捷键进行快速标注。我要好测试,我们耍内的景象,标注加上验证需要之耗时盖为就算半独小时到2独钟头免齐。

    美高梅4688.com 7
    提供FastReceiver的Tag进行标注

  3. 创建BinaryMeshTree。咱们最后选项下BinaryMeshTree这种布局,它跟OctMeshTree的区分见下图。其实这手续还待再多的测试来做对比,因为官方也明说small和large的底限具体是啊。

    美高梅4688.com 8
    两栽不同之MeshTree对比

创建BinaryMeshTree的过程也很简单,插件提供了右键Create菜单的支持:  

![](http://o9hm1ti4o.bkt.clouddn.com/FastReceiver5.png)
创建BinaryMeshTree
  1. 生成Mesh
    Tree。
    以标注了接收阴影的物体后,就可选中创建好之BinaryMeshTree,填写该Root
    Object为场景的彻底节点,设置好Layer进行build。我们建议美术检查最后创建了之后吃来的build信息遭到对此内存的占有而小于2M,这是一个辑几只场景过后的经验值而已,还索要再次多证。

    美高梅4688.com 9
    Mesh Tree生成时Layer的配置

![](http://o9hm1ti4o.bkt.clouddn.com/FastReceiver11.png)
Build之后的Mesh Tree信息统计
  1. 配置Projector和Mesh
    Tree信息。
    随即片为简化美术的布局工作,大部分底布置逻辑都写在了代码中,只待美术复制一卖prefab出来,将新创的Mesh
    Tree信息设置科学即可。需要注意就卖prefab是免保留在气象内之,编辑完毕Apply后会见从气象中剔除掉。

    美高梅4688.com 10
    创建BinaryMeshTree

这里一共只使用了两个组件,一个是图中LightProjector对象上的LightProjector组件,用于设置阴影使用的方向光对象以及一些Projector的参数,比如跟随的Target对象,扩展的Bound范围等;另外一个是MeshShadowReceiver组件,关联Mesh
Tree数据,场景渲染物体的根节点和Projecter对象,一些Fast Shadow
Receiver的裁剪、更新方式等属性也可以在这里进行设置。
  1. 于资源根节点上上加Shadow Receiver
    Controller组件,并开展配置。
    即时同零件是我们协调实现之,用于控制Fast
    Shadow
    Receiver的开关,它见面基于游戏配置在观加载、游戏配置切换等逻辑中针对Fast
    Shadow Receiver进行安装。并且根据这无异于零部件实现对Mesh
    Tree的懒加载功能。

    美高梅4688.com 11
    Shadow Receiver Controller组件配置

  2. 当耍运行状态下进行测试。上述配置了之后,就足以以打闹逻辑的中配置下见到优化后的影效果了,可以走跑打闹进行测试。

多数细节都以上述手续中讲述了,这里更作证以下几只地方:

a)
Projector和MeshShadowReceiver组件是勿默认放在场景里之。顿时是由于本地表物体较多之时光,Mesh
Tree的加载是发出工夫耗的(遇到了一个测试例子,Mesh
Tree的轻重有18M横,在PC上用5s上述之动静,具体由并未细查),也会见出格外的内存消耗,因此此一派建议美术确保这个文件不见面专门可怜,另一方面通过Lazy
Load的主意,在急需的当儿才加载,来保管在高配和低配的情事下,不需要另外附加的CPU和内存开销。
b)
为图提供更多造福的工具来号信息。出于标记地表是一个针锋相对琐碎的行事,验证标记是否成立吧是一个桩需要花很多时空跟生机的事体,除了前提到的快捷键可以一键号,还援引通过Layer的显隐功能,以及我们和好开发之Tag显隐功能拓展快速检查和题材一定。

美高梅4688.com 12

Unity原生的Layer过滤功能

2.3 导出方法相比

ToLua#导出使用的是白名单的不二法门,在CustomeSettings.cs文件被定义之接口才会导出,也提供了导出引擎有着的接口的成效;而SLua是为黑名单的办法展开,默认提供的效益是导出除了黑名单中的备模块接口,也提供了一个导出最简接口的法门。
从今运角度来拘禁,SLua黑名单的计于支付要于便于,默认会导出所有接口,因此不需要每次想要多一个就在的接近的Lua接口都设团结定义然后再度导出,发布之时呢堪运用最简接口的办法导出。维护起来ToLua#因为拥有的传输出类都是咱团结一心定义的,因此越清晰明确。
由这片情节有源码可以拓展改动,因此无是一个基本要考虑的内容,两种办法各有利弊。

5. 优化结果与代价

采用相同的测试方法,对比优化前后的玩耍运行帧率和时空消耗:

美高梅4688.com 13

优化前后的性质消耗对比

得看到,使用Fast Shadow Receiver在小米
Max2达标产生大约7.2ms的习性提升,帧率从26升及33,这里面有Batch数量暴跌的功德,应该为产生状况物体不需采样ShadowMap贴图带来的渲染性能提升,更加切实的数据就从来不去测试了。剩余的1.5ms的时空消耗包括了ShadowMap的绘图和Fast
Shadow
Receiver的创新消耗,这是延续之优化对象,但这次优化已经出深怪的晋级了,中配下完全效率提升了20%,已经是难得的“神级优化”了。当然,这树以情景通过关闭Shadow接收的宏能够降低较充分Batch数量之前提下。

这次优化的获益是格外酷的,但其为无咸是同等栽无损优化,需要交的代价来这样几沾:

  1. 美术工作量。消美术同学对场景进行地表接收阴影物体的标号,虽然提供了高效的工具,但是仍然需要花有年华本。
  2. 片物体不再会蒙动态阴影的影响。以事先基于ShadowMap的方案面临,几乎有的体都得记为接收阴影,而且得保功能的不利,但是目前这种方案要假定形成即点会造成Mesh
    Tree对于内存的占有比较多,对于外部的怪世界气象吧非适应,因此会面时有发生出现局部有些石块等物体不会见接角色阴影的题材,这是有些功效的下滑,但眼前关押是足以领之限制外。
  3. 同静态阴影的同甘共苦与ShadowMap的方案不同。ShadowMap的方案是于气象绘制的当儿进行拍卖的,一不良如从方质量的进程被见面采样lightmap和shadowmap两摆贴图,这即好判定出该像素点是否在静态阴影里,这样好做到按当屋檐下或树荫下如此的静态阴影中,角色的实时阴影可以跟静态阴影做一个比好的同甘共苦,如下图所展示。
    美高梅4688.com 14
    基于ShadowMap的方案动态阴影和静态阴影的齐心协力作用
而使用Fast Shadow
Receiver方案之后,就比较难做融合的效果,除非在新生成的mesh中保存之前mesh的uv2信息以及使用的lightmap贴图信息,再做一次lightmap的采样。但这比较麻烦,性价比也不高,于是在静态隐形中的角色动态阴影的效果就变成了如下图所示的样子。  
![](http://o9hm1ti4o.bkt.clouddn.com/FastReceiver18.png)
使用Fast Shadow Receiver方案的效果

除去这些之外的代价就是是程序及时边花费了约一半单多星期的流年来上及合这套方案,但是从优化结果达到看,还是取得颇非常,非常值得的~

2.4 我们的精选

至于这一点是不是是性质差别之重中之重因,因为没有工夫跟精力看外一些的源码,暂时也不顶好进行对比及评价。出于性能的设想,我们项目决定以ToLua#作为Lua部分集成的方案,并且因为接口的样式开展包装,来保证后面替换的可能。

6. 一个Projector同时处理多只角色的动态阴影

由我们是相近回合制的抽离式战斗方式,即玩家进入战斗后整场战斗都见面发出在相同小片固定区域外,这里其实对于ShadowMap结合Fast
Shadow
Receiver的方案是一个良合适的用场景——只需要以进战斗前生成一浅阴影接收的面片,整场战斗中都不需要针对那个进行改动及更改
俺们用LightProjector的Target锁得为战的主干区域点,然后经改Bound的章程扩大其投射范围及方方面面战场。前面早已讨论了因Projector的动态阴影方案的一个题材是当projector较生之时候rt的使用率较逊色,导致阴影质量下滑之题材,但为我们应用的是ShadowMap的黑影方案,因此扩大Projector的限制并无会见潜移默化阴影精度,也无欲处理多独Projector带来rt数量、draw
call增加等问题。

美高梅4688.com 15

战场面临多只角色公用一个LightProjector的方案

3. 怎样采取Lua语言

当开展了开头集成之后,怎样吃开发人员可以重好地采用Lua语言是搭下去要面临的题目。ToLua#本着应该一学之前ulua作者开发之LuaFramework,这一个框架集成了剧本打包和二进制脚本读取、UI制作流程等大多独职能,但是也使作者自己所说,这无异框架最初源自一个演示形式之Demo,因此其中代码有很多片段凡是同演示写死绑定的逻辑,比如启动逻辑、Lua二向前制脚本的加载需要手动指定等等。
交互呼应之,SLua也发差不多效仿已经开源的框架,其中最完善之是KSFramwork,这套框架集成了资源打包、导表、Lua热重载在内的差不多只职能,而且代码质量开始看起还不错,因此最终我们决定拿KSFramwork中的SLua部分替换成ToLua#的一对来成使用。
改建的长河还比较简单,由于拖欠有的应用Lua耦合的才生个别片内容,一凡是UIControler部分,二凡是LuaBehavior部分,所有的接口都出于LuaModule模块提供。因此改造之长河也即较明显了:

  1. 去源代码中之SLua部分,接入ToLua#的部分;
  2. 使用ToLua#重写LuaModule的实现;
  3. 改造LuaUIController,使用初的LuaModule接口实现之前的功能;
  4. 改造LUABehavior模块。

代码删除和LuaModule模块的又实现都比较简单,着重介绍一下LuaUIController及LUABehavior模块的改建。

7. 总及展望

Fast Shadow
Receiver这种通过CPU的实时计算来换取GPU的渲染性能的方案,正好解决了咱场景静态合批被动态阴影打断的题目,大大提升了俺们打在中配下的帧率,是近年所召开的优化中成效最好醒目的一个了,因此呢记录转详实的经过在此处享用出去。
对此此插件的感觉到,在这同全面的逐步熟识、应用、修改的经过遭到,也由心里存疑虑到由衷赞叹。目前对这个插件的魔改还免多,除了前方提到的加码Tag的支持、建立Mesh
Tree的上少有对此资源的一无是处兼容之外,只修改了有些Component的默认参数,更加符合我们项目的设定,让美术和次可以更加惠及地采用。它于运行时对内存的分红和CPU的特性消耗也为我们满意,因此在此处呢协助这个插件做一下广告——别给她的文档和以过程吓到,用好下,你的玩效率可以落充分特别的升官~

至于未来,当中配下的作用以及频率还为证明可以承受之后,可能考虑优化一些其的职能,将其呢采用到高配下,当然,对于贴花等得处理高低不平地面效果的地方,也可设想采用此插件进行效率的优化。

PS:从Fast Shadow
Receiver的启示来揣摩场景静态合批被从断的题目,其实另外一个思路是和谐来举行哪物体需要为领阴影的判定。Unity内部肯定吗是有这般的判断逻辑来设置各个场景Render的宏,由于Shadow的去设定较充分,Unity的论断范围也过广泛,导致了则咱于中配下单发角色渲染阴影,但是接受阴影的体数量过多,从而致使Batch被一再打断的问题。仿照Fast
Shadow
Receiver,使用一个随从角色的阴影,和气象物体相交来判定有什么样物体需要让安装为接受阴影,由于角色时的物体或不过见面有几个,因此Batch的数据为只有见面增多几单。目前没沿这思路来做的来头有吧是地表物体的面数实在是多少多,Fast
Shadow Receiver对于面数的大跌也是咱们纪念使的优化之一。

2017年12月24日叫杭州家(圣诞快乐~ \_

3.1 改造初衷

前面的KSFramwork还是一个中坚逻辑在C#,Lua只承载UI等逻辑的模块,这跟己之前由网易“继承”的“轻引擎,重脚本”的笔触并无适合。在即时同思路下,引擎可以看作渲染、资源加载、音效等功能的供者,脚本逻辑负责运用这些成效构建游戏内容。那这样大部分及逻辑相关的控制权就应该由发动机交给脚本部分来进展。Unity作为一个较特别的事例,虽然对于它吧,C#片已是脚论了,但是对于盼望要以Lua脚本的我们吧,因为C#不可更新,因此被视作了发动机部分。
最简练的宏图虽是当发动机初始化完毕后,通过一个接口调用把承的逻辑都交由下本来控制,大部分跟娱乐玩法相关的型加载、声音播放、特效播放、动画播放等由于下本来控制。tick逻辑为了减少调用次数,每帧也鉴于引擎调用注册之一个本子接口进行联调用,脚本层自己举行分发。

3.2 LuaUIController的改造

LuaUIController原始之法门是当C#臃肿通过ui模块的名号加载对应之一个lua文件,获取一个lua
table进行缓存,在比如OnInit当需接口调用的地方找找这table中对应的函数进行调用。这种措施的界面是由C#臃肿的逻辑来使加载与显示的,而且于加载过程中要发出文件的搜索以及自我批评过程。
如此这般会存在一个问题,就是下面本层的逻辑无法要特别麻烦去控制界面对象的生命周期。针对资源的生命周期,“哪位创建谁管理”的方针不再可以很有利地来明确责任的剪切,因此若开展改造。
改造之趋势十分简短,将界面加载与出示的接口开放至Lua层,然后在创造的下由于lua层传递一个table对象上,C#受到开展缓存,当界面资源异步加载了,需要展开接口调用的地方的实现同事先封存一致。这样,界面资源的生命周期全部届由下本层来保管,在本子构建一个结构合理功能齐全的UIManager来展开部分成效的包裹,就可满足大部分底需要。

3.3 LuaBehavior的改造

MonoBehavior是Unity为了放便开发要提供的一个要命好的效益,脚论以组件的法门挂接在GameObject身上,就足以在Awake、Start、Update等接口中处理想使的逻辑。为了能持续采取Unity的马上等同风味,在Lua层也实现了一个粗略的LuaBehavior封装。
KSFramwork中的笔触好简便,同样因名称来将一个LuaBehavior和一个Lua脚本进行绑定,在相应的逻辑中调用与之相应的接口就好了。比如Awake接口的兑现如下:

        protected virtual void Awake()
        {
            if (!string.IsNullOrEmpty(LuaPath))
            {
                Init();
                CallLuaFunction("Awake");
            } // else Null Lua Path, pass Awake!
        }

CallLuaFunction的实现吗甚肯定,从缓存的lua
table中取得名称为Awake的function进行调用。这种方法没有问题,但是当场景中挂载了LuaBehavior的GameObject很多的上,每一样幅都见面起十分频繁之update方法调用,这个调用从C#臃肿传递让Lua层,有众多格外的习性消耗。
前文也关乎了,比较好之点子是每帧只发一个C#交Lua层的Update方法调用,然后下本层自己开分发。因此,针对这同一要求,我们用ToLua#自从带的LuaLooper来实现即无异意义。
LuaLooper是大局只创造一个底MonoBehaviour,注意这里仅开创一个凡是由于逻辑来支配的,而非是一个单例模式。这里对单例模式适用场合的议论不再进行,此处由逻辑来管单独发一个Looper存在是一模一样起比较合理的事务,预留了片扩展的恐怕。
LuaLooper因事件之计拿三种Update分发出去:Update、LateUpdate、FixedUpdate,它以协调相应之函数中调用luaState的对应函数来用事件告诉脚本,脚本中需要的模块于分发模块注册回调来监听事件,就得成功每帧只来雷同糟Update调用了。
切切实实的代码实现好错过押ToLua#遇的LupLooper.cs的切近实现。

注意
这里有一个要小心的触发是当事件在脚本层分发的时节,要顾执行时序问题的影响,最好能够保证自由的执行各个都得以免影响游戏逻辑的结果,否则恐怕会见产出非常不便查的诡异bug。

对于Awake、Start等一次性调用的函数,由于无是一再的逻辑,因此保留了原有之落实方式,这样好被Lua层对应之代码实现更为从简。而动事件注册的道,让无待update逻辑的本子没有其余额外的特性消耗。

4. 结语

除非上述的这些有些,对于开发同缓缓商业娱乐来说还远远不够,但是通过导出的接口和对于KSFramwork的片改善,已经得以兑现一个粗略的出于Lua层来让的Demo了,它好加载场景,打开一个打包成AssetBundle的界面,设置界面上之控件属性,为按钮上加有回调时间,然后切换场景,加载一些卷入在AssetBudnle中之Prefab模型。
眼看是Lua初步集成的终结,也是当这款游戏中创造万物之初步。

发表评论

电子邮件地址不会被公开。 必填项已用*标注