using System.Collections.Generic; using Unity.XR.CoreUtils; using UnityEngine.UI; namespace UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard { /// /// This script is used to optimize the keyboard rendering performance by updating the canvas hierarchy /// into separate parent transforms based on UI Grouping. This will greatly reduce the number of draw calls. /// Optimization is only done at runtime to prevent breaking the prefab. /// public class KeyboardOptimizer : MonoBehaviour { [SerializeField] bool m_OptimizeOnStart = true; /// /// If enabled, the optimization will be called on . /// public bool optimizeOnStart { get => m_OptimizeOnStart; set => m_OptimizeOnStart = value; } [SerializeField] Transform m_BatchGroupParentTransform; /// /// The parent transform for batch groups. /// public Transform batchGroupParentTransform { get => m_BatchGroupParentTransform; set => m_BatchGroupParentTransform = value; } [SerializeField] Transform m_ButtonParentTransform; /// /// The parent transform for buttons. /// public Transform buttonParentTransform { get => m_ButtonParentTransform; set => m_ButtonParentTransform = value; } [SerializeField] Transform m_ImageParentTransform; /// /// The parent transform for images. /// public Transform imageParentTransform { get => m_ImageParentTransform; set => m_ImageParentTransform = value; } [SerializeField] Transform m_TextParentTransform; /// /// The parent transform for text elements. /// public Transform textParentTransform { get => m_TextParentTransform; set => m_TextParentTransform = value; } [SerializeField] Transform m_IconParentTransform; /// /// The parent transform for icons. /// public Transform iconParentTransform { get => m_IconParentTransform; set => m_IconParentTransform = value; } [SerializeField] Transform m_HighlightParentTransform; /// /// The parent transform for highlights. /// public Transform highlightParentTransform { get => m_HighlightParentTransform; set => m_HighlightParentTransform = value; } bool m_IsCurrentlyOptimized; /// /// Is the keyboard currently optimized? /// public bool isCurrentlyOptimized => m_IsCurrentlyOptimized; /// /// Horizontal layout groups need to be disabled when optimizing the keyboard /// otherwise the input field will not position correctly. /// HorizontalLayoutGroup[] m_LayoutGroups; /// /// List of key data. This is used to store information that allows us /// to revert the keyboard back to its original state (aka unoptimize). /// readonly List m_KeyData = new List(); /// /// See . /// protected void Start() { CheckReferences(); Canvas.ForceUpdateCanvases(); if (m_OptimizeOnStart) Optimize(); } /// /// Check all the references needed for optimization. /// void CheckReferences() { if (!TryGetOrCreateTransformReferences()) { Debug.LogError("Failed to get or create transform references. Optimization will not be possible.", this); return; } if (m_KeyData.Count == 0) GetKeys(); if (m_LayoutGroups == null || m_LayoutGroups.Length == 0) GetLayoutGroups(); } bool TryGetOrCreateTransformReferences() { if (m_BatchGroupParentTransform == null) { var canvasComponent = GetComponentInChildren(true); if (canvasComponent == null) { Debug.LogError("No Canvas component found in hierarchy. Optimization will not be possible.", this); return false; } m_BatchGroupParentTransform = CreateTransformAndSetParent("BatchingGroup", canvasComponent.transform); } if (m_ButtonParentTransform == null) m_ButtonParentTransform = CreateTransformAndSetParent("Buttons", m_BatchGroupParentTransform); if (m_ImageParentTransform == null) m_ImageParentTransform = CreateTransformAndSetParent("Images", m_BatchGroupParentTransform); if (m_TextParentTransform == null) m_TextParentTransform = CreateTransformAndSetParent("Text", m_BatchGroupParentTransform); if (m_IconParentTransform == null) m_IconParentTransform = CreateTransformAndSetParent("Icons", m_BatchGroupParentTransform); if (m_HighlightParentTransform == null) m_HighlightParentTransform = CreateTransformAndSetParent("Highlights", m_BatchGroupParentTransform); return true; } void GetKeys() { XRKeyboardKey[] keys = GetComponentsInChildren(); foreach (var keyboardKey in keys) { m_KeyData.Add(new KeyData { key = keyboardKey, batchFollow = keyboardKey.GetComponent(), parent = keyboardKey.transform.parent, childPosition = keyboardKey.transform.GetSiblingIndex(), }); } } void GetLayoutGroups() { m_LayoutGroups = GetComponentsInChildren(); } static Transform CreateTransformAndSetParent(string name, Transform parent) { var t = new GameObject(name).transform; t.SetParent(parent); t.SetLocalPose(Pose.identity); t.localScale = Vector3.one; return t; } /// /// Optimize the keyboard. This will set all the different components of each keyboard key into separate parent transforms for batching. /// public void Optimize() { m_IsCurrentlyOptimized = true; foreach (var layoutGroup in m_LayoutGroups) { layoutGroup.enabled = false; } foreach (var keyData in m_KeyData) { var key = keyData.key; if (key == null) continue; key.transform.SetParent(m_ButtonParentTransform); if (key.targetGraphic != null) key.targetGraphic.transform.SetParent(m_ImageParentTransform); if (key.textComponent != null) key.textComponent.transform.SetParent(m_TextParentTransform); if (key.iconComponent != null) key.iconComponent.transform.SetParent(m_IconParentTransform); if (key.highlightComponent != null) key.highlightComponent.transform.SetParent(m_HighlightParentTransform); if (keyData.batchFollow != null) keyData.batchFollow.enabled = true; } } /// /// Unoptimize the keyboard. This will set the keyboard back to its original state. /// public void Unoptimize() { m_IsCurrentlyOptimized = false; foreach (var layoutGroup in GetComponentsInChildren()) { layoutGroup.enabled = true; } foreach (var keyData in m_KeyData) { XRKeyboardKey key = keyData.key; if (key == null) continue; // NOTE: Order of objects setting their parent is important for sorting order. key.transform.SetParent(keyData.parent); key.transform.SetSiblingIndex(keyData.childPosition); if (key.targetGraphic != null) key.targetGraphic.transform.SetParent(key.transform); if (key.textComponent != null) key.textComponent.transform.SetParent(key.targetGraphic.transform); if (key.iconComponent != null) key.iconComponent.transform.SetParent(key.targetGraphic.transform); if (key.highlightComponent != null) key.highlightComponent.transform.SetParent(key.targetGraphic.transform); if (keyData.batchFollow != null) keyData.batchFollow.enabled = false; } } struct KeyData { public XRKeyboardKey key; public KeyboardBatchFollow batchFollow; public Transform parent; public int childPosition; } } }