using UnityEngine; using System.Collections.Generic; using System.Text; #if UNITY_EDITOR && CINEMACHINE_UIELEMENTS using UnityEditor; using UnityEngine.UIElements; #endif namespace Unity.Cinemachine { /// Manages onscreen positions for Cinemachine debugging output static class CinemachineDebug { static List s_AvailableStringBuilders; #if UNITY_EDITOR && CINEMACHINE_UIELEMENTS const string k_DebugUIName = "Cinemachine__internal__DebugUI"; static Dictionary s_CameraViewRectContainers = new(); static GameObject s_UIDocumentHolder; static UIDocument s_UIDocument; public static VisualElement GetOrCreateUIContainer(Camera outputCamera) { var uiDocumentHolder = GetUIDocumentHolder(); if (!uiDocumentHolder.TryGetComponent(out s_UIDocument)) { s_UIDocument = uiDocumentHolder.AddComponent(); const string path = CinemachineCore.kPackageRoot + "/Runtime/Debug/"; s_UIDocument.panelSettings = AssetDatabase.LoadAssetAtPath(path + "CinemachinePanelSettings.asset"); } if (s_CameraViewRectContainers.ContainsKey(outputCamera)) { if (s_CameraViewRectContainers[outputCamera].childCount != 0) return s_CameraViewRectContainers[outputCamera]; s_CameraViewRectContainers[outputCamera].RemoveFromHierarchy(); s_CameraViewRectContainers.Remove(outputCamera); } var viewportContainer = new VisualElement { name = "CinemachineDebugUI_ViewportContainer", style = { position = new StyleEnum(Position.Absolute), flexWrap = new StyleEnum(Wrap.Wrap), } }; SetRoot(viewportContainer); viewportContainer.schedule.Execute(() => PositionWithinCameraView(viewportContainer, outputCamera)).Every(0); s_CameraViewRectContainers.Add(outputCamera, viewportContainer); return viewportContainer; // local functions static GameObject GetUIDocumentHolder() { if (s_UIDocumentHolder == null) { // if the holder exists in the scene without any view containers, then it is better to delete it, // and create a new one, because UITK may have failed to create a rootVisualElement. s_UIDocumentHolder = GameObject.Find(k_DebugUIName); if (s_UIDocumentHolder != null && s_CameraViewRectContainers.Count == 0) { RuntimeUtility.DestroyObject(s_UIDocumentHolder); s_UIDocumentHolder = null; } if (s_UIDocumentHolder == null) { s_UIDocumentHolder = new GameObject(k_DebugUIName) { hideFlags = HideFlags.HideAndDontSave }; } } return s_UIDocumentHolder; } static void SetRoot(VisualElement viewportContainer) { // Need to wait until rootVisualElement is created. if (s_UIDocument.rootVisualElement == null) { EditorApplication.delayCall += () => SetRoot(viewportContainer); return; } s_UIDocument.rootVisualElement.Add(viewportContainer); } static void PositionWithinCameraView(VisualElement viewportContainer, Camera camera) { if (viewportContainer.childCount == 0 || camera == null) { viewportContainer.RemoveFromHierarchy(); s_CameraViewRectContainers.Remove(camera); } if (s_UIDocument == null || s_UIDocument.rootVisualElement == null || camera == null) return; var panel = s_UIDocument.rootVisualElement.panel; var screenPanelSpace = RuntimePanelUtils.ScreenToPanel(panel, new Vector2(Screen.width, Screen.height)); var viewport = camera.pixelRect; var viewPortMaxPanelSpace = RuntimePanelUtils.ScreenToPanel(panel, viewport.max); var viewPortMinPanelSpace = RuntimePanelUtils.ScreenToPanel(panel, viewport.min); viewportContainer.style.top = new Length(screenPanelSpace.y - viewPortMaxPanelSpace.y, LengthUnit.Pixel); viewportContainer.style.bottom = new Length(viewPortMinPanelSpace.y, LengthUnit.Pixel); viewportContainer.style.left = new Length(viewPortMinPanelSpace.x, LengthUnit.Pixel); viewportContainer.style.right = new Length(screenPanelSpace.x - viewPortMaxPanelSpace.x, LengthUnit.Pixel); } } #endif /// /// Delegate for OnGUI debugging. /// This will be called by the CinemachineBrain in its OnGUI (editor only) /// public delegate void OnGUIDelegate(CinemachineBrain brain); /// /// Delegate for OnGUI debugging. /// This will be called by the CinemachineBrain in its OnGUI (editor only) /// public static OnGUIDelegate OnGUIHandlers; /// Get a pre-allocated StringBuilder from the pool /// The pre-allocated StringBuilder from the pool. /// Client must call ReturnToPool when done public static StringBuilder SBFromPool() { if (s_AvailableStringBuilders == null || s_AvailableStringBuilders.Count == 0) return new StringBuilder(); var sb = s_AvailableStringBuilders[^1]; s_AvailableStringBuilders.RemoveAt(s_AvailableStringBuilders.Count - 1); sb.Length = 0; return sb; } /// Return a StringBuilder to the pre-allocated pool /// The string builder object to return to the pool public static void ReturnToPool(StringBuilder sb) { s_AvailableStringBuilders ??= new List(); s_AvailableStringBuilders.Add(sb); } } }