using DG.Tweening; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UVC.Data; using UVC.Factory.Component; using UVC.Util; namespace UVC.Factory.Alarm { public class AlarmUIController : MonoBehaviour, IPointerClickHandler { [Tooltip("알람 클러스터 뷰입니다.")] [SerializeField] private GameObject clusterView; [Tooltip("알람 개수를 표시하는 텍스트입니다.")] [SerializeField] private TextMeshProUGUI alarmCountText; [Tooltip("확장된 알람 뷰입니다. 개별 알람 아이콘을 표시합니다.")] [SerializeField] private GameObject expandedView; [Tooltip("개별 알람 아이콘 프리팹입니다. 이 프리팹은 개별 알람 정보를 표시하는 UI 요소를 포함해야 합니다.")] [SerializeField] private GameObject singleAlarmIconPrefab; // 개별 알람 아이콘 private Transform targetObject; private List alarms = new List(); private bool isExpanded = false; private SingleAlarmIcon singleAlarmIcon1 = null; private RectTransform rectTransform; private Canvas mainCanvas; private float uiSpacing = 20f; // 간격 private Tweener uiSpacingTweener; private bool isZoomIn = false; // 줌 인 상태를 추적하기 위한 변수 private void Awake() { rectTransform = GetComponent(); mainCanvas = GetComponentInParent(); } public void Initialize(FactoryObject target, DataObject initialAlarm) { this.targetObject = target.transform; AddAlarm(initialAlarm); } void LateUpdate() { if (targetObject == null || Camera.main == null || mainCanvas == null) { // 필수 컴포넌트가 없으면 UI를 비활성화합니다. if (clusterView.activeSelf) clusterView.SetActive(false); if (expandedView.activeSelf) expandedView.SetActive(false); if (alarmCountText != null && alarmCountText.gameObject.activeSelf) alarmCountText.gameObject.SetActive(false); if (singleAlarmIcon1 != null && singleAlarmIcon1.gameObject.activeSelf) singleAlarmIcon1.gameObject.SetActive(false); return; } // 카메라의 정면 방향과 타겟을 향하는 방향을 계산합니다. Vector3 cameraForward = Camera.main.transform.forward; Vector3 toTarget = (targetObject.position - Camera.main.transform.position).normalized; // 두 벡터의 내적을 계산하여 타겟이 카메라 앞에 있는지 확인합니다. // 내적 값이 0보다 크면 타겟이 카메라 앞에 있는 것입니다. if (Vector3.Dot(cameraForward, toTarget) > 0) { // 타겟이 앞에 있을 때만 UI를 활성화하고 위치를 업데이트합니다. if (!gameObject.activeSelf) { UpdateView(); // 비활성화 상태였다면 뷰를 다시 활성화합니다. } // targetObject의 월드 좌표를 스크린 좌표로 변환 Vector3 screenPoint = Camera.main.WorldToScreenPoint(targetObject.position + Vector3.up); // Canvas Render Mode가 Screen Space - Camera일 경우 if (mainCanvas.renderMode == RenderMode.ScreenSpaceCamera) { Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle(mainCanvas.transform as RectTransform, screenPoint, mainCanvas.worldCamera, out localPoint); localPoint.y += uiSpacing; rectTransform.localPosition = localPoint; } // Canvas Render Mode가 Screen Space - Overlay일 경우 else { screenPoint.y += uiSpacing; rectTransform.position = screenPoint; } } else { // 타겟이 카메라 뒤에 있으면 모든 관련 UI를 비활성화합니다. if (clusterView.activeSelf) clusterView.SetActive(false); if (expandedView.activeSelf) expandedView.SetActive(false); if (alarmCountText != null && alarmCountText.gameObject.activeSelf) alarmCountText.gameObject.SetActive(false); if (singleAlarmIcon1 != null && singleAlarmIcon1.gameObject.activeSelf) singleAlarmIcon1.gameObject.SetActive(false); } } public void AddAlarm(DataObject alarm) { alarms.Add(alarm); UpdateView(); } public void UpdateAlarm(DataObject alarm) { // 실제로는 alarmId로 찾아서 업데이트해야 함 int index = alarms.FindIndex(a => a.Id == alarm.Id); if (index >= 0) { foreach (var key in alarm.Keys) { alarms[index][key] = alarm[key]; // 기존 알람 데이터 업데이트 } UpdateView(); } else { Debug.LogWarning($"Alarm with ID {alarm.Id} not found for update."); } } public void RemoveAlarm(DataObject alarm) { // 실제로는 alarmId로 찾아서 지워야 함 alarms.Remove(alarm); UpdateView(); } private void UpdateView() { if (isExpanded) { // 확장된 상태라면 개별 아이콘들을 다시 그림 ExpandCluster(); } else { // 일반 상태 if (alarms.Count > 1) { clusterView.SetActive(true); expandedView.SetActive(false); if (singleAlarmIcon1 != null) singleAlarmIcon1.gameObject.SetActive(false); // 단일 뷰도 꺼야 함 alarmCountText.gameObject.SetActive(true); alarmCountText.text = alarms.Count.ToString(); } else if (alarms.Count == 1) { clusterView.SetActive(false); expandedView.SetActive(false); alarmCountText.gameObject.SetActive(false); // 알람 개수 텍스트 숨김 // 여기에 단일 알람 아이콘을 보여주는 로직 추가 if (singleAlarmIcon1 == null) { singleAlarmIcon1 = Instantiate(singleAlarmIconPrefab, transform).GetComponent(); } singleAlarmIcon1.gameObject.SetActive(true); singleAlarmIcon1.SetData(alarms[0], targetObject); } } } public void OnPointerClick() // 이 함수를 Event Trigger 등으로 호출 { if (isExpanded) { CollapseCluster(); } else { if (alarms.Count > 1) { isZoomIn = true; // 클러스터 확장 CameraController.Instance.FocusOnTarget(targetObject.position, 15.0f); // 예시: 5미터 거리로 줌 ExpandCluster(); } else if (alarms.Count == 1) { isZoomIn = true; //AnimateUISpace(1f); // 간격을 0으로 애니메이션 // 단일 알람 클릭 CameraController.Instance.FocusOnTarget(targetObject.position, 10.0f); // 추가로 알람 상세 정보 UI를 띄울 수 있음 } } } private void ExpandCluster() { isExpanded = true; clusterView.SetActive(false); expandedView.SetActive(true); singleAlarmIcon1.gameObject.SetActive(false); // 기존 아이콘들 삭제 foreach (Transform child in expandedView.transform) { Destroy(child.gameObject); } // 원형으로 아이콘 배치 float radius = 100.0f; // Canvas 좌표계에 맞는 반지름 값 for (int i = 0; i < alarms.Count; i++) { float angle = i * Mathf.PI * 2f / alarms.Count; Vector3 pos = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius; GameObject iconObj = Instantiate(singleAlarmIconPrefab, expandedView.transform); iconObj.transform.localPosition = pos; // iconObj의 SingleAlarmIcon 스크립트에 알람 데이터 전달 iconObj.GetComponent().SetData(alarms[i], targetObject); } //AnimateUISpace(0f); // 간격을 0으로 애니메이션 } private void CollapseCluster() { isExpanded = false; UpdateView(); } public int GetAlarmCount() => alarms.Count; public void OnPointerClick(PointerEventData eventData) { OnPointerClick(); } private void AnimateUISpace(float targetSpacing, float duration = 1.0f) { if (uiSpacingTweener != null && uiSpacingTweener.IsActive() && uiSpacingTweener.IsPlaying()) { uiSpacingTweener.Kill(); } uiSpacingTweener = DOVirtual.Float(uiSpacing, targetSpacing, duration, (value) => { uiSpacing = value; }); } private void OnDestroy() { if (uiSpacingTweener != null && uiSpacingTweener.IsActive() && uiSpacingTweener.IsPlaying()) { uiSpacingTweener.Kill(); } } } }