Unity UI 批量修復工具

2021-01-12 Unity3D遊戲開發精華教程乾貨
介紹

隨著項目的進行,許多最開始制定的 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

相關焦點

  • 騰訊正式開源面向 Unity 項目的 Bug 修復神器 InjectFix
    ps:也有一種思路是通過一個C#轉XX腳本工具來實現C#編碼,解析執行,但如果你是一個已有項目想這麼轉一下,大概率是失敗的,除非你一開始就在用這方式在開發,碰到坑就避開,因為這類方案往往不是完整支持全部語法,支持的語法也不一定能完全一致。 基於性能,實現便利性等的考慮,一般遊戲有些地方要以原生的方式跑,這些原生跑的代碼出了bug這種方式是無能為力的。
  • 10款實用的批量圖像處理工具
    首頁 > 評論 > 關鍵詞 > 工具最新資訊 > 正文 10款實用的批量圖像處理工具
  • Unity3d-UI-Shader-太極圖案
    準備階段基於ui的shader,先找到對應unity3d版本的內置shader包,下載下來之後找到UI-Default.shader,拷貝該文件修改名字為要編輯效果的名字。調整shader 名稱為 OEngine/UI/TaiChi,創建材質球TaiChi.mat,設置shader為OEngine/UI/TaiChi。
  • 使用unity製作RPG遊戲3——2D精靈
    context=%7B%22nid%22%3A%22news_9564882242542237691%22%2C%22sourceFrom%22%3A%22bjh%22%2C%22url_data%22%3A%22bjhauthor%22%7D下面需要利用Tiled2Unity把01地圖導入unity下載Tiled2Unity,在根目錄下打開可執行文件進入unity。
  • 微商相冊批量採集下載圖片的工具,快速批量保存微商相冊的原圖
    微商相冊是一款很好用的雲共享相冊,大部分賣家會把圖片存在相冊中,代理如果要下載這些相冊圖片的話,只要把連結地址給他們,用工具,就可以批量把相冊裡面的圖片都下載保存到本地電腦上,或者手機上。來看看他們都是怎麼操作的。
  • Ps-修復畫筆工具以及相關工具如何使用
    這章我們介紹一下修復畫筆工具以及展開欄中的其他工具的功能。針對海水來說,剛才的小黑點就相當於一個汙點,當用汙點修復畫筆工具擦拭後,就會修復海平面,小黑點就被擦拭了。(二)修復畫筆工具:修復畫筆工具剛才我們按住快捷鍵J,使用了「汙點修復畫筆工具
  • 快手、抖音爬蟲必備工具,批量爬取無水印短視頻
    快手視頻用什麼方法採集下載,可以保存無水印的視頻到電腦裡面,哪個工具可以一鍵批量保存作者頁裡面全部的短視頻,接著往下看,到底是什麼樣的方法,能夠快速保存快手、抖音等自媒體平臺的視頻。工具、材料:電腦,手機快手視頻連結快手作者頁連結固喬視頻助手操作步驟:打開工具"固喬視頻助手"點【自媒體視頻下載】進入一個新的頁面
  • 2021新年匯總:Unity項目原型快速開發資源,看這一篇就夠
    Example Game : https://assetstore.unity.com/packages/templates/flappy-bird-style-example-game-80330 Tower Defense Template : https://assetstore.unity.com/packages/essentials
  • Web經典B/S快速開發框架,強大後臺+簡潔UI一體化開發工具
    *框架涉及一些第三方插、組件:後端ASP.NET MVC5EntityFramework ORMDapper ORMNPOI Excel 操作log4net 系統日誌Newtonsoft.Json Json 處理signalR Websocketunity 依賴注入容器
  • Photoshop修復工具介紹及使用圖例
    一、塗抹、銳化、模糊工具調節大小:使用英文輸入狀態: "["、"]"模糊工具:主要是降低圖像中相鄰像素的對比度,將較硬的邊緣柔化,使圖像變得柔和。(變模糊)。銳化工具:可以增加相鄰像素的對比度,將模糊的邊緣銳化,使圖像聚焦變清晰。塗抹工具:可模擬在溼顏料中拖移手指的動作。該工具可拾取描邊開始位置的顏色,並沿拖移的方向展開這種顏色。二、加深、減淡、海綿減淡工具:可以使塗抹過的區域顏色減淡,變亮。
  • Semantic UI 0.13.1 發布,前端界面開發框架
    Semantic UI 0.13.1 發布,該版本主要是 bug 修復,包括:Modal - Fixes modal positioning appearing
  • mac版Unity Pro遊戲開發工具如何創建和使用腳本
    Unity Pro 2018 for mac是遊戲開發必備的軟體之一,unity mac版主要用於創建2D和3D跨平臺遊戲,比如三維視頻遊戲、實時三維動畫、建築可視化等類型,儘管Unity的內置組件可以實現多種用途,但是您很快就會發現,您需要超越它們提供的功能來實現自己的遊戲功能。
  • 電影是如何被修復的
    其中DRS共有10個修復工具,但我們常用的是其中的8個工具,搭配的cortex系統用來對修復好的影片畫面進行聲畫合成、調色、高清或4K上轉,它最具特色的功能是能夠進行壞像素檢測,自動偵測到畫面中出現問題的點,比人眼更加精準。
  • 6個免費獨立站和外貿工具(含谷歌商機分析工具、批量關鍵詞查詢)
    1、谷歌全球商機通:免費國際市場分析工具 當你決定將產品銷往國外,你知道哪裡是適合你產品的目標市場嗎?「谷歌全球商機通」是一款免費的全球市場洞察工具,能快速查詢各類商品的潛力市場、獲客成本、商業概況等實用數據,幫外貿人輕鬆發掘全球商機。
  • Element-ui簡單使用方法
    Element-ui,是一套為開發者、設計師和產品經理準備的基於Vue 2.0的由餓了麼公司出品的桌面端組件庫。下載安裝npm install vue #安裝Vuenpm i element-ui -S #安裝Element-ui圖標,el內置了許多圖標,使用icon="iconname"
  • KDE Plasma 5.1.2 發布,bug 修復版本
    KDE 發布了 Plasma 5 的 bug 修復版本,Plasma 5.1.2。此版本添加了這個月的新翻譯,還有 KDE 貢獻者發布的 bug 修復。這些 bug 修復非常經典,很小,但是很重要!Fix vertical aligment.
  • MP3音樂批量更名工具Tag&Rename v3.5
    MP3音樂批量更名工具Tag&Rename v3.5 2008年10月22日 09:58作者:陳濤編輯:陳濤文章出處:泡泡網原創
  • Unity約你來剁手啦!
    限時大禮包:免費贈送價值300美金的2個資源工具包,內含多款實用Asset Store資源商店上實用資源插件。免費贈送一款價值99美金的Asset Store熱銷音效插件ADX2。免費贈送價值1200元人民幣的1張Unite 2018 Beijing大會門票。
  • Unity 2018.3 Beta版發布
    直播課程:Facial AR Remote面部捕捉解決方案課程(第一期)直播地址:https://connect.unity.com/events/unitychina-facialarUnity官方教師培訓報名火熱進行中Unity將在10月22-26日,舉辦為期5天的專業的Unity官方教師培訓課程,誠邀廣大教師與
  • unity什麼意思
    unity什麼意思uni前綴,只包含一個的,更多例子還有:uniform, unique, unilateral, etc. 發音類似於有你,整個世界中有你就夠了,不需要別人,也就是只包含一個的。unity,聯合、統一、團結、和睦。學單詞,只記住意思可不行,會用才行,小夥伴們可以在評論區造句,我們一起學習哦!我先來:造句:Unity is strength. (團結就是力量)