Unity3D的几个琐碎的生活知识点备忘

刚学unity3d,跟着仿作了flappy bird,记下一些琐碎的心得!
1、关于场景,即scene。
一个正常的游戏至少要有三个场景,即菜单(或者目录)场景、游戏关卡场景、游戏结束场景。它们一般统一放在project目录下scene目录(自己创建)中,方便管理。
1.1场景切换
要实现游戏的正常进行,需要场景的切换。比如,从开始菜单如何进入到游戏中。
这些需要两步准备:
要将场景放在托放在上图所示位置,做好标记。
2、相关代码编写
public Texture startB
public Texture rankB
void OnGUI(){
//开始菜单,开始游戏,rect前一定要有new,且rect参数为位置左、上,按钮大小
if (GUI.Button (new Rect (100, 250, 135, 75), startButton)) {
Application.LoadLevel("Start");
GUI.Button(new Rect(460, 250, 135, 75), rankButton);
if (GUI.Button (new Rect (20, 20, 60, 30), "exit")) {
Application.Quit();
场景切换要用到函数Application.LoadLevel("Start"),Start是要切换场景的名称,当然这里也可以用数字1来代替,因为上图中地Start的标号就是1。
场景的切换一般是伴随着事件的发生,上面是由于发生了点击按钮(startButton)的事件才引起场景切换。
if (GUI.Button (new Rect (100, 250, 135, 75), startButton)){}用于判断是否点击按钮,
而GUI.Button (new Rect (100, 250, 135, 75), startButton);则是产生按钮。
1.2什么导致切换
一般而言,有点击按钮、另外一些触发器、碰撞器也能导致切换(暂时学到这里)
//小鸟与管道碰撞则游戏结束
void OnCollisionEnter(Collision other){
if(other.gameObject.tag == "Player"){
GameManager._intance.GameState=GameManager.GAMESTATE_END;
audio.Play();
Application.LoadLevel("End");
if(other.gameObject.tag == "projectile"){
OnCollisionEnter(Collision c)函数用于检测碰撞器发生反应,类似的有OnCollisionStay、OnCollisionExit,分别表示刚接触碰撞器、处于碰撞器中、离开碰撞器,这样就能根据情况作出相应的判断。这里小鸟、管道都是碰撞器(好像至少有一个必须加刚体组件rigidbody),当他们碰撞时,作出相应反应:
1、首先判断是什么碰撞管道(代码属于管道的部分),就需要用到tag,用于区分对象。
2、之后进行相关处理,这里audio.play用于播放音乐,需要事先要在管道上添加组件audioSource,并绑定音乐(因为代码是属于管道)。
LoadLevel()y切换场景。
2、物体移动
1、小鸟的移动
bird有刚体组件如上图
public void getLife(){
rigidbody.useGravity=
this.rigidbody.velocity = new Vector3(2,0,0);
初始时没有重力(因为这里设定刚进入游戏时小鸟没有动作,点击鼠标后才激活),getlife函数的rigidbody.useGravity=true使小鸟开启了重力,之后的this.rigidbody.velocity = new Vector3(2,0,0);给了它水平(x轴)方向的初速度。velocity描述角色当前的相对速度。
&pre name="code" class="csharp"& //上跳
void birdJump(){
if(Input.GetMouseButton(0) ){// left mouse button down
audio.Play();
Vector3 vel2
= this.rigidbody.
this.rigidbody.velocity = new Vector3(vel2.x,5,vel2.z);
左键按下实现上跳,首先获得当前速度,然后改变y方向(实际情况各不相同)的速度,实现向上的动作。
这样结合上面的代码就能实现小鸟的运动。
2、水平移动的实现
void Update () {
//根据分数来决定子弹的出现
if (GameManager._intance.score &= minScore)
renderer.enabled =
float outToMove = speed * Time.deltaT
transform.Translate(Vector3.up*outToMove);
//重新出现
if (transform.position.x &= (birdTransform.position.x-2.0f)) {
setPosition();
float outToMove = speed * Time.deltaT
transform.Translate(Vector3.up*outToMove);这两行代码实现某一轴方向的物体的移动,具体的查资料吧。
Unity2D 制作小游戏FlappyBird心得—Unity5学习笔记
【Unity】FlappyBird剖析-附源码
深度学习Flappy Bird
unity3d开发flappy bird之游戏逻辑控制(二)
初学Unity——Flappy Bird 开发实战
Unity3d版FlappyBird(像素小鸟)
网页JavaScript特效之flappy bird(像素鸟)
我的Unity3D学习日记-06(自己动手制作FlappyBird)
Unity3D游戏-愤怒的小鸟游戏源码和教程(二)
【Unity】Fly Bird(游戏实战)(1)
没有更多推荐了,&figure&&img src=&https://pic3.zhimg.com/v2-8cd923cfabcc55b0ad62581_b.jpg& data-rawwidth=&582& data-rawheight=&294& class=&origin_image zh-lightbox-thumb& width=&582& data-original=&https://pic3.zhimg.com/v2-8cd923cfabcc55b0ad62581_r.jpg&&&/figure&&p&
项目开始之初,有几件事情,是需要规划好的,其中最重要的一点,就是资源的管理。&/p&&p&
在早期Unity的很多初级教程中,为了方便,会告诉大家,将资源放在Resources目录下,通过Resources.Load()来加载数据。因为很多教程都是演示效果,或者是小型demo,小型休闲游戏,这样做,问题不大。&/p&&p&
但是当我们开始做一个上点规模的项目,并准备热更运营的时候,就需要考虑资源的使用场景。常见使用场景两种:&/p&&p&
1. 静态:直接拖拽一个资源到GameObject的Component上,例如往UI的image上拖一个sprite&/p&&p&
2. 动态:根据资源名字,动态加载一个资源,例如做一个可以根据角色类型换sprite的image&/p&&p&对于动态加载,又有2种方式:&/p&&p&
1. 在Editor下Load出资源&/p&&p&
2. 在手机上从assetbundle中load出资源(假设我们肯定是需要资源更新的)&/p&&p&那么,对于资源,我们需要考虑的关键问题是:&/p&&p&
1. 如何帮助美术、策划分类整理资源?&/p&&p&
2. 用什么方法标记一个资源,并可以方便策划配置资源?&/p&&p&
3. 如何保证资源在Editor和打包到手机上,用统一的API加载?&/p&&p&
4. 如果需要资源热更新,需要注意什么?&/p&&p&这几个问题,决定了游戏最基础的资源管理模块,我将一一分析这几个问题。&/p&&p&&b& 1. 如何帮助美术、策划分类整理资源?&/b&&/p&&p&资源的管理,最重要的是分类,例如分角色模型,UI图片,UI prefab,特效,声音等&/p&&p&
大家都会将一类资源,放在一起,方便查找,复用,整理。&/p&&p&
我的思路是,将需要动态加载的资源,分成三级。&/p&&p&
第一级是pack,大分类,例如,我现在项目分类如下:
&/p&&figure&&img src=&https://pic2.zhimg.com/v2-a82f3d5ad710fb22d3f49b54c1757067_b.jpg& data-size=&normal& data-rawwidth=&366& data-rawheight=&180& class=&content_image& width=&366&&&figcaption&常见大类型,UI 图片,model 角色模型,cfg 编辑器数据,scene 场景等&/figcaption&&/figure&&p&
第二级是group,按功能分类,用模型举例&/p&&figure&&img src=&https://pic2.zhimg.com/v2-05ad5665b3_b.jpg& data-size=&normal& data-rawwidth=&381& data-rawheight=&479& class=&content_image& width=&381&&&figcaption&在pack model下,按照角色分为group&/figcaption&&/figure&&p&
第三级是具体的资源,res,我们用pack model,group H10010举例&/p&&figure&&img src=&https://pic3.zhimg.com/v2-36e0ec59e7bc8d8def273e9e_b.jpg& data-size=&normal& data-rawwidth=&420& data-rawheight=&376& class=&content_image& width=&420&&&figcaption&角色最终的prefab&/figcaption&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-de65a45bf6a_b.jpg& data-size=&normal& data-rawwidth=&311& data-rawheight=&153& class=&content_image& width=&311&&&figcaption&细分角色对应的动画,材质,贴图等&/figcaption&&/figure&&p&
通过将每一个可加载资源分成三级,美术可以方便地通过pack,group,res三级索引到自己的资源。这是资源放置的思路,这块和大部分项目,应该是大同小异,我只是将可加载资源要三级定位这个规则制定,第三级后,美术可以根据需求自己细分。&/p&&p&
最后,我非常不喜欢这样的做法:让美术把资源放到目录Assets/Res下,而要求把生成的prefab,单独放到一个需要打包的Assets/BundlePrefab文件加下。这种方式方便了打包AssetBundle,但是撕裂了美术对资源整合放置的思路。美术的原始资源,和对应的prefab,就应该尽可能挨着。这是我一家之言,每个项目都有自己的方式,没有什么是绝对好的,只有合适的,和自己喜欢的。&/p&&p&&b& 2. 用什么方法标记一个资源,并可以方便策划配置资源?&/b&&/p&&p&如何定位一个资源?是重要的一环,我见过的绝大多数解决方案,对资源的索引方式,都是资源路径。例如有一个角色的prefab,路径是:Assets/Resources/Model/a.prefab&/p&&p&当需要加载这个prefab出来,Instantiate的时候,通常这样做&/p&&div class=&highlight&&&pre&&code class=&language-csharp&&&span&&/span&
&span class=&n&&GameObject&/span& &span class=&n&&prefab&/span& &span class=&p&&=&/span& &span class=&n&&Resources&/span&&span class=&p&&.&/span&&span class=&n&&Load&/span&&span class=&p&&(&/span&&span class=&s&&&Assets/Resources/Model/a.prefab&&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&或者是在配置文件中配置路径。处理好一点的,只配置 “a.prefab”。&/p&&p&这样的处理方式存在三个弊端:&/p&&p& a. 资源名字通常是英文和拼音,容易拼写错误&/p&&p& b. 一旦遇到资源名字修改,之前的配置将失效&/p&&p& c. 如果只配置资源名字,不配置路径,在unity中不方便定位资源位置(不知道资源放在哪个路径下,例如我很难通过a.prefab这样的名字,定位资源在哪里)&/p&&p&&br&&/p&&p&所以,我使用了一个资源Id数据结构来索引唯一资源,核心类ResId部分如下&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8b0ab5fc4d4cdcd70223_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&561& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic2.zhimg.com/v2-8b0ab5fc4d4cdcd70223_r.jpg&&&/figure&&p&当加载资源的时候,我们可以这样使用:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-e8e0ee05bde2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&592& data-rawheight=&42& class=&origin_image zh-lightbox-thumb& width=&592& data-original=&https://pic1.zhimg.com/v2-e8e0ee05bde2_r.jpg&&&/figure&&p&策划配置的时候,可以这样配置:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-4beb3f4fdb0ce5c46d28a_b.jpg& data-size=&normal& data-rawwidth=&635& data-rawheight=&537& class=&origin_image zh-lightbox-thumb& width=&635& data-original=&https://pic1.zhimg.com/v2-4beb3f4fdb0ce5c46d28a_r.jpg&&&figcaption&策划只需要配置id&/figcaption&&/figure&&p&可以看出,我们用rPack,rGroup, rRes三个数字组成唯一的ResId,这三个数字,对应的就是我们之前资源三级路径,两级路径和最后一级的资源文件用数字开头,数字必须唯一,后面是资源的名字。这样,美术和策划即使改了资源的名字,只要最前的id不改,就不会影响程序逻辑和策划的配置。&/p&&p&策划在配置里直接配资源id(2,1,1)就可以索引到资源,并且可以通过三个数字,很方便地找到对应的美术资源。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-8d5e36acec1bbd884c879a2e08cf20b8_b.jpg& data-size=&normal& data-rawwidth=&420& data-rawheight=&376& class=&content_image& width=&420&&&figcaption&例如,&1 H20011.prefab& 的ResId 就是 (2,1,1)&/figcaption&&/figure&&p&在经历了初期的适应阶段,策划和美术也能很好地对资源进行分类,规划。&/p&&p&策划,美术,程序沟通时要找某个资源,只需要出示ResId的三个数字,对方就能明确资源在哪里,大大减少沟通的成本。&/p&&p&这套方法的主要逻辑,用三数字标识唯一资源,是我当年做页游的时候,从当时的项目中学到。我将其融合到当前整个工具链和资源管理中,非常方便高效。&/p&&p&&b&3. 如何保证资源在Editor和打包到手机上,用统一的API加载?&/b&&/p&&p&当我们在调用API加载资源的时候,希望是一个统一的,不区分平台的调用方式,上层调动,不需要管是在Editor下调用,还是从assetbundle中读取,我只关心,我给你一个ResId,你给我资源。所以,我希望是这样简洁的调用:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ecc9f62aa452dd253e4b8_b.jpg& data-size=&normal& data-rawwidth=&389& data-rawheight=&55& class=&content_image& width=&389&&&figcaption&同步调用&/figcaption&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-fe283abbfb16e71c989b6a3c_b.jpg& data-size=&normal& data-rawwidth=&459& data-rawheight=&185& class=&origin_image zh-lightbox-thumb& width=&459& data-original=&https://pic4.zhimg.com/v2-fe283abbfb16e71c989b6a3c_r.jpg&&&figcaption&异步调用&/figcaption&&/figure&&p&对应类结构大致如下:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-6a70f374ec367ff71c6f_b.jpg& data-size=&normal& data-rawwidth=&582& data-rawheight=&294& class=&origin_image zh-lightbox-thumb& width=&582& data-original=&https://pic1.zhimg.com/v2-6a70f374ec367ff71c6f_r.jpg&&&figcaption&结构图&/figcaption&&/figure&&ul&&li&ResMgr:只是简单地根据是否Editor,返回一个IAssetRes给调用者&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-806b35a18cccf645fdda09bb5bd0d6be_b.jpg& data-size=&normal& data-rawwidth=&433& data-rawheight=&536& class=&origin_image zh-lightbox-thumb& width=&433& data-original=&https://pic3.zhimg.com/v2-806b35a18cccf645fdda09bb5bd0d6be_r.jpg&&&figcaption&ResMgr只简单处理逻辑&/figcaption&&/figure&&ul&&li&IAssetRes: 资源id对应的资源接口,将会有bundle和editor下读取的两种实现&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-efcd704a91e47f_b.jpg& data-size=&normal& data-rawwidth=&520& data-rawheight=&442& class=&origin_image zh-lightbox-thumb& width=&520& data-original=&https://pic3.zhimg.com/v2-efcd704a91e47f_r.jpg&&&figcaption&接口,最简单的几个api&/figcaption&&/figure&&ul&&li&BundleRes:在手机端,通过AssetbundleMgr来读取资源&/li&&li&EditorAssetRes:在Editor端,直接从工程目录中用UnityEditor.AssetDatabase读取资源&/li&&/ul&&p&为什么不直接load出资源,而要返回一个IAssetRes呢?&/p&&p&因为我们希望将一个资源的操作,都抽象到这个接口,ResMgr只是简单地返回给你这个接口,至于这个接口怎么实现,是从哪里读来的,上层调用者就不需要关心了。&/p&&p&例如,你可以cache一次战斗中load的所有IAssetRes,在战斗结束后,对这些IAssetRes clear,清除战斗中使用的资源。&/p&&p&所有的操作,都是基于IAssetRes的,当底层换了资源加载方式,上层调用不需要关心。&/p&&p&很多项目都做了类似的处理,最大的区别还是,我们使用ResId来做资源id。当然,我认为我们的封装和实现都要简洁明了些。&/p&&p&比如国内的KSFramework在资源管理,加载的实现是相当复杂,十几个Loader,相当绕,一个调用跳来跳去,非常糟糕(当然,他可能为了做大而全的框架,做了过度的设计。不过,这个框架也有可圈可点之处,所以我不建议用一个别人的框架,即使要用,也要根据自己项目的需求,做适当的裁剪和修改)。&/p&&p&&b&4. 如果需要资源热更新,需要注意什么?&/b&&/p&&p&
我看过好几个项目因为早期时间压力和其他原因,对资源的规划不足,导致快上线了,需要对资源做热更新了,发现各种问题:&/p&&p&
1. 当初所有的资源都放到Resources目录下,打包的时候,发现所有的资源,不管用没用,都一起打包了,包体非常大,包含了很多无用资源,很难做剔除。&/p&&p&
2. 需要更新了,不知道哪些资源需要挑出来打包成assetbundle更新,即无法简单分辨两个版本的资源变化。&/p&&p&
3. Resources下的资源,无法打包成Assetbundle(我之前测试的Unity版本是这样,不知后续版本是否有改变),需要将资源移动到Resources外,打完Assetbundle再移回Resources,着实蛋疼。&/p&&p&
4. 没有规划好统一的资源load接口,对于assetbundle打包的粒度难以划分,并且对于什么资源在哪个bundle下,需要专门处理对应关系。&/p&&p&
以上几点,如果在项目后期发现,将对项目造成不小的麻烦。&/p&&p&&br&&/p&&p&
因为最早做页游,热更新资源,是基础逻辑。所以,在做第一个Unity项目时,我仔细分析了资源管理,打包,更新的流程,并做了充足的测试。最终形成现在这样的处理方式:&/p&&p&
1. 所有资源都不放在Resources下,让所有资源都能热更,可以看到,我的Resources下,几乎没有资源&/p&&figure&&img src=&https://pic3.zhimg.com/v2-0b2be0c78cad57fbbebcf_b.jpg& data-size=&normal& data-rawwidth=&225& data-rawheight=&189& class=&content_image& width=&225&&&figcaption&除了一个启动项,啥也没有&/figcaption&&/figure&&p&
这样,能保证,不会把不需要的资源,打包到apk中。也完全用不大Resources.Load()。&/p&&p&
2. 在手机端,将所有用到的资源,都打包成assetbundle,并生成所有assetbundle的hash,当再次打包,只需要比较每个bundle 的hash是否变化,就知道哪些资源更新了,非常方便做差异热更新。这应该是最近比较普遍的方案了。&/p&&p&
3. 通过ResId 来决定哪些资源需要打包。我会遍历_res目录,通过分析目录和资源是否数字开头,只将能形成三数字ResId的资源进行assetbundle打包,在_res目录下,不符合ResId 规律的资源,我们就可以认为是没有使用到的资源,即使美术放了几个G的测试资源,只要没有用到,我们就不会打包。后续,如果要剔除一些不需要的资源,只需要去掉数字开头,就不会被打包。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-deccf843bbdcd88ea067bf56cccba37f_b.jpg& data-size=&normal& data-rawwidth=&539& data-rawheight=&452& class=&origin_image zh-lightbox-thumb& width=&539& data-original=&https://pic1.zhimg.com/v2-deccf843bbdcd88ea067bf56cccba37f_r.jpg&&&figcaption&这里,1 Level1 会被打包,而level 2不会被打包,level 2用到的资源,也就不会被打包&/figcaption&&/figure&&p&
在打包assetbundle的过程中,取得所有ResId的资源列表后,再获取他们依赖的资源,进行bundle粒度细分。例如,一个角色的prefab 资源id是(1,3,14),他包含prefab,prefab引用的模型,动画和贴图,我们就将其细分为三个bundle,一个是1/3/&i&14.bundle,一个是1/&/i&3/&i&14&/i&/model.bundle,一个是&i&1/&/i&3/&i&14&/i&_texture.bundle。当我们在用ResId (1,3,14)加载的时候,就能方便地定位到对应的bundle。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-1d5ce01dd99f52c95332_b.jpg& data-size=&normal& data-rawwidth=&602& data-rawheight=&420& class=&origin_image zh-lightbox-thumb& width=&602& data-original=&https://pic2.zhimg.com/v2-1d5ce01dd99f52c95332_r.jpg&&&figcaption&ResId可以直接获得对应的assetbundle路径&/figcaption&&/figure&&p&在热更的时候,可以单独地更新texture bundle或model bundle,甚至可以细化地把每一个动画打包一个bundle。粒度分到多细,就是项目根据需求来规划,取一个合适的平衡。&/p&&p&最后,还是说几点务虚的思考:&/p&&ul&&li&在项目之初,要考虑好项目的资源管理方式,考虑好对打包,热更新的支持&/li&&li&帮助美术理顺资源整理的思路,减少和美术的沟通成本。减少因为美术添加测试资源,对出包造成的影响&/li&&li&我们是否需要大而全的框架?我认为,不需要。放弃对大而全框架的幻想,根据项目需求,裁剪加工自己的小框架,更为合适。拿来就能用的,大多是繁杂冗余的。&/li&&li&通过程序的努力,想办法减少策划美术的犯错成本(比如用string 做资源索引,策划配置拼错单词),也是程序体现能力的重要环节。&/li&&li&客户端程序是一个项目的粘合剂,需要和策划美术,服务器都打交道,并且是最终效果的整合者。所以,好的客户端,会想办法去做好这个粘合剂,让策划,美术合作得更加顺滑。&/li&&/ul&&p&&br&&/p&&p&在说完了资源管理,资源的热更新,下一篇,我谈谈为什么用了lua,我还要做dll在 Android的热更。&/p&
项目开始之初,有几件事情,是需要规划好的,其中最重要的一点,就是资源的管理。 在早期Unity的很多初级教程中,为了方便,会告诉大家,将资源放在Resources目录下,通过Resources.Load()来加载数据。因为很多教程都是演示效果,或者是小型demo,小型休闲…
&figure&&img src=&https://pic3.zhimg.com/v2-b28e27c65fa9c4b2de086247adb8734b_b.jpg& data-rawwidth=&800& data-rawheight=&533& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/v2-b28e27c65fa9c4b2de086247adb8734b_r.jpg&&&/figure&&p&商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处&/p&&p&原文链接:&a href=&http://link.zhihu.com/?target=http%3A//wetest.qq.com/lab/view/387.html%3Ffrom%3Dcontent_zhihu& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&http://wetest.qq.com/lab/view/387.html&/a&&/p&&p&&br&&/p&&p&&b&WeTest 导读&/b&&/p&&p&CsToLua工具将客户端 C#源码自动转换为Lua,实现热更新,本文以麻将项目为例介绍客户端技术细节。&/p&&hr&&p&&b&麻将项目架构&/b&&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-a2bb76d539a5de342c4618b73bdb64d9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&766& data-rawheight=&509& class=&origin_image zh-lightbox-thumb& width=&766& data-original=&https://pic2.zhimg.com/v2-a2bb76d539a5de342c4618b73bdb64d9_r.jpg&&&/figure&&p&&br&&/p&&p&其中ChinaMahjong-CSLua为C#工程,实现麻将项目的主要业务流程。翻译工程的输入是C#项目生成的dll文件。其中Cecil负责分析类型 类成员关系 ,比如类字段函数结构,引用关系、类之间的继承关系等,ILSpy负责反编译函数体里的语句,比如条件语句,函数调用,算数运算等。下面逐个介绍具体的实现。&/p&&p&&br&&/p&&p&&b&Mono.Cecil&/b&&/p&&p&Mono.Cecil:一个可加载并浏览现有程序集并进行动态修改并保存的.NET框架。可以静态注入程序集(注入后生成新的程序集)和动态注入程序集(注入后不改变目标程序集,只在运行时改变程序集行为。麻将项目入口:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-bef1eb727dfe3079_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&812& data-rawheight=&423& class=&origin_image zh-lightbox-thumb& width=&812& data-original=&https://pic2.zhimg.com/v2-bef1eb727dfe3079_r.jpg&&&/figure&&p&&br&&/p&&p&举一个Mono.Cecil例子,这是原始的Unity C#代码:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-9bbfaf3751fcd0b24ecaab1d111be934_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&461& data-rawheight=&235& class=&origin_image zh-lightbox-thumb& width=&461& data-original=&https://pic1.zhimg.com/v2-9bbfaf3751fcd0b24ecaab1d111be934_r.jpg&&&/figure&&p&&br&&/p&&p&我们采用Cecil工具对生成的Dll进行代码嵌入,具体的嵌入逻辑如下:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-2a330deb23f8d09e2d78_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&718& data-rawheight=&526& class=&origin_image zh-lightbox-thumb& width=&718& data-original=&https://pic1.zhimg.com/v2-2a330deb23f8d09e2d78_r.jpg&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-0ba2a738eb8be4b271a7bc7c5b272422_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&922& data-rawheight=&335& class=&origin_image zh-lightbox-thumb& width=&922& data-original=&https://pic3.zhimg.com/v2-0ba2a738eb8be4b271a7bc7c5b272422_r.jpg&&&/figure&&p&&br&&/p&&p&&b&OpCodes.Ldstr 字段:&/b&推送对元数据中存储的字符串的新对象引用。指令将一个对象引用推送 (类型 O) 到一个新的字符串对象,表示存储的元数据中的特定字符串文字;&/p&&p&&br&&/p&&p&&b&OpCodes.Call 字段:&/b&调用由传递的方法说明符指示的方法。&/p&&p&&br&&/p&&p&反编译嵌入自定义逻辑代码,实现了原生代码功能的更新。也就是说在没有源代码的前提下,Mono.Ceil可以动态嵌入指定代码至可执行文件。(这也是一些外挂的套路,也有加壳和加密技术来提升反编译的难度了,此处省去一万字)上面的代码等价于如下:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-d7a4cc9e1bcdfe8dd3448fe_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&623& data-rawheight=&226& class=&origin_image zh-lightbox-thumb& width=&623& data-original=&https://pic3.zhimg.com/v2-d7a4cc9e1bcdfe8dd3448fe_r.jpg&&&/figure&&p&&br&&/p&&p&Mono.Cecil底层是如何处理的呢,再举一个例子,这是原始的C#代码:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d5294cff0af0d6be2b6f2fc2c61d9df4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&496& data-rawheight=&258& class=&origin_image zh-lightbox-thumb& width=&496& data-original=&https://pic1.zhimg.com/v2-d5294cff0af0d6be2b6f2fc2c61d9df4_r.jpg&&&/figure&&p&&br&&/p&&p&上面是C#逻辑打包成dll后,采用Cecil反编译得到的内容如下,具体逻辑见注释:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-99e5faacfc8102ffd377f25_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&610& data-rawheight=&635& class=&origin_image zh-lightbox-thumb& width=&610& data-original=&https://pic2.zhimg.com/v2-99e5faacfc8102ffd377f25_r.jpg&&&/figure&&p&&br&&/p&&p&&b&用Mono.Cecil得到了二进制文件的中间代码,中间代码是一种基于操作栈的虚拟机语言,指令间借助栈传递数据。&/b&&/p&&p&&br&&/p&&p&&b&ILSpy&/b&&/p&&p&ILSpy是一个开源.Net的反编译器,能把C#生成二进制文件转换为MSIL或者C#任选一种。因为项目C#程序集是团队开发,因此不需要破解加密算法和去壳等操作。相关的反编译软件有:ilasm、.Net Reflector和Just Decompile等。&br&&/p&&p&&b&ILspy的主要功能:&/b&从Mono.Cecil拿到具体类型,类型定义的方法,以及各自的MethodBody。然后对MethodBody中的IL Instructions(指令代码)做数据流分析和控制流分析。如下为ILSpy的输人内容:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-f8bbd5e97a5ebd9e1a795_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&633& data-rawheight=&441& class=&origin_image zh-lightbox-thumb& width=&633& data-original=&https://pic2.zhimg.com/v2-f8bbd5e97a5ebd9e1a795_r.jpg&&&/figure&&p&&br&&/p&&p&举个例子,说明一下ILSpy具体的实现流程,如下为C#源码:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-b2ba1a24511af21bcd494f0cc2361f5a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&487& data-rawheight=&349& class=&origin_image zh-lightbox-thumb& width=&487& data-original=&https://pic3.zhimg.com/v2-b2ba1a24511af21bcd494f0cc2361f5a_r.jpg&&&/figure&&p&&br&&/p&&p&通过Mono.Ceil和ILSpy分析后的输出:&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-38b6e9132e5def783dcdbef_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&872& data-rawheight=&446& class=&origin_image zh-lightbox-thumb& width=&872& data-original=&https://pic4.zhimg.com/v2-38b6e9132e5def783dcdbef_r.jpg&&&/figure&&p&&br&&/p&&p&&b&ILSpy对IL Instructions以跳转指令为界限,划分了基本的block,block间构成树形结构:&/b&&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d1e04bd13d5f36d02db8958497cab620_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&484& data-rawheight=&568& class=&origin_image zh-lightbox-thumb& width=&484& data-original=&https://pic1.zhimg.com/v2-d1e04bd13d5f36d02db8958497cab620_r.jpg&&&/figure&&p&&br&&/p&&p&&b&TK_CSLua&/b&&/p&&p&TK_CSLua根据不同的语句块实现具体的翻译逻辑,比如将C#中的while循环,生成Lua里面的while-end逻辑等。翻译过程是一个递归的过程,如图为不同类型的语句块处理逻辑:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ff94ad49d767fe67e72dffc_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&615& data-rawheight=&886& class=&origin_image zh-lightbox-thumb& width=&615& data-original=&https://pic1.zhimg.com/v2-ff94ad49d767fe67e72dffc_r.jpg&&&/figure&&p&&br&&/p&&p&while循环的处理逻辑为:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-5d2cfb7cfdb195_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&669& data-rawheight=&399& class=&origin_image zh-lightbox-thumb& width=&669& data-original=&https://pic2.zhimg.com/v2-5d2cfb7cfdb195_r.jpg&&&/figure&&p&&br&&/p&&p&最终自动生成了Lua代码,如下所示:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-9bd93ab8a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&665& data-rawheight=&261& class=&origin_image zh-lightbox-thumb& width=&665& data-original=&https://pic3.zhimg.com/v2-9bd93ab8a_r.jpg&&&/figure&&p&&br&&/p&&p&&b&ToLua&/b&&/p&&p&ToLua基于LuaInterface,LuaInterface是一个实现Lua和微软.Net平台的CLR混合编程的开源库,使得Lua脚本可以实例化CLR对象,访问属性,调用方法甚至使用Lua函数来处理事件。提供了一套中间层导出工具,对于需要访问的CLR、Unity及自定义类预生成Wrap文件,Lua访问时只访问Wrap文件,Wrap文件接收Lua传递来的参数,进行类型(值、对象、委托)转换,再调用真正工作的CLR对象和函数,最后将返回值返回给Lua ,有效地提高了效率。&/p&&p&&br&&/p&&p&Lua虚拟机启动主流程:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-b3711075cdd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1076& data-rawheight=&537& class=&origin_image zh-lightbox-thumb& width=&1076& data-original=&https://pic1.zhimg.com/v2-b3711075cdd_r.jpg&&&/figure&&p&&br&&/p&&p&Unity C#与Lua交互,麻将项目主要采用了Wrap文件这种非反射的方式实现。以下为生成绑定的具体流程:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-875b8fd21e7cdca39453a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&773& data-rawheight=&521& class=&origin_image zh-lightbox-thumb& width=&773& data-original=&https://pic3.zhimg.com/v2-875b8fd21e7cdca39453a_r.jpg&&&/figure&&p&&br&&/p&&p&生成后的WrapperConfig文件如下所示:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-236bfb9657fdd9eb97d392e5afe55865_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&410& data-rawheight=&596& class=&content_image& width=&410&&&/figure&&p&&br&&/p&&p&举个例子说明绑定的具体实现,C#代码如下:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-29a2db7c05ae293b82efacaab1d4a2c8_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&499& data-rawheight=&172& class=&origin_image zh-lightbox-thumb& width=&499& data-original=&https://pic1.zhimg.com/v2-29a2db7c05ae293b82efacaab1d4a2c8_r.jpg&&&/figure&&p&&br&&/p&&p&ToLua绑定后生成的代码:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2dcc6b8ef2fc586db1875_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1229& data-rawheight=&474& class=&origin_image zh-lightbox-thumb& width=&1229& data-original=&https://pic2.zhimg.com/v2-2dcc6b8ef2fc586db1875_r.jpg&&&/figure&&p&&br&&/p&&p&&b&C#中的对象在传给Lua时并不是直接把对象暴露给了Lua,而是在这个OjbectTranslator里面注册并返回一个索引,并把这个索引包装成一个userdata传递给Lua,并且设置元表:&/b&&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-dfed1c8c2ef7f5d7abf2c1_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&824& data-rawheight=&607& class=&origin_image zh-lightbox-thumb& width=&824& data-original=&https://pic2.zhimg.com/v2-dfed1c8c2ef7f5d7abf2c1_r.jpg&&&/figure&&p&&br&&/p&&p&数据包装如下:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-93cca16f478_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&556& data-rawheight=&401& class=&origin_image zh-lightbox-thumb& width=&556& data-original=&https://pic1.zhimg.com/v2-93cca16f478_r.jpg&&&/figure&&p&&br&&/p&&p&&b&游戏启动&/b&&/p&&p&麻将项目启动入口为Init.Lua:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-8b0a0b9ccb3bac9b55aca_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&866& data-rawheight=&463& class=&origin_image zh-lightbox-thumb& width=&866& data-original=&https://pic3.zhimg.com/v2-8b0a0b9ccb3bac9b55aca_r.jpg&&&/figure&&p&&br&&/p&&p&加载配置,进入登录场景。&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a13a2c757a0ec_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&590& data-rawheight=&676& class=&origin_image zh-lightbox-thumb& width=&590& data-original=&https://pic1.zhimg.com/v2-a13a2c757a0ec_r.jpg&&&/figure&&hr&&p&&b&UPA,一款针对Unity引擎的深度性能分析工具,由腾讯WeTest和Unity官方共同研发打造,可以帮助Unity开发者快速定位性能问题。旨在为游戏开发者提供更完善的手游性能解决方案,同时与开发环节形成闭环,保障游戏品质。&/b&&/p&&p&体验地址:&a href=&http://link.zhihu.com/?target=http%3A//wetest.qq.com/cube%3Ffrom%3Dcontent_zhihu& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&http://wetest.qq.com/cube/&/a&
,下载WeTest助手APP ,即可体验UPA和通用测试UPA。&/p&&p&如您有任何疑问,欢迎咨询腾讯WeTest企业QQ:。&/p&
商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处原文链接: WeTest 导读CsToLua工具将客户端 C#源码自动转换为Lua,实现热更新,本文以麻将项目为例介绍客户端技术细节。麻将项目架构 其中ChinaMahjong-CSL…
&figure&&img src=&https://pic2.zhimg.com/v2-4ec9bca7cbcfc0_b.jpg& data-rawwidth=&957& data-rawheight=&957& class=&origin_image zh-lightbox-thumb& width=&957& data-original=&https://pic2.zhimg.com/v2-4ec9bca7cbcfc0_r.jpg&&&/figure&&p&最近研究了一下如何在unity中实现c#的热更新,对于整个DLL热更新的过程和方案有一个初步的了解,这儿就写下来,便于后续的深入调查和方案选择。&/p&&p&&b&一、C# DLL的动态加载和卸载&/b&&/p&&p&既然要热更新,那么就是动态的加载c#的DLL,所以第一步就是研究如何实现DLL的动态加载和卸载。&/p&&p&在CLR Via C#中,对于DLL的加载有详细的讲解,这儿就不再长篇幅的讲解整个过程,简单的来说,在C#的工程中,都会生成一个默认的程序域appDomain,就叫做DefaultAppDomain吧,在这个程序域的基础上,我们可以加载多个不同的程序集。在.Net中,程序集不能卸载,但是可以随着程序域的释放而一起释放,所以我们可以利用程序域来实现程序集(DLL)的加载和释放。&/p&&p&上面的理论来自CLR Via C#, 具体的图为:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-0b41de35d6922e6bdae33_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&775& data-rawheight=&603& class=&origin_image zh-lightbox-thumb& width=&775& data-original=&https://pic4.zhimg.com/v2-0b41de35d6922e6bdae33_r.jpg&&&/figure&&p&基于这个理论,我们可以在DefaultAppDomain之外,再多次创建多个AppDomain,基于AppDomain来实现DLL的加载和卸载。基于此,编写相关的工程测试,参考网上的一个工程来进一步的测试,这儿是原文,文末有相关的代码下载:&/p&&p&&a href=&http://link.zhihu.com/?target=https%3A//www.cnblogs.com/Leo_wl/p/4255533.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&程序的热升级&/a&&/p&&p&在原代码的基础上,进一步构建。首先,构建4个Class Library:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-34ebddcacbf7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&337& data-rawheight=&447& class=&content_image& width=&337&&&/figure&&p&默认工程为MainServer,将Module1和Module2的Build路径设置到MainServer的bin中,这样MainServer就可以加载最新的Module1.DLL/Module2.DLL(PS:这儿的设置很重要,忽略会使得不能加载最新的DLL)&/p&&p&Module1和Module2都在References中添加CommonLib的引用,实现ICalculater接口,各自的实现为:&/p&&p&Module1:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-1c5d1c3db6c9c6f6d8e898_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&636& data-rawheight=&385& class=&origin_image zh-lightbox-thumb& width=&636& data-original=&https://pic1.zhimg.com/v2-1c5d1c3db6c9c6f6d8e898_r.jpg&&&/figure&&p&Module2:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-f7fe22d56a0b7a5ac2bd17_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&756& data-rawheight=&408& class=&origin_image zh-lightbox-thumb& width=&756& data-original=&https://pic4.zhimg.com/v2-f7fe22d56a0b7a5ac2bd17_r.jpg&&&/figure&&p&这样,就是两个不同的Class Library中,分别实现了ICalculater接口,分别为相加和相乘。在MainServer的主程序入口Program中:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-e3dbf85cb22_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&827& data-rawheight=&971& class=&origin_image zh-lightbox-thumb& width=&827& data-original=&https://pic3.zhimg.com/v2-e3dbf85cb22_r.jpg&&&/figure&&p&首先在默认appDomain的基础上,进一步加载2个appDomain,然后分别在这2个程序域的基础上加载DLL。得到的结果为:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-e3acf8a73fa957c4f40e33fd4281af26_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&528& data-rawheight=&323& class=&origin_image zh-lightbox-thumb& width=&528& data-original=&https://pic3.zhimg.com/v2-e3acf8a73fa957c4f40e33fd4281af26_r.jpg&&&/figure&&p&整个步骤都详细的解释了整个执行流程,先构建appDomain,在此基础上,加载dll,然后执行里面的方法。再一个新的appDomain中加载前面加载过的dll,再次执行,相互之间并不冲突。所以appDomain可以一对多个DLL,一个DLL可以被多个不同的AppDomain加载。&/p&&p&&br&&/p&&p&&b&二、Unity中测试DLL的加载&/b&&/p&&p&在第一部分的基础上,我们进一步的研究如何在Unity中实现Dll的加载,基本的操作步骤可以参考这篇文章:&a href=&http://link.zhihu.com/?target=https%3A//blog.csdn.net/baidu_/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&unity dll实现热更新&/a&&/p&&p&当然,文章并不是完全的实现热更新,实现的是windows和android平台下,对于dll文件的热更新。对于IOS为什么不能热更新,我们后续会讨论到,先看看安卓和windows下 dll的热更新步骤。&/p&&p&1、新建一个ClassLibrary(类库)的工程,在其中实现对应的类和方法;&/p&&p&2、将该工程导出为DLL;&/p&&p&3、将DLL改为bytes文件,存入Unity工程中的StreamingAssets文件夹下;&/p&&p&4、在工程运行的时候,读取StreamingAssets下的Dll文件,用Assembly.Load(byte[] bytes )的方法,将DLL文件读取出来,进而执行相关的操作。这一步的代码为:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-321ea241b436e49c2b9a16aedb200c63_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&759& data-rawheight=&1450& class=&origin_image zh-lightbox-thumb& width=&759& data-original=&https://pic4.zhimg.com/v2-321ea241b436e49c2b9a16aedb200c63_r.jpg&&&/figure&&p&对于DLL文件,是执行www.bytes,对于assetbundle文件,则是执行ab.mainAsset转换为TextAsset,进一步得到bytes。在windows和android平台下,都会得到这样的屏幕输出:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-62ef617c8e593ddba2e3c10_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&563& data-rawheight=&191& class=&origin_image zh-lightbox-thumb& width=&563& data-original=&https://pic1.zhimg.com/v2-62ef617c8e593ddba2e3c10_r.jpg&&&/figure&&p&这个方案的本质,和前面的本质相差不大,unity工程在执行的时候,会构建一个默认的appDomain,Assembly.Load,其实就是在这个程序域上加载Dll,所以相关的实质和前面一个部分相差不大,这就是c#热更新在unity中的应用(IOS不包括)。&/p&&p&下文我们会讲解IOS为什么不支持DLL的热更新,以及如何利用ILRuntime来实现Android和IOS的热更新。&/p&&p&PS: 最近进行来多种测试,有以下的消息更新&/p&&p&1、如果将热更新的DLL放在非StreamingAssets目录下,那么在unity工程启动的时候,会被默认加载到当前程序域,那么此时再Assembly.Load热更新的DLL,是不会覆盖前面的DLL的,这时候执行的是前面默认加载进来的DLL中的信息,热更新失效;&/p&&p&2、由于默认加载DLL的存在,所以不能在场景/UI中挂载热更新DLL中的继承自UnityEngine.Component以及其子类(比如MonoBehaviour)的组件,除非这个类不被热更,那其实没必要放在热更工程中了。&/p&&p&&/p&
最近研究了一下如何在unity中实现c#的热更新,对于整个DLL热更新的过程和方案有一个初步的了解,这儿就写下来,便于后续的深入调查和方案选择。一、C# DLL的动态加载和卸载既然要热更新,那么就是动态的加载c#的DLL,所以第一步就是研究如何实现DLL的动态加…
&p&目前在开发一款MOBA类射击游戏, 为了热更我们使用LuaFramework。除了战斗逻辑的逻辑写在Lua里,Lua,UI,导表,行为树等都使用了热更,和王者荣耀类似。一开始使用了SimpleFramework,但是SimpleFramework已经不进行维护了,新的lua框架更合理高效一些也刚好要支持64位就抽空升级了一下。因为对源码做了一些修改,所以有会有一些调整,这里是一些笔记和备忘。&/p&&p&&br&&/p&&ul&&li&Plugin目录下单独建了一个目录放Lua的dll&/li&&li&升级了LuaEncode目录,升级了Lua库&/li&&li&LuaFramework把Editor Examples Lua&br&
ToLua迁移到了LuaFramework目录&/li&&li&Scripts改名为LuaScripts&/li&&li&这些dll需要指定平台&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-81ebdd2d9a0_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&684& data-rawheight=&168& class=&origin_image zh-lightbox-thumb& width=&684& data-original=&https://pic3.zhimg.com/v2-81ebdd2d9a0_r.jpg&&&/figure&&ul&&li&网络下的LuaNetCoroutine里面的LuaCoroutine和新版本的重名,并且编译不过要修改SimpleFramework&/li&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\Scripts\LuaScripts\Utility&/li&&li&这个目录下的很多文件改了,并且自定义了一些&/li&&/ul&&p&string&br&sourceDir = AppConst.FrameworkRoot + &/ToLua/Source/Generate/&;&/p&&p&string&br&sourceDir = AppConst.LuaWrapP&/p&&ul&&li&LuaClient里少了定义&/li&&/ul&&p&UNITY_5_5&/p&&p&using&br&UnityEngine.SceneM&/p&&p&然后改了之后还需要自动升级到5.5代码&/p&&ul&&li&msgobj里面用了ByteBuffer,但是这个被人改成了在MobaNet下面了,新的是LuaFramework&/li&&li&PanelManager.cs需要升级&/li&&li&MobaGame.Framework.Message重名了&/li&&li&新加了MGameUtils不和Lua的混在一起,一些全局变量读常量表&/li&&li&替换音效管理器StartUpCommand里加AppFacade.Instance.AddManager&SoundManager&(ManagerName.Muisc);&/li&&li&AppConst.cs有比较大的修改&/li&&li&ResourceManager有大幅修改,支持热更&/li&&li&GameManager有大幅修改&/li&&li&ResManager.Initialize();去掉了&/li&&li&LuaBehaviour.cs大幅修改,增加了自己UI管理器&/li&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\Scripts\LuaFramework\Editor\Packager.cs大幅修改&/li&&li&ToLuaMenu.AutoGen();增加自动生成的功能。&/li&&li&默认的Lua文件保存位置&/li&&/ul&&p&&D:/WorkS/MGame/mgame/Program/trunk/Client/Assets/com.Lilith.MOBA3/ToLua/Source/Generate/&&/p&&ul&&li&ToLuaMenu下的beCheck这个是用来控制是否自动检测wrap目录没有文件,如果没有文件自动生成&/li&&li&AppConst下的FrameworkRoot是Framwork的根目录,D:/WorkS/MGame/mgame/Program/trunk/Client/Assets/com.Lilith.MOBA3修改为D:/WorkS/MGame/mgame/Program/trunk/Client/Assets/Scripts/LuaFramework&/li&&li&AppView下的输出都干掉了&/li&&li&GameManager大幅修改&/li&&li&LuaLoader.cs大幅修改,这里必须要改因为涉及到读取方式&/li&&li&LuaManager的InitLuaBundle修改,因为加载问题&/li&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\LuaFramework\Lua\Common &/li&&/ul&&p&define修改很多自己的接口&/p&&ul&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\LuaFramework\Lua\Controller&/li&&/ul&&p&全部覆盖&/p&&ul&&li&GameManager改到Game&/li&&li&CustomSetting里面添加很多Mgame的导出宏&/li&&li&Lua的协议目录&/li&&/ul&&p&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\LuaFramework\Lua&/p&&p&protobuf.lua&/p&&ul&&li&D:\WorkS\MGame\mgame\ProgrConfigMgr.luaam\trunk\Client\Assets\LuaFramework\Lua\Base我加的用来读表的ConfigMgr.lua&/li&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\LuaFramework\Lua\Logic&/li&&li&CtrlManager有修改&/li&&li&GeniusGtrl的278行list&/li&&li&list的是count改为Count&/li&&li&字典的是[]改为get_Item&/li&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\Scripts\LuaScripts\Network\ByteBuffer.cs 增加LuaByteBuffer等几个接口&/li&&li&一些路径修改&/li&&/ul&&p&Util.DataPath&/p&&p&&D:/WorkS/MGame/mgame/Program/trunk/Client/Assets/StreamingAssets/&&/p&&p&Application.streamingAssetsPath&/p&&p&&D:/WorkS/MGame/mgame/Program/trunk/Client/Assets/StreamingAssets/&&/p&&p&&D:/WorkS/MGame/mgame/Program/trunk/Client/Assets/LuaTemp/&&/p&&ul&&li&Package路径改回来&/li&&li&D:\WorkS\MGame\mgame\Program\trunk\Client\Assets\Scripts\LuaScripts\Controller\Command\StartUpCommand.cs这个也改了不少,否则report起不来&/li&&/ul&&p&&br&&/p&&p&PS:就性能来说Lua的性能还是不错的,开销比较高的往往是C#到Lua层的开销。我们游戏射速和战斗激烈程度比王者荣耀还有高出很多倍,因此战斗部分还是更追求性能和效率放到C#层,所有系统UI逻辑等放到Lua里支持热更。当时还跑了一下性能对比:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-52cdc61b1ba350ea572f3a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&328& data-rawheight=&404& class=&content_image& width=&328&&&/figure&&p&&/p&&p&&/p&&p&&/p&&p&&/p&&p&&/p&
目前在开发一款MOBA类射击游戏, 为了热更我们使用LuaFramework。除了战斗逻辑的逻辑写在Lua里,Lua,UI,导表,行为树等都使用了热更,和王者荣耀类似。一开始使用了SimpleFramework,但是SimpleFramework已经不进行维护了,新的lua框架更合理高效一些也刚…
&figure&&img src=&https://pic1.zhimg.com/v2-8f2bd5a192f6dcb24cc392_b.jpg& data-rawwidth=&800& data-rawheight=&313& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic1.zhimg.com/v2-8f2bd5a192f6dcb24cc392_r.jpg&&&/figure&&h2&&b&1. 前言&/b&&/h2&&p&近期断断续续地做了一些优化的工作,包括资源加载、ui优化、效果分级各个方面。优化本身是一件琐碎且耗神的事情,需要经历&b&问题定位&/b&、&b&原因探查&/b&、&b&优化方案设计和实现&/b&、&b&效果验证&/b&、&b&资源修改&/b&多个步骤,也会涉及到各个职位之间的配合和协调。在这其中,可能带来较大工作量的是对于之前普遍使用的一些方法/控件的优化,如果无法兼容之前的使用接口,可能会给美术和程序带来较大的迭代工作量。&/p&&p&UI是这其中可能越早发现问题收益越高的一块内容,所以整理一下这段时间做了一些基于Shader来进行优化的思路和方法,以及分享一下自己构建的代替ugui提供的通用控件的那些Component,希望在项目中前期的同学可以提前发现类似的问题进行尽早的改进。&/p&&h2&&b&2. 优化目标&/b&&/h2&&p&ugui已经提供了非常丰富的控件来给我们使用,但是出于通用性的考虑,其中很多控件的性能和效果都可能存在一些问题,又或者在频繁更改ui数值的需求下会引发持续的Mesh重建导致CPU的消耗。我们期望通过一些简单的Shader逻辑来提升效果或者提高效率,主要会集中在如下几个方面:&/p&&ul&&li&降低Draw Call;&/li&&li&减少Overdraw;&/li&&li&减少UI Mesh重建次数和范围。&/li&&/ul&&p&接下来的内容,我们就从具体的优化内容上来分享下使用简单的Shader进行UGUI优化的过程。&/p&&h2&&b&3. 小地图&/b&&/h2&&p&在我们游戏中,玩家移动的时候右上角会一直有小地图的显示,这个功能在最初的实现方案中是使用ugui的mask组件来做的,给了一个方形的mask组件,然后根据玩家位置计算出地图左下角的位置进行移动。这种实现方式虽然简单,但是会有两个问题:&/p&&ol&&li&&b&Overdraw特别大&/b&,几乎很多时候会有整个屏幕的overdraw;&/li&&li&玩家在移动过程中,因为一直在持续移动图片的位置(做了适当的降频处理),所以会一直有UI的&b&Mesh重建过程&/b&。&/li&&/ol&&p&当时的prefab已经被修改了,我简单模拟一下使用Mask的方法带来的Overdraw的效果如下图所示:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-dde79ad7ab08_b.jpg& data-size=&normal& data-rawwidth=&909& data-rawheight=&387& class=&origin_image zh-lightbox-thumb& width=&909& data-original=&https://pic2.zhimg.com/v2-dde79ad7ab08_r.jpg&&&figcaption&使用Mask组件带来的Overdraw的问题&/figcaption&&/figure&&p&在上图中可以看到,左侧是小地图在屏幕中的效果,右侧是选择Overdraw视图之后的效果,整张图片都会有一个绘制的过程,占据几乎整个屏幕(白框),而且Mask也是需要一次绘制过程,这样就是两个Drawcall。其实这里ui同学为了表现品质感,在小地图上又蒙了一层半透的外框效果,消耗更大一些。&/p&&p&针对这一问题,首先对于矩形的地图,可以使用运行效率更高一些的RectMask2D组件,但这并不能有本质的提升,解决Overdraw最根本的方法还是不要绘制那么大的贴图然后通过蒙版或者clip的方式去掉,这是很浪费的方法。有过基本Shader概念的朋友应该可以想到修改uv的方法,这也是我们采用的方法——思路很简单,就做一个和要显示的大小一样的RawImage控件,然后赋给它一个特殊的材质,在vs里面修改要显示的区域的uv就可以做到想要的效果。&/p&&p&直接贴出来Shader代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span&&span class=&n&&sampler2D&/span& &span class=&n&&_MainTex&/span&&span class=&p&&;&/span&
&span class=&n&&fixed4&/span& &span class=&n&&_UVScaleOffset&/span&&span class=&p&&;&/span&
&span class=&n&&sampler2D&/span& &span class=&n&&_BlendTexture&/span&&span class=&p&&;&/span&
&span class=&n&&v2f&/span& &span class=&nf&&vert&/span&&span class=&p&&(&/span&&span class=&n&&appdata_t&/span& &span class=&n&&IN&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&v2f&/span& &span class=&n&&OUT&/span&&span class=&p&&;&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span& &span class=&o&&=&/span& &span class=&n&&mul&/span&&span class=&p&&(&/span&&span class=&n&&UNITY_MATRIX_MVP&/span&&span class=&p&&,&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span& &span class=&o&&=&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&;&/span&
&span class=&c1&&//计算uv偏移&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&offsetcoord&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&=&/span& &span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&*&/span& &span class=&n&&_UVScaleOffset&/span&&span class=&p&&.&/span&&span class=&n&&zw&/span& &span class=&o&&+&/span& &span class=&n&&_UVScaleOffset&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span&&span class=&p&&;&/span&
&span class=&cp&&#ifdef UNITY_HALF_TEXEL_OFFSET
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&-=&/span& &span class=&p&&(&/span&&span class=&n&&_ScreenParams&/span&&span class=&p&&.&/span&&span class=&n&&zw&/span&&span class=&o&&-&/span&&span class=&mf&&1.0&/span&&span class=&p&&);&/span&
&span class=&cp&&#endif
&span class=&k&&return&/span& &span class=&n&&OUT&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&n&&fixed4&/span& &span class=&nf&&frag&/span&&span class=&p&&(&/span&&span class=&n&&v2f&/span& &span class=&n&&IN&/span&&span class=&p&&)&/span& &span class=&o&&:&/span& &span class=&n&&SV_Target&/span&
&span class=&p&&{&/span&
&span class=&n&&half4&/span& &span class=&n&&color&/span& &span class=&o&&=&/span& &span class=&n&&tex2D&/span&&span class=&p&&(&/span&&span class=&n&&_MainTex&/span&&span class=&p&&,&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&offsetcoord&/span&&span class=&p&&);&/span&
&span class=&n&&half4&/span& &span class=&n&&blendColor&/span& &span class=&o&&=&/span& &span class=&n&&tex2D&/span&&span class=&p&&(&/span&&span class=&n&&_BlendTexture&/span&&span class=&p&&,&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&);&/span&
&span class=&n&&color&/span&&span class=&p&&.&/span&&span class=&n&&rgb&/span& &span class=&o&&=&/span& &span class=&n&&blendColor&/span&&span class=&p&&.&/span&&span class=&n&&rgb&/span& &span class=&o&&+&/span& &span class=&n&&color&/span&&span class=&p&&.&/span&&span class=&n&&rgb&/span& &span class=&o&&*&/span& &span class=&p&&(&/span&&span class=&mi&&1&/span& &span class=&o&&-&/span& &span class=&n&&blendColor&/span&&span class=&p&&.&/span&&span class=&n&&a&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&n&&color&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&核心的代码就只有加粗的那一句,给uv一个整体的缩放之后再加上左下角的偏移。之后C#逻辑就只需要根据地图的大小和玩家所在的位置计算出想要显示的uv缩放和偏移值就可以了。玩家移动的时候只需要修改材质的参数,这也不会导致UI的mesh重建,一箭双雕,解决两个问题。&/p&&p&小地图的外框也在材质中一并做了,减少一个draw call。最终的效果如下图所示:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-caf2a6d33e6d_b.jpg& data-size=&normal& data-rawwidth=&744& data-rawheight=&336& class=&origin_image zh-lightbox-thumb& width=&744& data-original=&https://pic4.zhimg.com/v2-caf2a6d33e6d_r.jpg&&&figcaption&优化后的Overdraw对比图&/figcaption&&/figure&&blockquote&这里需要注意的是,对于image控件的material进行赋值时,如果它在一个Mask控件之下,可能会遇到赋值失效的问题,采用materialForRendering或者强制更新材质的方式可能会有新的Material的创建过程导致内存分配,这些在优化之后可能带来问题的点也是需要优化后进行验证的。&/blockquote&&h2&&b&4. Mask的使用&/b&&/h2&&p&除了小地图部分,游戏中比如头像、技能界面等处都大量地使用了Mask。当然通常情况下Mask不会带来像小地图那么高Overdraw,但是因为ugui中的Mask需要一遍绘制过程,因此对于Drawcall的增加还是会有不少。而且Mask也存在边缘锯齿的问题,效果上UI同学也不够满意,因此我们针对像头像这样单张的Mask也进行了一下优化,具体的过程可以参考之前的Unity填坑笔记——《&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&Unity填坑笔记(三)—ugui中针对单独图片的Mask优化&/a&》,比较详细地记录了整个优化过程。&/p&&p&这里补充两点:&/p&&ul&&li&在那篇文章的最后提到,我们自己拷贝了一个ThorImage类,开放部分接口然后继承。我们后来改成了从Image直接继承的方式,否则之前编写的游戏逻辑要在代码上兼容两种Image,会比较烦,这些&b&向前兼容的需求&/b&也是在优化过程中需要额外考虑和处理的点。&/li&&li&针对滚动列表这样需要Mask的地方,一方面建议UI同学使用Rect Mask 2D组件,另外一方面为了边缘的渐变效果为UI引入了&a href=&https://link.zhihu.com/?target=https%3A//assetstore.unity.com/packages/tools/gui/soft-mask-80667& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Soft Mask&/a& 插件来提供边缘的渐变处理。放一张Soft Mask自己的效果对比截图,需要类似效果的朋友可以自己购买。&/li&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-aabafc53c_b.jpg& data-size=&normal& data-rawwidth=&569& data-rawheight=&431& class=&origin_image zh-lightbox-thumb& width=&569& data-original=&https://pic4.zhimg.com/v2-aabafc53c_r.jpg&&&figcaption&Soft Mask 插件效果对比&/figcaption&&/figure&&blockquote&UWA针对我们项目的深度优化报告里提到:Soft Mask插件的Component在Disable逻辑里有明显的性能消耗,目前我们未开始针对这块进行优化,不过只在ui关闭的时候才有,所以优先级也比较低,想尝试的朋友可以提前评估下性能。&/blockquote&&h2&&b&5. 基于DoTween的动画效果优化&/b&&/h2&&p&在游戏中,UI比较大量地使用了DoTween插件制作动画效果来强调一些需要醒目提醒玩家的信息。DoTween是一个非常好用的插件,无论是对于程序还是对于UI来说,都可以经过简单的操作来实现较为好的动画效果。&/p&&p&然而,对于UGUI来说,DoTween往往意味着持续的Canvas的重建,因为动画通常是位置、旋转和缩放的变化,这些都会导致其所在的Canvas在动画过程中一直有重建操作,比如我们游戏中会有的如下图所示的旋转提醒的效果:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d52b72ca661d39292ddcc6aad7627d3e_b.jpg& data-size=&normal& data-rawwidth=&350& data-rawheight=&189& data-thumbnail=&https://pic1.zhimg.com/v2-d52b72ca661d39292ddcc6aad7627d3e_b.jpg& class=&content_image& width=&350&&&figcaption&不断旋转的提示信息&/figcaption&&/figure&&p&这一效果在DoTween中通过不断改变图片的旋转来实现的,在我们profile过程中发现了可疑的持续canvas重建,最后通常会定位到类似这样界面动画的地方。使用Shader进行优化,只需要把旋转的过程拿到Shader的vs阶段来做,同样是修改uv信息,材质代码的vert函数如下:&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&n&&v2f&/span& &span class=&nf&&vert&/span&&span class=&p&&(&/span&&span class=&n&&appdata_t&/span& &span class=&n&&IN&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&v2f&/span& &span class=&n&&OUT&/span&&span class=&p&&;&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span& &span class=&o&&=&/span& &span class=&n&&mul&/span&&span class=&p&&(&/span&&span class=&n&&UNITY_MATRIX_MVP&/span&&span class=&p&&,&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span& &span class=&o&&=&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&;&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&-=&/span& &span class=&mf&&0.5&/span&&span class=&p&&;&/span&
&span class=&c1&&//避免时间过长的时候影响精度&/span&
&span class=&n&&half&/span& &span class=&n&&t&/span& &span class=&o&&=&/span& &span class=&n&&fmod&/span&&span class=&p&&(&/span&&span class=&n&&_Time&/span&&span class=&p&&.&/span&&span class=&n&&y&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span& &span class=&o&&*&/span& &span class=&n&&UNITY_PI&/span&&span class=&p&&);&/span&
&span class=&n&&t&/span& &span class=&o&&*=&/span& &span class=&n&&_RotationSpeed&/span&&span class=&p&&;&/span&
&span class=&n&&half&/span& &span class=&n&&s&/span&&span class=&p&&,&/span& &span class=&n&&c&/span&&span class=&p&&;&/span&
&span class=&n&&sincos&/span&&span class=&p&&(&/span&&span class=&n&&t&/span&&span class=&p&&,&/span& &span class=&n&&s&/span&&span class=&p&&,&/span& &span class=&n&&c&/span&&span class=&p&&);&/span&
&span class=&n&&half2x2&/span& &span class=&n&&rotationMatrix&/span& &span class=&o&&=&/span& &span class=&n&&half2x2&/span&&span class=&p&&(&/span&&span class=&n&&c&/span&&span class=&p&&,&/span& &span class=&o&&-&/span&&span class=&n&&s&/span&&span class=&p&&,&/span& &span class=&n&&s&/span&&span class=&p&&,&/span& &span class=&n&&c&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&=&/span& &span class=&n&&mul&/span&&span class=&p&&(&/span&&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span&&span class=&p&&,&/span& &span class=&n&&rotationMatrix&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&+=&/span& &span class=&mf&&0.5&/span&&span class=&p&&;&/span&
&span class=&cp&&#ifdef UNITY_HALF_TEXEL_OFFSET
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&-=&/span& &span class=&p&&(&/span&&span class=&n&&_ScreenParams&/span&&span class=&p&&.&/span&&span class=&n&&zw&/span&&span class=&o&&-&/span&&span class=&mf&&1.0&/span&&span class=&p&&);&/span&
&span class=&cp&&#endif&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&color&/span& &span class=&o&&=&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&color&/span&&span class=&p&&;&/span&
&span class=&k&&return&/span& &span class=&n&&OUT&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&注意,这里因为要求UI控件使用的是一张RawImage,因此uv的中心点就认为了是&b&(0.5, 0.5)&/b&位置。通过参数可以做到从C#中传递uv的偏移和缩放信息从而兼容Image,但是因为材质不同导致本来就无法合批,所以使用Image和Atlas带来的优势就没有了,所以这里只简单地支持RawImage。&/p&&blockquote&Tips:注意所使用贴图的边缘处理,因为uv的旋转可能会导致超过之前0和1的范围。首先贴图的采样方式要使用&b&Clamp&/b&的方式,其次贴图的边缘要留出几个像素的透明区域,保证即使在设备上贴图压缩之后依然可以让边缘的效果正确。&/blockquote&&p&另外使用材质进行优化的地方是自动寻路的提示效果:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2eda9b8f5df4ae8d79b8c7fdffdddb99_b.jpg& data-size=&normal& data-rawwidth=&350& data-rawheight=&171& data-thumbnail=&https://pic2.zhimg.com/v2-2eda9b8f5df4ae8d79b8c7fdffdddb99_b.jpg& class=&content_image& width=&350&&&figcaption&常见的动态字体提示动画&/figcaption&&/figure&&p&最初UI想使用DoTween来制作,但是觉得工作量有点大所以想找程序写DoTween的代码进行开发,为了减少ui的Canvas重建,使用材质来控制每一个字的缩放过程。同样是在vert函数中针对uv进行修改即可:&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span&&/span&&span class=&n&&v2f&/span& &span class=&nf&&vert&/span&&span class=&p&&(&/span&&span class=&n&&appdata_t&/span& &span class=&n&&IN&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&v2f&/span& &span class=&n&&OUT&/span&&span class=&p&&;&/span&
&span class=&n&&UNITY_SETUP_INSTANCE_ID&/span&&span class=&p&&(&/span&&span class=&n&&IN&/span&&span class=&p&&);&/span&
&span class=&n&&UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO&/span&&span class=&p&&(&/span&&span class=&n&&OUT&/span&&span class=&p&&);&/span&
&span class=&c1&&//根据时间和配置参数对顶点进行缩放操作&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&worldPosition&/span& &span class=&o&&=&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span&&span class=&p&&;&/span&
&span class=&n&&half&/span& &span class=&n&&t&/span& &span class=&o&&=&/span& &span class=&n&&fmod&/span&&span class=&p&&((&/span&&span class=&n&&_Time&/span&&span class=&p&&.&/span&&span class=&n&&y&/span& &span class=&o&&-&/span& &span class=&n&&_TimeOffset&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&n&&_TimeScale&/span&&span class=&p&&,&/span& &span class=&mi&&1&/span&&span class=&p&&);&/span&
&span class=&n&&t&/span& &span class=&o&&=&/span& &span class=&mi&&4&/span& &span class=&o&&*&/span& &span class=&p&&(&/span&&span class=&n&&t&/span& &span class=&o&&-&/span& &span class=&n&&t&/span& &span class=&o&&*&/span& &span class=&n&&t&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&worldPosition&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&-=&/span& &span class=&n&&_VertexParmas&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span&&span class=&p&&;&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&worldPosition&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&*=&/span& &span class=&p&&(&/span&&span class=&mi&&1&/span& &span class=&o&&+&/span& &span class=&n&&t&/span&&span class=&o&&*&/span&&span class=&n&&_ScaleRatio&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&worldPosition&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span& &span class=&o&&+=&/span& &span class=&n&&_VertexParmas&/span&&span class=&p&&.&/span&&span class=&n&&xy&/span&&span class=&p&&;&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&vertex&/span& &span class=&o&&=&/span& &span class=&n&&UnityObjectToClipPos&/span&&span class=&p&&(&/span&&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&worldPosition&/span&&span class=&p&&);&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span& &span class=&o&&=&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&texcoord&/span&&span class=&p&&;&/span&
&span class=&n&&OUT&/span&&span class=&p&&.&/span&&span class=&n&&color&/span& &span class=&o&&=&/span& &span class=&n&&IN&/span&&span class=&p&&.&/span&&span class=&n&&color&/span& &span class=&o&&*&/span& &span class=&n&&_Color&/span&&span class=&p&&;&/span&
&span class=&k&&return&/span& &span class=&n&&OUT&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&blockquote&Tips:这里使用UWA群里一位朋友之前提供的三角函数近似的方法来略微减少一下指令消耗:&/blockquote&&figure&&img src=&https://pic3.zhimg.com/v2-e6dea6f68c75_b.jpg& data-size=&normal& data-rawwidth=&413& data-rawheight=&340& class=&content_image& width=&413&&&figcaption&sin函数的近似模拟&/figcaption&&/figure&&p&这一效果的实现不像之前的旋转效果那么简单,只有Shader的修改就可以了。这里需要额外处理的部分有如下几点:&/p&&p&1. 逐个缩放的效果需要控制“自动寻路中...”这句话中的每一个字,因此在这个效果中&b&每一个字都是一个Text控件&/b&。正在缩放的字使用特殊的缩放材质来绘制,其他的字依然使用默认的UI材质绘制,这意味着需要&b&2个DrawCall&/b&来实现整体效果。&/p&&p&2. vert函数中需要获取字体的中心点和长宽大小,然后进行缩放计算,也就是参数_VertexParmas的内容。经过测试,在ugui中,&b&顶点的位置信息worldPosition是其相对于Canvas的位置信息&/b&,因此这里需要在C#中进行计算,计算过程借助RectTransformUtility的ScreenPointToLocalPointInRectangle函数:&/p&&div class=&highlight&&&pre&&code class=&language-csharp&&&span&&/span&&span class=&n&&Vector2&/span& &span class=&n&&tempPos&/span&&span class=&p&&;&/span&
&span class=&n&&RectTransformUtility&/span&&span class=&p&&.&/span&&span class=&n&&ScreenPointToLocalPointInRectangle&/span&&span class=&p&&(&/span&&span class=&n&&ParentCanvas&/span&&span class=&p&&.&/span&&span class=&n&&transform&/span& &span class=&k&&as&/span& &span class=&n&&RectTransform&/span&&span class=&p&&,&/span&
&span class=&n&&ParentCanvas&/span&&span class=&p&&.&/span&&span class=&n&&worldCamera&/span&&span class=&p&&.&/span&&span class=&n&&WorldToScreenPoint&/span&&span class=&p&&(&/span&&span class=&n&&img&/span&&span class=&p&&.&/span&&span class=&n&&transform&/span&&span class=&p&&.&/span&&span class=&n&&position&/span&&span class=&p&&),&/span&
&span class=&n&&ParentCanvas&/span&&span class=&p&&.&/span&&span class=&n&&worldCamera&/span&&span class=&p&&,&/span& &span class=&k&&out&/span& &span class=&n&&tempPos&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&这里的ParentCanvas是当前控件向上遍历找到的第一个Canvas对象。&/p&&blockquote&Tips:这里,搞清楚了vs中顶点的worldPosition对应的属性之后,可以做很多有趣的事情,包括之前的旋转效果也可以不再旋转uv而是对顶点位置进行旋转。&/blockquote&&p&3. 关于_Time,它的y值与C#中对应的是Time.timeSinceLevelLoad。这里为了实现界面一开始的时候第一个字是从0开始放大的,需要从C#传递给Shader一个起始时间,通过&code&_Time.y - _TimeOffset&/code& 来确保起始效果。&/p&&p&最后直接贴一下C#部分的代码好了,也很简单,提供给UI配置缩放尺寸、缩放持续时间和间隔等参数,然后通过协程来控制字体的材质参数:&/p&&div class=&highlight&&&pre&&code class=&language-csharp&&&span&&/span&&span class=&k&&using&/span& &span class=&nn&&System&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&System.Collections.Generic&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&UnityEngine&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&UnityEngine.EventSystems&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&UnityEngine.UI&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&DG.Tweening&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&ThorUtils&/span&&span class=&p&&;&/span&
&span class=&k&&using&/span& &span class=&nn&&System.Collections&/span&&span class=&p&&;&/span&
&span class=&k&&namespace&/span& &span class=&nn&&KEngine.UI&/span&
&span class=&p&&{&/span&
&span class=&k&&public&/span& &span class=&k&&class&/span& &span class=&nc&&UIAutoExpand&/span& &span class=&p&&:&/span& &span class=&n&&MonoBehaviour&/span&
&span class=&p&&{&/span&
&span class=&k&&public&/span& &span class=&kt&&float&/span& &span class=&n&&ScaleDuration&/span& &span class=&p&&=&/span& &span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&public&/span& &span class=&kt&&float&/span& &span class=&n&&CycleInterval&/span& &span class=&p&&=&/span& &span class=&m&&5&/span&&span class=&p&&;&/span&
&span class=&k&&public&/span& &span class=&kt&&float&/span& &span class=&n&&ScaleRatio&/span& &span class=&p&&=&/span& &span class=&m&&0.5f&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&n&&Canvas&/span& &span class=&n&&ParentCanvas&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&n&&Image&/span&&span class=&p&&[]&/span& &span class=&n&&images&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&n&&Vector4&/span&&span class=&p&&[]&/span& &span class=&n&&meshSizes&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&kt&&int&/span& &span class=&n&&currentScaleIdx&/span& &span class=&p&&=&/span& &span class=&p&&-&/span&&span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&n&&Coroutine&/span& &span class=&n&&playingCoroutine&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&k&&static&/span& &span class=&n&&Material&/span& &span class=&n&&UIExpandMat&/span& &span class=&p&&=&/span& &span class=&k&&null&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&k&&static&/span& &span class=&kt&&int&/span& &span class=&n&&VertexParamsID&/span& &span class=&p&&=&/span& &span class=&p&&-&/span&&span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&k&&static&/span& &span class=&kt&&int&/span& &span class=&n&&TimeOffsetID&/span& &span class=&p&&=&/span& &span class=&p&&-&/span&&span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&k&&static&/span& &span class=&kt&&int&/span& &span class=&n&&TimeScaleID&/span& &span class=&p&&=&/span& &span class=&p&&-&/span&&span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&private&/span& &span class=&k&&static&/span& &span class=&kt&&int&/span& &span class=&n&&ScaleRatioID&/span& &span class=&p&&=&/span& &span class=&p&&-&/span&&span class=&m&&1&/span&&span class=&p&&;&/span&
&span class=&k&&void&/span& &span class=&nf&&Awake&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&c1&&//初始化静态变量&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&UIExpandMat&/span& &span class=&p&&==&/span& &span class=&k&&null&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&UIExpandMat&/span& &span class=&p&&=&/span& &span class=&k&&new&/span& &span class=&n&&Material&/span&&span class=&p&&(&/span&&span class=&n&&Shader&/span&&span class=&p&&.&/span&&span class=&n&&Find&/span&&span class=&p&&(&/span&&span class=&s&&&ThorShader/UI/UIExpand&&/span&&span class=&p&&));&/span&
&span class=&n&&VertexParamsID&/span& &span class=&p&&=&/span& &span class=&n&&Shader&/span&&span class=&p&&.&/span&&span class=&n&&PropertyToID&/span&&span class=&p&&(&/span&&span class=&s&&&_VertexParmas&&/span&&span class=&p&&);&/span&
&span class=&n&&TimeOffsetID&/span& &span class=&p&&=&/span& &span class=&n&&Shader&/span&&span class=&p&&.&/span&&span class=&n&&PropertyToID&/span&&span class=&p&&(&/span&&span class=&s&&&_TimeOffset&&/span&&span class=&p&&);&/span&
&span class=&n&&TimeScaleID&/span& &span class=&p&&=&/span& &span class=&n&&Shader&/span&&span class=&p&&.&/span&&span class=&n&&PropertyToID&/span&&span class=&p&&(&/span&&span class=&s&&&_TimeScale&&/span&&span class=&p&&);&/span&
&span class=&n&&ScaleRatioID&/span& &span class=&p&&=&/span& &span class=&n&&Shader&/span&&span class=&p&&.&/span&&span class=&n&&PropertyToID&/span&&span class=&p&&(&/span&&span class=&s&&&_ScaleRatio&&/span&&span clas

我要回帖

更多关于 如何记忆琐碎的知识点 的文章

 

随机推荐