diff --git a/Assets/Scripts/Manager/MachineUIManager.cs b/Assets/Scripts/Manager/MachineUIManager.cs index 1b47ae06..e9c26fdb 100644 --- a/Assets/Scripts/Manager/MachineUIManager.cs +++ b/Assets/Scripts/Manager/MachineUIManager.cs @@ -1,3 +1,4 @@ +using UnityEngine.Profiling; using System; using System.Collections; using System.Collections.Generic; @@ -11,233 +12,114 @@ namespace CHN public class MachineUIManager : MonoBehaviour, ISingle { private OrbitalController cam; - + private Building building; public UI_MachineKPI prefab_MachineKPI; + private Machine[] machines; private List matchingMachines = new(); private Dictionary machineKPIs = new(); private Dictionary kpiToMachines = new(); - private List kpis = new List(); + //private List kpis = new List(); public Action onClickKPIToMachine; public Action onClickKPIToData; - public float defaultNameHeight; - public Vector3 originScale; + public float defaultNameHeight = 2f; + [Range(0.1f, 2f)] public float updateInterval = 0.05f; + //public Vector3 originScale; - [Range(0.1f, 0.8f)] - public float minScale; - [Range(0.5f, 1.5f)] - public float maxScale; - [Range(0.1f, 2f)] - public float scaleClamp; + [Header("Scale Settings")] + [Range(0.1f, 0.8f)] public float minScale; + [Range(0.5f, 1.5f)] public float maxScale; + [Range(0.1f, 2f)] public float scaleClamp; + private List activeUIElements = new(); + private Vector3[] worldCorners = new Vector3[4]; // °³¼±»çÇ×: GC ¹æÁö¿ë ¹Ì¸® ÇÒ´ç - public void Clear() + private void Start() { - uiElements.Clear(); - matchingMachines.Clear(); - kpiToMachines.Clear(); + cam = FindSingle(); // °³¼±»çÇ×: Start¿¡¼­ ÇÑ ¹ø¸¸ È£ÃâÇÏ¿© ij½Ì + building = FindSingle(); + // ÁÖ±âÀûÀ¸·Î ¹«°Å¿î ¿¬»êÀ» ½ÇÇà (¸Å ÇÁ·¹ÀÓ X) + StartCoroutine(UpdateLoop()); + } + + private System.Collections.IEnumerator UpdateLoop() + { + var wait = new WaitForSeconds(updateInterval); // °³¼±»çÇ×: ¸Å ÇÁ·¹ÀÓ ½ÇÇàÇÏ´ø ·ÎÁ÷À» updateInterval¸¶´Ù ½ÇÇàÇϵµ·Ï º¯°æ + while (true) + { + Profiler.BeginSample("MachineKPI_TotalUpdate"); + if (matchingMachines.Count > 0) + { + UpdateMachineVisibilityAndScale(); + GroupOverlappingUIElements(); + } + Profiler.EndSample(); + yield return wait; // °³¼±»çÇ×: ·çÇÁ ¹Û¿¡¼­ µü Çѹø »ý¼ºÇÏ¿© ij½ÌÇØµÐ °ÍÀ» Àç»ç¿ë -> ¸Þ¸ð¸® ÇÒ´ç 0 + } } #region Machine public void SetMachineKPI(List machineData) { - cam = FindSingle(); prefab_MachineKPI = Resources.Load("Prefabs/UI/PRF_UI_MachineKPI"); - - var building = FindSingle(); machines = building.floors.SelectMany(f => f.machines).ToArray(); Clear(); foreach (var data in machineData) { - var machineName = machines.ToList().Find(machine => machine.code == data.machineName).machineName; + var targetMachine = Array.Find(machines, m => m.code == data.machineName); + if (targetMachine == null) continue; - if (machineKPIs.ContainsKey(data.machineName)) + if (!machineKPIs.TryGetValue(data.machineName, out var machineKPI)) { - machineKPIs[data.machineName].SetData(data, machineName); - uiElements.Add(machineKPIs[data.machineName].rectTransform); - continue; + machineKPI = Instantiate(prefab_MachineKPI, transform); + machineKPI.onClickKPI += OnClickMachineKPI; + machineKPI.name = data.machineName; + machineKPIs.Add(data.machineName, machineKPI); } - - var machineKPI = Instantiate(prefab_MachineKPI, transform); - machineKPI.SetData(data, machineName); - machineKPI.onClickKPI += OnClickMachineKPI; - machineKPI.name = data.machineName; - machineKPI.SetActive(false); - uiElements.Add(machineKPI.rectTransform); - kpis.Add(machineKPI); - machineKPIs.Add(data.machineName, machineKPI); + machineKPI.SetData(data, targetMachine.machineName); + machineKPI.SetActive(false); } foreach (var machine in machines) { - if (machineKPIs.ContainsKey(machine.code)) + if (machineKPIs.TryGetValue(machine.code, out var kpi)) { - machine.machineKPI = machineKPIs[machine.code]; + machine.machineKPI = kpi; matchingMachines.Add(machine); - kpiToMachines.Add(machine.machineKPI, machine); + kpiToMachines.Add(kpi, machine); machine.SetAnimationSpeed(); } } } - private void OnClickMachineKPI(UI_MachineKPI machineKPI) + + private void UpdateMachineVisibilityAndScale() { - machineKPI.transform.SetAsLastSibling(); - var currentMachine = kpiToMachines[machineKPI]; - onClickKPIToMachine?.Invoke(currentMachine); - onClickKPIToData?.Invoke(machineKPI.data, currentMachine); - } - #endregion - - private void Update() - { - GroupOverlappingUIElements(); - RangeDetection(); - } - - public List uiElements = new List(); - public List> groupedElements = new List>(); - - void GroupOverlappingUIElements() - { - foreach(var matchingMachine in matchingMachines) - { - var machinePos = matchingMachine.centerPos; - var screenPos = cam.camera.WorldToScreenPoint(new Vector3(machinePos.x, machinePos.y + defaultNameHeight, machinePos.z)); - - matchingMachine.machineKPI.transform.position = screenPos; - } - - // ±×·ìÈ­µÈ UI ¿ä¼ÒµéÀ» ÃʱâÈ­ - groupedElements.Clear(); - - // UI ¿ä¼ÒµéÀ» °ãħ ¿©ºÎ¿¡ µû¶ó ±×·ìÈ­ - var uncheckedElements = new HashSet(uiElements); // °ãħ ¿©ºÎ üũ ¾ÈµÈ UI ¿ä¼Òµé - - // °ãħÀ» È®ÀÎÇÒ UI ¿ä¼ÒµéÀ» ¼øÂ÷ÀûÀ¸·Î ±×·ìÈ­ - while (uncheckedElements.Count > 0) - { - var currentElement = uncheckedElements.First(); - uncheckedElements.Remove(currentElement); - var group = new List { currentElement }; - - // ±×·ìÈ­µÈ UI ¿ä¼ÒµéÀ» Ãß°¡ - var overlappingElements = uncheckedElements.Where(element => AreRectanglesOverlapping(currentElement, element)).ToList(); - foreach (var overlappingElement in overlappingElements) - { - uncheckedElements.Remove(overlappingElement); - group.Add(overlappingElement); - } - - groupedElements.Add(group); - } - - foreach (var group in groupedElements) - { - var centerPos = GroupCenterCalculate(group); - - for (int i = 0; i < group.Count; i++) - { - var kpi = group[i]; - var newPos = new Vector3(centerPos.x, centerPos.y + kpi.rect.height * i * kpi.transform.localScale.y, centerPos.z); - - kpi.transform.localPosition = newPos; - } - } - } - private bool AreRectanglesOverlapping(RectTransform rectA, RectTransform rectB) - { - if (!rectB.gameObject.activeSelf) - return false; - - if (!rectA.gameObject.activeSelf) - return false; - - Rect rectAWorld = GetWorldRect(rectA); - Rect rectBWorld = GetWorldRect(rectB); - - return rectAWorld.Overlaps(rectBWorld); - } - private Rect GetWorldRect(RectTransform rectTransform) - { - Vector3[] worldCorners = new Vector3[4]; - rectTransform.GetWorldCorners(worldCorners); - - Vector2 min = new Vector2(worldCorners[0].x, worldCorners[0].y); - Vector2 max = new Vector2(worldCorners[2].x, worldCorners[2].y); - - return new Rect(min, max - min); - } - private Vector3 GroupCenterCalculate(List group) - { - var centerPos = Vector3.zero; - group.Sort((a, b) => a.transform.localPosition.y.CompareTo(b.transform.localPosition.y)); - - foreach (var kpi in group) - { - centerPos += kpi.transform.localPosition; - } - centerPos /= group.Count; - - return centerPos; - } - void RangeDetection() - { - var layerMask = LayerMask.GetMask("Camera", "Floor Wall"); - + // °³¼±»çÇ×: ±âÁ¸ RangeDetection ÇÔ¼ö -> ÄÚ·çÆ¾ ¾È¿¡¼­ ½ÇÇàµÇ´Â °¡½Ã¼º ¾÷µ¥ÀÌÆ® ·çÇÁ¿¡ Æ÷ÇÔ + int layerMask = LayerMask.GetMask("Camera", "Floor Wall"); float t = Mathf.InverseLerp(cam.option.maxDistance, 0f, cam.option.currentDistance); - float scale = Mathf.Lerp(minScale, maxScale, t); - var newScale = new Vector3(scale, scale, scale); + float scaleValue = Mathf.Lerp(minScale, maxScale, t); + Vector3 newScale = new Vector3(scaleValue, scaleValue, scaleValue); + + Floor currentFloor = building.currentFloor; + activeUIElements.Clear(); foreach (var machine in matchingMachines) { - MachineKPIsActive(machine, layerMask); - var machineKPI = machine.machineKPI; - machineKPI.transform.localScale = newScale; - } - } - bool IsScreenRange(Machine machine) - { - Vector3 viewPos = cam.camera.WorldToViewportPoint(machine.centerPos); + bool shouldBeActive = IsMachineVisible(machine, currentFloor, layerMask); - if (viewPos.x >= 0 && viewPos.x <= 1 && viewPos.y >= 0 && viewPos.y <= 1 && viewPos.z > 0) - { - return true; - } - return false; - } - void MachineKPIsActive(Machine machine, LayerMask layerMask) - { - var currentFloor = FindSingle().currentFloor; - var machineInFloor = machine.GetComponentInParent(); - - var dir = cam.camera.transform.position - machine.centerPos; - var hit = new RaycastHit(); - - if (machineInFloor != currentFloor) - { - machine.machineKPI.Deactive(); - return; - } - - if (Physics.Raycast(machine.centerPos, dir, out hit, Mathf.Infinity, layerMask)) - { - var hitCameraLayer = hit.collider.gameObject.layer.Equals(LayerMask.NameToLayer("Camera")); - - if (hitCameraLayer) + if (shouldBeActive) { - if (!IsScreenRange(machine)) - { - machine.machineKPI.Deactive(); - } - else - { - machine.machineKPI.Active(); - } + machine.machineKPI.Active(); + machine.machineKPI.transform.localScale = newScale; + // ¿ùµå ÁÂÇ¥¸¦ ½ºÅ©¸° ÁÂÇ¥·Î º¯È¯ÇÏ¿© 1Â÷ ¹èÄ¡ + Vector3 screenPos = cam.camera.WorldToScreenPoint(machine.centerPos + Vector3.up * defaultNameHeight); + machine.machineKPI.transform.position = screenPos; + activeUIElements.Add(machine.machineKPI.rectTransform); } else { @@ -245,5 +127,79 @@ namespace CHN } } } + + private bool IsMachineVisible(Machine machine, Floor currentFloor, int layerMask) + { + // °³¼±»çÇ×: °¡Àå °¡º­¿î ¿¬»êºÎÅÍ ¼öÇà -> ½ÇÆÐ ½Ã ¹Ù·Î ¸®ÅÏÇϵµ·Ï (Á¶±â ¸®ÅÏ) + + // Ãþ °Ë»ç (±âÁ¸ MachineKPIsActive ÇÔ¼ö) + if (machine.GetComponentInParent() != currentFloor) return false; + + // È­¸é ¾È¿¡ ÀÖ´ÂÁö °Ë»ç (±âÁ¸ IsScreenRange ÇÔ¼ö) + Vector3 viewPos = cam.camera.WorldToViewportPoint(machine.centerPos); + if (!(viewPos.x >= 0 && viewPos.x <= 1 && viewPos.y >= 0 && viewPos.y <= 1 && viewPos.z > 0)) return false; + + // ·¹ÀÌij½ºÆ® (±âÁ¸ MachineKPIsActive ÇÔ¼ö) + Vector3 dir = cam.camera.transform.position - machine.centerPos; + if (Physics.Raycast(machine.centerPos, dir, out RaycastHit hit, Mathf.Infinity, layerMask)) + { + return hit.collider.gameObject.layer == LayerMask.NameToLayer("Camera"); + } + + return false; + } + #endregion + + void GroupOverlappingUIElements() + { + if (activeUIElements.Count == 0) return; + + // °³¼±»çÇ×: uncheckedElements.Where(...) °°Àº ÇÒ´ç ¿¬»ê Á¦°Å -> º¹ÀâÇÑ ±×·ì °è»ê ¾øÀÌ Á¤·Ä + // Á¤·Ä: YÁÂÇ¥ ±âÁØÀ¸·Î Á¤·ÄÇÏ¿© ¾Æ·¡¼­ºÎÅÍ ½×À½ (LINQ ´ë½Å List.Sort·Î ÇÒ´ç ¹æÁö) + activeUIElements.Sort((a, b) => a.position.y.CompareTo(b.position.y)); + + // ´Ü¼øÈ­µÈ Áßø ¹æÁö ·ÎÁ÷ (±×¸®µå³ª ±×·ìÇÎ ´ë½Å ¼øÂ÷Àû üũ) + for (int i = 0; i < activeUIElements.Count; i++) + { + for (int j = i + 1; j < activeUIElements.Count; j++) + { + RectTransform rectA = activeUIElements[i]; + RectTransform rectB = activeUIElements[j]; + + if (AreRectsOverlapping(rectA, rectB)) + { + // °ãÄ¡¸é B¸¦ A À§·Î ¿Ã¸² + float offset = rectA.rect.height * rectA.lossyScale.y; + rectB.position = new Vector3(rectB.position.x, rectA.position.y + offset, rectB.position.z); + } + } + } + } + private bool AreRectsOverlapping(RectTransform rectA, RectTransform rectB) + { + return GetWorldRect(rectA).Overlaps(GetWorldRect(rectB)); + } + private Rect GetWorldRect(RectTransform rectTransform) + { + rectTransform.GetWorldCorners(worldCorners); // °³¼±»çÇ×: worldCorners¸¦ ¸â¹ö º¯¼ö·Î ij½Ì -> GetWorldRect È£Ã⠽ø¶´Ù ¹ß»ýÇÏ´Â ¹è¿­ »ý¼º ¸·À½ + return new Rect(worldCorners[0], worldCorners[2] - worldCorners[0]); + } + + private void OnClickMachineKPI(UI_MachineKPI machineKPI) + { + machineKPI.transform.SetAsLastSibling(); + if (kpiToMachines.TryGetValue(machineKPI, out var machine)) + { + onClickKPIToMachine?.Invoke(machine); + onClickKPIToData?.Invoke(machineKPI.data, machine); + } + } + + public void Clear() + { + activeUIElements.Clear(); + matchingMachines.Clear(); + kpiToMachines.Clear(); + } } } \ No newline at end of file