Files
XRLib/Assets/Scripts/UVC/Factory/Component/AGV.cs
2025-06-25 18:50:19 +09:00

187 lines
9.4 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UVC.Data;
namespace UVC.Factory.Component
{
/// <summary>
/// 씬에 표시되는 개별 AGV(무인 운반차)를 제어하는 클래스입니다.
/// FactoryObject를 상속받아, AGV의 데이터 처리, 3D 모델의 이동 및 회전, 정보 표시 기능을 구현합니다.
/// </summary>
/// <remarks>
/// 이 클래스는 AGVManager에 의해 동적으로 생성되고 관리됩니다.
/// AGVManager로부터 실시간 데이터를 받아 ProcessData 메서드에서 처리하고,
/// Unity의 Update 메서드에서 매 프레임마다 부드러운 시각적 이동을 구현합니다.
/// </remarks>
public class AGV : FactoryObject
{
// 서버에서 받은 좌표(예: 밀리미터 단위)를 Unity 씬의 단위(미터)로 변환하기 위한 스케일 값입니다.
// 예를 들어, 서버 좌표 1000이 Unity에서 1미터가 되려면 0.001f로 설정합니다.
private float scaleFactor = 0.0005f; // Unity에서 사용하는 단위로 변환하기 위한 스케일 팩터
// 데이터로부터 수신한 AGV의 목표 위치와 목표 회전값입니다.
// AGV는 현재 위치에서 이 목표 지점을 향해 부드럽게 움직입니다.
private Vector3 targetPosition;
private Quaternion targetRotation;
// 움직임과 회전의 부드러움을 조절할 속도 변수입니다.
// Unity 인스펙터 창에서 실시간으로 값을 조절하며 최적의 움직임을 찾을 수 있습니다.
[Tooltip("목표 지점까지의 이동 속도를 조절합니다.")]
public float moveSpeed = 1.0f;
[Tooltip("목표 방향까지의 회전 속도를 조절합니다.")]
public float rotationSpeed = 2.0f;
[Tooltip("이 거리(미터)를 초과하면 보간 없이 즉시 위치를 변경합니다.")]
public float teleportDistanceThreshold = 5.0f; // 5미터 이상 차이나면 순간이동
/// <summary>
/// AGV 객체가 생성될 때 처음 한 번 호출되는 초기화 메서드입니다.
/// </summary>
private void Start()
{
// 시작 시에는 현재 위치를 목표 위치로 설정하여 의도치 않은 움직임을 방지합니다.
targetPosition = transform.position;
targetRotation = transform.rotation;
// 사용자가 AGV를 클릭했을 때 정보창에 표시될 데이터 항목과 순서를 정의합니다.
DataOrderedMask = new List<string>
{
"VHL_NAME",
"NODE_ID",
"REAL_ID",
"VHL_STATE",
"BATT",
"JOB_ID",
"TIMESTAMP",
};
}
/// <summary>
/// AGVManager로부터 새로운 데이터를 받았을 때 호출되는 핵심 메서드입니다.
/// 받은 데이터를 기반으로 AGV의 내부 상태와 목표 위치를 갱신합니다.
/// </summary>
/// <remarks>
/// 이 메서드는 FactoryObject의 추상 메서드를 재정의한 것입니다.
/// AGV의 위치, 회전, 상태 등 모든 동적인 정보는 이 메서드를 통해 업데이트됩니다.
/// </remarks>
/// <param name="newData">AGV의 최신 정보가 담긴 데이터 객체입니다.</param>
protected override void ProcessData(DataObject newData)
{
// 처음 데이터를 받는 경우 (data가 null일 때)
if (data == null)
{
// 새 데이터로 위치와 회전을 즉시 설정합니다.
UpdatePositionAndRotation(newData);
// 받은 데이터를 내부 데이터 저장소에 저장합니다.
data = newData;
// 시작 시에는 현재 위치를 목표 위치로 설정하여 움직이지 않도록 합니다.
targetPosition = transform.position;
targetRotation = transform.rotation;
}
else // 이미 데이터가 있는 경우 (업데이트)
{
// 새 데이터를 기반으로 목표 위치와 회전을 갱신합니다.
UpdatePositionAndRotation(newData);
// 기존 데이터(data)에 새로운 데이터(newData)의 내용을 덮어씁니다.
foreach (var keyValue in newData)
{
if (data.ContainsKey(keyValue.Key))
{
data[keyValue.Key] = keyValue.Value;
}
}
}
}
/// <summary>
/// 데이터 객체로부터 위치(X, Y) 및 각도(DEGREE) 값을 읽어와 AGV의 목표 위치와 회전을 설정합니다.
/// </summary>
/// <param name="newData">위치와 각도 정보가 포함된 데이터 객체입니다.</param>
private void UpdatePositionAndRotation(DataObject newData)
{
// 처음 데이터를 받는 경우, 받은 데이터로 즉시 위치를 설정합니다.
if (data == null)
{
float x = newData.GetFloat("X") * scaleFactor;
float y = newData.GetFloat("Y") * scaleFactor;
Quaternion rotation = Quaternion.Euler(0, newData.GetFloat("DEGREE"), 0);
transform.position = new Vector3(x, 0, y);
transform.rotation = rotation;
}
else // 이후 업데이트의 경우
{
// 새 데이터로부터 목표 위치와 회전값을 계산합니다.
float x = data.GetFloat("X") * scaleFactor;
float y = data.GetFloat("Y") * scaleFactor;
Quaternion rotation = Quaternion.Euler(0, data.GetFloat("DEGREE"), 0);
float newX = newData.GetFloat("X") * scaleFactor;
float newY = newData.GetFloat("Y") * scaleFactor;
Quaternion newRotation = Quaternion.Euler(0, newData.GetFloat("DEGREE"), 0);
Vector3 newTargetPosition = new Vector3(x, transform.position.y, y);
Quaternion newTargetRotation = rotation;
if (x != newX || y != newY) newTargetPosition = new Vector3(newX, transform.position.y, newY);
if (rotation != newRotation) newTargetRotation = newRotation;
// 현재 위치와 새로운 목표 위치 사이의 거리를 계산합니다.
float distanceToTarget = Vector3.Distance(transform.position, newTargetPosition);
// 거리가 설정된 임계값을 초과하면, 보간을 건너뛰고 즉시 위치/회전을 설정합니다.
if (distanceToTarget > teleportDistanceThreshold)
{
transform.position = newTargetPosition;
transform.rotation = newTargetRotation;
}
// 새로운 목표 지점을 설정합니다.
// (순간이동을 했든 안 했든, 다음 프레임부터의 보간을 위해 목표 지점은 항상 갱신되어야 합니다.)
this.targetPosition = newTargetPosition;
this.targetRotation = newTargetRotation;
}
}
/// <summary>
/// Unity에 의해 매 프레임마다 호출되는 메서드입니다.
/// AGV의 현재 위치/회전을 목표 위치/회전으로 부드럽게 이동시키는 시각적 처리를 담당합니다.
/// </summary>
void Update()
{
// 현재 위치가 목표 위치와 다를 경우에만 이동 로직을 실행합니다.
if (transform.position != targetPosition)
{
// 목표 지점과의 거리가 매우 가까우면 (0.01미터 미만) 그냥 목표 위치로 설정하여 미세한 떨림을 방지합니다.
if (Vector3.Distance(transform.position, targetPosition) < 0.1f)
{
// 현재 위치와 목표 위치 사이의 거리가 임계값을 초과하면 순간이동합니다.
transform.position = targetPosition;
}
else
{
// Vector3.Lerp를 사용하여 현재 위치에서 목표 위치로 부드럽게 이동시킵니다.
// Time.deltaTime * moveSpeed는 프레임 속도에 관계없이 일정한 속도를 보장합니다.
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * moveSpeed);
}
}
// 현재 회전이 목표 회전과 다를 경우에만 회전 로직을 실행합니다.
if (transform.rotation != targetRotation)
{
// 목표 회전과의 각도 차이가 매우 작으면 (0.1도 미만) 그냥 목표 회전으로 설정합니다.
if (Quaternion.Angle(transform.rotation, targetRotation) < 0.1f)
{
transform.rotation = targetRotation;
}
else
{
// Quaternion.Slerp를 사용하여 현재 회전에서 목표 회전으로 부드럽게 회전시킵니다.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
}
}
}
}
}