alarm class 정리
This commit is contained in:
97
Assets/Scripts/UVC/Factory/Alarm/AlarmClusterIcon.cs
Normal file
97
Assets/Scripts/UVC/Factory/Alarm/AlarmClusterIcon.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.Factory.Alarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 여러 개의 알람이 한 곳에 발생했을 때 이를 묶어서 표시하는 '클러스터' 아이콘을 관리하는 클래스입니다.
|
||||
/// 아이콘에는 총 알람 개수가 표시되며, 클릭 시 개별 알람을 펼쳐보는 기능을 트리거합니다.
|
||||
/// </summary>
|
||||
public class AlarmClusterIcon : MonoBehaviour
|
||||
{
|
||||
[Tooltip("클릭 가능한 UI 버튼입니다. 클러스터를 나타냅니다.")]
|
||||
[SerializeField] private Button button;
|
||||
|
||||
// 알람 개수를 표시할 TextMeshPro 컴포넌트입니다.
|
||||
private TextMeshProUGUI buttonText;
|
||||
|
||||
/// <summary>
|
||||
/// 클러스터 아이콘에 표시될 알람 개수 텍스트를 가져오거나 설정하는 프로퍼티입니다.
|
||||
/// </summary>
|
||||
public string AlarmCount
|
||||
{
|
||||
get => buttonText != null ? buttonText.text : string.Empty;
|
||||
set
|
||||
{
|
||||
if (buttonText != null)
|
||||
{
|
||||
buttonText.text = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 클러스터 아이콘이 클릭되었을 때 호출될 이벤트를 정의합니다.
|
||||
/// 상위 관리자인 AlarmIconManager가 이 이벤트를 구독하여 클러스터를 펼치는 동작을 수행합니다.
|
||||
/// </summary>
|
||||
public Action OnClickHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 생성될 때 가장 먼저 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
// 자식 오브젝트에서 TextMeshProUGUI 컴포넌트를 찾아 할당합니다.
|
||||
buttonText = GetComponentInChildren<TextMeshProUGUI>();
|
||||
if (buttonText == null)
|
||||
{
|
||||
Debug.LogWarning("Text component not found in children.", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 첫 번째 프레임 업데이트 전에 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
// 버튼의 onClick 이벤트에 HandleClick 메서드를 리스너로 등록합니다.
|
||||
button.onClick.AddListener(HandleClick);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Button is not assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 버튼 클릭 이벤트가 발생했을 때 실행되는 메서드입니다.
|
||||
/// </summary>
|
||||
private void HandleClick()
|
||||
{
|
||||
// OnClickHandler 이벤트에 등록된 메서드가 있다면 호출합니다.
|
||||
OnClickHandler?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 파괴될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
// Start에서 등록했던 클릭 이벤트 리스너를 제거하여 메모리 누수를 방지합니다.
|
||||
button.onClick.RemoveListener(HandleClick);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Button is not assigned.");
|
||||
}
|
||||
// 이벤트 핸들러 참조를 정리합니다.
|
||||
OnClickHandler = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 287d101765c6b454c9827e68416792fc
|
||||
94
Assets/Scripts/UVC/Factory/Alarm/AlarmDetailView.cs
Normal file
94
Assets/Scripts/UVC/Factory/Alarm/AlarmDetailView.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Data;
|
||||
namespace UVC.Factory.Alarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 알람의 상세 정보를 표시하는 UI를 관리하는 클래스입니다.
|
||||
/// DataObject에 담긴 모든 키-값 쌍을 리스트 형태로 보여주는 역할을 합니다.
|
||||
/// </summary>
|
||||
public class AlarmDetailView : MonoBehaviour
|
||||
{
|
||||
|
||||
[Tooltip("알람 상세 내용이 표시될 TextMeshPro UI 컴포넌트입니다.")]
|
||||
[SerializeField] private TextMeshProUGUI detailTxt;
|
||||
|
||||
[Tooltip("상세 정보창을 닫기 위한 버튼 UI 컴포넌트입니다.")]
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 처음 활성화될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// UI의 초기 상태를 설정합니다.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
// 닫기 버튼에 클릭 이벤트 리스너를 추가합니다. 버튼을 클릭하면 Hide() 메서드가 호출됩니다.
|
||||
closeButton.onClick.AddListener(Hide);
|
||||
// 초기에는 상세 정보창이 보이지 않도록 비활성화합니다.
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 알람 데이터를 받아와 상세 정보 UI에 표시하고, 창을 활성화합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">표시할 알람의 모든 정보가 담긴 DataObject</param>
|
||||
public void Show(DataObject data)
|
||||
{
|
||||
string combinedString = string.Empty;
|
||||
// DataObject에 있는 모든 키-값 쌍을 순회합니다.
|
||||
foreach (var kvp in data)
|
||||
{
|
||||
// <indent> 태그를 사용하여 줄바꿈 시에도 정렬이 유지되도록 합니다.
|
||||
//combinedString += $"{kvp.Key}<pos=40%><indent=40%>{kvp.Value ?? "null"}</indent>\n";
|
||||
// <margin-right=60%>: 키가 텍스트 컨테이너 너비의 40%를 넘지 않도록 제한합니다.
|
||||
// 키가 길면 이 지점에서 자동으로 줄바꿈됩니다.
|
||||
// <pos=40%>: 값의 시작 위치를 40% 지점으로 지정합니다.
|
||||
// <margin-right=0>: 다음 줄에 영향을 주지 않도록 마진을 리셋합니다.
|
||||
//combinedString += $"<margin-right=60%>{kvp.Key}</margin-right><pos=40%><margin-right=0>{kvp.Value ?? "null"}</margin-right>\n";
|
||||
// <width=40%>: 키 텍스트의 너비를 전체의 40%로 제한합니다. 키가 이 너비를 초과하면 자동으로 줄바꿈됩니다.
|
||||
// <pos=40%>: 값 텍스트의 시작 위치를 40% 지점으로 설정합니다.
|
||||
combinedString += $"<width=40%>{kvp.Key}</width><pos=40%><valign='top'><indent=40%>{kvp.Value ?? "null"}</indent></valign>\n";
|
||||
}
|
||||
combinedString = combinedString.TrimEnd('\n'); // 마지막 줄바꿈 제거
|
||||
detailTxt.text = combinedString;
|
||||
|
||||
// 내용의 길이에 맞게 상세 정보창의 높이를 동적으로 조절합니다.
|
||||
RectTransform rect = GetComponent<RectTransform>();
|
||||
if (rect != null && detailTxt != null)
|
||||
{
|
||||
RectTransform textRect = detailTxt.GetComponent<RectTransform>();
|
||||
// 현재 배경(rect)의 높이와 텍스트(textRect) 영역 높이의 차이를 계산하여 상하 여백(margin)을 구합니다.
|
||||
float marginHeight = rect.rect.height - textRect.rect.height;
|
||||
// 배경의 높이를 '텍스트의 실제 높이(preferredHeight)' + '상하 여백'으로 설정하여 내용이 잘리지 않게 합니다.
|
||||
rect.sizeDelta = new Vector2(rect.rect.width, detailTxt.preferredHeight + marginHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상세 정보 UI를 비활성화하여 화면에서 숨깁니다.
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 파괴될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// 이벤트 리스너를 제거하여 메모리 누수를 방지합니다.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (closeButton != null)
|
||||
{
|
||||
// Awake에서 등록했던 클릭 이벤트 리스너를 제거합니다.
|
||||
closeButton.onClick.RemoveListener(Hide);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Detail Close Button is not assigned.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Factory/Alarm/AlarmDetailView.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/Alarm/AlarmDetailView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bdd056a90a112140b16875c3162bbea
|
||||
448
Assets/Scripts/UVC/Factory/Alarm/AlarmIconManager.cs
Normal file
448
Assets/Scripts/UVC/Factory/Alarm/AlarmIconManager.cs
Normal file
@@ -0,0 +1,448 @@
|
||||
using DG.Tweening;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
using UVC.Factory.Component;
|
||||
|
||||
namespace UVC.Factory.Alarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 특정 설비(FactoryObject)에 연결된 알람 아이콘 UI를 관리하는 클래스입니다.
|
||||
/// 알람이 하나일 때는 단일 아이콘, 여러 개일 때는 클러스터(묶음) 아이콘을 표시합니다.
|
||||
/// 클러스터 아이콘을 클릭하면 개별 알람 아이콘들을 원형으로 펼쳐 보여주는 기능을 담당합니다.
|
||||
/// </summary>
|
||||
public class AlarmIconManager : MonoBehaviour
|
||||
{
|
||||
[Header("UI Components")]
|
||||
[Tooltip("알람이 여러 개일 때 표시될 묶음 아이콘 UI입니다.")]
|
||||
[SerializeField] private AlarmClusterIcon clusterView;
|
||||
|
||||
[Tooltip("알람 아이콘들이 펼쳐질 때 부모가 될 UI 영역입니다.")]
|
||||
[SerializeField] private GameObject expandedView;
|
||||
[Tooltip("개별 알람을 표시할 아이콘의 프리팹입니다.")]
|
||||
[SerializeField] private GameObject alarmSingleIconManagerPrefab; // 개별 알람 아이콘
|
||||
|
||||
[Header("UI Layout Settings")]
|
||||
[Tooltip("3D 설비 객체와 UI 아이콘 사이의 수직(Y) 간격입니다.")]
|
||||
[SerializeField] private float objectYOffset = 0f;
|
||||
[Tooltip("아이콘을 펼쳤을 때 개별 아이콘 사이의 간격입니다.")]
|
||||
[SerializeField] private float iconSpacing = 20f;
|
||||
[Tooltip("아이콘을 펼쳤을 때의 중심점 X축 오프셋입니다.")]
|
||||
[SerializeField] private float expandOffsetX = 0f;
|
||||
[Tooltip("아이콘을 펼쳤을 때의 중심점 Y축 오프셋입니다.")]
|
||||
[SerializeField] private float expandOffsetY = 0f;
|
||||
|
||||
// 이 UI가 따라다닐 3D 설비 객체의 Transform입니다.
|
||||
private Transform targetObject;
|
||||
// 이 UI가 관리하는 모든 알람 데이터의 리스트입니다.
|
||||
private List<DataObject> alarms = new List<DataObject>();
|
||||
// 현재 클러스터가 펼쳐진 상태인지 여부를 나타내는 플래그입니다.
|
||||
private bool isExpanded = false;
|
||||
// 알람이 하나일 때 사용되는 단일 알람 아이콘의 인스턴스입니다.
|
||||
private AlarmSigleIconManager singleAlarmIcon1 = null;
|
||||
|
||||
// UI 위치 계산에 필요한 컴포넌트들입니다.
|
||||
private RectTransform rectTransform;
|
||||
private Canvas canvas;
|
||||
|
||||
// UI 애니메이션을 위한 DoTween 핸들러입니다.
|
||||
private Tweener uiSpacingTweener;
|
||||
|
||||
// 사용자가 클러스터를 클릭하여 줌인했는지 상태를 추적하는 변수입니다.
|
||||
private bool isZoomIn = false;
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour가 활성화될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// UI 위치 계산에 필요한 RectTransform과 Canvas 컴포넌트를 찾아 저장합니다.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
canvas = GetComponentInParent<Canvas>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AlarmIconManager를 초기화합니다. AlarmManager에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="target">알람이 발생한 3D 설비 객체</param>
|
||||
/// <param name="initialAlarm">최초로 추가될 알람 데이터</param>
|
||||
public void Initialize(FactoryObject target, DataObject initialAlarm)
|
||||
{
|
||||
this.targetObject = target.transform;
|
||||
AddAlarm(initialAlarm); // 첫 알람을 리스트에 추가
|
||||
|
||||
if (clusterView == null || expandedView == null || alarmSingleIconManagerPrefab == null)
|
||||
{
|
||||
Debug.LogError("필수 UI 컴포넌트가 할당되지 않았습니다. AlarmUIController를 확인하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 클러스터 아이콘 클릭 시 OnPointerClick 메서드가 호출되도록 이벤트 핸들러를 등록합니다.
|
||||
clusterView.OnClickHandler += OnPointerClick;
|
||||
// 카메라 위치가 변경될 때마다 OnCameraPositionChangedHandler가 호출되도록 등록합니다.
|
||||
FactoryCameraController.Instance.OnCameraPositionChanged += OnCameraPositionChangedHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 매 프레임의 마지막에 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// 3D 객체의 위치를 2D 스크린 좌표로 변환하여 UI 아이콘이 객체를 따라다니도록 위치를 업데이트합니다.
|
||||
/// </summary>
|
||||
void LateUpdate()
|
||||
{
|
||||
if (targetObject == null || Camera.main == null || canvas == null)
|
||||
{
|
||||
// 필수 객체가 없으면 모든 UI를 비활성화하고 종료합니다.
|
||||
if (gameObject.activeSelf) gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 카메라의 정면 방향과 카메라에서 타겟을 향하는 방향 벡터를 계산합니다.
|
||||
Vector3 cameraForward = Camera.main.transform.forward;
|
||||
Vector3 toTarget = (targetObject.position - Camera.main.transform.position).normalized;
|
||||
|
||||
// 두 벡터의 내적(Dot product)을 계산하여 타겟 객체가 카메라 앞에 있는지 확인합니다.
|
||||
// 내적 값이 0보다 크면 객체가 카메라 시야각 내에 있다는 의미입니다.
|
||||
if (Vector3.Dot(cameraForward, toTarget) > 0)
|
||||
{
|
||||
// 객체가 카메라 앞에 있을 때만 UI를 활성화하고 위치를 업데이트합니다.
|
||||
if (!gameObject.activeSelf)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
UpdateView(); // 비활성화 상태였다면 뷰를 다시 활성화합니다.
|
||||
}
|
||||
|
||||
// 3D 월드 좌표를 2D 스크린 좌표로 변환합니다.
|
||||
Vector3 screenPoint = Camera.main.WorldToScreenPoint(targetObject.position + Vector3.up);
|
||||
|
||||
// 캔버스의 렌더 모드에 따라 좌표 변환 방식을 다르게 처리합니다.
|
||||
// Canvas Render Mode가 Screen Space - Camera일 경우
|
||||
if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
|
||||
{
|
||||
Vector2 localPoint;
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, screenPoint, canvas.worldCamera, out localPoint);
|
||||
localPoint.y += objectYOffset; // Y축 오프셋 적용
|
||||
rectTransform.localPosition = localPoint;
|
||||
}
|
||||
// Canvas Render Mode가 Screen Space - Overlay일 경우
|
||||
else
|
||||
{
|
||||
screenPoint.y += objectYOffset; // Y축 오프셋 적용
|
||||
rectTransform.position = screenPoint;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 타겟이 카메라 뒤에 있으면 UI를 비활성화합니다.
|
||||
if (gameObject.activeSelf) gameObject.SetActive(false);
|
||||
//if (clusterView.gameObject.activeSelf) clusterView.gameObject.SetActive(false);
|
||||
//if (expandedView.activeSelf) expandedView.SetActive(false);
|
||||
//if (singleAlarmIcon1 != null && singleAlarmIcon1.gameObject.activeSelf) singleAlarmIcon1.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 매니저가 특정 알람을 이미 관리하고 있는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <param name="alarm">확인할 알람 데이터</param>
|
||||
/// <returns>포함하고 있으면 true, 아니면 false</returns>
|
||||
public bool ContainsAlarm(DataObject alarm)
|
||||
{
|
||||
return alarms.Exists(a => a.Id == alarm.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 새로운 알람을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="alarm">추가할 알람 데이터</param>
|
||||
public void AddAlarm(DataObject alarm)
|
||||
{
|
||||
alarms.Add(alarm);
|
||||
UpdateView(); // 알람 리스트가 변경되었으므로 뷰를 업데이트합니다.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 알람의 정보를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="alarm">업데이트할 알람 데이터</param>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 알람을 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="alarm">제거할 알람 데이터</param>
|
||||
public void RemoveAlarm(DataObject alarm)
|
||||
{
|
||||
// 실제로는 alarmId로 찾아서 지워야 함
|
||||
alarms.Remove(alarm);
|
||||
UpdateView(); // 뷰 업데이트
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 알람 상태에 맞게 UI를 갱신합니다.
|
||||
/// 알람 개수와 확장 상태(isExpanded)에 따라 어떤 UI를 보여줄지 결정합니다.
|
||||
/// </summary>
|
||||
private void UpdateView()
|
||||
{
|
||||
if (isExpanded)
|
||||
{
|
||||
// 확장된 상태라면, 개별 아이콘들을 다시 그립니다. (알람 추가/제거 시 대응)
|
||||
ExpandCluster();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alarms.Count == 1)
|
||||
{
|
||||
// 알람이 1개이면 단일 아이콘 뷰를 활성화합니다.
|
||||
clusterView.gameObject.SetActive(false);
|
||||
expandedView.SetActive(false);
|
||||
if (singleAlarmIcon1 == null)
|
||||
{
|
||||
// 단일 아이콘이 없으면 새로 생성합니다.
|
||||
singleAlarmIcon1 = Instantiate(alarmSingleIconManagerPrefab, transform).GetComponent<AlarmSigleIconManager>();
|
||||
}
|
||||
singleAlarmIcon1.gameObject.SetActive(true);
|
||||
singleAlarmIcon1.SetData(alarms[0], targetObject);
|
||||
}
|
||||
else if (alarms.Count > 1) // 축소된(기본) 상태
|
||||
{
|
||||
// 알람이 2개 이상이면 클러스터 뷰를 활성화합니다.
|
||||
clusterView.gameObject.SetActive(true);
|
||||
clusterView.AlarmCount = alarms.Count.ToString(); // 알람 개수 텍스트 업데이트
|
||||
expandedView.SetActive(false);
|
||||
if (singleAlarmIcon1 != null) singleAlarmIcon1.gameObject.SetActive(false); // 단일 뷰도 꺼야 함
|
||||
}
|
||||
else // alarms.Count == 0
|
||||
{
|
||||
// 알람이 없으면 모든 UI를 비활성화하고 이 게임 오브젝트를 파괴합니다.
|
||||
// (AlarmManager에서 처리하므로 여기서는 비활성화만)
|
||||
clusterView.gameObject.SetActive(false);
|
||||
expandedView.SetActive(false);
|
||||
if (singleAlarmIcon1 != null) singleAlarmIcon1.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클러스터 아이콘이 클릭되었을 때 호출되는 이벤트 핸들러입니다.
|
||||
/// </summary>
|
||||
public void OnPointerClick()
|
||||
{
|
||||
isZoomIn = true; // 사용자가 줌인 동작을 시작했음을 기록
|
||||
if (alarms.Count > 1)
|
||||
{
|
||||
if (singleAlarmIcon1 != null && singleAlarmIcon1.IsDetailViewActive)
|
||||
{
|
||||
singleAlarmIcon1.HideDetail(); // 단일 아이콘 숨김
|
||||
}
|
||||
// 카메라를 타겟 객체에 포커스하고, 클러스터를 펼칩니다.
|
||||
FactoryCameraController.Instance.FocusOnTarget(targetObject.position, 15.0f); // 예시: 5미터 거리로 줌
|
||||
ExpandCluster();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클러스터 뷰를 펼쳐서 개별 알람 아이콘들을 원형으로 배열합니다.
|
||||
/// </summary>
|
||||
private void ExpandCluster()
|
||||
{
|
||||
isExpanded = true;
|
||||
clusterView.gameObject.SetActive(false); // 클러스터 뷰는 숨김
|
||||
expandedView.SetActive(true); // 확장 뷰는 보임
|
||||
if (singleAlarmIcon1 != null) singleAlarmIcon1.gameObject.SetActive(false);
|
||||
|
||||
// 재활용을 위해 기존에 생성된 아이콘들을 딕셔너리에 저장합니다. (Key: 알람 ID, Value: 아이콘 인스턴스)
|
||||
var existingIcons = new Dictionary<string, AlarmSigleIconManager>();
|
||||
foreach (Transform child in expandedView.transform)
|
||||
{
|
||||
AlarmSigleIconManager icon = child.GetComponent<AlarmSigleIconManager>();
|
||||
if (icon != null && icon.Data != null)
|
||||
{
|
||||
if (!existingIcons.ContainsKey(icon.Data.Id))
|
||||
{
|
||||
existingIcons.Add(icon.Data.Id, icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var activeIcons = new List<AlarmSigleIconManager>();
|
||||
|
||||
// 현재 알람 목록을 순회하며 아이콘을 업데이트하거나 새로 생성
|
||||
foreach (var alarmData in alarms)
|
||||
{
|
||||
AlarmSigleIconManager icon;
|
||||
if (existingIcons.TryGetValue(alarmData.Id, out icon))
|
||||
{
|
||||
// 기존 아이콘 재사용
|
||||
icon.SetData(alarmData, targetObject);
|
||||
existingIcons.Remove(alarmData.Id); // 처리된 아이콘은 딕셔너리에서 제거
|
||||
}
|
||||
else
|
||||
{
|
||||
// 새 아이콘 생성
|
||||
GameObject iconObj = Instantiate(alarmSingleIconManagerPrefab, expandedView.transform);
|
||||
icon = iconObj.GetComponent<AlarmSigleIconManager>();
|
||||
icon.SetData(alarmData, targetObject);
|
||||
icon.OnDetail = OnDetailSingle;
|
||||
}
|
||||
activeIcons.Add(icon);
|
||||
}
|
||||
|
||||
// 더 이상 사용되지 않는 기존 아이콘들 삭제
|
||||
foreach (var icon in existingIcons.Values)
|
||||
{
|
||||
icon.OnDetail = null;
|
||||
Destroy(icon.gameObject);
|
||||
}
|
||||
|
||||
// alarmSingleIconPrefab의 RectTransform을 가져와 아이콘의 너비를 계산합니다.
|
||||
RectTransform iconRect = alarmSingleIconManagerPrefab.GetComponent<RectTransform>();
|
||||
if (iconRect == null)
|
||||
{
|
||||
Debug.LogError("alarmSingleIconPrefab에 RectTransform 컴포넌트가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 아이콘들을 원형으로 배치하는 로직
|
||||
float iconWidth = iconRect.rect.width;
|
||||
|
||||
// 아이콘의 개수와 너비, 간격을 고려하여 필요한 원주를 계산합니다.
|
||||
float circumference = (iconWidth + iconSpacing) * activeIcons.Count;
|
||||
|
||||
// 원주를 이용하여 반지름(radius)을 계산합니다.
|
||||
float radius = circumference / (2f * Mathf.PI);
|
||||
|
||||
// 중심 오프셋을 적용할 Vector3를 생성합니다.
|
||||
Vector3 centerOffset = new Vector3(expandOffsetX, expandOffsetY, 0);
|
||||
|
||||
for (int i = 0; i < activeIcons.Count; i++)
|
||||
{
|
||||
float angle = i * Mathf.PI * 2f / activeIcons.Count;
|
||||
// 오프셋을 적용하여 아이콘의 위치를 계산합니다.
|
||||
Vector3 pos = centerOffset + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
|
||||
activeIcons[i].transform.localPosition = pos;
|
||||
}
|
||||
|
||||
// 상세 보기가 활성화된 아이콘을 찾아 맨 위로 올립니다.
|
||||
foreach (var icon in activeIcons)
|
||||
{
|
||||
if (icon.IsDetailViewActive)
|
||||
{
|
||||
icon.transform.SetAsLastSibling();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//AnimateUISpace(0f); // 간격을 0으로 애니메이션
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 개별 알람 아이콘 중 하나가 상세 보기를 활성화했을 때 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="alarmSingle">상세 보기를 활성화한 아이콘</param>
|
||||
private void OnDetailSingle(AlarmSigleIconManager alarmSingle)
|
||||
{
|
||||
// 다른 모든 아이콘들의 상세 보기는 숨깁니다.
|
||||
foreach (Transform child in expandedView.transform)
|
||||
{
|
||||
AlarmSigleIconManager existingIcon = child.GetComponent<AlarmSigleIconManager>();
|
||||
if (existingIcon != null)
|
||||
{
|
||||
if (existingIcon != alarmSingle) existingIcon.HideDetail(); // 다른 아이콘은 숨김
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 펼쳐진 클러스터를 다시 원래의 묶음 아이콘 상태로 되돌립니다.
|
||||
/// </summary>
|
||||
private void CollapseCluster()
|
||||
{
|
||||
isExpanded = false;
|
||||
// 모든 개별 아이콘의 상세 보기를 숨깁니다.
|
||||
foreach (Transform child in expandedView.transform)
|
||||
{
|
||||
AlarmSigleIconManager existingIcon = child.GetComponent<AlarmSigleIconManager>();
|
||||
if (existingIcon != null && existingIcon.IsDetailViewActive) existingIcon.HideDetail();
|
||||
}
|
||||
if (singleAlarmIcon1 != null && singleAlarmIcon1.IsDetailViewActive)
|
||||
{
|
||||
singleAlarmIcon1.HideDetail(); // 단일 아이콘도 숨김
|
||||
}
|
||||
UpdateView(); // 뷰를 다시 축소된 상태로 업데이트합니다.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 관리 중인 알람의 개수를 반환합니다.
|
||||
/// </summary>
|
||||
public int GetAlarmCount() => alarms.Count;
|
||||
|
||||
private void AnimateUISpace(float targetSpacing, float duration = 1.0f)
|
||||
{
|
||||
if (uiSpacingTweener != null && uiSpacingTweener.IsActive() && uiSpacingTweener.IsPlaying())
|
||||
{
|
||||
uiSpacingTweener.Kill();
|
||||
}
|
||||
uiSpacingTweener = DOVirtual.Float(objectYOffset, targetSpacing, duration, (value) =>
|
||||
{
|
||||
objectYOffset = value;
|
||||
});
|
||||
}
|
||||
|
||||
private float cameraY = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// 카메라 위치가 변경될 때 호출되는 이벤트 핸들러입니다.
|
||||
/// 사용자가 줌 아웃(카메라가 멀어짐)하면 펼쳐진 클러스터를 자동으로 닫습니다.
|
||||
/// </summary>
|
||||
private void OnCameraPositionChangedHandler(Vector3 position)
|
||||
{
|
||||
Debug.Log($"Camera position changed: {cameraY}, {position.y}, {isZoomIn}");
|
||||
if (cameraY < position.y && isZoomIn)
|
||||
{
|
||||
|
||||
// 줌 인 상태에서 카메라 위치가 변경되면 클러스터를 해제
|
||||
CollapseCluster();
|
||||
isZoomIn = false; // 줌 인 상태를 해제합니다.
|
||||
}
|
||||
cameraY = position.y; // 카메라의 Y 위치를 업데이트
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// DoTween 애니메이션이 실행 중이면 중지시킵니다.
|
||||
if (uiSpacingTweener != null && uiSpacingTweener.IsActive() && uiSpacingTweener.IsPlaying())
|
||||
{
|
||||
uiSpacingTweener.Kill();
|
||||
}
|
||||
|
||||
FactoryCameraController.Instance.OnCameraPositionChanged -= OnCameraPositionChangedHandler;
|
||||
clusterView.OnClickHandler -= OnPointerClick;
|
||||
targetObject = null;
|
||||
alarms.Clear();
|
||||
alarms = null;
|
||||
canvas = null;
|
||||
|
||||
if (singleAlarmIcon1 != null)
|
||||
{
|
||||
Destroy(singleAlarmIcon1.gameObject);
|
||||
singleAlarmIcon1 = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 150b885e8a191f140a76ab655436b950
|
||||
@@ -9,21 +9,32 @@ using UVC.Core;
|
||||
using UVC.Data;
|
||||
using UVC.Extention;
|
||||
using UVC.Factory.Component;
|
||||
using UVC.Util;
|
||||
|
||||
namespace UVC.Factory.Alarm
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 씬에 발생하는 모든 알람을 관리하는 싱글톤 클래스입니다.
|
||||
/// MQTT로부터 알람 데이터를 수신하여, 해당하는 설비(FactoryObject)에 알람 UI를 생성, 업데이트, 제거하는 역할을 담당합니다.
|
||||
/// </summary>
|
||||
public class AlarmManager : SingletonScene<AlarmManager>
|
||||
{
|
||||
[Tooltip("알람 UI 프리팹입니다. 이 프리팹은 알람 정보를 표시하는 UI 요소를 포함해야 합니다.")]
|
||||
[SerializeField]
|
||||
protected GameObject alarmUIPrefab; // 알람 UI 프리팹 (아래에서 설명)
|
||||
private Dictionary<string, AlarmUIController> activeAlarmUIs = new Dictionary<string, AlarmUIController>();
|
||||
|
||||
// 현재 활성화된 알람 UI들을 관리하는 딕셔너리입니다.
|
||||
// Key: 설비 ID (TRANSPORT_EQP_ID), Value: 해당 설비에 연결된 AlarmIconManager 인스턴스
|
||||
private Dictionary<string, AlarmIconManager> activeAlarmUIs = new Dictionary<string, AlarmIconManager>();
|
||||
|
||||
// 테스트용으로 사용할 AGV 이름 리스트입니다.
|
||||
private List<string> agvNames = new List<string>();
|
||||
|
||||
// 어떤 알람(Id)이 어떤 AGV(TRANSPORT_EQP_ID)에 할당되었는지 추적하기 위한 딕셔너리입니다.
|
||||
private Dictionary<string, string> alarmAgvNames = new Dictionary<string, string>();
|
||||
|
||||
// FactoryDataManager에서 찾을 수 있도록 참조를 저장
|
||||
// 씬에 있는 모든 FactoryObject를 관리하는 매니저의 참조입니다.
|
||||
// 알람 데이터에 포함된 설비 ID를 이용해 실제 3D 객체를 찾기 위해 사용됩니다.
|
||||
private FactoryObjectManager? dataManager;
|
||||
|
||||
|
||||
@@ -33,15 +44,21 @@ namespace UVC.Factory.Alarm
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
// SceneMain의 초기화가 완료되었을 때 OnSceneInitialized 메서드를 호출하도록 이벤트에 등록합니다.
|
||||
// 이를 통해 필요한 다른 매니저들이 준비된 후에 로직을 실행할 수 있습니다.
|
||||
SceneMain.Instance.Initialized += OnSceneInitialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 씬의 주요 구성요소들이 초기화된 후 호출되는 메서드입니다.
|
||||
/// </summary>
|
||||
private void OnSceneInitialized()
|
||||
{
|
||||
// FactoryObjectManager의 인스턴스를 가져와 나중에 사용할 수 있도록 저장합니다.
|
||||
dataManager = FactoryObjectManager.Instance;
|
||||
|
||||
//test code
|
||||
//알람 데이터가 AGV와 관련 없는것이 많아서, AGV 이름을 미리 정의합니다.
|
||||
// 테스트 코드: 실제 데이터가 없을 경우를 대비하여 가상의 AGV 이름 목록을 생성합니다.
|
||||
// 현재는 알람 데이터에 AGV 정보가 없는 경우가 많아, 임시로 AGV 이름을 할당하기 위해 사용됩니다.
|
||||
for (int i = 1; i <= 115; i++)
|
||||
{
|
||||
//agvNames.Add($"HFF09CNA8{i.ToString("D3")}");
|
||||
@@ -49,14 +66,14 @@ namespace UVC.Factory.Alarm
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Alarm 데이터를 수신하기 위한 MQTT 파이프라인을 설정합니다.
|
||||
/// MQTT로부터 알람 데이터를 수신하기 위한 파이프라인을 설정하고 실행합니다.
|
||||
/// 이 메서드는 외부에서 호출되어야 알람 데이터 수신이 시작됩니다.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
//데이터를 어떤 형식으로 받을지 정의합니다.
|
||||
// 1. 데이터 마스크(DataMask) 정의:
|
||||
// 수신할 데이터의 구조를 미리 정의합니다. 여기서 정의된 키(Key)들을 기준으로 데이터를 파싱합니다.
|
||||
var dataMask = new DataMask();
|
||||
dataMask.ObjectName = "Alarm"; // Alarm 객체의 이름을 설정합니다.
|
||||
dataMask.ObjectIdKey = "ID"; // Alarm의 고유 식별자로 사용할 키를 설정합니다.
|
||||
@@ -79,35 +96,39 @@ namespace UVC.Factory.Alarm
|
||||
dataMask["UPDATE_TIME"] = DateTime.Now;
|
||||
dataMask["TIMESTAMP"] = DateTime.Now;
|
||||
|
||||
// MQTT 파이프라인 정보를 생성합니다.
|
||||
// 'ALARM' 토픽을 구독하고, 받은 데이터는 위에서 정의한 dataMask로 매핑하며,
|
||||
// 데이터 유효성 검사를 위해 DataValidator를 설정합니다.
|
||||
// 데이터가 업데이트되면 OnUpdateData 메서드를 호출하여 처리합니다.
|
||||
|
||||
// 2. 데이터 유효성 검사기(DataValidator) 설정:
|
||||
// 수신된 데이터가 유효한지 검사하는 규칙을 추가합니다.
|
||||
// 여기서는 "MACHINENAME" 필드가 null이 아닌지 확인합니다.
|
||||
DataValidator validator = new DataValidator();
|
||||
validator.AddValidator("MACHINENAME", value => value != null);
|
||||
|
||||
// 3. MQTT 파이프라인 정보(MQTTPipeLineInfo) 생성:
|
||||
// - "ALARM" 토픽을 구독합니다.
|
||||
// - 위에서 정의한 dataMask를 사용해 수신된 JSON 데이터를 DataObject로 변환합니다.
|
||||
// - validator를 사용해 데이터의 유효성을 검사합니다.
|
||||
// - 유효한 데이터가 수신되면 OnUpdateData 메서드를 호출하여 처리합니다.
|
||||
var pipelineInfo = new MQTTPipeLineInfo("ALARM")
|
||||
.setDataMapper(new DataMapper(dataMask))
|
||||
.setValidator(validator)
|
||||
.setHandler(OnUpdateData);
|
||||
|
||||
// 생성한 파이프라인 정보를 전역 MQTT 파이프라인에 추가합니다.
|
||||
// 4. 생성한 파이프라인을 전역 MQTT 파이프라인에 추가하여 데이터 수신을 시작합니다.
|
||||
AppMain.Instance.MQTTPipeLine.Add(pipelineInfo);
|
||||
}
|
||||
|
||||
// 테스트용으로, 새로 발생하는 알람에 AGV ID를 순차적으로 할당하기 위한 인덱스입니다.
|
||||
int agvIdx = 50;
|
||||
|
||||
/// <summary>
|
||||
/// 데이터 수신 시 호출되는 공개 핸들러입니다.
|
||||
/// 수신된 ALARM 데이터 배열을 비동기적으로 처리하여 씬에 반영합니다.
|
||||
/// 추가, 제거, 수정된 ALARM 데이터를 각각 구분하여 처리합니다.
|
||||
/// MQTT 파이프라인으로부터 데이터가 업데이트될 때마다 호출되는 메인 핸들러입니다.
|
||||
/// 수신된 알람 데이터 배열을 분석하여 추가, 수정, 제거된 알람을 각각 처리합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">수신된 데이터 객체 (DataArray 형태)</param>
|
||||
/// <param name="data">수신된 데이터 객체 (DataArray 형태여야 함)</param>
|
||||
public void OnUpdateData(IDataObject? data)
|
||||
{
|
||||
if (data == null) return;
|
||||
|
||||
// 데이터를 DataArray 형태로 변환합니다. 아니거나 비어있으면 처리를 중단합니다.
|
||||
DataArray? arr = data as DataArray;
|
||||
if (arr == null || arr.Count == 0) return;
|
||||
|
||||
@@ -118,7 +139,8 @@ namespace UVC.Factory.Alarm
|
||||
|
||||
Debug.Log($"AlarmManager OnUpdateData: Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
|
||||
|
||||
// clear_time이 있는 항목만 제거 리스트에 추가합니다.
|
||||
// 'CLEAR_TIME'이 설정된 항목은 '해제된 알람'으로 간주하고, 제거 리스트에 추가합니다.
|
||||
// AddedItems나 ModifiedList에 포함되어 있더라도 CLEAR_TIME이 있으면 즉시 해제 처리하기 위함입니다.
|
||||
foreach (var item in AddedItems.ToList())
|
||||
{
|
||||
if (item.GetDateTime("CLEAR_TIME") != null)
|
||||
@@ -126,7 +148,7 @@ namespace UVC.Factory.Alarm
|
||||
if (RemovedItems.FindIndex((i) => i.Id == item.Id) == -1) RemovedItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var item in ModifiedList.ToList())
|
||||
{
|
||||
if (item.GetDateTime("CLEAR_TIME") != null)
|
||||
@@ -134,51 +156,60 @@ namespace UVC.Factory.Alarm
|
||||
if (RemovedItems.FindIndex((i) => i.Id == item.Id) == -1) RemovedItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 새로 추가된 ALARM 처리
|
||||
|
||||
// [처리 1] 새로 추가된 알람 처리
|
||||
foreach (var item in AddedItems.ToList())
|
||||
{
|
||||
// 알람이 해제되지 않았고(CLEAR_TIME == null), 고유 ID가 있는 경우에만 신규 알람으로 처리합니다.
|
||||
if (item.GetDateTime("CLEAR_TIME") == null && !item.Id.IsNullOrEmpty())
|
||||
{
|
||||
item["TRANSPORT_EQP_ID"] = agvNames[agvIdx]; // AGV 이름을 TRANSPORT_EQP_ID에 설정
|
||||
alarmAgvNames.Add(item.Id!, agvNames[agvIdx]);
|
||||
HandleNewAlarm(item);
|
||||
// 테스트 코드: 수신된 알람 데이터에 임시로 AGV ID를 할당합니다.
|
||||
item["TRANSPORT_EQP_ID"] = agvNames[agvIdx];
|
||||
alarmAgvNames.Add(item.Id!, agvNames[agvIdx]); // 알람 ID와 할당된 AGV ID를 매핑하여 저장합니다.
|
||||
HandleNewAlarm(item); // 신규 알람 처리 로직 호출
|
||||
agvIdx++;
|
||||
if(agvIdx >= agvNames.Count) agvIdx = 0; // AGV 이름이 부족할 경우 순환
|
||||
if (agvIdx >= agvNames.Count) agvIdx = 0; // AGV ID를 순환하며 사용합니다.
|
||||
}
|
||||
}
|
||||
|
||||
// 정보가 수정된 ALARM 처리
|
||||
// [처리 2] 정보가 수정된 알람 처리
|
||||
foreach (var item in ModifiedList.ToList())
|
||||
{
|
||||
// 해제되지 않았고, ID가 있으며, 이미 관리 중인 알람인 경우에만 수정 처리합니다.
|
||||
if (item.GetDateTime("CLEAR_TIME") == null && !item.Id.IsNullOrEmpty() && alarmAgvNames.ContainsKey(item.Id!))
|
||||
{
|
||||
item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존 AGV 이름 유지
|
||||
HandleModifyAlarm(item);
|
||||
item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존에 할당된 AGV ID를 유지합니다.
|
||||
HandleModifyAlarm(item); // 수정 알람 처리 로직 호출
|
||||
}
|
||||
}
|
||||
|
||||
// 제거된 ALARM 처리
|
||||
// [처리 3] 제거(해제)된 알람 처리
|
||||
foreach (var item in RemovedItems.ToList())
|
||||
{
|
||||
// ID가 있고, 관리 중인 알람인 경우에만 해제 처리합니다.
|
||||
if (!item.Id.IsNullOrEmpty() && alarmAgvNames.ContainsKey(item.Id!))
|
||||
{
|
||||
item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존 AGV 이름 유지
|
||||
HandleClearedAlarm(item);
|
||||
item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존 AGV ID를 설정하여 어떤 UI를 제거할지 찾도록 합니다.
|
||||
HandleClearedAlarm(item); // 해제 알람 처리 로직 호출
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AlarmManager가 파괴될 때 호출됩니다.
|
||||
/// MQTT 파이프라인에서 'ALARM' 핸들러를 제거하여 메모리 누수를 방지합니다.
|
||||
/// 등록했던 MQTT 파이프라인 핸들러를 제거하여 메모리 누수 및 원치 않는 동작을 방지합니다.
|
||||
/// </summary>
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
// AppMain의 MQTTPipeLine에서 "ALARM" 토픽에 대한 핸들러를 제거합니다.
|
||||
AppMain.Instance.MQTTPipeLine.Remove("ALARM");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 새로운 알람 데이터를 받아 UI를 생성하고 관리 목록에 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">새로운 알람 정보</param>
|
||||
public void HandleNewAlarm(DataObject data)
|
||||
{
|
||||
if (data.Id == null)
|
||||
@@ -187,17 +218,20 @@ namespace UVC.Factory.Alarm
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 알람 데이터에서 설비 ID(TRANSPORT_EQP_ID)를 가져옵니다.
|
||||
string? eqpId = data.GetString("TRANSPORT_EQP_ID");
|
||||
if (eqpId == null) return;
|
||||
|
||||
// 설비 ID를 이용해 씬에서 실제 FactoryObject를 찾습니다.
|
||||
FactoryObject? targetObject = dataManager!.FindByName(eqpId);
|
||||
Debug.Log($"AlarmManager {targetObject==null} {data.Id}, {eqpId}");
|
||||
Debug.Log($"AlarmManager {targetObject == null} {data.Id}, {eqpId}");
|
||||
if (targetObject == null) return;
|
||||
|
||||
|
||||
// 해당 설비에 이미 활성화된 알람 UI가 있는지 확인합니다.
|
||||
if (activeAlarmUIs.ContainsKey(eqpId))
|
||||
{
|
||||
if(activeAlarmUIs[eqpId].ContainsAlarm(data))
|
||||
// 이미 UI가 있다면, 해당 UI에 새로운 알람 정보를 추가하거나 업데이트합니다.
|
||||
if (activeAlarmUIs[eqpId].ContainsAlarm(data))
|
||||
{
|
||||
activeAlarmUIs[eqpId].UpdateAlarm(data);
|
||||
}
|
||||
@@ -208,17 +242,22 @@ namespace UVC.Factory.Alarm
|
||||
}
|
||||
else
|
||||
{
|
||||
// 없으면 새로 생성
|
||||
// 활성화된 UI가 없다면 새로 생성합니다.
|
||||
GameObject newUIObject = Instantiate(alarmUIPrefab, transform); // 매니저 하위에 생성
|
||||
AlarmUIController newUiController = newUIObject.GetComponent<AlarmUIController>();
|
||||
newUiController.Initialize(targetObject, data);
|
||||
AlarmIconManager newUiController = newUIObject.GetComponent<AlarmIconManager>(); // AlarmManager 하위에 UI 생성
|
||||
newUiController.Initialize(targetObject, data); // 찾은 3D 객체와 알람 데이터로 UI 초기화
|
||||
|
||||
// 새로 생성된 UI를 관리 목록에 추가합니다.
|
||||
activeAlarmUIs.Add(eqpId, newUiController);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 알람의 정보가 변경되었을 때 UI를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">수정된 알람 정보</param>
|
||||
public void HandleModifyAlarm(DataObject data)
|
||||
{
|
||||
if (data.Id == null)
|
||||
@@ -229,22 +268,26 @@ namespace UVC.Factory.Alarm
|
||||
string? eqpId = data.GetString("TRANSPORT_EQP_ID");
|
||||
if (eqpId == null) return;
|
||||
|
||||
// 이미 해당 설비에 알람 UI가 떠 있는지 확인
|
||||
if (activeAlarmUIs.TryGetValue(eqpId, out AlarmUIController uiController))
|
||||
// 해당 설비에 연결된 알람 UI가 있는지 확인합니다.
|
||||
if (activeAlarmUIs.TryGetValue(eqpId, out AlarmIconManager uiController))
|
||||
{
|
||||
// 있으면 기존 UI에 알람 정보 업데이트
|
||||
// 이미 존재하는 알람 정보가 수정된 것이라면 업데이트합니다.
|
||||
if (uiController.ContainsAlarm(data))
|
||||
{
|
||||
uiController.UpdateAlarm(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 없으면 새로 추가
|
||||
// 드물지만, 수정 목록에 왔으나 새로운 알람인 경우 추가합니다.
|
||||
uiController.AddAlarm(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 해제된 알람을 받아 해당하는 UI를 제거하거나 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">해제된 알람 정보</param>
|
||||
public void HandleClearedAlarm(DataObject data)
|
||||
{
|
||||
if (data.Id.IsNullOrEmpty())
|
||||
@@ -256,15 +299,19 @@ namespace UVC.Factory.Alarm
|
||||
string? eqpId = data.GetString("TRANSPORT_EQP_ID");
|
||||
if (eqpId == null) return;
|
||||
|
||||
if (activeAlarmUIs.TryGetValue(eqpId, out AlarmUIController uiController))
|
||||
// 해당 설비에 연결된 알람 UI가 있는지 확인합니다.
|
||||
if (activeAlarmUIs.TryGetValue(eqpId, out AlarmIconManager uiController))
|
||||
{
|
||||
// 있으면 기존 UI에 알람 정보 업데이트
|
||||
// UI가 관리하는 알람 목록에 해당 알람이 있는지 확인합니다.
|
||||
if (uiController.ContainsAlarm(data))
|
||||
{
|
||||
// 목록에서 알람을 제거합니다.
|
||||
uiController.RemoveAlarm(data);
|
||||
|
||||
// 만약 해당 UI에 더 이상 표시할 알람이 없다면,
|
||||
if (uiController.GetAlarmCount() == 0)
|
||||
{
|
||||
// 관리 목록에서 제거하고 씬에서 UI 게임 오브젝트를 파괴합니다.
|
||||
activeAlarmUIs.Remove(data.Id!);
|
||||
Destroy(uiController.gameObject);
|
||||
}
|
||||
|
||||
156
Assets/Scripts/UVC/Factory/Alarm/AlarmSigleIconManager.cs
Normal file
156
Assets/Scripts/UVC/Factory/Alarm/AlarmSigleIconManager.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
|
||||
namespace UVC.Factory.Alarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 개별 알람 아이콘 하나를 관리하는 클래스입니다.
|
||||
/// 평소에는 간단한 아이콘(AlarmSingleIcon)으로 표시되고, 클릭하면 상세 정보(AlarmDetailView)를 보여줍니다.
|
||||
/// </summary>
|
||||
public class AlarmSigleIconManager : MonoBehaviour
|
||||
{
|
||||
[Header("UI Components")]
|
||||
[Tooltip("알람을 간략하게 표시하는 아이콘 UI입니다.")]
|
||||
[SerializeField] private AlarmSingleIcon singleIcon;
|
||||
|
||||
[Tooltip("알람 상세 정보를 표시하는 UI입니다.")]
|
||||
[SerializeField] private AlarmDetailView detailView;
|
||||
|
||||
/// <summary>
|
||||
/// 이 아이콘의 상세 보기가 활성화될 때 호출되는 이벤트입니다.
|
||||
/// 상위 관리자인 AlarmIconManager가 다른 아이콘들의 상세 보기를 닫는 등의 관리를 위해 사용합니다.
|
||||
/// </summary>
|
||||
public Action<AlarmSigleIconManager> OnDetail;
|
||||
|
||||
// 이 아이콘이 표시하는 실제 알람 데이터입니다.
|
||||
private DataObject data;
|
||||
// 알람이 발생한 설비의 Transform 정보입니다. (현재 코드에서는 직접 사용되지 않음)
|
||||
private Transform equipmentTransform;
|
||||
|
||||
/// <summary>
|
||||
/// 이 아이콘이 가지고 있는 알람 데이터를 외부에서 읽을 수 있도록 노출하는 프로퍼티입니다.
|
||||
/// </summary>
|
||||
public DataObject Data => data;
|
||||
|
||||
/// <summary>
|
||||
/// 상세 정보 UI가 현재 활성화되어 있는지 여부를 반환합니다.
|
||||
/// </summary>
|
||||
public bool IsDetailViewActive => detailView != null && detailView.gameObject.activeSelf;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 시작될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (singleIcon != null)
|
||||
{
|
||||
// 간단한 아이콘(singleIcon)이 클릭되었을 때 OnPointerClick 메서드가 호출되도록 이벤트를 등록합니다.
|
||||
singleIcon.OnClickHandler += OnPointerClick;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Single Icon이 할당되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 파괴될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 참조를 정리하여 메모리 누수를 방지합니다.
|
||||
data = null;
|
||||
equipmentTransform = null;
|
||||
|
||||
if (singleIcon != null)
|
||||
{
|
||||
// Start에서 등록했던 클릭 이벤트를 해제합니다.
|
||||
singleIcon.OnClickHandler -= OnPointerClick;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Single Icon is not assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 아이콘에 표시할 알람 데이터를 설정하거나 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="newData">설정할 새로운 알람 데이터</param>
|
||||
/// <param name="equipment">알람이 발생한 설비의 Transform</param>
|
||||
public void SetData(DataObject newData, Transform equipment)
|
||||
{
|
||||
// 데이터 업데이트
|
||||
if (data == null)
|
||||
{
|
||||
data = newData;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 기존 데이터가 있는 경우, 새로운 데이터의 내용으로 덮어씁니다.
|
||||
foreach (var keyValue in newData)
|
||||
{
|
||||
if (data.ContainsKey(keyValue.Key))
|
||||
{
|
||||
data[keyValue.Key] = keyValue.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
equipmentTransform = equipment;
|
||||
|
||||
// 데이터에서 아이콘 정보를 가져와 UI 텍스트로 표시합니다.
|
||||
string icon = data.GetString("ICON");
|
||||
if (singleIcon.Text != null && icon != null)
|
||||
{
|
||||
singleIcon.Text = icon;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상세 정보 UI를 보여줍니다.
|
||||
/// </summary>
|
||||
private void ShowDetail()
|
||||
{
|
||||
// 이미 상세 보기가 활성화되어 있다면 아무것도 하지 않습니다.
|
||||
if (detailView.gameObject.activeSelf) return;
|
||||
|
||||
// 상세 보기를 열었다는 사실을 외부(AlarmIconManager)에 알립니다.
|
||||
OnDetail?.Invoke(this);
|
||||
|
||||
// 이 아이콘이 다른 아이콘들보다 위에 그려지도록 렌더링 순서를 맨 뒤로 보냅니다.
|
||||
// (Unity UI에서 RectTransform의 계층 순서가 뒤에 있을수록 화면에 위에 그려짐)
|
||||
transform.SetAsLastSibling();
|
||||
|
||||
// 상세 보기 UI를 활성화하고, 알람 데이터를 전달하여 내용을 채웁니다.
|
||||
detailView.gameObject.SetActive(true);
|
||||
detailView.Show(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상세 정보 UI를 숨깁니다.
|
||||
/// </summary>
|
||||
public void HideDetail()
|
||||
{
|
||||
if (detailView != null)
|
||||
{
|
||||
detailView.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이콘(singleIcon)이 클릭되었을 때 호출되는 메서드입니다.
|
||||
/// </summary>
|
||||
public void OnPointerClick()
|
||||
{
|
||||
if (detailView == null)
|
||||
{
|
||||
Debug.LogWarning("Detail view or text is not assigned.");
|
||||
return;
|
||||
}
|
||||
// 상세 보기를 보여주는 메서드를 호출합니다.
|
||||
ShowDetail();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a651278faa6ca71468e19553474e6bb5
|
||||
@@ -1,51 +1,100 @@
|
||||
using TMPro;
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
using UVC.Util;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.Factory.Alarm
|
||||
{
|
||||
/// <summary>
|
||||
/// 개별 알람을 나타내는 가장 기본적인 UI 컴포넌트입니다.
|
||||
/// 이 컴포넌트는 버튼 기능과 텍스트 표시 기능을 가지고 있으며,
|
||||
/// 상위 관리자인 'AlarmSigleIconManager'에 의해 제어됩니다.
|
||||
/// </summary>
|
||||
public class AlarmSingleIcon : MonoBehaviour
|
||||
{
|
||||
[Tooltip("클릭 가능한 UI 버튼입니다.")]
|
||||
[SerializeField] private Button button;
|
||||
|
||||
[Tooltip("알람 내용을 표시하는 텍스트입니다.")]
|
||||
[SerializeField] private TextMeshProUGUI text;
|
||||
// 버튼 내에 텍스트를 표시하기 위한 TextMeshPro 컴포넌트입니다.
|
||||
private TextMeshProUGUI buttonText;
|
||||
|
||||
private DataObject data;
|
||||
private Transform equipmentTransform;
|
||||
|
||||
public void SetData(DataObject newData, Transform equipment)
|
||||
/// <summary>
|
||||
/// 아이콘에 표시될 텍스트를 가져오거나 설정하는 프로퍼티입니다.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
if (data == null)
|
||||
get => buttonText != null ? buttonText.text : string.Empty;
|
||||
set
|
||||
{
|
||||
data = newData;
|
||||
if (buttonText != null)
|
||||
{
|
||||
buttonText.text = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 버튼이 클릭되었을 때 호출될 이벤트를 정의합니다.
|
||||
/// 상위 관리자(AlarmSigleIconManager)가 이 이벤트를 구독하여 클릭 시 특정 동작(예: 상세 정보 표시)을 수행합니다.
|
||||
/// </summary>
|
||||
public Action OnClickHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 생성될 때 가장 먼저 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
buttonText = GetComponentInChildren<TextMeshProUGUI>();
|
||||
if (buttonText == null)
|
||||
{
|
||||
Debug.LogWarning("Text component not found in children.", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 첫 번째 프레임 업데이트 전에 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
// 버튼의 onClick 이벤트에 HandleClick 메서드를 리스너로 등록합니다.
|
||||
// 이렇게 하면 버튼이 클릭될 때마다 HandleClick 메서드가 실행됩니다.
|
||||
button.onClick.AddListener(HandleClick);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var keyValue in newData)
|
||||
{
|
||||
if (data.ContainsKey(keyValue.Key))
|
||||
{
|
||||
data[keyValue.Key] = keyValue.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
equipmentTransform = equipment;
|
||||
|
||||
string icon = data.GetString("ICON");
|
||||
if (text != null && icon != null)
|
||||
{
|
||||
text.text = icon;
|
||||
Debug.LogWarning("Button is not assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerClick()
|
||||
/// <summary>
|
||||
/// 버튼 클릭 이벤트가 발생했을 때 실행되는 메서드입니다.
|
||||
/// </summary>
|
||||
private void HandleClick()
|
||||
{
|
||||
// 클릭 시 해당 설비로 카메라 포커스
|
||||
CameraController.Instance.FocusOnTarget(equipmentTransform.position, 3.0f);
|
||||
Debug.Log($"알람 [{data.GetString("MESSAGE")}]이 발생한 설비로 이동합니다.");
|
||||
// 여기서 알람 상세정보 패널을 띄워도 좋음
|
||||
// OnClickHandler 이벤트에 등록된 메서드가 있는지 확인하고, 있다면 호출합니다.
|
||||
// '?'는 Null 조건부 연산자로, OnClickHandler가 null이 아닐 경우에만 Invoke()를 실행합니다.
|
||||
OnClickHandler?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 파괴될 때 호출되는 Unity 생명주기 메서드입니다.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
// Start에서 등록했던 클릭 이벤트 리스너를 제거합니다.
|
||||
// 이를 통해 메모리 누수를 방지할 수 있습니다.
|
||||
button.onClick.RemoveListener(HandleClick);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Button is not assigned.");
|
||||
}
|
||||
// 이벤트 핸들러 참조를 null로 만들어 잠재적인 메모리 문제를 방지합니다.
|
||||
OnClickHandler = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd395cde180b3094a91d87699139bbcf
|
||||
guid: 850e5c55cc9c41a419204fc6e16ae821
|
||||
@@ -1,286 +0,0 @@
|
||||
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 alarmSingleIconPrefab; // 개별 알람 아이콘
|
||||
[Tooltip("아이콘이 중심에서의 Y Offset")]
|
||||
[SerializeField] private float iconYOffset = 10f;
|
||||
[Tooltip("확장 시 아이콘 간의 간격입니다.")]
|
||||
[SerializeField] private float iconSpacing = 10f;
|
||||
[Tooltip("확장 시 아이콘들의 중심 X 오프셋입니다.")]
|
||||
[SerializeField] private float expandOffsetX = 0f;
|
||||
[Tooltip("확장 시 아이콘들의 중심 Y 오프셋입니다.")]
|
||||
[SerializeField] private float expandOffsetY = 0f;
|
||||
|
||||
private Transform targetObject;
|
||||
private List<DataObject> alarms = new List<DataObject>();
|
||||
private bool isExpanded = false;
|
||||
private AlarmSingleIcon singleAlarmIcon1 = null;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Canvas mainCanvas;
|
||||
|
||||
private Tweener uiSpacingTweener;
|
||||
|
||||
private bool isZoomIn = false; // 줌 인 상태를 추적하기 위한 변수
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
mainCanvas = GetComponentInParent<Canvas>();
|
||||
}
|
||||
|
||||
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 += iconYOffset;
|
||||
rectTransform.localPosition = localPoint;
|
||||
}
|
||||
// Canvas Render Mode가 Screen Space - Overlay일 경우
|
||||
else
|
||||
{
|
||||
screenPoint.y += iconYOffset;
|
||||
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 bool ContainsAlarm(DataObject alarm)
|
||||
{
|
||||
return alarms.Exists(a => a.Id == alarm.Id);
|
||||
}
|
||||
|
||||
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(alarmSingleIconPrefab, transform).GetComponent<AlarmSingleIcon>();
|
||||
}
|
||||
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);
|
||||
alarmCountText.gameObject.SetActive(false);
|
||||
|
||||
// 기존 아이콘들 삭제
|
||||
foreach (Transform child in expandedView.transform)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
// alarmSingleIconPrefab의 RectTransform을 가져와 아이콘의 너비를 계산합니다.
|
||||
RectTransform iconRect = alarmSingleIconPrefab.GetComponent<RectTransform>();
|
||||
if (iconRect == null)
|
||||
{
|
||||
Debug.LogError("alarmSingleIconPrefab에 RectTransform 컴포넌트가 없습니다.");
|
||||
return;
|
||||
}
|
||||
float iconWidth = iconRect.rect.width;
|
||||
|
||||
// 아이콘의 개수와 너비, 간격을 고려하여 필요한 원주를 계산합니다.
|
||||
float circumference = (iconWidth + iconSpacing) * alarms.Count;
|
||||
|
||||
// 원주를 이용하여 반지름(radius)을 계산합니다.
|
||||
float radius = circumference / (2f * Mathf.PI);
|
||||
|
||||
// 중심 오프셋을 적용할 Vector3를 생성합니다.
|
||||
Vector3 centerOffset = new Vector3(expandOffsetX, expandOffsetY, 0);
|
||||
|
||||
for (int i = 0; i < alarms.Count; i++)
|
||||
{
|
||||
float angle = i * Mathf.PI * 2f / alarms.Count;
|
||||
// 오프셋을 적용하여 아이콘의 위치를 계산합니다.
|
||||
Vector3 pos = centerOffset + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * radius;
|
||||
|
||||
GameObject iconObj = Instantiate(alarmSingleIconPrefab, expandedView.transform);
|
||||
iconObj.transform.localPosition = pos;
|
||||
// iconObj의 AlarmSingleIcon 스크립트에 알람 데이터 전달
|
||||
iconObj.GetComponent<AlarmSingleIcon>().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(iconYOffset, targetSpacing, duration, (value) =>
|
||||
{
|
||||
iconYOffset = value;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (uiSpacingTweener != null && uiSpacingTweener.IsActive() && uiSpacingTweener.IsPlaying())
|
||||
{
|
||||
uiSpacingTweener.Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 773fa02b59601044b8be752f78f63e55
|
||||
Reference in New Issue
Block a user