Unity3D心得分享

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

 

2018/12/16 最近更新: 播放特效顿卡问题,Process执行的程序当前目录不正确问题

 

 

Unity DEMO学习

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

 

Unity3D Adam Demo的学习与研究

 

Unity3D The Blacksmith Demo部分内容学习

 

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

 

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

 

The Courtyard demo学习

 

ShadowGun Demo学习(非技术向)

 

Unity AngryBots愤怒的机器人demo研究

 

注:关于《Book of the Dead》 demo,由于展示内容偏向unity新特性或者必须依赖HDRP的特性,故没写。

 

Unity Generic Tips

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

 

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

 

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

 

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

 

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

 

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

 

使anim,unity,prefab文件不显示乱码

 

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; } }

 

 

 

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

Alt+鼠标右键滑动

 

 

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

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

 

 

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

ctrl+shift+f

 

 

快速修改脚本执行顺序优先级

打开代码的meta文件,修改executionOrder选项数值

 

 

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

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

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

xx.position.ToString("f10")

 

 

获得GameObject挂载任意Component中的接口

unity5.x可以直接GetComponent得到接口(unity5.3.4或更高版本有效)

GetComponent<IFoo>();

 

 

 

比较浮点数一致

Mathf.Approximately

 

 

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

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

 

 

Unity5 GI缓存目录

C:\Users\...\AppData\LocalLow\Unity\Caches\GiCache

 

 

unity资源商店缓存目录

C:\Users\...\AppData\Roaming\Unity

 

 

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

...\AppData\Roaming\Unity\Asset Store-(具体unity版本).x\

 

 

在Unity中使用快捷键重命名

按F2

 

 

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

复制中间元素: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();

 

 

关于Unity事件函数空调用

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

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

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

 

 

使用Matrix MultiplyPoint3x4而不是MultiplyPoint

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

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

 

 

NGUI Panel优化

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

以提高性能。

 

 

 

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;
}