王在京同学 2018-02-20T12:59:00+00:00 spencer.wong@hotmail.com MVC Pattern for UGUI 2018-02-09T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/02/09/ugui-mvc 最近看了 PureMVC for UGUI 的实现,感觉有点过度设计了。Unity 基于组件模式的风格,没有层次关系管理或者约定的话,组件之间复杂的相互依赖会是噩梦,但这个问题 PureMVC 并没有解决,而且还多出来N多冗余脚本。

自己实现了一个 MVC Pattern,放到github上了,还做了一个 Editor 工具来生成模板脚本。为了与国际接轨,特地做了英文的 readme.md 括弧允悲,赏脸给个 star 吧

UMVC

=== MVC Pattern Framework for Unity3d GUI System

Diagram

Model

  • Holds no view data nor view state data.
  • Is accessed by the Controller and other Models only
  • Will trigger events to notify external system of changes.

View

  • UGUI Prefabs

Presenter

  • Each Presenter corresponds to a View
  • Holds references to elements needed for drawing
  • Receive User Input
  • Notify Controller when an user input
  • This script is a UI refresh operation function set

Controller

  • Controls view flow.
  • Holds the application state needed for that view
  • Will trigger events to notify external system of changes.
  • Handles events either triggered by the player in the View or triggered by the Models
  • Each Controller corresponds to a Presenter and holds the reference of it's Presenter
  • Holds the references of the small Controllers under this controller

NotificationCenter

  • A notification dispatch mechanism that enables the broadcast of information to registered observers.
  • Add observer in Controllers
  • Post notifications in Models
    void Start()
    {
        NotificationCenter.DefaultCenter.AddObserver(this, "UserDataChanged", UserDataChanged);
    }
    void OnDestroy()
    {
        NotificationCenter.DefaultCenter.RemoveObserver(this, "UserDataChanged");
    }

Create Controller and Presenter from Template

  • Click menu Template Scripts and Create
  • Type class name and namespace
  • Then it will create two scripts, one Controller and one Presenter Diagram

--EOF--

]]>
UGUI 'World Space' Canvas 不响应触摸 2017-11-10T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/11/10/ugui-raycast 最近做航母飞机的预瞄,用的World Space的Canvas绘制在3d空间,但是不响应触摸事件,Canvas的Sort Order设置无效,事件永远被Screen Space Camera的Canvas截获,之前的UGUI版本GraphicRaycaster脚本有显示的sort order设置,现在也没有了。

去bitbucket看了EventSystem源码发现比较顺序为
1.GraphicRaycaster不同
1) 都有event camera 先比较camera 的 depth
2) 比较GraphicRaycaster 的 sortOrderPriority
3) 比较GraphicRaycaster 的 renderOrderPriority

2.GraphicRaycaster相同
1) 比较Canvas 的 sortingLayer
2) 比较Canvas 的 sortingOrder
3) 比较控件的depth
4) 比较与摄像机的distance,overlay为0

private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
    if (lhs.module != rhs.module)
    {
        if (lhs.module.eventCamera != null && rhs.module.eventCamera != null && lhs.module.eventCamera.depth != rhs.module.eventCamera.depth)
        {
            // need to reverse the standard compareTo
            if (lhs.module.eventCamera.depth < rhs.module.eventCamera.depth)
                return 1;
            if (lhs.module.eventCamera.depth == rhs.module.eventCamera.depth)
                return 0;

            return -1;
        }

        if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
            return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);

        if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
            return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
    }

    if (lhs.sortingLayer != rhs.sortingLayer)
    {
        // Uses the layer value to properly compare the relative order of the layers.
        var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
        var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
        return rid.CompareTo(lid);
    }


    if (lhs.sortingOrder != rhs.sortingOrder)
        return rhs.sortingOrder.CompareTo(lhs.sortingOrder);

    if (lhs.depth != rhs.depth)
        return rhs.depth.CompareTo(lhs.depth);

    if (lhs.distance != rhs.distance)
        return lhs.distance.CompareTo(rhs.distance);

    return lhs.index.CompareTo(rhs.index);
}

World Space Canvas 的 eventCamera 是MainCamera,他的 depth一定是要小于UICamera的,基本没救了啊,只能把UI Canvas设置为Overlay来解决了,再做一个GraphicRaycaster子类设置sortOrderPriority来解决。

public class WorldRaycaster : GraphicRaycaster
{
    [SerializeField]
    private int SortOrder = 0;

    public override int sortOrderPriority
    {
        get
        {
            return SortOrder;
        }
    }
}

找个时间通读一遍UGUI的源码,画下类图。

--EOF--

]]>
Unity3d iOS crash 'Ran out of trampolines' 2017-07-09T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/07/09/ios-crash-randomly iOS平台不允许JIT,会通过Full AOT直接编译为ARM汇编代码。但在编译的时候仍有一些无法静态确定的事情:例如泛型接口可能需要不同的虚拟表(运行时存放执行方法的集合),这取决于接口被实例化时的类型。(对于这种情况,从技术上讲确定虚拟表的最大数量是可能的,但是这数量可能会是庞大的--即泛型接口数乘以应用中类型数).我们无法为这些虚拟表在运行时动态的分配内存,所以我们指定了一个合理的默认值并且允许用户在运行时出现问题的情况下增加默认值。这就是蹦床的基本理论(实际情况略微不同,这依赖于蹦床数的类型,但这并不重要)以上是Rolf Bjarne Kvinge的解释

Ran out of trampolines of type 0

如果你再运行时,输出这个错误,你可以在项目option--iphone build处添加额外的参数-aot "ntrampolines=2048" 这个参数默认值为1024,试着增加这个值直到满足你的应用的需求。

Ran out of trampolines of type 1

如果你使用了过多的泛型嵌套,如List中还有List成员,你可以同上通过添加额外的参数-aot "nrgctx-trampolines=2048" 来解决。 蹦床类型1的默认值为1024.

Ran out of trampolines of type 2

如果你界面操作频繁,你可以通过添加额外的参数-aot "nimt-trampolines=512" 来解决。 这里的默认值为128. 

以上参数在Build settings的 AOT Compilation Options 里添加

--EOF--

]]>
iOS Mono Full AOT 模式下的限制 2017-07-08T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/07/08/ios-crash-bugs IL
IL是.NET框架中中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件, 但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)使用中间语言可以实现平台无关性,既与特定CPU无关;实现.NET框架中语言之间的交互操作,这就是为什么unity3D里面可以c#和js混编。

JIT
即时编译(英语:Just-in-time compilation),又译及时编译、实时编译,动态编译的一种形式,是一种提高程序运行效率的方法。通常,程序有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,而解释执行的则是一句一句边运行边翻译。JIT狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。

AOT
静态编译、提前编译(Ahead-of-time compilation)

Mono Full AOT 模式的限制
在iOS平台中,Mono是以Full AOT模式运行的,无法使用JIT引擎,Full AOT常见的限制:

1.不支持泛型虚方法。

因为对于泛型代码,Mono通过静态分析以确定要实例化的类型并生成代码,但静态分析无法确定运行时实际调用的方法(C++也因此不支持虚模版函数)。

2.不支持对泛型类的P/Invoke。

3.目前不能使用反射中的Property.SetInfo给非空类型赋值。

4.值类型作为Dictionary的Key时会有问题.

实际上实现了IEquatable的类型都会有此问题,因为Dictionary的默认构造函数会使用EqualityComparer.Default作为比较器,而对于实现了IEquatable的类型,EqualityComparer.Default要通过反射来实例化一个实现了IEqualityComparer的类(可以参考EqualityComparer的实现)。 解决方案是自己实现一个IEqualityComparer,然后使用Dictionary(IEqualityComparer)构造器创建Dictionary实例。之前GC优化篇有提过。

5.由于不允许动态生成代码,不允许使用System.Reflection.Emit,不允许动态创建类型。

由于不允许使用System.Reflection.Emit,无法使用DLR及基于DLR的任何语言。 不要混淆了Reflection.Emit和反射,所有反射的API均可用

--EOF--

]]>
优化IL2CPP的build时间 2017-07-07T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/07/07/il2cpp 编译IL2CPP版本耗时太长,尤其是Android版本,公司项目出一个Android IL2CPP版本的build时间大概是70分钟。以下是Unity官方的优化方案。

使用 'incremental building'
如果使用 incremental building, C++ compiler 只会重新编译上次改变的文件. 使用 incremental building,只需编版本的路径和上次相同即可(之前的build的文件夹不能删除)

不要让反恶意软件工具扫描你的工程目录和build target目录
编版本之前关闭反恶意软件工具能提高build效率。(Unity Technologies 自己测试说在一台新装的Win 10 上关掉Windows Defender,build时间降低了50-60%)

工程目录和build target目录放到SSD上
SSD读写速度比HDD快的多,IL文件转到C++过程中需要大量的硬盘读写操作,用SSD会加快这个过程。

官方文档:https://docs.unity3d.com/Manual/IL2CPP-OptimizingBuildTimes.html
--EOF--

]]>
Xcode error 'MapFileParser.sh Permission denied' 2017-06-22T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/06/22/mac-compile-error 公司的项目很大,而我的macbook ssd 只有128G。为了调试公司Unity项目的iOS版本,我都是先在PC上编译好,然后拷贝到mac编译。 这样做没什么问题,但是会有一些脚本文件失去可执行权限,比如可能会报以下的错:

'MapFileParser.sh: Permission denied - not set as executable'

解决方法也比较简单,再将文件赋予可执行权限即可。

chmod +x /path/to/MapFileParser

--EOF--

]]>
Unity Reimport 'UnityEngine.UI.dll' 2017-03-14T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/03/14/reimport-dll 代码在切换分支后,Unity工程下Library文件夹(如果没有加入到版本控制中)可能会被污染,导致各种Unity的dll文件找不到,例如:

"Asset 'C:/Program Files (x86)/Unity/Editor/Data/UnityExtensions/Unity/GUISystem/4.6.0/UnityEngine.UI.dll'

Unity的dll文件不能单独reimport,只能remiport all,虽然能解决问题,但是太慢太慢了,尤其是Android平台Load贴图,非常浪费时间。
一番google之后,在Unity的坛子里,网友'j-robichaud'贡献了脚本,解决了问题,如救世主一般,感动。

using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
using System.Text.RegularExpressions;
using System.IO;
using System.Text;
 
public class ReimportUnityEngineUI
{
    [MenuItem( "Assets/Reimport UI Assemblies", false, 100 )]
    public static void ReimportUI()
    {
#if UNITY_4_6
        var path = EditorApplication.applicationContentsPath + "/UnityExtensions/Unity/GUISystem/{0}/{1}";
        var version = Regex.Match( Application.unityVersion,@"^[0-9]+\.[0-9]+\.[0-9]+").Value;
#else
        var path = EditorApplication.applicationContentsPath + "/UnityExtensions/Unity/GUISystem/{1}";
        var version = string.Empty;
#endif
        string engineDll = string.Format( path, version, "UnityEngine.UI.dll");
        string editorDll = string.Format( path, version, "Editor/UnityEditor.UI.dll");
        ReimportDll( engineDll );
        ReimportDll( editorDll );
               
    }
    static void ReimportDll(string path )
    {
        if ( File.Exists( path ) )
            AssetDatabase.ImportAsset( path, ImportAssetOptions.ForceUpdate| ImportAssetOptions.DontDownloadFromCacheServer );
        else
            Debug.LogError( string.Format( "DLL not found {0}", path ) );
    }
}

--EOF--

]]>
Jenkins 'Waiting for next available executor' 2017-01-12T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/01/12/jenkins-error 用Jenkins部署Unity3d项目自动编译,新建一个Job,配置完成后,点击构建一直提示:

连接等待中 - Waiting for next available executor

一番google之后发现原因可能有几个:一个是节点offline了,一个是节点disk不够了,还有number of executor是0。 但是我去[系统管理->进入节点->节点管理]里看了下,都是好的。

没办法只能用重启大法了
jenkinsurl/safeRestart等待现有构建完成后重启。
jenkinsurl/restart直接重启。
重启之后就好了。

嗯,重启大法好。

--EOF--

]]>
Unity3d 'Fatal signal 11 (SIGSEGV), code 1' 2017-01-07T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2017/01/07/unity-crash 项目一切都是OK的,接入某渠道SDK后,打开app 就 crash。人啊,这一辈子注定要遇到几个 'SIGSEGV',摊手。

01-05 18:48:12.124: A/libc(727): Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 776 (UnityMain)
01-05 18:48:12.124: E/libEGL(727): call to OpenGL ES API with no current context (logged once per thread)
01-05 18:48:12.234: A/DEBUG(533): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-05 18:48:12.234: A/DEBUG(533): Build fingerprint: 'samsung/c5pltezc/c5pltechn:6.0.1/MMB29M/C5000ZCU1APJ4:user/release-keys'
01-05 18:48:12.234: A/DEBUG(533): Revision: '10'
01-05 18:48:12.234: A/DEBUG(533): ABI: 'arm'
01-05 18:48:12.234: A/DEBUG(533): pid: 727, tid: 776, name: UnityMain  >>> com.kongzhong.c1.uc <<<
01-05 18:48:12.234: A/DEBUG(533): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
01-05 18:48:12.254: A/DEBUG(533):     r0 9ebc82a8  r1 00000008  r2 00000000  r3 00000000
01-05 18:48:12.254: A/DEBUG(533):     r4 a169ec36  r5 9ebc7010  r6 9eaadb10  r7 fffff0d4
01-05 18:48:12.254: A/DEBUG(533):     r8 00001298  r9 0000002b  sl 00000001  fp 00000002
01-05 18:48:12.254: A/DEBUG(533):     ip 0000002b  sp a082e378  lr ffffffff  pc a0d01c3c  cpsr 60070010
01-05 18:48:12.254: A/DEBUG(533): backtrace:
01-05 18:48:12.254: A/DEBUG(533):     #00 pc 004d2c3c  /data/app/com.kongzhong.c1.uc-1/lib/arm/libunity.so
01-05 18:48:12.254: A/DEBUG(533):     #01 pc 00333cb4  /data/app/com.kongzhong.c1.uc-1/lib/arm/libunity.so
01-05 18:48:12.254: A/DEBUG(533):     #02 pc 0030e0d8  /data/app/com.kongzhong.c1.uc-1/lib/arm/libunity.so
01-05 18:48:12.254: A/DEBUG(533):     #03 pc 0035e680  /data/app/com.kongzhong.c1.uc-1/lib/arm/libunity.so
01-05 18:48:12.254: A/DEBUG(533):     #04 pc 00509e58  /data/app/com.kongzhong.c1.uc-1/lib/arm/libunity.so
01-05 18:48:12.254: A/DEBUG(533):     #05 pc 0050cc00  /data/app/com.kongzhong.c1.uc-1/lib/arm/libunity.so
01-05 18:48:12.254: A/DEBUG(533):     #06 pc 003f3441  /data/app/com.kongzhong.c1.uc-1/oat/arm/base.odex (offset 0x21f000)

毫无头绪,看backtarce,挂在unity的C++代码里,鬼知道是什么,当然可以用ndk-stack tool来看具体的堆栈信息,如何使用 参考链接,但并没有任何有价值的信息。

SIGSEGV之前有可能还会遇到说GL找不到上下文的报错。

call to OpenGL ES API with no current context (logged once per thread)

一开始以为Unity开了Multithreaded Rendering,导致crash。 关掉了之后,确实不crash了,但是屏幕全粉了,心想要么材质掉了,要么shader跪了,开始报下面错:

-------- GLSL link failed, no info log provided.

一番google之后,在Unity 坛子里名叫haruki_tachihara的网友(貌似霓虹国友人)解决了我的问题,很简单
it was resolved by loading the game scene after the empty scene.
竟然添加一个空场景中转一下就好了,而且多线程打开也没问题了,就这样所有问题都好了,也不crash了。现在想想大概是渠道sdk在splash界面就初始化, 把UnityMain进程挂起导致的。 现在国内渠道为了让接入的游戏打开就先显示自己的logo,也是操碎了心。

--EOF--

]]>
错误的Import Settings会拖垮你的游戏[Part 2] 2016-12-29T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2016/12/29/importsettings2 [Part 1]里说到贴图的improt settings会影响性能,这次来说下Audio Clip的import settings.

Unity的默认设置大部分情况下是ok的,当然默认设置有些情景下不是最优的,很不幸Audio Clip Import Settings 默认设置就蛮糟糕。

与贴图的import settings 类似. Unity 支持各种音频格式,目前,Unity 5.2.1 支持 PCM, ADPCM, Vorbis/MP3 和 HEVAG. 并不是每个平台都支持所有的压缩格式,有些只支持一种(WebGL只能用AAC)

主要还是Memory matters吧

Default Import Settings:
Load Type: Decompress On Load
Compression Format: Vorbis

默认设置会占用非常大的内存!如果有很多音频文件移动设备可能顶不住。

Load Types: Compressed In Memory – Audio Clip 存储在RAM里,播放时解压. 播放时不需要额外的内存 Streaming – Audio Clip 存储在 persistent memory (hard drive, flash drive etc)里.存储和播放都不需要RAM(播放时需要极少). Decompress On Load – Audio Clip 无压缩存储在RAM里. 需要最多的RAM,但是不耗CPU 到底选哪个视情况而定.

Music and/or Ambient Sounds

音乐、BGM、环境声音一般比较大,存到RAM里很占内存,我们肯定不选 Decompress On Load, 至少要压缩过放到RAM里。

所以有两种选择

1.Load Type: Streaming Compression Format: Vorbis. 这个组合是最不占用内存的,但是耗CPU和磁盘I/O.

2.Load Type: Compressed In Memory Compression Format: Vorbis. 跟第一种的区别是把I/O占用换成一部份内存占用了。

你可以调整 Quality 来降低大小,100%最高,一般推荐70%.空间换时间,代价就是这个比较耗CPU,可以在profiler里看下。

Sound Effects

Sounds effects 一般为中短型. 播放频率要么很多,要么很少。

播放频率高而且很短的使用 Decompress On Load,Compression 格式 使用PCM 或者 ADPCM. 选择PCM就不用解压了,音频很短,也会load很快. ADPCM需要解压,但比Vorbis轻量。

播放频率高中型长度的使用 Compressed In Memory 和 ADPCM. ADPCM 差不多比PCM小3.5倍,解压比Vorbis快的多。

播放频率低而且很短的使用 Compressed In Memory 和 ADPCM.

播放频率低中型长度的使用 Compressed In Memory 和 Vorbis. 用ADPCM可能还是会太占用RAM,反正不经常用,不会很耗CPU.

总结

build 版本前check 下 Audio Clip Import Settings.

个人翻译.转载请注明出处.原文

--EOF--

]]>