agv 개발
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UVC.Extention;
|
||||
using UVC.Log;
|
||||
|
||||
@@ -470,7 +471,7 @@ namespace UVC.Data
|
||||
changedProperies.Clear();
|
||||
foreach (var keyValue in (DataObject)other)
|
||||
{
|
||||
if (!this.ContainsKey(keyValue.Key) || !this[keyValue.Key].Equals(keyValue.Value))
|
||||
if (!this.ContainsKey(keyValue.Key) || this[keyValue.Key] != keyValue.Value)
|
||||
{
|
||||
this[keyValue.Key] = keyValue.Value;
|
||||
changedProperies.Add(keyValue.Key);
|
||||
|
||||
@@ -18,6 +18,49 @@ namespace UVC.Data
|
||||
/// 해당 메시지를 지정된 데이터 매퍼를 통해 변환한 후 핸들러에게 전달합니다.
|
||||
/// 여러 MQTT 토픽을 동시에 관리하고 각각에 대한 처리 방식을 개별적으로 설정할 수 있습니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 1. 데이터 구조를 정의하는 DataMask 생성
|
||||
/// var dataMask = new DataMask();
|
||||
/// dataMask["deviceId"] = "";
|
||||
/// dataMask["temperature"] = 0.0;
|
||||
/// dataMask["humidity"] = 0.0;
|
||||
/// dataMask["timestamp"] = DateTime.Now;
|
||||
///
|
||||
/// // 2. DataMapper 생성
|
||||
/// var dataMapper = new DataMapper(dataMask);
|
||||
///
|
||||
/// // 3. 데이터 처리 핸들러 정의
|
||||
/// Action<IDataObject?> dataHandler = (data) =>
|
||||
/// {
|
||||
/// if (data != null)
|
||||
/// {
|
||||
/// // 데이터 처리 로직
|
||||
/// Console.WriteLine($"Received data: {data.ToJson()}");
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// // 4. MQTTPipeLineInfo 생성 및 설정
|
||||
/// var pipelineInfo = new MQTTPipeLineInfo("sensor/+/data")
|
||||
/// .setDataMapper(dataMapper)
|
||||
/// .setHandler(dataHandler);
|
||||
///
|
||||
/// // 5. MQTTPipeLine 인스턴스 생성
|
||||
/// var mqttPipeline = new MQTTPipeLine("mqtt.eclipseprojects.io", 1883);
|
||||
///
|
||||
/// // 6. 파이프라인 정보 추가
|
||||
/// mqttPipeline.Add(pipelineInfo);
|
||||
///
|
||||
/// // 7. 파이프라인 실행
|
||||
/// mqttPipeline.Execute();
|
||||
///
|
||||
/// // ... 애플리케이션 로직 수행 ...
|
||||
///
|
||||
/// // 8. 파이프라인 중지 및 리소스 해제
|
||||
/// mqttPipeline.Stop();
|
||||
/// mqttPipeline.Dispose();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class MQTTPipeLine
|
||||
{
|
||||
|
||||
@@ -40,6 +83,8 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
public int port;
|
||||
|
||||
private List<string> topics = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 토픽별 파이프라인 정보를 저장하는 딕셔너리
|
||||
/// </summary>
|
||||
@@ -61,10 +106,24 @@ namespace UVC.Data
|
||||
{
|
||||
this.domain = string.IsNullOrEmpty(domain) ? Constants.MQTT_DOMAIN : domain;
|
||||
this.port = port;
|
||||
mqtt = new MQTTService(Constants.MQTT_DOMAIN, Constants.MQTT_PORT);
|
||||
mqtt = new MQTTService(this.domain, this.port);
|
||||
infoList = new Dictionary<string, MQTTPipeLineInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 컬렉션에 주제가 없으면 추가합니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 컬렉션에 중복된 주제가 추가되지 않도록 합니다. 지정된 주제가 이미 있으면
|
||||
/// 컬렉션은 변경되지 않습니다.</remarks>
|
||||
/// <param name="topic">컬렉션에 추가할 주제입니다. null이거나 비어 있을 수 없습니다.</param>
|
||||
public void AddTopic(string topic)
|
||||
{
|
||||
if (!topics.Contains(topic))
|
||||
{
|
||||
topics.Add(topic);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 토픽에 대한 파이프라인 정보를 추가합니다.
|
||||
/// </summary>
|
||||
@@ -103,7 +162,7 @@ namespace UVC.Data
|
||||
{
|
||||
if (!UseMockup)
|
||||
{
|
||||
foreach (var topic in infoList.Keys)
|
||||
foreach (var topic in topics)
|
||||
{
|
||||
mqtt.AddTopicHandler(topic, OnTopicMessage);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ namespace UVC.Data
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var dataMask = new DataMask();
|
||||
/// dataMask["deviceId"] = "";
|
||||
/// dataMask["temperature"] = 0.0;
|
||||
/// dataMask["humidity"] = 0.0;
|
||||
/// dataMask["timestamp"] = DateTime.Now;
|
||||
///
|
||||
/// var pipelineInfo = new MQTTPipeLineInfo("device/status")
|
||||
/// .setDataMapper(new DataMapper(dataMask))
|
||||
/// .setHandler(data => Console.WriteLine(data));
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace UVC.Data
|
||||
/// URL을 저장하는 내부 Dictionary 컬렉션
|
||||
/// </summary>
|
||||
private static Dictionary<string, string> urls = new Dictionary<string, string>();
|
||||
public static IReadOnlyDictionary<string, string> Urls => urls;
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 키와 URL을 컬렉션에 추가합니다. 키가 이미 존재하면 URL을 업데이트합니다.
|
||||
|
||||
8
Assets/Scripts/UVC/Factory.meta
Normal file
8
Assets/Scripts/UVC/Factory.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 389dc5701e61e5b49b6504ff46501a0c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
78
Assets/Scripts/UVC/Factory/AGV.cs
Normal file
78
Assets/Scripts/UVC/Factory/AGV.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
|
||||
namespace UVC.Factory
|
||||
{
|
||||
public class AGV: FactoryObject
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
DataOrderedMask = new List<string>
|
||||
{
|
||||
"Name",
|
||||
"Type",
|
||||
"Status",
|
||||
"BatteryLevel",
|
||||
"Location",
|
||||
"LastMaintenance"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 내부 상태를 업데이트하고 정렬된 마스크에 정의된 특정 키에 대한 작업을 수행하여 제공된 데이터 객체를 처리합니다.
|
||||
// </summary>
|
||||
/// <remarks>이 메서드는 초기화되지 않은 경우 내부 데이터 상태를 업데이트하고, 제공된
|
||||
/// <see cref="DataOrderedMask"/>에 정의된 키를 반복하여 제공된
|
||||
/// <paramref name="newData"/> 객체의 일치하는 항목에 대한 작업을 수행합니다. <paramref name="newData"/>에 처리에 필요한 키가 포함되어 있는지
|
||||
/// 확인합니다.</remarks>
|
||||
/// <param name="newData">처리할 데이터 객체입니다. null이 아니어야 하며 정렬된 마스크와 관련된 키-값 쌍을 포함해야 합니다.
|
||||
protected override void ProcessData(DataObject newData)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
UpdatePositionAndRotation(newData);
|
||||
data = newData;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePositionAndRotation(newData);
|
||||
foreach (var keyValue in newData)
|
||||
{
|
||||
if (data.ContainsKey(keyValue.Key))
|
||||
{
|
||||
data[keyValue.Key] = keyValue.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionAndRotation(DataObject newData)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
float x = newData.GetFloat("X") * .001f;
|
||||
float y = newData.GetFloat("Y") * .001f;
|
||||
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") * .001f;
|
||||
float y = data.GetFloat("Y") * .001f;
|
||||
Quaternion rotation = Quaternion.Euler(0, data.GetFloat("DEGREE"), 0);
|
||||
float x2 = newData.GetFloat("X") * .001f;
|
||||
float y2 = newData.GetFloat("Y") * .001f;
|
||||
Quaternion rotation2 = Quaternion.Euler(0, newData.GetFloat("DEGREE"), 0);
|
||||
|
||||
if (x != x2 || y != y2) transform.position = new Vector3(x, 0, y);
|
||||
if (rotation != rotation2) transform.rotation = rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Factory/AGV.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/AGV.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80f19a8fc3a570b46b06e92624a35168
|
||||
160
Assets/Scripts/UVC/Factory/AGVManager.cs
Normal file
160
Assets/Scripts/UVC/Factory/AGVManager.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using SampleProject;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UVC.Core;
|
||||
using UVC.Data;
|
||||
|
||||
namespace UVC.Factory
|
||||
{
|
||||
public class AGVManager : SingletonScene<AGVManager>
|
||||
{
|
||||
|
||||
private readonly string prefabPath = "Prefabs/SampleProject/Factory/AGV";
|
||||
private GameObject? prefab;
|
||||
|
||||
|
||||
|
||||
private List<AGV> agvList;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AGVManager의 초기화 메서드입니다.
|
||||
/// Awake 메서드에서 호출되며, MonoBehaviour가 생성될 때 한 번만 실행됩니다.
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
agvList = new List<AGV>();
|
||||
SceneMain.Instance.Initialized += OnSceneInitialized;
|
||||
}
|
||||
|
||||
private void OnSceneInitialized()
|
||||
{
|
||||
//데이터를 어떤 형식으로 받을지 정의합니다.
|
||||
var dataMask = new DataMask();
|
||||
dataMask["VHL_NAME"] = "";
|
||||
dataMask["AGV_IDX"] = "";
|
||||
dataMask["B_INSTALL"] = "";
|
||||
dataMask["NODE_ID"] = "";
|
||||
dataMask["REAL_ID"] = "";
|
||||
dataMask["VHL_STATE"] = "";
|
||||
dataMask["BAY_LIST"] = "";
|
||||
dataMask["X"] = 0;
|
||||
dataMask["Y"] = 0;
|
||||
dataMask["DEGREE"] = 0.0;
|
||||
dataMask["MODE"] = "";
|
||||
dataMask["BATT"] = "";
|
||||
dataMask["SUB_GOAL"] = "";
|
||||
dataMask["FINAL_GOAL"] = "";
|
||||
dataMask["JOB_ID"] = "";
|
||||
dataMask["TIMESTAMP"] = DateTime.Now;
|
||||
|
||||
var pipelineInfo = new MQTTPipeLineInfo("AGV")
|
||||
.setDataMapper(new DataMapper(dataMask))
|
||||
.setHandler(OnUpdateData);
|
||||
|
||||
// MQTTPipeLine에 파이프라인 정보를 추가합니다.
|
||||
AppMain.Instance.MQTTPipeLine.Add(pipelineInfo);
|
||||
}
|
||||
|
||||
public void OnUpdateData(IDataObject? data)
|
||||
{
|
||||
OnUpdateDataAsync(data).Forget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 데이터와 비교하여 변경된 부분만 전달 됩니다.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public async UniTask OnUpdateDataAsync(IDataObject? data)
|
||||
{
|
||||
if (data == null) return;
|
||||
DataArray? arr = data as DataArray;
|
||||
if (arr == null || arr.Count == 0) return;
|
||||
|
||||
var AddedItems = arr.AddedItems;
|
||||
var RemovedItems = arr.RemovedItems;
|
||||
var ModifiedList = arr.ModifiedList;
|
||||
|
||||
foreach (var item in AddedItems.ToList())
|
||||
{
|
||||
AGV? agv = await CreateAGV(item);
|
||||
if(agv != null)
|
||||
{
|
||||
agvList.Add(agv);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in RemovedItems.ToList())
|
||||
{
|
||||
AGV agv = agvList.Find(x => x.Info != null && x.Info.Name == item.GetString("VHL_NAME"));
|
||||
if (agv != null)
|
||||
{
|
||||
Destroy(agv.gameObject);
|
||||
agvList.Remove(agv);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in ModifiedList.ToList())
|
||||
{
|
||||
AGV agv = agvList.Find(x => x.Info != null && x.Info.Name == item.GetString("VHL_NAME"));
|
||||
if (agv != null)
|
||||
{
|
||||
agv.transform.position = new Vector3(
|
||||
item.GetFloat("X") * .001f,
|
||||
0,
|
||||
item.GetFloat("Y") * .001f
|
||||
);
|
||||
agv.transform.rotation = Quaternion.Euler(0, item.GetFloat("DEGREE"), 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async UniTask<AGV?> CreateAGV(DataObject data)
|
||||
{
|
||||
if(prefab == null)
|
||||
{
|
||||
prefab = await Resources.LoadAsync<GameObject>(prefabPath) as GameObject;
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError($"Prefab not found at path: {prefabPath}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
GameObject prefabInstance = Instantiate(prefab, transform);
|
||||
Debug.Log($"AGVManager: prefabInstance == null {prefabInstance == null}");
|
||||
if (prefabInstance == null)
|
||||
{
|
||||
Debug.LogError("Failed to instantiate AGV prefab.");
|
||||
return null;
|
||||
}
|
||||
var agv = prefabInstance.GetComponent<AGV>();
|
||||
agv.Info = new FactoryObjectInfo(
|
||||
data.GetString("VHL_NAME"),
|
||||
data.GetString("NODE_ID"),
|
||||
"",
|
||||
"",
|
||||
data.GetString("MODE")
|
||||
);
|
||||
agv.transform.position = new Vector3(
|
||||
data.GetFloat("X") * .001f,
|
||||
0,
|
||||
data.GetFloat("Y") * .001f
|
||||
);
|
||||
agv.transform.rotation = Quaternion.Euler(0, data.GetFloat("DEGREE"), 0);
|
||||
return agv;
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
AppMain.Instance.MQTTPipeLine.Remove("AGV");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Factory/AGVManager.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/AGVManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3a4e2d3e90ee0d4387588358f34e018
|
||||
@@ -2,42 +2,39 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UVC.Extention;
|
||||
using UVC.Data;
|
||||
using UVC.Object3d;
|
||||
using UVC.UI.Info;
|
||||
|
||||
namespace UVC.Object3d
|
||||
namespace UVC.Factory
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class FactoryObject : InteractiveObject
|
||||
{
|
||||
// InfoWindow 인스턴스에 대한 참조
|
||||
protected InfoWindow infoWindow;
|
||||
|
||||
protected Camera mainCamera;
|
||||
|
||||
protected FactoryObjectInfo? factoryObjectInfo;
|
||||
public FactoryObjectInfo? FactoryObjectInfo
|
||||
protected FactoryObjectInfo? info;
|
||||
public FactoryObjectInfo? Info
|
||||
{
|
||||
get => factoryObjectInfo;
|
||||
get => info;
|
||||
set
|
||||
{
|
||||
factoryObjectInfo = value;
|
||||
info = value;
|
||||
if (value != null)
|
||||
{
|
||||
// 객체의 이름을 GameObject의 이름으로 설정합니다.
|
||||
// 객체의 이름을 GameObject의 이름으로 설정합니다.
|
||||
gameObject.name = value.Name;
|
||||
FactoryObjectManager.Instance.RegisterFactoryObject(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Dictionary<string, object> data = new Dictionary<string, object>();
|
||||
protected DataObject? data;
|
||||
|
||||
protected List<string>? dataOrderedMask;
|
||||
/// <summary>
|
||||
/// InfoWindow에 표시할 데이터의 순서와 항목을 지정하는 마스크입니다.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// InfoWindow에 표시할 데이터의 순서와 항목을 지정하는 마스크입니다.
|
||||
/// </summary>
|
||||
public List<string>? DataOrderedMask
|
||||
{
|
||||
get => dataOrderedMask;
|
||||
@@ -47,19 +44,10 @@ namespace UVC.Object3d
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
|
||||
// 씬에 있는 InfoWindow 인스턴스를 동적으로 찾습니다.
|
||||
// FindObjectOfType은 씬에서 해당 타입의 활성화된 첫 번째 객체를 반환합니다.
|
||||
infoWindow = InfoWindow.Create();
|
||||
|
||||
if (infoWindow == null)
|
||||
{
|
||||
Debug.LogError("씬에서 InfoWindow 컴포넌트를 찾을 수 없습니다. InfoWindow가 씬에 존재하고 활성화되어 있는지 확인해주세요.");
|
||||
enabled = false; // infoWindow가 없으면 이 스크립트를 비활성화합니다.
|
||||
}
|
||||
FactoryObjectManager.Instance.UnregisterFactoryObject(Info!);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +59,9 @@ namespace UVC.Object3d
|
||||
/// 사용 가능한 모든 데이터가 표시됩니다. 정보 창은 현재
|
||||
/// 변환을 기준으로 배치됩니다.</remarks>
|
||||
/// <param name="eventData">포인터 클릭과 관련된 이벤트 데이터입니다.</param>
|
||||
public override void OnPointerClick(PointerEventData eventData)
|
||||
public override void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if(!infoWindow.IsVisible && data != null && data.Count > 0)
|
||||
if (data != null && data.Count > 0)
|
||||
{
|
||||
Dictionary<string, object> info = new Dictionary<string, object>();
|
||||
// dataOrderedMask가 설정되어 있으면 해당 순서대로 정보를 가져옵니다.
|
||||
@@ -92,28 +80,44 @@ namespace UVC.Object3d
|
||||
// dataOrderedMask가 설정되어 있지 않으면 모든 데이터를 사용합니다.
|
||||
info = new Dictionary<string, object>(data);
|
||||
}
|
||||
infoWindow.Show(transform, info);
|
||||
InfoWindow.Instance.Show(transform, info);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 변경된 데이터만 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void UpdateData(IDictionary<string, object> data)
|
||||
/// <param name="newData"></param>
|
||||
public void UpdateData(IDataObject? newData)
|
||||
{
|
||||
foreach (var kvp in data)
|
||||
{
|
||||
if (this.data.ContainsKey(kvp.Key))
|
||||
{
|
||||
this.data[kvp.Key] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.data.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
if(newData == null) return;
|
||||
DataObject? dataObject = newData as DataObject;
|
||||
if (dataObject == null || dataObject.Count == 0) return;
|
||||
ProcessData(dataObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 데이터 객체를 처리합니다. 이 메서드는 파생 클래스에서 재정의되어
|
||||
/// 사용자 지정 데이터 처리 로직을 구현하도록 설계되었습니다.
|
||||
/// </summary>
|
||||
/// <remarks>파생 클래스는 <paramref name="newData"/> 매개변수에 대한 특정 처리 동작을 제공하기 위해
|
||||
/// 이 메서드를 재정의해야 합니다.
|
||||
/// 사용하기 전에 매개변수의 유효성을 검사해야 합니다.</remarks>
|
||||
/// <param name="newData">처리할 데이터 객체입니다. null일 수 없습니다.</param>
|
||||
protected virtual void ProcessData(DataObject newData) {}
|
||||
|
||||
public Vector3 GetPosition(bool world = false)
|
||||
{
|
||||
if (!world) return transform.position;
|
||||
return transform.TransformPoint(transform.position);
|
||||
}
|
||||
|
||||
|
||||
public Vector3 GetEntrancePosition(bool world = false)
|
||||
{
|
||||
if (!world) return transform.position;
|
||||
return transform.TransformPoint(transform.position);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Factory/FactoryObject.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/FactoryObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0c4eb9b7dc850e4b8446d36ed68ce07
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace UVC.Object3d
|
||||
namespace UVC.Factory
|
||||
{
|
||||
public class FactoryObjectInfo
|
||||
{
|
||||
2
Assets/Scripts/UVC/Factory/FactoryObjectInfo.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/FactoryObjectInfo.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60693d3d20cd32e4eb8adc209cff38db
|
||||
106
Assets/Scripts/UVC/Factory/FactoryObjectManager.cs
Normal file
106
Assets/Scripts/UVC/Factory/FactoryObjectManager.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UVC.Core;
|
||||
|
||||
namespace UVC.Factory
|
||||
{
|
||||
public class FactoryObjectManager : SingletonScene<FactoryObjectManager>
|
||||
{
|
||||
public Dictionary<FactoryObjectInfo, FactoryObject> FactoryObjects { get; private set; } = new Dictionary<FactoryObjectInfo, FactoryObject>();
|
||||
|
||||
public void RegisterFactoryObject(FactoryObject factoryObject)
|
||||
{
|
||||
if (factoryObject == null || factoryObject.Info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factoryObject), "FactoryObject cannot be null.");
|
||||
}
|
||||
if (!FactoryObjects.ContainsKey(factoryObject.Info!))
|
||||
{
|
||||
FactoryObjects.Add(factoryObject.Info!, factoryObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterFactoryObject(FactoryObjectInfo factoryObjectInfo)
|
||||
{
|
||||
if (factoryObjectInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factoryObjectInfo), "factoryObjectInfo cannot be null.");
|
||||
}
|
||||
FactoryObjects.Remove(factoryObjectInfo);
|
||||
}
|
||||
|
||||
public FactoryObject? GetFactoryObject(FactoryObjectInfo factoryObjectInfo)
|
||||
{
|
||||
if (factoryObjectInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factoryObjectInfo), "factoryObjectInfo cannot be null.");
|
||||
}
|
||||
FactoryObjects.TryGetValue(factoryObjectInfo, out var factoryObject);
|
||||
return factoryObject;
|
||||
}
|
||||
|
||||
public FactoryObject? FindByName(string name)
|
||||
{
|
||||
foreach (var kvp in FactoryObjects)
|
||||
{
|
||||
if (kvp.Key.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return kvp.Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public FactoryObject? FindById(string id)
|
||||
{
|
||||
foreach (var kvp in FactoryObjects)
|
||||
{
|
||||
if (kvp.Key.Id.Equals(id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return kvp.Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public FactoryObject? FindByPosition(string position)
|
||||
{
|
||||
foreach (var kvp in FactoryObjects)
|
||||
{
|
||||
if (kvp.Key.Position.Equals(position, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return kvp.Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<FactoryObject> FindByArea(string area)
|
||||
{
|
||||
List<FactoryObject> foundObjects = new List<FactoryObject>();
|
||||
foreach (var kvp in FactoryObjects)
|
||||
{
|
||||
if (kvp.Key.Area.Equals(area, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foundObjects.Add(kvp.Value);
|
||||
}
|
||||
}
|
||||
return foundObjects;
|
||||
}
|
||||
|
||||
public List<FactoryObject> FindByFloor(string floor)
|
||||
{
|
||||
List<FactoryObject> foundObjects = new List<FactoryObject>();
|
||||
foreach (var kvp in FactoryObjects)
|
||||
{
|
||||
if (kvp.Key.Floor.Equals(floor, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foundObjects.Add(kvp.Value);
|
||||
}
|
||||
}
|
||||
return foundObjects;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Factory/FactoryObjectManager.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/FactoryObjectManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 170d63a2fc580274a92e3eb05f43dc1a
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06c8fe736f6ab0d4787d720284f536ea
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88a40c66f1e6dad4daa5a1339bd1a0bb
|
||||
@@ -12,13 +12,9 @@ namespace UVC.UI.Info
|
||||
/// 3D 객체를 따라다니며 정보를 표시하는 UI 창입니다.
|
||||
/// 이 컴포넌트는 World Space Canvas 내의 UI 요소에 추가되어야 합니다.
|
||||
/// </summary>
|
||||
public class InfoWindow : MonoBehaviour
|
||||
public class InfoWindow : SingletonScene<InfoWindow>
|
||||
{
|
||||
|
||||
[Tooltip("InfoWindow의 프리팹")]
|
||||
[SerializeField]
|
||||
private static GameObject infoWindowPrefab;
|
||||
|
||||
[Tooltip("정보 텍스트를 표시할 UI 요소")]
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI infoText;
|
||||
@@ -34,30 +30,16 @@ namespace UVC.UI.Info
|
||||
// 정보 창이 따라다닐 3D 객체의 Transform
|
||||
private Transform? target;
|
||||
|
||||
// 메인 카메라 참조
|
||||
private Camera mainCamera;
|
||||
|
||||
/// <summary>
|
||||
/// 정보 창이 현재 화면에 표시되고 있는지 여부를 반환합니다.
|
||||
/// </summary>
|
||||
public bool IsVisible => gameObject.activeSelf;
|
||||
|
||||
/// <summary>
|
||||
/// 정보 창을 생성하여 반환합니다.
|
||||
/// </summary>
|
||||
public static InfoWindow? Create()
|
||||
{
|
||||
if(infoWindowPrefab == null)
|
||||
{
|
||||
Debug.LogError("InfoWindow 프리팹이 할당되지 않았습니다. Inspector에서 할당해주세요.");
|
||||
return null;
|
||||
}
|
||||
return Instantiate(infoWindowPrefab).GetComponent<InfoWindow>();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
protected override void Awake()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
base.Awake();
|
||||
|
||||
// 닫기 버튼이 할당되었으면 클릭 이벤트를 연결합니다.
|
||||
if (closeButton != null)
|
||||
@@ -79,9 +61,6 @@ namespace UVC.UI.Info
|
||||
{
|
||||
// 위치 업데이트
|
||||
transform.position = target.position + worldOffset;
|
||||
|
||||
// 항상 카메라를 바라보도록 방향 업데이트 (빌보드 효과)
|
||||
transform.rotation = mainCamera.transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System; // System.Type 사용을 위해 추가
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Modal
|
||||
@@ -55,6 +55,8 @@ namespace UVC.UI.Modal
|
||||
/// </summary>
|
||||
private const string DefaultBlockerPrefabPath = "Prefabs/UI/Modal/ModalBlocker";
|
||||
|
||||
private static GameObject? blockerPrefabObj;
|
||||
|
||||
/// <summary>
|
||||
/// ✨ 모달아, 열려라! ✨
|
||||
/// 이 마법 주문을 외치면 화면에 새로운 알림 상자(모달)가 뿅! 하고 나타나요.
|
||||
@@ -124,7 +126,10 @@ namespace UVC.UI.Modal
|
||||
// --- 투명 방패(Blocker) 준비 ---
|
||||
CanvasGroup blockerCanvasGroup = null;
|
||||
// 📜 이야기: '투명 방패' 디자인 파일을 불러와요. Resources.LoadAsync는 비동기(기다리지 않고 다음 일 하기)로 파일을 불러오는 마법이에요.
|
||||
GameObject blockerPrefabObj = await Resources.LoadAsync<GameObject>(blockerPrefabPath) as GameObject;
|
||||
if (blockerPrefabObj == null)
|
||||
{
|
||||
blockerPrefabObj = await Resources.LoadAsync<GameObject>(blockerPrefabPath) as GameObject;
|
||||
}
|
||||
if (blockerPrefabObj != null)
|
||||
{
|
||||
// 화면에서 가장 큰 그림판(Canvas)을 찾아서 그 위에 방패를 놓을 거예요.
|
||||
@@ -147,7 +152,7 @@ namespace UVC.UI.Modal
|
||||
|
||||
// --- 모달 창(Modal) 준비 ---
|
||||
// 📜 이야기: 이제 진짜 모달 창 디자인 파일을 불러올 차례예요. 이것도 비동기로!
|
||||
GameObject modalPrefabObj = await Resources.LoadAsync<GameObject>(currentContent.PrefabPath) as GameObject;
|
||||
GameObject? modalPrefabObj = await Resources.LoadAsync<GameObject>(currentContent.PrefabPath) as GameObject;
|
||||
if (modalPrefabObj == null)
|
||||
{
|
||||
ULog.Error($"[Modal] 모달 프리팹을 다음 경로에서 찾을 수 없습니다: {currentContent.PrefabPath}");
|
||||
@@ -185,7 +190,8 @@ namespace UVC.UI.Modal
|
||||
// 그런 상황에 대비해서, 모달 창이 파괴될 때 자동으로 "취소"된 것처럼 처리하도록 등록해둬요.
|
||||
// GetCancellationTokenOnDestroy()는 "이 게임 오브젝트가 파괴되면 알려줘!"라는 신호예요.
|
||||
var modalDestroyToken = currentModalInstance.GetCancellationTokenOnDestroy();
|
||||
modalDestroyToken.RegisterWithoutCaptureExecutionContext(async () => {
|
||||
modalDestroyToken.RegisterWithoutCaptureExecutionContext(async () =>
|
||||
{
|
||||
// 이 코드는 모달 인스턴스가 파괴될 때 실행돼요.
|
||||
// 만약 우리가 아직 결과를 기다리고 있는(Pending) 모달이었다면,
|
||||
if (Modal.activeTcs == tcs && tcs.Task.Status == UniTaskStatus.Pending)
|
||||
|
||||
@@ -55,6 +55,8 @@ namespace UVC.UI.Tooltip
|
||||
|
||||
protected string tooltipPrefabPath = "Prefabs/UI/Tooltip/Tooltip";
|
||||
|
||||
protected GameObject tooltipPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// Resources 폴더 내의 툴팁 UI 프리팹 경로입니다.
|
||||
/// 상속을 통해 다른 경로를 사용하도록 재정의할 수 있습니다.
|
||||
@@ -111,9 +113,12 @@ namespace UVC.UI.Tooltip
|
||||
this.tooltipPrefabPath = tooltipPrefabPath; // 사용자 지정 경로가 제공되면 업데이트
|
||||
}
|
||||
|
||||
GameObject loadedTooltipPrefab = Resources.Load<GameObject>(this.tooltipPrefabPath);
|
||||
if (tooltipPrefab == null)
|
||||
{
|
||||
tooltipPrefab = Resources.Load<GameObject>(this.tooltipPrefabPath);
|
||||
}
|
||||
|
||||
if (loadedTooltipPrefab == null)
|
||||
if (tooltipPrefab == null)
|
||||
{
|
||||
Debug.LogError($"TooltipManager 초기화 실패: Resources 폴더에서 '{this.tooltipPrefabPath}' 경로의 프리팹을 로드할 수 없습니다.");
|
||||
return;
|
||||
@@ -123,7 +128,7 @@ namespace UVC.UI.Tooltip
|
||||
_rootCanvas = rootCanvas;
|
||||
|
||||
// 툴팁 인스턴스 생성 및 초기화
|
||||
_activeTooltipInstance = GameObject.Instantiate(loadedTooltipPrefab, _defaultParentTransform);
|
||||
_activeTooltipInstance = GameObject.Instantiate(tooltipPrefab, _defaultParentTransform);
|
||||
_tooltipTextElement = _activeTooltipInstance.GetComponentInChildren<TextMeshProUGUI>();
|
||||
_tooltipRectTransform = _activeTooltipInstance.GetComponent<RectTransform>();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace UVC.Util
|
||||
public static GameObject GetGameObjectFromPrefabPath(this string prefabPath, Transform parent = null)
|
||||
{
|
||||
GameObject prefab = Resources.Load(prefabPath, typeof(GameObject)) as GameObject;
|
||||
return UnityEngine.Object.Instantiate(prefab);
|
||||
return UnityEngine.Object.Instantiate(prefab, parent);
|
||||
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ namespace UVC.Util
|
||||
public static T GetComponentFromPrefabPath<T>(this string prefabPath, Transform parent = null)
|
||||
{
|
||||
GameObject prefab = Resources.Load(prefabPath, typeof(GameObject)) as GameObject;
|
||||
GameObject go = UnityEngine.Object.Instantiate(prefab);
|
||||
if (parent != null) go.transform.SetParent(parent, false);
|
||||
GameObject go = UnityEngine.Object.Instantiate(prefab, parent);
|
||||
return go.GetComponent<T>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user