302 lines
11 KiB
C#
302 lines
11 KiB
C#
using AZTECHWB.Command;
|
|
using AZTECHWB.Constants;
|
|
using AZTECHWB.Core;
|
|
using AZTECHWB.Extensions;
|
|
using Cysharp.Threading.Tasks;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace AZTECHWB.Management
|
|
{
|
|
public class MachineStatusItemManager : Manager
|
|
{
|
|
[Header("References")]
|
|
private MachineStatusItem machineStatusItem;
|
|
|
|
[Header("Position Settings")]
|
|
public Vector3 worldOffset = new Vector3(0, 1.5f, 0);
|
|
public float verticalPadding = 5f;
|
|
public float overlapThreshold = 2f;
|
|
public float smoothSpeed = 10f;
|
|
|
|
[Header("Visibility Settings")]
|
|
public LayerMask occlusionLayer;
|
|
public float occlusionCheckInterval = 0.1f;
|
|
|
|
private Dictionary<MachineStatusItem, Vector2> smoothedPositions = new Dictionary<MachineStatusItem, Vector2>();
|
|
private float occlusionTimer;
|
|
|
|
[Header("Scale Settings")]
|
|
public float minScale = 0.5f; // 가장 멀 때 크기
|
|
public float maxScale = 1.2f; // 가장 가까울 때 크기
|
|
public float scaleSmoothing = 5f; // 스케일 변화 스무딩 속도
|
|
private float maxDistance = 50f; // 스케일 계산 기준 최대 거리
|
|
|
|
private Dictionary<MachineStatusItem, float> smoothedScales = new Dictionary<MachineStatusItem, float>();
|
|
private Dictionary<string, MachineStatusItem> machineIcons = new();
|
|
private Dictionary<MachineStatusItem, Machine> iconToMachines = new();
|
|
private OrbitalController controller;
|
|
private Machine[] machines;
|
|
private bool activeIconEnable;
|
|
|
|
public override async UniTask Init()
|
|
{
|
|
machineStatusItem = Resources.Load<MachineStatusItem>($"{ResourceURL.uiPrefabFolderPath}{nameof(MachineStatusItem)}");
|
|
|
|
controller = AZTECHAppMain.Instance.cameraController;
|
|
machines = AZTECHSceneMain.Instance.building.machineList.ToArray();
|
|
|
|
maxDistance = controller.maxDistance;
|
|
|
|
await UniTask.CompletedTask;
|
|
}
|
|
|
|
public void SetInteractableIcons(bool isActive)
|
|
{
|
|
foreach (var machineIcon in machineIcons.Values)
|
|
{
|
|
machineIcon.isInteractable = isActive;
|
|
}
|
|
}
|
|
public void SetActiveIcons(bool isActive)
|
|
{
|
|
activeIconEnable = isActive;
|
|
|
|
if(isActive)
|
|
return;
|
|
|
|
foreach (var machineIcon in machineIcons.Values)
|
|
{
|
|
machineIcon.Deactive();
|
|
}
|
|
}
|
|
|
|
|
|
public void SetMachineData(List<CompleteInfo> machineData)
|
|
{
|
|
foreach (var data in machineData)
|
|
{
|
|
var machine = FindMachineWithCode(data.worknm);
|
|
|
|
if (machine == null)
|
|
continue;
|
|
|
|
if (machineIcons.ContainsKey(data.worknm))
|
|
{
|
|
machineIcons[data.worknm].SetData(data);
|
|
continue;
|
|
}
|
|
|
|
var machineIcon = Instantiate(machineStatusItem, transform);
|
|
machineIcon.Init(machine);
|
|
machineIcon.SetData(data);
|
|
machineIcon.onClickIcon += OnClickMachineKPI;
|
|
|
|
machineIcons.Add(data.worknm, machineIcon);
|
|
machineIcon.Deactive();
|
|
|
|
machine.machineStatusItem = machineIcon;
|
|
iconToMachines.Add(machineIcon, machine);
|
|
}
|
|
}
|
|
|
|
private void OnClickMachineKPI(MachineStatusItem machineKPI)
|
|
{
|
|
machineKPI.transform.SetAsLastSibling();
|
|
var currentMachine = iconToMachines[machineKPI];
|
|
|
|
new OpenMachineDetailDashboardPanelCommand(currentMachine, machineKPI.data).Execute();
|
|
}
|
|
|
|
private Machine FindMachineWithCode(string worknm)
|
|
{
|
|
Machine machine = null;
|
|
for (int i = 0; i < machines.Length; i++)
|
|
{
|
|
if (machines[i].machineName == worknm)
|
|
{
|
|
machine = machines[i];
|
|
break;
|
|
}
|
|
}
|
|
return machine;
|
|
}
|
|
private void LateUpdate()
|
|
{
|
|
if (machineIcons.Values.Count == 0 || controller.Camera == null)
|
|
return;
|
|
|
|
List<MachineStatusItem> allLabels = new List<MachineStatusItem>(machineIcons.Values);
|
|
|
|
// 1단계: 전체 라벨 스크린 위치 & 거리 계산
|
|
UpdateScreenData(allLabels);
|
|
|
|
// 2단계: 활성화 여부 판정 (화면 이탈 + 가려짐)
|
|
occlusionTimer += Time.deltaTime;
|
|
if (occlusionTimer >= occlusionCheckInterval)
|
|
{
|
|
UpdateVisibility(allLabels);
|
|
occlusionTimer = 0f;
|
|
}
|
|
// 3단계: 활성화된 라벨만 카메라 거리순 정렬
|
|
List<MachineStatusItem> visibleLabels = allLabels.FindAll(l => l.gameObject.activeSelf);
|
|
visibleLabels.Sort((a, b) => a.distanceToCamera.CompareTo(b.distanceToCamera));
|
|
|
|
// 4단계: 거리에 따른 UI 크기 조정
|
|
UpdateScales(visibleLabels);
|
|
// 5단계: 겹침 해소 → 목표 위치 계산
|
|
Dictionary<MachineStatusItem, Vector2> targetPositions = ResolveOverlaps(visibleLabels);
|
|
// 6단계: 스무딩 적용 후 RectTransform에 반영
|
|
ApplyPositions(targetPositions);
|
|
}
|
|
|
|
void UpdateScreenData(List<MachineStatusItem> labels)
|
|
{
|
|
foreach (var label in labels)
|
|
{
|
|
if (label.targetTransform == null) continue;
|
|
|
|
Vector3 worldPos = label.targetTransform.GetMeshCenter() + worldOffset;
|
|
Vector3 screenPos = controller.Camera.WorldToScreenPoint(worldPos);
|
|
|
|
label.screenPosition = new Vector2(screenPos.x, screenPos.y);
|
|
label.distanceToCamera = Vector3.Distance(controller.Camera.transform.position, label.targetTransform.position);
|
|
}
|
|
}
|
|
|
|
void UpdateVisibility(List<MachineStatusItem> labels)
|
|
{
|
|
if (!activeIconEnable) return;
|
|
|
|
foreach (var label in labels)
|
|
{
|
|
// 카메라 뒤쪽 체크
|
|
Vector3 screenPos = controller.Camera.WorldToScreenPoint(label.targetTransform.position + worldOffset);
|
|
if (screenPos.z < 0)
|
|
{
|
|
label.Deactive();
|
|
continue;
|
|
}
|
|
|
|
// 화면 이탈 체크
|
|
if (!IsOnScreen(label.screenPosition))
|
|
{
|
|
label.Deactive();
|
|
continue;
|
|
}
|
|
|
|
// 특정 레이어에 가려짐 체크
|
|
if (IsOccluded(label.targetTransform.position))
|
|
{
|
|
label.Deactive();
|
|
continue;
|
|
}
|
|
|
|
label.Active();
|
|
}
|
|
}
|
|
|
|
bool IsOnScreen(Vector2 screenPos)
|
|
{
|
|
return screenPos.x >= 0 && screenPos.x <= Screen.width && screenPos.y >= 0 && screenPos.y <= Screen.height;
|
|
}
|
|
|
|
bool IsOccluded(Vector3 targetWorldPos)
|
|
{
|
|
Vector3 camPos = controller.Camera.transform.position;
|
|
Vector3 direction = targetWorldPos - camPos;
|
|
float distance = direction.magnitude;
|
|
|
|
return Physics.Raycast(camPos, direction.normalized, distance - 0.1f, occlusionLayer);
|
|
}
|
|
|
|
Dictionary<MachineStatusItem, Vector2> ResolveOverlaps(List<MachineStatusItem> visibleLabels)
|
|
{
|
|
Dictionary<MachineStatusItem, Vector2> targetPositions = new Dictionary<MachineStatusItem, Vector2>();
|
|
List<Rect> placedRects = new List<Rect>();
|
|
|
|
foreach (var label in visibleLabels)
|
|
{
|
|
Vector2 size = label.GetSize();
|
|
Vector2 targetPos = label.screenPosition;
|
|
|
|
bool overlapping = true;
|
|
int maxIterations = 20;
|
|
|
|
while (overlapping && maxIterations-- > 0)
|
|
{
|
|
overlapping = false;
|
|
Rect currentRect = GetRect(targetPos, size);
|
|
|
|
foreach (Rect placed in placedRects)
|
|
{
|
|
if (IsOverlapping(currentRect, placed))
|
|
{
|
|
// X는 겹친 라벨의 X로 고정, Y만 위로 밀어올림
|
|
targetPos.x = placed.center.x;
|
|
targetPos.y = placed.yMax + verticalPadding + size.y * 0.5f;
|
|
overlapping = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
targetPositions[label] = targetPos;
|
|
placedRects.Add(GetRect(targetPos, size));
|
|
}
|
|
|
|
return targetPositions;
|
|
}
|
|
|
|
void ApplyPositions(Dictionary<MachineStatusItem, Vector2> targetPositions)
|
|
{
|
|
foreach (var kvp in targetPositions)
|
|
{
|
|
MachineStatusItem label = kvp.Key;
|
|
Vector2 target = kvp.Value;
|
|
|
|
if (!smoothedPositions.ContainsKey(label))
|
|
smoothedPositions[label] = target;
|
|
|
|
smoothedPositions[label] = Vector2.Lerp(smoothedPositions[label],target,Time.deltaTime * smoothSpeed);
|
|
|
|
// Manager가 직접 RectTransform 조작
|
|
label.rectTransform.position = new Vector3(smoothedPositions[label].x, smoothedPositions[label].y, 0);
|
|
}
|
|
}
|
|
|
|
void UpdateScales(List<MachineStatusItem> visibleLabels)
|
|
{
|
|
foreach (var label in visibleLabels)
|
|
{
|
|
// 거리 비율 → 목표 스케일 계산 (원본과 동일한 방식)
|
|
float t = Mathf.InverseLerp(maxDistance, 0f, label.distanceToCamera);
|
|
float targetScale = Mathf.Lerp(minScale, maxScale, t);
|
|
|
|
// 스무딩 초기값 설정
|
|
if (!smoothedScales.ContainsKey(label))
|
|
smoothedScales[label] = targetScale;
|
|
|
|
// Lerp로 부드럽게 스케일 변화
|
|
smoothedScales[label] = Mathf.Lerp(
|
|
smoothedScales[label],
|
|
targetScale,
|
|
Time.deltaTime * scaleSmoothing
|
|
);
|
|
|
|
label.transform.localScale = Vector3.one * smoothedScales[label];
|
|
}
|
|
}
|
|
|
|
Rect GetRect(Vector2 centerPos, Vector2 size)
|
|
{
|
|
return new Rect(centerPos.x - size.x * 0.5f, centerPos.y - size.y * 0.5f, size.x, size.y);
|
|
}
|
|
|
|
bool IsOverlapping(Rect a, Rect b)
|
|
{
|
|
return a.xMin < b.xMax - overlapThreshold && a.xMax > b.xMin + overlapThreshold &&
|
|
a.yMin < b.yMax - overlapThreshold && a.yMax > b.yMin + overlapThreshold;
|
|
}
|
|
}
|
|
} |