王在京同学 2018-06-11T08:08:48+00:00 spencer.wong@hotmail.com 查找 Unity ManagedStaticReferences 2018-06-11T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/06/11/untiy-static-ref 当我们使用 Unity Profiler 查看内存时,经常有些贴图等资源的引用只有一个 ManagedStaticReferences() 引用,怎么都卸载不掉。 使用 Memory Profiler 也查找不到谁引用的。现在终于找到方法,开心,分享给大家。

具体思路:

  1. 维护一个 key 是 物件 Hierarchy 路径, value 是 WeakReference 的字典,收集所有可能会泄漏的组件
  2. 在需要 Check 的物件挂靠的脚本里 添加该 Componet 的 弱引用到字典
  3. 查看 Alive 状态,Alive 为 true ,但 target 为空的即为泄漏者,打印他的路径
  4. 如果单纯查找 UI 贴图的内存泄漏,例如 NGUI 可以只在 UIWidget 的 Awake 里添加弱引用到字典, UGUI 的话需要下载 UGUI 源码 在 Graphic 构造里添加引用,再打包 dll 来测试。这样就可以覆盖所有 UI 贴图
  5. 其他的,怀疑那个脚本,就把他的引用添加到字典里

代码如下:

public class ComponentReferenceManager
{
    public static ComponentReferenceManager Instance = new ComponentReferenceManager();
    Dictionary<string, WeakReference> refs = new Dictionary<string, WeakReference>();

    private int count = 0;
    public void AddRef(Component c)
    {
        var key = string.Format("Index:<color=red>{0}</color> ComponentType:<color=red>{1}</color> GameObject:<color=red>{2}</color>", 
                  count,  c.GetType().ToString(), GetGameObjectPath(c));

        refs[key] = new WeakReference(c);
        ++count;
    }

    private string GetGameObjectPath(Component c)
    {
        var obj = c.gameObject;
        string path = "/" + obj.name;
        while (obj.transform.parent != null)
        {
            obj = obj.transform.parent.gameObject;
            path = "/" + obj.name + path;
        }
        return path;
    }

    public void PrintLog()
    {
        GC.Collect();
        Debug.LogError("打印销毁了但还被引用的物件:");
        foreach(var kv in refs)
        {
            if (kv.Value.IsAlive)
            {
                if (kv.Value.Target == null)
                {
                    Debug.LogErrorFormat("Target is null {0}", kv.Key);
                    continue;
                }

                var w = kv.Value.Target as Component;
                if (w == null)
                {
                    Debug.LogErrorFormat("Component is null {0}", kv.Key);
                    continue;
                }

                if (w.gameObject == null)
                {
                    Debug.LogErrorFormat("Component attached game object is null {0}", kv.Key);
                }
            }
        }
    }
}

UIWidget 的 Awake 里 或者你怀疑的其他脚本里添加调用

    #if UNITY_EDITOR
        ComponentReferenceManager.Instance.AddRef(this);
    #endif

编辑器脚本

public partial class ReportEditor
{
    [MenuItem("Jinggle/打印销毁了但还被引用的物件")]
    static void ReportUIRef () 
    {
        ComponentReferenceManager.Instance.PrintLog();
    }
}

这样就会找到所有在场景里已经销毁了,但还是被引用到的物件了。 下一步就是要查为什么明明被销毁了,还会被引用的原因了。

经过我排查我们游戏,发现导致 ManagedStaticReferences() 绝大部分原因都是 static 变量的使用不当。 真真儿是 静态变量一时爽,内存泄漏火葬场。

--EOF--

]]>
NGUI Atlas Maker Bug 2018-05-30T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d%20tips/2018/05/30/ngui-texture-packer 既有项目大部分界面还在使用 NGUI 制作 UI, 简直上古社会。 最近美术同学发现了一个 bug,说使用 NGUI Atlas Maker 打图,既有的碎图会变的越来越糊。 经过调试,发现是 Unity 更新了贴图导入机制导致的。 需要在 NGUIEditorTools.cs 的 MakeTextureReadable 函数里写行代码,显示的将导入的 Atlas 的大图设为不压缩格式,不然之前的大图如果有 compression, GetPixels32 函数获取的像素就为失真的,会越来越糊。

ti.textureCompression = TextureImporterCompression.Uncompressed; 
static public bool MakeTextureReadable (string path, bool force)
{
    if (string.IsNullOrEmpty(path)) return false;
    TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
    if (ti == null) return false;

    TextureImporterSettings settings = new TextureImporterSettings();
    ti.ReadTextureSettings(settings);

    if (force || !settings.readable || settings.npotScale != TextureImporterNPOTScale.None || settings.alphaIsTransparency)
    {
        settings.readable = true;
        if (NGUISettings.trueColorAtlas) settings.textureFormat = TextureImporterFormat.AutomaticTruecolor;
        settings.npotScale = TextureImporterNPOTScale.None;
        settings.alphaIsTransparency = false;
        ti.SetTextureSettings(settings);
        ti.textureCompression = TextureImporterCompression.Uncompressed; 
        AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
    }
    return true;
}

--EOF--

]]>
Unity Tips (#1) 2018-05-28T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d%20tips/2018/05/28/unity-tips Unity3d 可以同时打开两个 Inspector 面板,查看两个物件的信息,lock 当前的 Inspector 然后右键 Add tab ,打开一个新的 Inspector 界面,就可以了。

Diagram

--EOF--

]]>
S.A.C 与信息不对等 2018-05-07T00:00:00+00:00 王在京同学 http://peakcoder.com/s.a.c/2018/05/07/sac S.A.C(STAND ALONE COMPLEX) 出自攻壳机动队,独立复合体。理解为独立趋同与交互生异悖论: 即每个个体都认为自己是独立思考,自己的思想具有独立性以及原创性,所做的选择出自于自我自由意志的选择,但整体所呈现出来的却是大家一致的共同性,然而没有个体意识到这种共同性并坚信自己的原创性。

信息不对等能获取巨大利益,所以会有这样一群人恰好拥有相似的信息量和行动力,进行了充分的理性分析,明智地选择了那同样的唯一解,却造就了愚蠢的群体意志。

如果人类像大刘笔下的三体人一样,没有信息处理中枢,感官、交流器官、思维器官一体。如此思维透明,信息共享,群体意志会趋于最优么?

我有时候会想变成三体人,这样你就会了解我的心意了吧。

--EOF--

]]>
Haptic for U3D 震动反馈 2018-05-06T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/05/06/haptics 之前玩 Ken Wong 的新游戏 Florence 里面大量使用了 iOS 10 的触觉震动反馈,尤其在点击和滑动时,体验非常好。

特此封装了 OC 的接口,做了一个插件 HapticU3DU3DC# 里调用,代码在开源在 GitHub 戳此

--EOF--

]]>
UGUI World Space UI 渲染在所有物体之上 2018-04-25T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/04/25/ztest-off 改造一下 UGUI 默认的着色器,做一个自定义的着色器,关掉 ZTest, 设置 QueueOverlay 就好了

 Shader "UI/Default_OverlayNoZTest"
 {
     Properties
     {
         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
         _Color ("Tint", Color) = (1,1,1,1)

         _StencilComp ("Stencil Comparison", Float) = 8
         _Stencil ("Stencil ID", Float) = 0
         _StencilOp ("Stencil Operation", Float) = 0
         _StencilWriteMask ("Stencil Write Mask", Float) = 255
         _StencilReadMask ("Stencil Read Mask", Float) = 255

         _ColorMask ("Color Mask", Float) = 15
     }

     SubShader
     {
         Tags
         { 
             "Queue"="Overlay" 
             "IgnoreProjector"="True" 
             "RenderType"="Transparent" 
             "PreviewType"="Plane"
             "CanUseSpriteAtlas"="True"
         }

         Stencil
         {
             Ref [_Stencil]
             Comp [_StencilComp]
             Pass [_StencilOp] 
             ReadMask [_StencilReadMask]
             WriteMask [_StencilWriteMask]
         }

         Cull Off
         Lighting Off
         ZWrite Off
         ZTest Off
         Blend SrcAlpha OneMinusSrcAlpha
         ColorMask [_ColorMask]

         Pass
         {
         CGPROGRAM
             #pragma vertex vert
             #pragma fragment frag
             #include "UnityCG.cginc"

             struct appdata_t
             {
                 float4 vertex   : POSITION;
                 float4 color    : COLOR;
                 float2 texcoord : TEXCOORD0;
             };

             struct v2f
             {
                 float4 vertex   : SV_POSITION;
                 fixed4 color    : COLOR;
                 half2 texcoord  : TEXCOORD0;
             };

             fixed4 _Color;

             v2f vert(appdata_t IN)
             {
                 v2f OUT;
                 OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                 OUT.texcoord = IN.texcoord;
 #ifdef UNITY_HALF_TEXEL_OFFSET
                 OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
 #endif
                 OUT.color = IN.color * _Color;
                 return OUT;
             }

             sampler2D _MainTex;

             fixed4 frag(v2f IN) : SV_Target
             {
                 half4 color = tex2D(_MainTex, IN.texcoord) * IN.color;
                 clip (color.a - 0.01);
                 return color;
             }
         ENDCG
         }
     }
 }

--EOF--

]]>
Creating plugins C# sripts to .dll files 2018-04-08T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/04/08/create-dll 想着把第三方库的 C# 文件编译成 .dll 不然看着凌乱,方便管理

1) 新建 VS Class Library 工程

2) 删除工程的所有 references

3) Unity API 的 dll 引用 添加 UnityEngine.dll 的引用
编辑器代码的dll 则需要 UnityEditor 生成 runtime 的 dll 不能添加这个 dll
路径在 *Unity Install Dir*\Editor\Data\Managed

有使用新 UI 则需要 UnityEngine.UI
路径在 *Unity Install Dir*\Editor\Data\UnityExtensions\Unity\GUISystem

4) 添加条件编译的宏 工程->属性->生成 添加 需要的宏 UNITY_EDITOR UNITY_IPHONE UNITY_ANDROID

5) 修改 Target Framework 工程->属性->应用 修改目标框架改为 Unity 3.5 .net full base class libraries

我尝试用 NGUI 生成了一下,但是工程里已有的物件挂的 NGUI 脚本全掉了,毕竟之前挂靠的对应关系是在 .meta 文件中存储的,dll 里没有了,这有点坑,拉到了,拉到了。

--EOF--

]]>
www 'unsupported url' error on iOS 2018-04-02T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/04/02/ios-unsupported-url 上周遇到一个奇葩问题,用 WWW 访问一个http 链接,在 iOS 上会报错 unsupported url,在 android 上没事,url 的编码都是英文和数字并没有特殊字符和汉字等。

用命令 file --mime-encoding < file >
发现 c# 脚本的格式是 us-ascii 的,怀疑跟这个有关系

先转成 utf-8 再说
用命令 iconv -f us-ascii -t utf-8 < file > < newfile >
发现新文件还是 us-ascii 的没变化,难道 us-asciiutf-8 的子集,才没变化的么?

最后没办法了,在脚本里写了一行中文注释,保存,变成 utf-8encoding 了,问题解决
好傻啊

--EOF--

]]>
获取 provisioning profile 的 UUID 2018-03-23T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/03/23/profile-location 昨天公司编译机 Jekins 编译失败,一看证书过期,替换新的 provision 文件还是失败,原因是编译参数里provision 文件的 UUID 没有更改。

获取 UUID 的方法

  • 双击文件用 Xcode 安装
  • 打开目录 Open ~/Library/MobileDevice/Provisioning Profiles
  • 最新修改的 .mobileprovision 文件的文件名就是 UUID

--EOF--

]]>
Unity Google Play Plugin 的坑 2018-03-22T00:00:00+00:00 王在京同学 http://peakcoder.com/unity3d/2018/03/22/google-play-shit 项目的海外版本要接入 Google Play 遇到了一些天坑,本篇作为记录,希望能帮助遇到同样问题的人,节省一点时间。

使用服务器验证

1 一定要使用 web client id

Unity plugin 的 android setup 里填的 ClientId 是关联的 web 应用的 id,不是关联的android应用的id

2 请求 token 不是每次都有

客户端登陆成功,第一次会返回 token ,在时效期间 登陆成功只返回 userid

3 新建一个 Android 关联应用

api console 里新建一个android 应用,关联 upload cerSHA1 这样 local 登陆也能成功,不用发布到 store 然后下载

--EOF--

]]>