隨著項目的進行,許多最開始制定的 UI 結構沒有嚴格執行,導致一部分 UI 變得不規範,如何批量查找並處理這些不規範的 UI,最簡單的方式就是使用腳本批量處理。
環境需求其實最重要的工作是先理清需求,即 UI 的規範。
有了需求,那麼編碼處理就很容易了。
原理所有的 UI 都是 Prefab,只要對 Prefab 上的屬性進行檢查,所有不合規範的屬性修改正確即可。
將 Prefab 實例化到場景內,對 Prefab 進行修改,再將 Prefab 保存即可。
注意事項Windows 與 macOS 支持只有一處與路徑相關的地方需要進行替換處理。在 Windows 下,從文件系統獲得的路徑需要轉換為 Unix 路徑,即將 \ 替換為 / 即可,Unity 內部 API 使用 / 風格路徑。
Prefab 惰式保存由於 Prefab 實例化後即使不做任何修改保存,也有可能會產生變化,例如 RectTransform 受當前遊戲視圖大小影響之類的。
所以儘可能減少改動的地方,同時增加主動改動的標記,只有發生主動改動時才會去保存 Prefab。
Transform UI 根結點如果希望 UI 的根結點統一為 Transform,而不是 RectTransform,那麼需要新建空結點,將原有 UI 子結點移動到新建空結點下。
由於原有根結點上的組件上依然有很多其他子結點的引用,不可以直接新建組件的方式處理,引用會複製不全,因此這裡使用反射調用 Unity 內置功能:Copy & Paste Component。這是一個取巧的辦法,非常有效。
Canvas 排序與移位由於 UI 中可能存在 UI 與特效混合顯示的問題,會新建若干個 Canvas 與特效進行層級調整。為了在編輯器下方便調整,需要將 Canvas 拉開間隔,調整其 Z 值使其互相遠離。
Canvas 屬性相機處理基本上就是上面列出的需要調整的屬性:正交、大小、位置、近裁面、遠裁面、深度等等。
需要儘可能的統一,去除如反走樣、HDR、Occlusion Culling 等功能。
Transform 子結點屬性位置坐標向下取整,Z 強制修改為 0
旋轉坐標取整
局部縮放取整並強制設為 1 或 -1
大小取整
層級必須為 UI 或 UIEffects
代碼將以下代碼保存為 cs 文件放在 Editor 目錄下即可使用,建議根據實際的需要進行裁剪優化。
using System.Collections.Generic;using System.IO;using UnityEditor;using UnityEngine;using UnityEngine.UI;
public static class UIStructureEditor{ public static string UIDirRoot = "Assets/UI";
public static Vector3 CameraLocalPosition = new Vector3(960f, 540f, 0f); public static float CameraSize = 540f; public static float CameraNearClipPlane = 0f; public static float CameraFarClipPlane = 100f; public static Rect CameraRect = new Rect(0f, 0f, 1f, 1f); public static float CameraDepth = 5f;
public static string CanvasSortingLayer = "Default"; public static int UILayer = LayerMask.NameToLayer("UI"); public static int UIEffectLayer = LayerMask.NameToLayer("UIEffect");
[MenuItem("Tools/UI/Structure/Process All")] public static void ProcessAll() { var sw = new System.Diagnostics.Stopwatch(); sw.Start();
var files = Directory.GetFiles(UIDirRoot, "*.prefab", SearchOption.AllDirectories); foreach (var file in files) { string path = file.Replace("\\", "/").Replace(Application.dataPath, "Assets"); ProcessUI(AssetDatabase.LoadAssetAtPath<GameObject>(path)); }
sw.Stop(); Debug.Log(string.Format("Total used time {0}ms", sw.ElapsedMilliseconds)); }
[MenuItem("Tools/UI/Structure/Process Selected")] public static void ProcessSelected() { ProcessUI(Selection.activeGameObject); }
public static void ProcessUI(GameObject prefab) { var go = PrefabUtility.InstantiatePrefab(prefab) as GameObject; go = ProcessPrefab(go); bool hasChanged = false;
hasChanged |= ProcessRoot(go); hasChanged |= ProcessCanvases(go); hasChanged |= ProcessLayers(go);
if (hasChanged) { PrefabUtility.ReplacePrefab(go, prefab, ReplacePrefabOptions.Default); }
Object.DestroyImmediate(go); }
public static GameObject ProcessPrefab(GameObject go) { if (go.transform is RectTransform && go.GetComponent<LayoutElement>() == null && go.GetComponent<Graphic>() == null) { var newGo = new GameObject(go.name); newGo.SetActive(false);
while (go.transform.childCount > 0) { var child = go.transform.GetChild(0); child.SetParent(newGo.transform, false); }
var comps = go.GetComponents<Component>(); foreach (var comp in comps) { if (comp == null || comp is Transform || comp is CanvasRenderer) { continue; }
bool success = true; success &= UnityEditorInternal.ComponentUtility.CopyComponent(comp); success &= UnityEditorInternal.ComponentUtility.PasteComponentAsNew(newGo); if (!success) { Debug.LogError(string.Format("Failed to copy component {0} in {1}", comp.GetType().FullName, go.name)); } }
Object.DestroyImmediate(go); return newGo; }
return go; }
private static bool ProcessRoot(GameObject go) { return EnsureTransform(go.transform, Vector3.zero, Quaternion.identity, Vector3.one); }
private static bool ProcessCanvases(GameObject go) { bool hasChanged = false; var canvases = GetSubCanvases(go); for (int i = 0; i < canvases.Count; i++) { float planeDistance = Mathf.Ceil((canvases.Count - i) * ((CameraFarClipPlane - CameraNearClipPlane) / (canvases.Count + 1))); hasChanged |= ProcessCanvas(canvases[i], planeDistance, i); }
return hasChanged; }
private static bool ProcessCamera(GameObject go) { bool hasChanged = false; hasChanged |= EnsureTransform(go.transform, CameraLocalPosition, Quaternion.identity, Vector3.one);
var camera = go.GetComponent<Camera>(); if (camera.clearFlags != CameraClearFlags.Depth) { camera.clearFlags = CameraClearFlags.Depth; hasChanged = true; }
int layer = 1 << LayerMask.NameToLayer("UI") | 1 << LayerMask.NameToLayer("UIEffect"); if (camera.cullingMask != layer) { camera.cullingMask = layer; hasChanged = true; }
if (!camera.orthographic) { camera.orthographic = true; hasChanged = true; }
if (camera.orthographicSize != CameraSize) { camera.orthographicSize = CameraSize; hasChanged = true; }
if (camera.nearClipPlane != CameraNearClipPlane) { camera.nearClipPlane = CameraNearClipPlane; hasChanged = true; }
if (camera.farClipPlane != CameraFarClipPlane) { camera.farClipPlane = CameraFarClipPlane; hasChanged = true; }
if (camera.rect != CameraRect) { camera.rect = CameraRect; hasChanged = true; }
if (camera.depth != CameraDepth) { camera.depth = CameraDepth; hasChanged = true; }
if (camera.renderingPath != RenderingPath.UsePlayerSettings) { camera.renderingPath = RenderingPath.UsePlayerSettings; hasChanged = true; }
if (camera.targetTexture != null) { camera.targetTexture = null; hasChanged = true; }
if (camera.useOcclusionCulling) { camera.useOcclusionCulling = false; hasChanged = true; }
if (camera.allowHDR) { camera.allowHDR = false; hasChanged = true; }
if (camera.allowMSAA) { camera.allowMSAA = false; hasChanged = true; }
if (camera.allowDynamicResolution) { camera.allowDynamicResolution = false; hasChanged = true; }
var comps = go.GetComponents<Component>(); foreach (var comp in comps) { if (comp is Transform || comp is Camera) { continue; }
Object.DestroyImmediate(comp); hasChanged = true; }
return hasChanged; }
private static bool ProcessCanvas(GameObject go, float planeDistance, int index) { bool hasChanged = false;
hasChanged |= ProcessCanvasChildren(go);
var canvas = go.GetComponent<Canvas>(); if (canvas.renderMode != RenderMode.ScreenSpaceCamera) { canvas.renderMode = RenderMode.ScreenSpaceCamera; hasChanged = true; }
if (canvas.pixelPerfect) { canvas.pixelPerfect = false; hasChanged = true; }
if (canvas.worldCamera == null) { Camera camera;
var t = go.transform.parent.Find("Camera"); if (t != null) { camera = t.GetComponent<Camera>(); } else { var cameraGo = new GameObject("Camera"); cameraGo.transform.SetParent(go.transform.parent); cameraGo.transform.SetSiblingIndex(Mathf.Max(canvas.transform.GetSiblingIndex() - 1, 0)); camera = cameraGo.AddComponent<Camera>(); }
canvas.worldCamera = camera; hasChanged = true; }
hasChanged |= ProcessCamera(canvas.worldCamera.gameObject);
if (canvas.planeDistance != planeDistance) { canvas.planeDistance = planeDistance; hasChanged = true; }
if (canvas.sortingLayerName != CanvasSortingLayer) { canvas.sortingLayerName = CanvasSortingLayer; hasChanged = true; }
if (canvas.sortingOrder != index) { canvas.sortingOrder = index; hasChanged = true; }
if (canvas.additionalShaderChannels != AdditionalCanvasShaderChannels.None) { canvas.additionalShaderChannels = AdditionalCanvasShaderChannels.None; hasChanged = true; }
return hasChanged; }
private static bool ProcessCanvasChildren(GameObject go) { bool hasChanged = false;
var stack = new Stack<Transform>(); stack.Push(go.transform);
while (stack.Count > 0) { var t = stack.Pop(); for (int i = t.childCount - 1; i >= 0; i--) { stack.Push(t.GetChild(i)); }
if (t.GetComponent<Canvas>() != null) { continue; }
hasChanged |= ProcessTransform(t); }
return hasChanged; }
private static bool ProcessTransform(Transform t) { bool hasChanged = false;
var processedLocalEulerAngles = Round(t.localEulerAngles); if (t.localEulerAngles != processedLocalEulerAngles) { t.localEulerAngles = processedLocalEulerAngles; hasChanged = true; }
var processedLocalScale = Round(t.localScale); if (t.localScale != processedLocalScale) { t.localScale = processedLocalScale; hasChanged = true; }
var rectTransform = t as RectTransform; if (rectTransform != null) { var anchoredPosition3D = rectTransform.anchoredPosition3D; var processedAnchoredPosition3D = new Vector3 { x = Mathf.Round(anchoredPosition3D.x), y = Mathf.Round(anchoredPosition3D.y), z = 0f }; if (anchoredPosition3D != processedAnchoredPosition3D) { rectTransform.anchoredPosition3D = processedAnchoredPosition3D; hasChanged = true; }
var sizeDelta = rectTransform.sizeDelta; var processedSizeDelta = new Vector2 { x = Mathf.Round(sizeDelta.x), y = Mathf.Round(sizeDelta.y) }; if (sizeDelta != processedSizeDelta) { rectTransform.sizeDelta = processedSizeDelta; hasChanged = true; }
Vector3 localScale = rectTransform.localScale; if ((localScale.x != -1f && localScale.x != 1f) || (localScale.y != -1f && localScale.y != 1f) || localScale.z != 1f) { rectTransform.localScale = Vector3.one; hasChanged = true; } } else { var processedLocalPosition = Round(t.localPosition); if (t.localPosition != processedLocalPosition) { t.localPosition = processedLocalPosition; hasChanged = true; } }
return hasChanged; }
private static bool ProcessLayers(GameObject go) { bool hasChanged = false;
var stack = new Stack<Transform>(); stack.Push(go.transform);
while (stack.Count > 0) { var t = stack.Pop(); for (int i = t.childCount - 1; i >= 0; i--) { stack.Push(t.GetChild(i)); }
if (t.gameObject.layer != UILayer && t.gameObject.layer != UIEffectLayer) { t.gameObject.layer = UILayer; hasChanged = true; } }
return hasChanged; }
private static List<GameObject> GetSubCanvases(GameObject go) { var result = new List<GameObject>();
for (int i = 0; i < go.transform.childCount; i++) { var child = go.transform.GetChild(i); if (child.GetComponent<Canvas>() != null) { result.Add(child.gameObject); } }
return result; }
private static bool EnsureTransform(Transform t, Vector3 localPosition, Quaternion localRotation, Vector3 localScale) { bool hasChanged = false;
if (t.localPosition != localPosition) { t.localPosition = localPosition; hasChanged = true; }
if (t.localRotation != localRotation) { t.localRotation = localRotation; hasChanged = true; }
if (t.localScale != localScale) { t.localScale = localScale; hasChanged = true; }
return hasChanged; }
private static Vector3 Round(Vector3 origin) { origin.x = Mathf.Round(origin.x); origin.y = Mathf.Round(origin.y); origin.z = Mathf.Round(origin.z); return origin; }}
聲明:發布此文是出於傳遞更多知識以供交流學習之目的。若有來源標註錯誤或侵犯了您的合法權益,請作者持權屬證明與我們聯繫,我們將及時更正、刪除,謝謝。
作者:狂飆
來源:https://networm.me/2019/12/01/unity-ui-batch-repair-tool/
More:【微信公眾號】 u3dnotes