本篇文章的內容以各種tips為主,不間斷更新
2020/7/29 最近更新:不打開Prefab快速修改Prefab屬性字段
Unity DEMO學習
===========================
Unity3D The Blacksmith Demo部分內容學習
Viking Village維京村落demo中的地面積水效果
Viking Village維京村落demo中的粒子距離消隱
Unity Generic Tips
===========================
解決攝像機旋轉約束問題(RotateDelta,RotateClamp)
運行時修改代碼不自動刷新
然后按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處將其關閉
不打開Prefab快速修改Prefab屬性字段
在修改的字段上右鍵,選擇Apply to Prefab '..'即可。
Color字段和Gradient字段添加HDR色彩支持
使用GradientUsage屬性和ColorUsage屬性,例如:
[ColorUsage(true, true)] public Color colorField;
GradientUsage需要2018.3以上版本
打印Vector3類型,但不保留2位小數
vector3有一個重載,可以指定format,其中f10就是保留到小數10位
不過由於是自己實現的,不能在string.format里用
xx.position.ToString("f10")
比較浮點數一致
Mathf.Approximately
刪除Project面板里文件夾的展開狀態
在Library/expandedItems,刪除這個文件會重置展開狀態
N卡顯存占用查看
C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe
控制台內執行
Unity GI緩存目錄
C:\Users\...\AppData\LocalLow\Unity\Caches\GiCache
unity資源商店緩存目錄
C:\Users\...\AppData\Roaming\Unity
unity資源商店下載的資源包目錄
...\AppData\Roaming\Unity\Asset Store-(具體unity版本).x\
Unity打包后Log日志文件存放路徑
C:\Users\XXXX\AppData\LocalLow\DefaultCompany
在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
===========================
在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; }