• Unity3D心得分享


    本篇文章的内容以各种tips为主,不间断更新

    2020/5/27 最近更新:Unity节点视图中快速定位回中心位置

    Unity DEMO学习

    ===========================

    Unity3D Adam Demo的学习与研究

    Unity3D The Blacksmith Demo部分内容学习

    Viking Village维京村落demo中的地面积水效果

    Viking Village维京村落demo中的粒子距离消隐

    The Courtyard demo学习

    ShadowGun Demo学习(非技术向)

    Unity AngryBots愤怒的机器人demo研究

    Unity Generic Tips

    ===========================

    Unity3D 材质球设置参数无效果的解决方法

    Unity3D欧拉和四元数两种旋转的用法

    使用四元数点乘比较插值是否即将完成

    解决摄像机旋转约束问题(RotateDelta,RotateClamp)

    Unity中通过类名字符串取组件的方法

    Unity在编辑器状态下清空控制台信息

    双面MeshCollider脚本

    运行时修改代码不自动刷新

    然后按Ctrl+R手动刷新,或者在Project面板下右键刷新

    在代码中指定,暂停当前帧

    Debug.Break或者Debug.LogError然后在控制台勾选报错暂停。

    快速查看非闭合的MeshCollider

    关闭MeshRenderer组件,即可显示出Collider的网格。

    去除当前脚本警告

    #pragma warning disable 0168

    此示例可以清除变量未使用警告,具体对应警告ID。并且该预编译指令只对本文件有效。

    也可以包裹使用

    #pragma warning disable 0168
            ....
    #pragma warning restore 0168

    一种更为简短的惰性字段初始化写法

    object mObj;
    object Obj { get { return mObj ?? (mObj = new object()); } }

    之前一直这么写:

    object Obj { get { mObj = mObj ?? new object(); return mObj; } }

    无重复随机数创建

    public int EliminateRepeatRandom(int last, int min, int max)
    {
        var current = Random.Range(min, max);
    
        if (last == current)
            return (current + (int)Mathf.Sign(Random.value) * Random.Range(min + 1, max - 1)) % max;
        else
            return current;
    }

    编辑器窗口滑动缩放(相较直接滑动滚轮步幅更小)

    Alt+鼠标右键滑动

    编辑器Scene窗口,便捷操控3D场景的方法

    按住鼠标右键,wasdqe按键移动,分别对应3个轴向

    将选中物品立刻移动到编辑器相机位置

    ctrl+shift+f

    显示当前选中物体的网格

    在gizmos中勾选Selection Wire

    去除恼人的standard shader高光

    我们在用standard shader调制反射质感时,会有一块高光区域非常碍眼。

    可以在Specular Hightlights处将其关闭

    打印Vector3类型,但不保留2位小数

    vector3有一个重载,可以指定format,其中f10就是保留到小数10位

    不过由于是自己实现的,不能在string.format里用

    xx.position.ToString("f10")

    比较浮点数一致

    Mathf.Approximately

    删除Project面板里文件夹的展开状态

    在Library/expandedItems,删除这个文件会重置展开状态

    N卡显存占用查看

    C:Program FilesNVIDIA CorporationNVSMI vidia-smi.exe

    控制台内执行 

    Unity GI缓存目录

    C:Users...AppDataLocalLowUnityCachesGiCache

    unity资源商店缓存目录

    C:Users...AppDataRoamingUnity

    unity资源商店下载的资源包目录

    ...AppDataRoamingUnityAsset Store-(具体unity版本).x

    Unity打包后Log日志文件存放路径

    C:UsersXXXXAppDataLocalLowDefaultCompany

    在Unity中使用快捷键重命名

    按F2

    Unity节点视图中快速定位回中心位置

    按F(Animator、VisualEffectGraph等都支持)

    从中间删除数组元素,或者复制数组中间元素

    复制中间元素:ctrl+d

    从中间删除数组元素:shift+delete

    或者右键数组中某个元素也会出现操作选项

    快速打开所有可展开内容

    Shift+Ctrl+Alt 点击可展开内容

     判断目标是否在相机的平截头体内

    var planes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
    var isContain = GeometryUtility.TestPlanesAABB(planes, bounds);
            
    if(isContain)
    {
        //in frustum..
    }

    即使做了池来缓存,依旧第一次激活GameObject时游戏会卡一下(即播技能特效顿卡问题)

    个人猜测是贴图和mesh没传到GPU,mesh倒还好,主要是贴图。

    目前我建议的方法是用Graphics把所有经常用的对象全部实时DrawMesh去画,且包含贴图材质球。

    可以新建一个16X16的RT画上去,骗过unity。缺点是会多几百个批次。

    方法很极端,但有用。

    自定义ScriptableObject所生成对象的图标

    只需要为原始ScriptableObject脚本对象赋上图标即可

    清空StringBuilder

    StringBuilder sb = new StringBuilder();
    sb.Length = 0;

    有多种方法,但修改Length属性效率最高,参考这位园友的测试:https://www.cnblogs.com/SpiderKevin/p/3891425.html

    Process执行的程序当前目录不正确问题(例.bat文件在d盘,但取到的当前目录是unity文件的目录)

    var cacheDirectory = Directory.GetCurrentDirectory();
    Directory.SetCurrentDirectory("your directory");
    Process.Start("...");
    Directory.SetCurrentDirectory(cacheDirectory);

    执行之前设置一次当前目录即可。

    Unity中快速将当前窗口放大至全屏

    shift+space

    获取当前项目中的所有程序集

    AppDomain.CurrentDomain.GetAssemblies()

    Unity Optimize Tips

    ===========================

    遍历list或者数组时,缓存count,可以减少调用次数(10万次循环测试)

    for (int i = 0, iMax = temp.Count; i < iMax; i++)
        temp[i].GetHashCode();

    在极端情况下,直接用枚举器遍历字典会更快,而且不会产生GC

    UnityEngine.Profiling.Profiler.BeginSample("-----1");
    foreach (var item in temp)
        item.Value.GetHashCode();
    UnityEngine.Profiling.Profiler.EndSample();
    
    UnityEngine.Profiling.Profiler.BeginSample("-----2");
    foreach (var item in temp.Values)
        item.GetHashCode();
    UnityEngine.Profiling.Profiler.EndSample();
    
    UnityEngine.Profiling.Profiler.BeginSample("-----3");
    using (var handle = temp.GetEnumerator())
    {
        while (handle.MoveNext())
            handle.Current.Value.GetHashCode();
    }
    UnityEngine.Profiling.Profiler.EndSample();

    使用Instantiate初始化参数去实例对象

    public class Foo : MonoBehaviour
    {
        void Start()
        {
            var sw = new Stopwatch();
            sw.Start();
    
            var go = new GameObject();
            go.transform.position = Vector3.one;
    
            for (int i = 0; i < 10000; i++)
            {
                var instancedGO = Instantiate(go);
                instancedGO.transform.SetParent(transform);
                instancedGO.transform.localPosition = Vector3.zero;
                instancedGO.transform.localRotation = Quaternion.identity;
                instancedGO.transform.localScale = Vector3.one;
                //102ms
    
                //Instantiate(go, transform.position, transform.rotation, transform);//74ms
            }
            sw.Stop();
            UnityEngine.Debug.Log("sw: " + sw.ElapsedMilliseconds);
        }
    }

    可以实例化出来再赋值,也可以使用参数直接复制

    但是1万次循环测试下两种方法差了20多ms

    动态批次/GPU Instancing的优化方向

    动态批次的合批操作依然是在渲染前处理,将网格较少的动态模型筛选后在渲染前使用CPU合并。

    类似使用MeshBaker手动去合并动态对象,但是限制比MeshBaker多,所以存在关闭系统动态合批,手动处理的优化考量。

    GPU Instancing需要硬件/底层接口支持,其内部类似于享元机制,通过索引访问差异的数据。

    可以减少较多非GPU的额外开销,但缺点是绘制网格是固定的(如使用GPU Instancing代替以往Geom shader生成草坪,草的类型就被限定死几种了)。

    在条件允许下,使用位运算代替乘除法

    左移代替2为倍数的整数乘法:

    var a = 50;
    var b = a << 1;
    //b = 100   50*2
    b = a << 2;
    //b = 200   50*4

    右移代替2为倍数的整数除法:

    var a = 256;
    var b = a >> 1;
    //b = 128 256/2
    b = a >> 4;
    //b = 64 256/4

    经测试没有编译器优化的情况,并且有一定速度提升。

    关于Unity事件函数空调用

    尽量少使用封装Unity事件的通用基类,这样会造成函数空调

    unity会在C++层面检测是否实现某个事件函数(Start,Update,LateUpdate...etc)

    如果没有这个函数则不会加入调用列表中 

    使用localPosition代替position

    调用Position时背后会有许多操作执行,在代码允许的情况下可使用localPositiond代替position

    使用质数作为延迟量可避免最小公倍数出现的情况

    例如设计某BOSS的AI时,特殊弹幕A的发射延迟为7,特殊弹幕B的发射延迟为11。

    那么将低概率出现这两种特殊弹幕被同时发射的情况。

    使用Matrix MultiplyPoint3x4而不是MultiplyPoint

    对于非投影矩阵,使用MultiplyPoint3x4进行变换更快。

    详见官方文档:https://docs.unity3d.com/ScriptReference/Matrix4x4.MultiplyPoint3x4.html

    使用CopyTo将List无GC的转为数组,而不是ToArray

    var tempArray = new int[3];
    void Test()
    {
        list.CopyTo(tempArray);//0 GC
    }

    避免直接调用Camera.main

    Camera.main内部会去调用FindGameObjectWithTag,1万次循环的测试下造成了1ms左右的开销

    而缓存后大约在0.2ms

    mCacheCamera = mCacheCamera ?? Camera.main;
    
    Profiler.BeginSample("Foo");
    for (int i = 0; i < 10000; i++)
        mCacheCamera.GetHashCode();
    Profiler.EndSample();

    高效向量投影

    有时候我们只需要投影轴上的值,而不是坐标。所以可以一行代码搞定:

    var value = Vector3.Dot(point, onNormal);

    并且少了normal向量的检查。比起Vector3的向量投影省了2步操作。

    设置合理的各向异性过滤级别Aniso Level

    贴图Aniso Level(Anisotropic filtering level)设置,可以改善mipmap贴图造成的远处模糊

    但会增加图形硬件的性能开销,对该值要求不高的贴图,我们可以降低它的值进行优化

    实时反射探针优化

    如果需求必须使用实时反射探针,可以将其更新模式设为脚本驱动,在渲染前对反射内容进行模型lod替换或shader、材质上的替换

    可达到一定优化作用。

    阴影优化

    在场景中我们可以使用lod模型来投射阴影,场景中对于一些阴影对画面美观影响不大的模型,我们可以关闭这些模型的阴影投射

    来进行优化。

    NGUI Panel优化(动静分离)

    Ngui中panel内的内容是会静态合批的,所以当内容较多时把静态的物件和会变化的动态物件放在不同的panel里

    以提高性能。而相比uGUI在其内部有一定的优化操作,类似的优化方式在uGUI中并不会立竿见影。

    Unity Editor Tips

    ===========================

    Animator在Editor状态下预览工具

    Unity3D中Console控制台的扩展

    在Editor下获得时间

    EditorApplication.timeSinceStartup

    获得编译器自打开到当前的时间。

    Editor下获得滚轮滑动值

    Event.current.delta

    https://docs.unity3d.com/ScriptReference/Event-delta.html

    注意要先判断当前鼠标按钮id,滚轮的id是1

    获取Project面板中当前选中物体的路径

    AssetDatabase.GetAssetPath(Selection.activeObject);

    取Unity当前目录的路径(是Unity自身安装目录,取当前项目目录请用Directory.GetCurrentDirectory())

    EditorApplication.applicationPath

    EditorApplication.applicationContentsPath

    显示对话框

    EditorUtility.DisplayDialog

    UnityDatabase拷贝文件

    AssetDatabase.CopyAsset(需要拷贝文件,目标目录);

    1.只能拷贝单个文件,不能拷贝目录

    2.目标必须是目录路径,不能是文件路径

    3.不用System.io而用它,因为插件能够跨平台

    Editor状态下设置Scene窗口的相机位置

    SceneView.lastActiveSceneView.pivot = ...;
    SceneView.lastActiveSceneView.Repaint();

    Editor下使用右键菜单

    使用GenericMenu可以直接调出Unity的右键菜单

    Editor下检查prefab是否都打了标签/读写标签Labels

    AssetDatabase.GetLabels

    AssetDatabase.SetLabels

    Editor下拿到当前拖拽对象(跨窗口)

    DragAndDrop.objectReferences

    在DragExited时处理拖拽内容,就可以在松手时触发了

    if (Event.current.type == EventType.DragExited && DragAndDrop.objectReferences.Length > 0)
    {
        var dragItem = DragAndDrop.objectReferences[0];
        ...

    如果是多选拖拽,数组里的就是多选的所有对象,否则就是一个。

    Editor下控制控件的焦点

    通常只要把focus设为空,就可以取消焦点

    GUI.FocusControl("");

    一些特殊的情况,比如弹出性组件,需要知道控件是否被改变过,而非值是否改变过

    可以这么做,通过GetNameOfFocusedControl拿到Focus的name进行比较

    var oldFocus = GUI.GetNameOfFocusedControl();
    var changedIndex = EditorGUI.Popup(rect, index, array);
    var newFocus = GUI.GetNameOfFocusedControl();
    
    if (newFocus != oldFocus)
    {
        //Do something...
    
        GUI.FocusControl("");
    }

    Editor下获得'剪切','拷贝','撤销'等命令

    可以使用commandName获得

    Event.current.commandName == "Copy"

    EditorWindow窗口大小锁死后没有边框的解决方法

    var window = GetWindow(typeof(MyWindow), true);
    
    window.minSize = new Vector2(960, 540);
    window.maxSize = window.minSize;

    用GetWindow创建窗口时,第二个参数填true。创建为独立的工具窗口,即可恢复边框

    Editor下Dirty掉当前修改过的场景对象内容

    一般非场景对象用

    EditorUtility.SetDirty(target);

    而场景对象则用

    EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());

    Editor下BeginScrollView报错InvalidCastException

    和调用顺序有关,也是Layout/Repaint和输入之间的问题。

    改变调用先后顺序有可能解决。

    在Editor下绘制自定义光标

    比如缩放需要绘制缩放的自定义光标,滑动又需要滑动的

    使用下方法即可绘制,Rect自定义Cursor区域的Rect。

    需要每次OnGUI更新都调,而非只Add一次

    EditorGUIUtility.AddCursorRect(Rect, MouseCursor.Pan);

    Editor下的LayerMaskField

    Unity并没有提供LayerMask控件,其Layer控件返回的只是层编号

    下面是一个扩展的LayerMaskField:

    public static LayerMask LayerMaskField(string label, LayerMask layerMask)
    {
        List<string> layers = new List<string>();
        List<int> layerNumbers = new List<int>();
    
        for (int i = 0; i < 32; i++)
        {
            string layerName = LayerMask.LayerToName(i);
            if (layerName != "")
            {
                layers.Add(layerName);
                layerNumbers.Add(i);
            }
        }
        int maskWithoutEmpty = 0;
        for (int i = 0; i < layerNumbers.Count; i++)
        {
            if (((1 << layerNumbers[i]) & layerMask.value) > 0)
                maskWithoutEmpty |= (1 << i);
        }
        maskWithoutEmpty = EditorGUILayout.MaskField(label, maskWithoutEmpty, layers.ToArray());
        int mask = 0;
        for (int i = 0; i < layerNumbers.Count; i++)
        {
            if ((maskWithoutEmpty & (1 << i)) > 0)
                mask |= (1 << layerNumbers[i]);
        }
        layerMask.value = mask;
        return layerMask;
    }
  • 相关阅读:
    safeNet
    网店
    微信公众号自定义菜单与回车
    西游记对教育的启发
    zencart资源
    cmd批处理常用符号详解
    div垂直居中
    git工作量统计
    VS2012变化的快捷键:
    sql 树 递归
  • 原文地址:https://www.cnblogs.com/hont/p/3305769.html
Copyright © 2020-2023  润新知