asdf
This commit is contained in:
@@ -44,12 +44,8 @@ namespace Simulator.Data.Transport
|
|||||||
|
|
||||||
protected override void Init()
|
protected override void Init()
|
||||||
{
|
{
|
||||||
InitializeNodePoolAsync().ContinueWith(() =>
|
InitializePoolAsync<AGVNode>(nodePool, "node").ContinueWith(pool => { nodePool = pool; });
|
||||||
{
|
InitializePoolAsync<AGVPath>(pathPool, "path").ContinueWith(pool => { pathPool = pool; });
|
||||||
});
|
|
||||||
InitializePathPoolAsync().ContinueWith(() =>
|
|
||||||
{
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3 GetNodePosition(string name)
|
public Vector3 GetNodePosition(string name)
|
||||||
@@ -81,29 +77,16 @@ namespace Simulator.Data.Transport
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTask InitializeNodePoolAsync()
|
private async UniTask<GameObjectPool<T>> InitializePoolAsync<T>(GameObjectPool<T> existingPool, string key) where T : MonoBehaviour
|
||||||
{
|
{
|
||||||
if (nodePool != null) return;
|
if (existingPool != null) return existingPool;
|
||||||
|
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths[key]) as GameObject;
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["node"]) as GameObject;
|
|
||||||
if (prefab == null)
|
if (prefab == null)
|
||||||
{
|
{
|
||||||
Debug.LogError($"Prefab not found at path: {prefabPaths["node"]}");
|
Debug.LogError($"Prefab not found at path: {prefabPaths[key]}");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
nodePool = new GameObjectPool<AGVNode>(prefab, transform);
|
return new GameObjectPool<T>(prefab, transform);
|
||||||
}
|
|
||||||
private async UniTask InitializePathPoolAsync()
|
|
||||||
{
|
|
||||||
if (pathPool != null) return;
|
|
||||||
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["path"]) as GameObject;
|
|
||||||
if (prefab == null)
|
|
||||||
{
|
|
||||||
Debug.LogError($"Prefab not found at path: {prefabPaths["path"]}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pathPool = new GameObjectPool<AGVPath>(prefab, transform);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,12 +39,13 @@ namespace Simulator.Data.Transport
|
|||||||
|
|
||||||
public void InitializeAllPools()
|
public void InitializeAllPools()
|
||||||
{
|
{
|
||||||
InitializeAGVPoolAsync().ContinueWith(() =>
|
InitializePoolAsync<AGV>(agvPool, "agv").ContinueWith(pool =>
|
||||||
{
|
{
|
||||||
|
agvPool = pool;
|
||||||
InitializeDataMappers();
|
InitializeDataMappers();
|
||||||
});
|
});
|
||||||
InitializeAFLPoolAsync().ContinueWith(() => { });
|
InitializePoolAsync<AGV>(aflPool, "afl").ContinueWith(pool => { aflPool = pool; });
|
||||||
InitializeForkLiftPoolAsync().ContinueWith(() => { });
|
InitializePoolAsync<AGV>(forkLiftPool, "forkLift").ContinueWith(pool => { forkLiftPool = pool; });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeDataMappers()
|
private void InitializeDataMappers()
|
||||||
@@ -135,43 +136,16 @@ namespace Simulator.Data.Transport
|
|||||||
return agvMap.TryGetValue(name, out var agv) ? agv : null;
|
return agvMap.TryGetValue(name, out var agv) ? agv : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTask InitializeAGVPoolAsync()
|
private async UniTask<GameObjectPool<T>> InitializePoolAsync<T>(GameObjectPool<T> existingPool, string key) where T : MonoBehaviour
|
||||||
{
|
{
|
||||||
if (agvPool != null) return;
|
if (existingPool != null) return existingPool;
|
||||||
|
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths[key]) as GameObject;
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["agv"]) as GameObject;
|
|
||||||
if (prefab == null)
|
if (prefab == null)
|
||||||
{
|
{
|
||||||
Debug.LogError($"Prefab not found at path: {prefabPaths["agv"]}");
|
Debug.LogError($"Prefab not found at path: {prefabPaths[key]}");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
agvPool = new GameObjectPool<AGV>(prefab, _parent);
|
return new GameObjectPool<T>(prefab, _parent);
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeAFLPoolAsync()
|
|
||||||
{
|
|
||||||
if (aflPool != null) return;
|
|
||||||
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["afl"]) as GameObject;
|
|
||||||
if (prefab == null)
|
|
||||||
{
|
|
||||||
Debug.LogError($"Prefab not found at path: {prefabPaths["afl"]}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
aflPool = new GameObjectPool<AGV>(prefab, _parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeForkLiftPoolAsync()
|
|
||||||
{
|
|
||||||
if (forkLiftPool != null) return;
|
|
||||||
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["forkLift"]) as GameObject;
|
|
||||||
if (prefab == null)
|
|
||||||
{
|
|
||||||
Debug.LogError($"Prefab not found at path: {prefabPaths["forkLift"]}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
forkLiftPool = new GameObjectPool<AGV>(prefab, _parent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ namespace Simulator.Data
|
|||||||
|
|
||||||
public void InitializeAllPools()
|
public void InitializeAllPools()
|
||||||
{
|
{
|
||||||
InitializeSourcePoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.Source, CreateDataMapper(ComponentType.Source)));
|
InitializePoolAsync<SourceComponent>(_sourcePool, "source").ContinueWith(pool => { _sourcePool = pool; _dataMapperDict.Add(ComponentType.Source, CreateDataMapper(ComponentType.Source)); });
|
||||||
InitializeSinkPoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.Sink, CreateDataMapper(ComponentType.Sink)));
|
InitializePoolAsync<SinkComponent>(_sinkPool, "sink").ContinueWith(pool => { _sinkPool = pool; _dataMapperDict.Add(ComponentType.Sink, CreateDataMapper(ComponentType.Sink)); });
|
||||||
InitializeQueuePoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.Queue, null));
|
InitializePoolAsync<QueueComponent>(_queuePool, "queue").ContinueWith(pool => { _queuePool = pool; _dataMapperDict.Add(ComponentType.Queue, null); });
|
||||||
InitializeRackPoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.Rack, CreateDataMapper(ComponentType.Rack)));
|
InitializePoolAsync<RackComponent>(_rackPool, "rack").ContinueWith(pool => { _rackPool = pool; _dataMapperDict.Add(ComponentType.Rack, CreateDataMapper(ComponentType.Rack)); });
|
||||||
InitializeAsrsPoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.ASRS, CreateDataMapper(ComponentType.ASRS)));
|
InitializePoolAsync<ASRSComponent>(_asrsPool, "asrs").ContinueWith(pool => { _asrsPool = pool; _dataMapperDict.Add(ComponentType.ASRS, CreateDataMapper(ComponentType.ASRS)); });
|
||||||
InitializeRobotArmPoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.RobotArm, CreateDataMapper(ComponentType.RobotArm)));
|
InitializePoolAsync<RobotArmComponent>(_robotArmPool, "robotArm").ContinueWith(pool => { _robotArmPool = pool; _dataMapperDict.Add(ComponentType.RobotArm, CreateDataMapper(ComponentType.RobotArm)); });
|
||||||
InitializeProcessorPoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.Processor, CreateDataMapper(ComponentType.Processor)));
|
InitializePoolAsync<ProcessorComponent>(_processorPool, "processor").ContinueWith(pool => { _processorPool = pool; _dataMapperDict.Add(ComponentType.Processor, CreateDataMapper(ComponentType.Processor)); });
|
||||||
InitializeWorkerPoolAsync().ContinueWith(() => _dataMapperDict.Add(ComponentType.Worker, CreateDataMapper(ComponentType.Worker)));
|
InitializePoolAsync<WorkerComponent>(_workerPool, "worker").ContinueWith(pool => { _workerPool = pool; _dataMapperDict.Add(ComponentType.Worker, CreateDataMapper(ComponentType.Worker)); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataMapper CreateDataMapper(ComponentType type)
|
private DataMapper CreateDataMapper(ComponentType type)
|
||||||
@@ -113,68 +113,12 @@ namespace Simulator.Data
|
|||||||
|
|
||||||
#region Pool Initialization
|
#region Pool Initialization
|
||||||
|
|
||||||
private async UniTask InitializeSourcePoolAsync()
|
private async UniTask<GameObjectPool<T>> InitializePoolAsync<T>(GameObjectPool<T> existingPool, string key) where T : MonoBehaviour
|
||||||
{
|
{
|
||||||
if (_sourcePool != null) return;
|
if (existingPool != null) return existingPool;
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["source"]) as GameObject;
|
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths[key]) as GameObject;
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["source"]}"); return; }
|
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths[key]}"); return null; }
|
||||||
_sourcePool = new GameObjectPool<SourceComponent>(prefab, _parentTransform);
|
return new GameObjectPool<T>(prefab, _parentTransform);
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeSinkPoolAsync()
|
|
||||||
{
|
|
||||||
if (_sinkPool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["sink"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["sink"]}"); return; }
|
|
||||||
_sinkPool = new GameObjectPool<SinkComponent>(prefab, _parentTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeQueuePoolAsync()
|
|
||||||
{
|
|
||||||
if (_queuePool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["queue"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["queue"]}"); return; }
|
|
||||||
_queuePool = new GameObjectPool<QueueComponent>(prefab, _parentTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeRackPoolAsync()
|
|
||||||
{
|
|
||||||
if (_rackPool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["rack"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["rack"]}"); return; }
|
|
||||||
_rackPool = new GameObjectPool<RackComponent>(prefab, _parentTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeAsrsPoolAsync()
|
|
||||||
{
|
|
||||||
if (_asrsPool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["asrs"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["asrs"]}"); return; }
|
|
||||||
_asrsPool = new GameObjectPool<ASRSComponent>(prefab, _parentTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeRobotArmPoolAsync()
|
|
||||||
{
|
|
||||||
if (_robotArmPool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["robotArm"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["robotArm"]}"); return; }
|
|
||||||
_robotArmPool = new GameObjectPool<RobotArmComponent>(prefab, _parentTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeProcessorPoolAsync()
|
|
||||||
{
|
|
||||||
if (_processorPool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["processor"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["processor"]}"); return; }
|
|
||||||
_processorPool = new GameObjectPool<ProcessorComponent>(prefab, _parentTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeWorkerPoolAsync()
|
|
||||||
{
|
|
||||||
if (_workerPool != null) return;
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(_prefabPaths["worker"]) as GameObject;
|
|
||||||
if (prefab == null) { Debug.LogError($"Prefab not found at path: {_prefabPaths["worker"]}"); return; }
|
|
||||||
_workerPool = new GameObjectPool<WorkerComponent>(prefab, _parentTransform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Simulator.Data
|
namespace Simulator.Data
|
||||||
@@ -6,127 +8,64 @@ namespace Simulator.Data
|
|||||||
{
|
{
|
||||||
private ProgressPopupView _progressPopupView;
|
private ProgressPopupView _progressPopupView;
|
||||||
|
|
||||||
private SourceProperty _sourceProperty;
|
|
||||||
private SinkProperty _sinkProperty;
|
|
||||||
private RackProperty _rackProperty;
|
|
||||||
private QueueProperty _queueProperty;
|
|
||||||
private ASRSProperty _asrsProperty;
|
|
||||||
private RobotArmProperty _robotArmProperty;
|
|
||||||
private ProcessorProperty _processorProperty;
|
|
||||||
private ConveyorProperty _conveyorProperty;
|
private ConveyorProperty _conveyorProperty;
|
||||||
private NodeProperty _nodeProperty;
|
private NodeProperty _nodeProperty;
|
||||||
|
|
||||||
|
private struct BindingEntry
|
||||||
|
{
|
||||||
|
public Action<ComponentType, ComponentDataBase> PropertyHandler;
|
||||||
|
public bool ShowProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<ComponentType, BindingEntry> _bindings = new Dictionary<ComponentType, BindingEntry>();
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_progressPopupView = Object.FindAnyObjectByType<ProgressPopupView>(FindObjectsInactive.Include);
|
_progressPopupView = GameObject.FindAnyObjectByType<ProgressPopupView>(FindObjectsInactive.Include);
|
||||||
|
_conveyorProperty = GameObject.FindAnyObjectByType<ConveyorProperty>();
|
||||||
|
_nodeProperty = GameObject.FindAnyObjectByType<NodeProperty>();
|
||||||
|
|
||||||
_sourceProperty = Object.FindAnyObjectByType<SourceProperty>();
|
Register<SourceProperty>(ComponentType.Source, (p, t, d) => p.SetPropertyWindow(t, d), showProgress: true);
|
||||||
_sinkProperty = Object.FindAnyObjectByType<SinkProperty>();
|
Register<SinkProperty>(ComponentType.Sink, (p, t, d) => p.SetPropertyWindow(t, d), showProgress: true);
|
||||||
_rackProperty = Object.FindAnyObjectByType<RackProperty>();
|
Register<ProcessorProperty>(ComponentType.Processor, (p, t, d) => p.SetPropertyWindow(t, d), showProgress: true);
|
||||||
_queueProperty = Object.FindAnyObjectByType<QueueProperty>();
|
Register<RackProperty>(ComponentType.Rack, (p, t, d) => p.SetPropertyWindow(t, d));
|
||||||
_asrsProperty = Object.FindAnyObjectByType<ASRSProperty>();
|
Register<QueueProperty>(ComponentType.Queue, (p, t, d) => p.SetPropertyWindow(t, d));
|
||||||
_robotArmProperty = Object.FindAnyObjectByType<RobotArmProperty>();
|
Register<ASRSProperty>(ComponentType.ASRS, (p, t, d) => p.SetPropertyWindow(t, d));
|
||||||
_processorProperty = Object.FindAnyObjectByType<ProcessorProperty>();
|
Register<RobotArmProperty>(ComponentType.RobotArm, (p, t, d) => p.SetPropertyWindow(t, d));
|
||||||
_conveyorProperty = Object.FindAnyObjectByType<ConveyorProperty>();
|
}
|
||||||
_nodeProperty = Object.FindAnyObjectByType<NodeProperty>();
|
|
||||||
|
private void Register<T>(ComponentType type, Action<T, ComponentType, ComponentDataBase> setup, bool showProgress = false) where T : MonoBehaviour
|
||||||
|
{
|
||||||
|
var property = GameObject.FindAnyObjectByType<T>();
|
||||||
|
if (property == null) return;
|
||||||
|
|
||||||
|
_bindings[type] = new BindingEntry
|
||||||
|
{
|
||||||
|
PropertyHandler = (t, d) => setup(property, t, d),
|
||||||
|
ShowProgress = showProgress
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindStandardComponent(ComponentBase component)
|
public void BindStandardComponent(ComponentBase component)
|
||||||
{
|
{
|
||||||
switch (component.componentType)
|
if (!_bindings.TryGetValue(component.componentType, out var entry))
|
||||||
{
|
return;
|
||||||
case ComponentType.Source:
|
|
||||||
if (_sourceProperty != null)
|
component.onComponentClicked += entry.PropertyHandler;
|
||||||
component.onComponentClicked += _sourceProperty.SetPropertyWindow;
|
|
||||||
if (_progressPopupView != null)
|
if (entry.ShowProgress && _progressPopupView != null)
|
||||||
component.onComponentClicked += _progressPopupView.Show;
|
component.onComponentClicked += _progressPopupView.Show;
|
||||||
break;
|
|
||||||
case ComponentType.Sink:
|
|
||||||
if (_sinkProperty != null)
|
|
||||||
component.onComponentClicked += _sinkProperty.SetPropertyWindow;
|
|
||||||
if (_progressPopupView != null)
|
|
||||||
component.onComponentClicked += _progressPopupView.Show;
|
|
||||||
break;
|
|
||||||
case ComponentType.Processor:
|
|
||||||
if (_processorProperty != null)
|
|
||||||
component.onComponentClicked += _processorProperty.SetPropertyWindow;
|
|
||||||
if (_progressPopupView != null)
|
|
||||||
component.onComponentClicked += _progressPopupView.Show;
|
|
||||||
break;
|
|
||||||
case ComponentType.Rack:
|
|
||||||
if (_rackProperty != null)
|
|
||||||
component.onComponentClicked += _rackProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.Queue:
|
|
||||||
if (_queueProperty != null)
|
|
||||||
component.onComponentClicked += _queueProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.ASRS:
|
|
||||||
if (_asrsProperty != null)
|
|
||||||
component.onComponentClicked += _asrsProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.RobotArm:
|
|
||||||
if (_robotArmProperty != null)
|
|
||||||
component.onComponentClicked += _robotArmProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.Worker:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnbindStandardComponent(ComponentBase component)
|
public void UnbindStandardComponent(ComponentBase component)
|
||||||
{
|
{
|
||||||
switch (component.componentType)
|
if (!_bindings.TryGetValue(component.componentType, out var entry))
|
||||||
{
|
return;
|
||||||
case ComponentType.Source:
|
|
||||||
if (_sourceProperty != null)
|
|
||||||
component.onComponentClicked -= _sourceProperty.SetPropertyWindow;
|
|
||||||
if (_progressPopupView != null)
|
|
||||||
component.onComponentClicked -= _progressPopupView.Show;
|
|
||||||
break;
|
|
||||||
case ComponentType.Sink:
|
|
||||||
if (_sinkProperty != null)
|
|
||||||
component.onComponentClicked -= _sinkProperty.SetPropertyWindow;
|
|
||||||
if (_progressPopupView != null)
|
|
||||||
component.onComponentClicked -= _progressPopupView.Show;
|
|
||||||
break;
|
|
||||||
case ComponentType.Processor:
|
|
||||||
if (_processorProperty != null)
|
|
||||||
component.onComponentClicked -= _processorProperty.SetPropertyWindow;
|
|
||||||
if (_progressPopupView != null)
|
|
||||||
component.onComponentClicked -= _progressPopupView.Show;
|
|
||||||
break;
|
|
||||||
case ComponentType.Rack:
|
|
||||||
if (_rackProperty != null)
|
|
||||||
component.onComponentClicked -= _rackProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.Queue:
|
|
||||||
if (_queueProperty != null)
|
|
||||||
component.onComponentClicked -= _queueProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.ASRS:
|
|
||||||
if (_asrsProperty != null)
|
|
||||||
component.onComponentClicked -= _asrsProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.RobotArm:
|
|
||||||
if (_robotArmProperty != null)
|
|
||||||
component.onComponentClicked -= _robotArmProperty.SetPropertyWindow;
|
|
||||||
break;
|
|
||||||
case ComponentType.Worker:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnbindConveyor(ConveyorComponent conveyor)
|
component.onComponentClicked -= entry.PropertyHandler;
|
||||||
{
|
|
||||||
if (_conveyorProperty != null)
|
|
||||||
conveyor.onConveyorClicked -= _conveyorProperty.SetPropertyWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnbindNode(NodeComponent node)
|
if (entry.ShowProgress && _progressPopupView != null)
|
||||||
{
|
component.onComponentClicked -= _progressPopupView.Show;
|
||||||
if (_nodeProperty != null)
|
|
||||||
node.onConveyorClicked -= _nodeProperty.SetPropertyWindow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindConveyor(ConveyorComponent conveyor)
|
public void BindConveyor(ConveyorComponent conveyor)
|
||||||
@@ -135,10 +74,22 @@ namespace Simulator.Data
|
|||||||
conveyor.onConveyorClicked += _conveyorProperty.SetPropertyWindow;
|
conveyor.onConveyorClicked += _conveyorProperty.SetPropertyWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UnbindConveyor(ConveyorComponent conveyor)
|
||||||
|
{
|
||||||
|
if (_conveyorProperty != null)
|
||||||
|
conveyor.onConveyorClicked -= _conveyorProperty.SetPropertyWindow;
|
||||||
|
}
|
||||||
|
|
||||||
public void BindNode(NodeComponent node)
|
public void BindNode(NodeComponent node)
|
||||||
{
|
{
|
||||||
if (_nodeProperty != null)
|
if (_nodeProperty != null)
|
||||||
node.onConveyorClicked += _nodeProperty.SetPropertyWindow;
|
node.onConveyorClicked += _nodeProperty.SetPropertyWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UnbindNode(NodeComponent node)
|
||||||
|
{
|
||||||
|
if (_nodeProperty != null)
|
||||||
|
node.onConveyorClicked -= _nodeProperty.SetPropertyWindow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,26 +133,7 @@ public class ASRSProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(ASRSDataClass asrs)
|
private PropertyGroup CreatePositionGroup(ASRSDataClass asrs)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("asrs_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("asrs_position", asrs, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", asrs.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {asrs.physical.position.x = v;SaveChange(asrs,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", asrs.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {asrs.physical.position.y = v;SaveChange(asrs,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", asrs.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {asrs.physical.orientation = v;SaveChange(asrs,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyGroup CreateRackLayoutGroup(ASRSDataClass asrs)
|
private PropertyGroup CreateRackLayoutGroup(ASRSDataClass asrs)
|
||||||
|
|||||||
@@ -80,58 +80,12 @@ public class NodeProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreateInputDetailGroup(ConveyorNode node)
|
private PropertyGroup CreateInputDetailGroup(ConveyorNode node)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("inputs", "투입 대상 컴포넌트", isExpanded: true);
|
return PropertyHelper.CreateConnectionGroup("inputs", "투입 대상 컴포넌트", node.inputs, SaveChange);
|
||||||
if (node.inputs == null||node.inputs.Count == 0)
|
|
||||||
{
|
|
||||||
group.AddItem(new StringProperty($"inputs.no_locs", "", "연결된 항목이 없습니다.")
|
|
||||||
{
|
|
||||||
IsReadOnly = true
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var input in node.inputs)
|
|
||||||
{
|
|
||||||
string inputId = $"input.count.{input.target}";
|
|
||||||
|
|
||||||
var countProp = new IntProperty(inputId, input.target, input.required_items)
|
|
||||||
.Bind(setter: v =>
|
|
||||||
{
|
|
||||||
input.required_items = v; SaveChange(input, input.required_items, "required_items");
|
|
||||||
});
|
|
||||||
|
|
||||||
group.AddItem(countProp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyGroup CreateOutputDetailGroup(ConveyorNode node)
|
private PropertyGroup CreateOutputDetailGroup(ConveyorNode node)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("outputs", "출하 대상 컴포넌트", isExpanded: true);
|
return PropertyHelper.CreateConnectionGroup("outputs", "출하 대상 컴포넌트", node.outputs, SaveChange);
|
||||||
if (node.outputs == null||node.outputs.Count == 0)
|
|
||||||
{
|
|
||||||
group.AddItem(new StringProperty($"outputs.no_locs", "", "연결된 항목이 없습니다.")
|
|
||||||
{
|
|
||||||
IsReadOnly = true
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var output in node.outputs)
|
|
||||||
{
|
|
||||||
string outputId = $"output.count.{output.target}";
|
|
||||||
|
|
||||||
var countProp = new IntProperty(outputId, output.target, output.required_items)
|
|
||||||
.Bind(setter: v =>
|
|
||||||
{
|
|
||||||
output.required_items = v; SaveChange(output, output.required_items, "required_items");
|
|
||||||
});
|
|
||||||
|
|
||||||
group.AddItem(countProp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleDisplayModeChanged_InputOutput(string mode)
|
private void HandleDisplayModeChanged_InputOutput(string mode)
|
||||||
|
|||||||
@@ -144,26 +144,7 @@ public class ProcessorProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(ProcessorDataClass processor)
|
private PropertyGroup CreatePositionGroup(ProcessorDataClass processor)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("processor_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("processor_position", processor, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", processor.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {processor.physical.position.x = v;SaveChange(processor,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", processor.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {processor.physical.position.y = v;SaveChange(processor,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", processor.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {processor.physical.orientation = v;SaveChange(processor,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region
|
#region
|
||||||
|
|||||||
104
Assets/Scripts/Simulator/UI/PropertyWindow/PropertyHelper.cs
Normal file
104
Assets/Scripts/Simulator/UI/PropertyWindow/PropertyHelper.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Simulator.Data;
|
||||||
|
using UVC.UI.Window.PropertyWindow;
|
||||||
|
|
||||||
|
public static class PropertyHelper
|
||||||
|
{
|
||||||
|
public static void SaveChange(object source, object value, string name)
|
||||||
|
{
|
||||||
|
var path = PathIndexer.GetNodePath(source);
|
||||||
|
Patch updateData = new Patch();
|
||||||
|
updateData.value = value;
|
||||||
|
UpdateValueStack.AddPatch($"{path}.{name}", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyGroup CreatePositionGroup(
|
||||||
|
string groupId,
|
||||||
|
ComponentDataBase data,
|
||||||
|
PropertyWindow propertyWindow,
|
||||||
|
Action<object, object, string> saveChange)
|
||||||
|
{
|
||||||
|
var group = new PropertyGroup(groupId, "위치 및 회전", isExpanded: true);
|
||||||
|
group.AddItems(new IPropertyItem[]
|
||||||
|
{
|
||||||
|
new FloatProperty("x_position", "X 좌표(m)", data.physical.position.x)
|
||||||
|
{
|
||||||
|
}.Bind(
|
||||||
|
setter: v => { data.physical.position.x = v; saveChange(data, v, "physical.position.x"); }
|
||||||
|
),
|
||||||
|
new FloatProperty("y_position", "y 좌표(m)", data.physical.position.y)
|
||||||
|
{
|
||||||
|
}.Bind(
|
||||||
|
setter: v => { data.physical.position.y = v; saveChange(data, v, "physical.position.y"); }
|
||||||
|
),
|
||||||
|
new FloatProperty("orientation", "회전(°)", data.physical.orientation)
|
||||||
|
{
|
||||||
|
}.Bind(
|
||||||
|
setter: v => { data.physical.orientation = v; saveChange(data, v, "physical.orientation"); propertyWindow.ApplyExternalValue("orientation", RotationSnap.SnapOrientation(v), false); }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyGroup CreatePositionGroup(
|
||||||
|
string groupId,
|
||||||
|
Physical physical,
|
||||||
|
object dataOwner,
|
||||||
|
PropertyWindow propertyWindow,
|
||||||
|
Action<object, object, string> saveChange)
|
||||||
|
{
|
||||||
|
var group = new PropertyGroup(groupId, "위치 및 회전", isExpanded: true);
|
||||||
|
group.AddItems(new IPropertyItem[]
|
||||||
|
{
|
||||||
|
new FloatProperty("x_position", "X 좌표(m)", physical.position.x)
|
||||||
|
{
|
||||||
|
}.Bind(
|
||||||
|
setter: v => { physical.position.x = v; saveChange(dataOwner, v, "physical.position.x"); }
|
||||||
|
),
|
||||||
|
new FloatProperty("y_position", "y 좌표(m)", physical.position.y)
|
||||||
|
{
|
||||||
|
}.Bind(
|
||||||
|
setter: v => { physical.position.y = v; saveChange(dataOwner, v, "physical.position.y"); }
|
||||||
|
),
|
||||||
|
new FloatProperty("orientation", "회전(°)", physical.orientation)
|
||||||
|
{
|
||||||
|
}.Bind(
|
||||||
|
setter: v => { physical.orientation = v; saveChange(dataOwner, v, "physical.orientation"); propertyWindow.ApplyExternalValue("orientation", RotationSnap.SnapOrientation(v), false); }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyGroup CreateConnectionGroup(
|
||||||
|
string groupId,
|
||||||
|
string label,
|
||||||
|
List<InOutItem> items,
|
||||||
|
Action<object, object, string> saveChange)
|
||||||
|
{
|
||||||
|
var group = new PropertyGroup(groupId, label, isExpanded: true);
|
||||||
|
if (items == null || items.Count == 0)
|
||||||
|
{
|
||||||
|
group.AddItem(new StringProperty($"{groupId}.no_locs", "", "연결된 항목이 없습니다.")
|
||||||
|
{
|
||||||
|
IsReadOnly = true
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
string itemId = $"{groupId}.count.{item.target}";
|
||||||
|
|
||||||
|
var countProp = new IntProperty(itemId, item.target, item.required_items)
|
||||||
|
.Bind(setter: v =>
|
||||||
|
{
|
||||||
|
item.required_items = v; saveChange(item, item.required_items, "required_items");
|
||||||
|
});
|
||||||
|
|
||||||
|
group.AddItem(countProp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 02a054131b8b79749a6255f3830fd0b3
|
||||||
@@ -88,26 +88,7 @@ public class QueueProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(QueueDataClass queue)
|
private PropertyGroup CreatePositionGroup(QueueDataClass queue)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("queue_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("queue_position", queue, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", queue.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {queue.physical.position.x = v;SaveChange(queue,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", queue.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {queue.physical.position.y = v;SaveChange(queue,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", queue.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {queue.physical.orientation = v;SaveChange(queue,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
|
|||||||
@@ -113,26 +113,7 @@ public class RackProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(RackDataClass rack)
|
private PropertyGroup CreatePositionGroup(RackDataClass rack)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("rack_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("rack_position", rack, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", rack.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {rack.physical.position.x = v;SaveChange(rack,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", rack.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {rack.physical.position.y = v;SaveChange(rack,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", rack.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {rack.physical.orientation = v;SaveChange(rack,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyGroup CreateRackLayoutGroup(RackDataClass rack)
|
private PropertyGroup CreateRackLayoutGroup(RackDataClass rack)
|
||||||
|
|||||||
@@ -87,26 +87,7 @@ public class RobotArmProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(RobotArmDataClass robotArm)
|
private PropertyGroup CreatePositionGroup(RobotArmDataClass robotArm)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("robotArm_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("robotArm_position", robotArm, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", robotArm.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {robotArm.physical.position.x = v;SaveChange(robotArm,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", robotArm.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {robotArm.physical.position.y = v;SaveChange(robotArm,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", robotArm.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {robotArm.physical.orientation = v;SaveChange(robotArm,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyGroup CreateInputsGroup(RobotArmDataClass robotArm)
|
PropertyGroup CreateInputsGroup(RobotArmDataClass robotArm)
|
||||||
|
|||||||
@@ -57,53 +57,11 @@ public class SinkProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(SinkDataClass sink)
|
private PropertyGroup CreatePositionGroup(SinkDataClass sink)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("robotArm_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("sink_position", sink, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", sink.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {sink.physical.position.x = v;SaveChange(sink,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", sink.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {sink.physical.position.y = v;SaveChange(sink,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", sink.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {sink.physical.orientation = v;SaveChange(sink,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyGroup CreateInputDetailGroup(SinkDataClass sink)
|
private PropertyGroup CreateInputDetailGroup(SinkDataClass sink)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("inputs", "입력 연결", isExpanded: true);
|
return PropertyHelper.CreateConnectionGroup("inputs", "입력 연결", sink.inputs, SaveChange);
|
||||||
if (sink.inputs == null || sink.inputs.Count == 0)
|
|
||||||
{
|
|
||||||
group.AddItem(new StringProperty($"inputs.no_locs", "", "연결된 항목이 없습니다.")
|
|
||||||
{
|
|
||||||
IsReadOnly = true
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var input in sink.inputs)
|
|
||||||
{
|
|
||||||
string inputId = $"input.count.{input.target}";
|
|
||||||
|
|
||||||
var countProp = new IntProperty(inputId, input.target, input.required_items)
|
|
||||||
.Bind(setter: v =>
|
|
||||||
{
|
|
||||||
input.required_items = v; SaveChange(input, input.required_items, "required_items");
|
|
||||||
});
|
|
||||||
|
|
||||||
group.AddItem(countProp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,26 +117,7 @@ public class SourceProperty : MonoBehaviour
|
|||||||
|
|
||||||
private PropertyGroup CreatePositionGroup(SourceDataClass source)
|
private PropertyGroup CreatePositionGroup(SourceDataClass source)
|
||||||
{
|
{
|
||||||
var group = new PropertyGroup("robotArm_position", "위치 및 회전", isExpanded: true);
|
return PropertyHelper.CreatePositionGroup("source_position", source, propertyWindow, SaveChange);
|
||||||
group.AddItems(new IPropertyItem[]
|
|
||||||
{
|
|
||||||
new FloatProperty("x_position", "X 좌표(m)", source.physical.position.x)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {source.physical.position.x = v;SaveChange(source,v,"physical.position.x"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("y_position", "y 좌표(m)", source.physical.position.y)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {source.physical.position.y = v;SaveChange(source,v,"physical.position.y"); }
|
|
||||||
),
|
|
||||||
new FloatProperty("orientation", "회전(°)", source.physical.orientation)
|
|
||||||
{
|
|
||||||
}.Bind(
|
|
||||||
setter: v => {source.physical.orientation = v;SaveChange(source,v,"physical.orientation");propertyWindow.ApplyExternalValue("orientation",RotationSnap.SnapOrientation(v),false); }
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"com.coplaydev.unity-mcp": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity",
|
||||||
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
|
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
|
||||||
"com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
|
"com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
|
||||||
"com.unity.2d.sprite": "1.0.0",
|
"com.unity.2d.sprite": "1.0.0",
|
||||||
|
|||||||
155
README.md
155
README.md
@@ -46,4 +46,157 @@ Digital Twin 가상공장 개발 용 라이브러리입니다.
|
|||||||
- https://github.com/HolyShovelSoft/log4uni 다운로드 압축해제 > Assets > Plugins 폴더에 복사
|
- https://github.com/HolyShovelSoft/log4uni 다운로드 압축해제 > Assets > Plugins 폴더에 복사
|
||||||
- **NuGetForUnity_4.5.0**
|
- **NuGetForUnity_4.5.0**
|
||||||
- Window > Package Manager > + > Add packacge from git URL > https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity
|
- Window > Package Manager > + > Add packacge from git URL > https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity
|
||||||
- Menu > Nuget > Manage Nuget Packages > MessagePack 3.1.4
|
- Menu > Nuget > Manage Nuget Packages > MessagePack 3.1.4
|
||||||
|
|
||||||
|
### 아키텍처 (MVVM/MVC)
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||||
|
│ View │◄────│ ViewModel │◄────│ Model │
|
||||||
|
│ (UXML/USS) │ │ (Presenter) │ │ (Service) │
|
||||||
|
└─────────────┘ └──────────────┘ └─────────────┘
|
||||||
|
│ │
|
||||||
|
└── 이벤트 전달 ──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
| 레이어 | 책임 | 금지 사항 |
|
||||||
|
|--------|------|-----------|
|
||||||
|
| **View** | 표시/레이아웃, 이벤트 라우팅 | 비즈니스 로직, 상태 보유 |
|
||||||
|
| **ViewModel/Presenter** | 상태 관리, 데이터 변환, 바인딩 속성 | Unity API 직접 호출 (테스트 용이) |
|
||||||
|
| **Model/Service** | 도메인 로직, 데이터 접근 | UI 참조 |
|
||||||
|
|
||||||
|
- **MVVM**: UI 상태/양방향 동기화가 많을 때
|
||||||
|
- **MVC**: 입력 → 도메인 액션 → UI 반영 흐름이 단순할 때
|
||||||
|
|
||||||
|
### 필수 규약
|
||||||
|
- 파일 선두에 `#nullable enable`, 모든 참조형에 `?` 명시
|
||||||
|
- 비동기는 `UniTask` + `CancellationToken` 사용 (`Task`/코루틴 지양)
|
||||||
|
- 느슨한 결합: 인터페이스/이벤트로 연결
|
||||||
|
|
||||||
|
## 메모리 관리
|
||||||
|
|
||||||
|
### 이벤트 구독/해제
|
||||||
|
```csharp
|
||||||
|
private EventCallback<ClickEvent>? _onClick;
|
||||||
|
|
||||||
|
void OnEnable() {
|
||||||
|
_onClick = OnButtonClick;
|
||||||
|
_button?.RegisterCallback(_onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable() {
|
||||||
|
_button?.UnregisterCallback(_onClick);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 체크리스트
|
||||||
|
- [ ] `RegisterCallback<T>` ↔ `UnregisterCallback<T>` 대칭 확인
|
||||||
|
- [ ] `CancellationTokenSource`는 `OnDestroy`에서 `Cancel/Dispose`
|
||||||
|
- [ ] VisualTreeAsset/USS 동일 리소스 반복 로드 방지 (캐싱)
|
||||||
|
- [ ] 클로저/람다 캡처로 인한 누수 점검
|
||||||
|
- [ ] 오브젝트 풀: `IPoolable.OnRent/OnReturn` 훅, 반환 시 `DOTween.Kill()`
|
||||||
|
|
||||||
|
## 주석 원칙 (C# XML)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>
|
||||||
|
/// 사용자 데이터를 비동기로 로드합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">사용자 ID.</param>
|
||||||
|
/// <param name="ct">취소 토큰.</param>
|
||||||
|
/// <returns>사용자 데이터 또는 null.</returns>
|
||||||
|
public async UniTask<UserData?> LoadUserAsync(string userId, CancellationToken ct)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 클래스: 역할/책임/사용 예
|
||||||
|
- 메서드: 요약 + 파라미터/반환 (복잡 로직만 상세)
|
||||||
|
- 속성: 한 줄 요약
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 디자인 패턴 요약
|
||||||
|
|
||||||
|
| 패턴 | 사용 시점 |
|
||||||
|
|------|-----------|
|
||||||
|
| **DI/Composition Root** | 서비스/ViewModel 주입 일원화 |
|
||||||
|
| **Event Aggregator** | 컴포넌트 간 느슨한 통신 |
|
||||||
|
| **Command + Undo** | UI 액션에 되돌리기 필요 시 |
|
||||||
|
| **Strategy** | 정렬/필터 규칙 교체 |
|
||||||
|
| **State/FSM** | 모드 전환 (편집/선택/드래그) |
|
||||||
|
| **Factory** | 뷰/프리팹 생성 캡슐화 |
|
||||||
|
| **Object Pool** | 대량 아이템 재사용 |
|
||||||
|
| **Repository** | 데이터 소스 추상화 |
|
||||||
|
|
||||||
|
|
||||||
|
## 핵심 개발 원칙
|
||||||
|
|
||||||
|
* [cite_start]항상 TDD 사이클을 따릅니다: Red → Green → Refactor [cite: 4]
|
||||||
|
* [cite_start]가장 간단하게 실패하는 테스트를 먼저 작성합니다[cite: 4].
|
||||||
|
* [cite_start]테스트를 통과시키기 위한 최소한의 코드만 구현합니다[cite: 4].
|
||||||
|
* [cite_start]테스트가 통과한 후에만 리팩토링합니다[cite: 4].
|
||||||
|
* [cite_start]구조적 변경과 행위적 변경을 분리하여 Beck의 "Tidy First" 접근 방식을 따릅니다[cite: 4].
|
||||||
|
* [cite_start]개발 전반에 걸쳐 높은 코드 품질을 유지합니다[cite: 4].
|
||||||
|
* [cite_start]항상 한 번에 하나의 테스트를 작성하고, 실행시킨 다음 구조를 개선합니다[cite: 8].
|
||||||
|
|
||||||
|
## TDD 방법론 가이드라인
|
||||||
|
|
||||||
|
* [cite_start]작은 기능 증분을 정의하는 실패하는 테스트를 작성하는 것으로 시작합니다[cite: 4].
|
||||||
|
* [cite_start]행위를 설명하는 의미 있는 테스트 이름을 사용합니다 (예: "ShouldSumTwoPositiveNumbers")[cite: 4].
|
||||||
|
* [cite_start]테스트 실패를 명확하고 유익하게 만듭니다[cite: 4].
|
||||||
|
* 테스트를 통과시키기 위한 충분한 코드만 작성합니다. [cite_start]더 이상은 안 됩니다[cite: 4].
|
||||||
|
* [cite_start]테스트가 통과하면 리팩토링이 필요한지 고려합니다[cite: 5].
|
||||||
|
* [cite_start]새로운 기능을 위해 사이클을 반복합니다[cite: 5].
|
||||||
|
* [cite_start]항상 모든 테스트(오래 실행되는 테스트 제외)를 매번 실행합니다[cite: 9].
|
||||||
|
|
||||||
|
## Tidy First 접근 방식
|
||||||
|
|
||||||
|
* [cite_start]모든 변경 사항을 두 가지 유형으로 명확히 분리합니다[cite: 4]:
|
||||||
|
1. [cite_start]**구조적 변경 (STRUCTURAL CHANGES)**: 동작을 변경하지 않고 코드를 재배열하는 것 (이름 변경, 메서드 추출, 코드 이동)[cite: 4].
|
||||||
|
2. [cite_start]**행위적 변경 (BEHAVIORAL CHANGES)**: 실제 기능을 추가하거나 수정하는 것[cite: 4].
|
||||||
|
* [cite_start]구조적 변경과 행위적 변경을 동일한 커밋에 절대 섞지 않습니다[cite: 4].
|
||||||
|
* [cite_start]둘 다 필요한 경우 항상 구조적 변경을 먼저 수행합니다[cite: 4].
|
||||||
|
* [cite_start]변경 전후에 테스트를 실행하여 구조적 변경이 동작을 변경하지 않는지 확인합니다[cite: 4].
|
||||||
|
|
||||||
|
## 코드 품질 표준
|
||||||
|
|
||||||
|
* [cite_start]중복을 철저히 제거합니다[cite: 6].
|
||||||
|
* [cite_start]명명 및 구조를 통해 의도를 명확하게 표현합니다[cite: 6].
|
||||||
|
* [cite_start]의존성을 명시적으로 만듭니다[cite: 6].
|
||||||
|
* [cite_start]메서드를 작고 단일 책임에 집중하도록 유지합니다[cite: 6].
|
||||||
|
* [cite_start]상태와 부작용을 최소화합니다[cite: 6].
|
||||||
|
* [cite_start]가능한 가장 간단한 솔루션을 사용합니다[cite: 6].
|
||||||
|
|
||||||
|
* ## 리팩토링 가이드라인
|
||||||
|
|
||||||
|
* [cite_start]테스트가 통과할 때만 리팩토링합니다 ("Green" 단계)[cite: 6].
|
||||||
|
* [cite_start]확립된 리팩토링 패턴을 올바른 이름으로 사용합니다[cite: 6].
|
||||||
|
* [cite_start]한 번에 하나의 리팩토링 변경만 수행합니다[cite: 6].
|
||||||
|
* [cite_start]각 리팩토링 단계 후에 테스트를 실행합니다[cite: 6].
|
||||||
|
* [cite_start]중복을 제거하거나 명확성을 향상시키는 리팩토링을 우선시합니다[cite: 7].
|
||||||
|
|
||||||
|
## 예시 워크플로우
|
||||||
|
|
||||||
|
새로운 기능을 구현할 때:
|
||||||
|
1. [cite_start]기능의 작은 부분에 대한 간단한 실패 테스트를 작성합니다[cite: 7].
|
||||||
|
2. [cite_start]이를 통과시키기 위한 최소한의 코드를 구현합니다[cite: 7].
|
||||||
|
3. [cite_start]테스트가 통과하는지 확인하기 위해 테스트를 실행합니다 (Green)[cite: 7].
|
||||||
|
4. [cite_start]필요한 구조적 변경을 수행합니다 (Tidy First), 각 변경 후에 테스트를 실행합니다[cite: 7].
|
||||||
|
5. [cite_start]구조적 변경을 별도로 커밋합니다[cite: 7].
|
||||||
|
6. [cite_start]다음 작은 기능 증분을 위한 다른 테스트를 추가합니다[cite: 7].
|
||||||
|
7. [cite_start]기능이 완료될 때까지 반복하며, 행위적 변경을 구조적 변경과 별도로 커밋합니다[cite: 7].
|
||||||
|
|
||||||
|
[cite_start]이 프로세스를 정확하게 따르며, 항상 빠른 구현보다 깔끔하고 잘 테스트된 코드를 우선시합니다[cite: 7].
|
||||||
|
|
||||||
|
## C# 특정 지침 (Unity 환경)
|
||||||
|
|
||||||
|
* **Unity 컴포넌트 라이프사이클:** `Awake`, `Start`, `Update`, `FixedUpdate`, `LateUpdate`, `OnEnable`, `OnDisable`, `OnDestroy`와 같은 Unity 컴포넌트 라이프사이클 메서드의 사용 목적을 명확히 이해하고, 각 메서드에서 수행해야 할 작업의 종류를 제한합니다.
|
||||||
|
* **컴포넌트 기반 아키텍처:** 각 Unity 컴포넌트가 단일 책임을 갖도록 설계합니다. 관련된 기능은 별도의 컴포넌트로 분리합니다.
|
||||||
|
* **GameObject 및 Transform 접근:** `GetComponent<T>()` 호출을 최소화하고, 필요한 컴포넌트는 `Awake` 또는 `Start`에서 캐시하여 성능을 최적화합니다.
|
||||||
|
* **Coroutines 및 Events:** 비동기 작업에는 코루틴을 사용하되, 완료 시 콜백이나 이벤트를 사용하여 느슨한 결합을 유지합니다. UnityEvent 또는 C# 이벤트/델리게이트를 적극 활용합니다.
|
||||||
|
* **스크립터블 오브젝트(ScriptableObjects):** 데이터와 로직을 분리하기 위해 스크립터블 오브젝트를 활용하여 게임 데이터, 설정, 자원 등을 관리합니다.
|
||||||
|
* **메모리 관리:** 불필요한 GC(Garbage Collection) 할당을 피하기 위해 `new` 키워드 사용을 최소화하고, 컬렉션 사용 시 재사용 가능한 풀링 기법을 고려합니다.
|
||||||
|
* **Physics 상호작용:** 물리 계산은 `FixedUpdate`에서 수행하며, `Rigidbody`를 통해 물리적 상호작용을 처리합니다.
|
||||||
|
* **씬 관리:** 씬 로딩 및 언로딩 시 발생하는 의존성 문제를 최소화하기 위해 씬 간의 통신 방법을 명확히 정의합니다.
|
||||||
|
* **네임스페이스 사용:** 코드의 가독성과 충돌 방지를 위해 적절한 네임스페이스를 사용합니다.
|
||||||
|
* **람다식 및 LINQ:** 필요할 때 람다식과 LINQ를 사용하여 코드를 간결하게 만들 수 있지만, 성능 영향을 고려하여 과도한 사용을 피합니다.
|
||||||
|
* **null 체크:** Unity 오브젝트에 대한 `null` 체크는 일반 C# 객체와 다르게 동작할 수 있으므로, `== null` 연산자 사용 시 Unity의 오버로드된 연산자를 이해하고 사용합니다.
|
||||||
|
* **에디터 확장:** 개발 워크플로우를 개선하기 위해 Custom Editor 또는 EditorWindow와 같은 Unity 에디터 확장을 고려할 수 있습니다.
|
||||||
Reference in New Issue
Block a user