Unity3D心得分享


本篇文章的內容以各種tips為主,不間斷更新

 

2020/7/29 最近更新:不打開Prefab快速修改Prefab屬性字段

 

 

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歐拉和四元數兩種旋轉的用法

 

使用四元數點乘比較插值是否即將完成

 

解決攝像機旋轉約束問題(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處將其關閉

 

 

不打開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

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

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM