TwinAgentAutoProcessor 가중치 완료조건 및 Entity별 터미널 로그 분리
- CompletionResult enum 추가 (Complete/Warning/Error)로 완료 상태 분류 - WeightedCompletion으로 SubStep별 가중치 기반 랜덤 완료 조건 구현 - Server: OctopusHub 75% / NotFound 25%(Error, 중지) - Protocol: API 75% / MQTT 25% - ServerStatusCheck: Done 85% / Busy 10%(Warning, 중지) / Error 5%(중지) - Speed: 5~120ms 80% / "300ms~" 20% - IsFullPipelineCompleted 속성으로 전체 파이프라인 완료 여부 판별 - MessageHistory 버퍼로 Entity별 터미널 로그 이력 관리 - Entity 전환 시 ClearAllProcessorScopedBindings()로 이전 UI 바인딩 해제 - EntityPropertyAdapter에서 PropertyItem 복원과 터미널 복원 순서 분리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene 내의 물리적 객체를 나타내는 베이스 클래스입니다.
|
||||
/// 순수한 데이터 클래스로, UI와 직접적인 의존성이 없습니다.
|
||||
/// PropertyWindow 연동은 EntityPropertyAdapter를 통해 이루어집니다.
|
||||
/// 프로세서 컨테이너 역할을 수행합니다.
|
||||
/// Scene 상에 물리적으로 존재하는 객체와 연동된 데이터 클래스 입니다.
|
||||
/// UI와 직접적인 의존성을 가지면 안됩니다.
|
||||
/// UI와의 연동은 Adapter 패턴을 사용하여 수행합니다.
|
||||
/// </summary>
|
||||
public abstract class Entity
|
||||
{
|
||||
@@ -20,18 +19,18 @@ namespace UVC.Entity
|
||||
/// <summary>
|
||||
/// 엔티티와 연결된 GameObject
|
||||
/// </summary>
|
||||
public abstract GameObject GameObject { get; set; }
|
||||
public abstract GameObject gameObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 엔티티의 이름
|
||||
/// </summary>
|
||||
public virtual string Name
|
||||
{
|
||||
get => GameObject != null ? GameObject.name : "Unknown";
|
||||
get => gameObject != null ? gameObject.name : "Unknown";
|
||||
set
|
||||
{
|
||||
if (GameObject != null)
|
||||
GameObject.name = value;
|
||||
if (gameObject != null)
|
||||
gameObject.name = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +39,11 @@ namespace UVC.Entity
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, EntityProcessor> _processors = new Dictionary<string, EntityProcessor>();
|
||||
|
||||
/// <summary>
|
||||
/// 프로세서가 등록될 때 발생합니다.
|
||||
/// </summary>
|
||||
public event Action<EntityProcessor> OnProcessorRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// 프로세서를 등록합니다.
|
||||
/// </summary>
|
||||
@@ -60,6 +64,7 @@ namespace UVC.Entity
|
||||
|
||||
_processors[processor.ProcessorId] = processor;
|
||||
Debug.Log($"[Entity] 프로세서 등록: {processor.ProcessorId}");
|
||||
OnProcessorRegistered?.Invoke(processor);
|
||||
}
|
||||
|
||||
public bool TryGetProcessor<T>(out T processor) where T : EntityProcessor
|
||||
@@ -76,6 +81,19 @@ namespace UVC.Entity
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 프로세서의 스코프 바인딩을 해제합니다.
|
||||
/// Entity 전환 시 이전 Entity의 UI 핸들러를 정리하기 위해 사용합니다.
|
||||
/// 프로세서 자체는 백그라운드에서 계속 실행됩니다.
|
||||
/// </summary>
|
||||
public void ClearAllProcessorScopedBindings()
|
||||
{
|
||||
foreach (var processor in _processors.Values)
|
||||
{
|
||||
processor.ClearScopedBindings();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 프로세서를 제거합니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,259 @@ using UnityEngine;
|
||||
|
||||
namespace UVC.Entity.Processors
|
||||
{
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Enums
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>Auto 프로세스 MainStep (실행 순서대로 정의)</summary>
|
||||
public enum AutoStep
|
||||
{
|
||||
GetEntityInfo = 0,
|
||||
ExtractNetworkInfo = 1,
|
||||
Connecting = 2,
|
||||
Server = 3,
|
||||
Port = 4,
|
||||
Protocol = 5,
|
||||
ServerStatusCheck = 6,
|
||||
Speed = 7,
|
||||
}
|
||||
|
||||
/// <summary>MainStep 내의 개별 진행 단계 식별자</summary>
|
||||
public enum SubStepId
|
||||
{
|
||||
// ── GetEntityInfo ──
|
||||
ReadEntityMetaData,
|
||||
ReadEntityDefaultData,
|
||||
ReadEntityDetailInfo,
|
||||
ReadEntityStatus,
|
||||
Coalescing,
|
||||
GenerateGeneralInfo,
|
||||
|
||||
// ── ExtractNetworkInfo (일부 공유) ──
|
||||
Processing,
|
||||
Perusing,
|
||||
Inferring,
|
||||
|
||||
// ── Connecting ──
|
||||
Crunching,
|
||||
Sussing,
|
||||
|
||||
// ── Server ──
|
||||
Spelunking,
|
||||
GenerateServerList,
|
||||
Forming,
|
||||
TargetServer,
|
||||
|
||||
// ── Port ──
|
||||
Scanning,
|
||||
PortNumber,
|
||||
|
||||
// ── Protocol ──
|
||||
Shimmying,
|
||||
Transmuting,
|
||||
ProtocolType,
|
||||
|
||||
// ── ServerStatusCheck ──
|
||||
ServerStatus,
|
||||
|
||||
// ── Speed ──
|
||||
Speed,
|
||||
}
|
||||
|
||||
/// <summary>MainStep 완료 상태 식별자</summary>
|
||||
public enum CompletionId
|
||||
{
|
||||
Done,
|
||||
Busy,
|
||||
Canceled,
|
||||
NotFound,
|
||||
OctopusHub,
|
||||
API,
|
||||
MQTT,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// <summary>완료 상태의 결과 분류 (UI 색상 매핑에 사용)</summary>
|
||||
public enum CompletionResult
|
||||
{
|
||||
/// <summary>정상 완료 (프로세스 계속)</summary>
|
||||
Complete,
|
||||
/// <summary>경고 (프로세스 중지)</summary>
|
||||
Warning,
|
||||
/// <summary>에러 (프로세스 중지)</summary>
|
||||
Error,
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// String 매핑 (단일 소스)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// 모든 enum → 문자열 매핑을 관리합니다.
|
||||
/// SendMessage로 전달되는 문자열은 이 클래스에서만 정의합니다.
|
||||
/// </summary>
|
||||
public static class StepConfig
|
||||
{
|
||||
private static readonly Dictionary<AutoStep, string> PropertyIds = new()
|
||||
{
|
||||
[AutoStep.GetEntityInfo] = "get_entity_info_status",
|
||||
[AutoStep.ExtractNetworkInfo] = "extract_network_info_status",
|
||||
[AutoStep.Connecting] = "connecting_status",
|
||||
[AutoStep.Server] = "server_status",
|
||||
[AutoStep.Port] = "port_status",
|
||||
[AutoStep.Protocol] = "protocol_status",
|
||||
[AutoStep.ServerStatusCheck] = "server_status_check",
|
||||
[AutoStep.Speed] = "speed_status",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<SubStepId, string> SubStepTexts = new()
|
||||
{
|
||||
[SubStepId.ReadEntityMetaData] = "Read Entity Meta Data",
|
||||
[SubStepId.ReadEntityDefaultData] = "Read Entity Default Data",
|
||||
[SubStepId.ReadEntityDetailInfo] = "Read Entity Detail Info",
|
||||
[SubStepId.ReadEntityStatus] = "Read Entity Status",
|
||||
[SubStepId.Coalescing] = "Coalescing",
|
||||
[SubStepId.GenerateGeneralInfo] = "Generate General Info",
|
||||
[SubStepId.Processing] = "Processing",
|
||||
[SubStepId.Perusing] = "Perusing",
|
||||
[SubStepId.Inferring] = "Inferring",
|
||||
[SubStepId.Crunching] = "Crunching",
|
||||
[SubStepId.Sussing] = "Sussing",
|
||||
[SubStepId.Spelunking] = "Spelunking",
|
||||
[SubStepId.GenerateServerList] = "Generate Server List",
|
||||
[SubStepId.Forming] = "Forming",
|
||||
[SubStepId.TargetServer] = "Target Server",
|
||||
[SubStepId.Scanning] = "Scanning",
|
||||
[SubStepId.PortNumber] = "Port Number",
|
||||
[SubStepId.Shimmying] = "Shimmying",
|
||||
[SubStepId.Transmuting] = "Transmuting",
|
||||
[SubStepId.ProtocolType] = "Protocol Type",
|
||||
[SubStepId.ServerStatus] = "ServerStatus",
|
||||
[SubStepId.Speed] = "Speed",
|
||||
};
|
||||
|
||||
public static string GetPropertyId(AutoStep step) => PropertyIds[step];
|
||||
public static string GetSubStepText(SubStepId id) => SubStepTexts[id];
|
||||
public static string GetCompletionText(CompletionId id) => id.ToString();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Definition Types
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>가중치가 포함된 완료 상태</summary>
|
||||
public readonly struct WeightedCompletion
|
||||
{
|
||||
public readonly CompletionId Id;
|
||||
public readonly float Weight;
|
||||
public readonly CompletionResult Result;
|
||||
private readonly Func<string> _textOverride;
|
||||
|
||||
/// <summary>프로세스 중지 여부 (Complete가 아니면 중지)</summary>
|
||||
public bool StopsProcess => Result != CompletionResult.Complete;
|
||||
|
||||
public WeightedCompletion(CompletionId id, float weight = 1f,
|
||||
CompletionResult result = CompletionResult.Complete)
|
||||
{
|
||||
Id = id;
|
||||
Weight = weight;
|
||||
Result = result;
|
||||
_textOverride = null;
|
||||
}
|
||||
|
||||
/// <summary>동적 텍스트를 생성하는 완료 상태 (예: 랜덤 속도값)</summary>
|
||||
public WeightedCompletion(float weight, Func<string> textGenerator,
|
||||
CompletionResult result = CompletionResult.Complete)
|
||||
{
|
||||
Id = CompletionId.Done;
|
||||
Weight = weight;
|
||||
Result = result;
|
||||
_textOverride = textGenerator;
|
||||
}
|
||||
|
||||
public string Status => _textOverride?.Invoke() ?? StepConfig.GetCompletionText(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MainStep 정의. SubStep 순서와 완료 조건을 포함합니다.
|
||||
/// </summary>
|
||||
public class StepDefinition
|
||||
{
|
||||
public AutoStep Step { get; }
|
||||
public string PropertyId { get; }
|
||||
public SubStepId[] SubSteps { get; }
|
||||
public WeightedCompletion[] Completions { get; }
|
||||
|
||||
/// <summary>전체 카운트 (SubSteps + completion)</summary>
|
||||
public int TotalCount => SubSteps.Length + 1;
|
||||
|
||||
public StepDefinition(AutoStep step, SubStepId[] subSteps, params WeightedCompletion[] completions)
|
||||
{
|
||||
Step = step;
|
||||
PropertyId = StepConfig.GetPropertyId(step);
|
||||
SubSteps = subSteps;
|
||||
Completions = completions.Length > 0
|
||||
? completions
|
||||
: new[] { new WeightedCompletion(CompletionId.Done) };
|
||||
}
|
||||
|
||||
/// <summary>가중치 기반으로 완료 상태를 선택합니다.</summary>
|
||||
public WeightedCompletion SelectCompletion()
|
||||
{
|
||||
if (Completions.Length == 1) return Completions[0];
|
||||
|
||||
float totalWeight = 0f;
|
||||
foreach (var c in Completions) totalWeight += c.Weight;
|
||||
|
||||
float random = UnityEngine.Random.Range(0f, totalWeight);
|
||||
float cumulative = 0f;
|
||||
foreach (var c in Completions)
|
||||
{
|
||||
cumulative += c.Weight;
|
||||
if (random <= cumulative) return c;
|
||||
}
|
||||
return Completions[^1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>SubStep 변경 시 전달되는 진행 정보</summary>
|
||||
public readonly struct StepProgressInfo
|
||||
{
|
||||
/// <summary>현재 MainStep</summary>
|
||||
public readonly AutoStep Step;
|
||||
/// <summary>MainStep의 PropertyId</summary>
|
||||
public readonly string PropertyId;
|
||||
/// <summary>현재 상태 문자열</summary>
|
||||
public readonly string Status;
|
||||
/// <summary>현재 인덱스 (0-based)</summary>
|
||||
public readonly int CurrentIndex;
|
||||
/// <summary>전체 수 (SubSteps + completion)</summary>
|
||||
public readonly int TotalCount;
|
||||
/// <summary>MainStep 완료 여부</summary>
|
||||
public readonly bool IsCompleted;
|
||||
/// <summary>완료 결과 분류 (SubStep 진행 중이면 null)</summary>
|
||||
public readonly CompletionResult? Result;
|
||||
|
||||
/// <summary>진행률 (0.0 ~ 1.0)</summary>
|
||||
public float Progress => TotalCount > 0 ? (float)(CurrentIndex + 1) / TotalCount : 0f;
|
||||
|
||||
public StepProgressInfo(AutoStep step, string propertyId, string status,
|
||||
int currentIndex, int totalCount, bool isCompleted, CompletionResult? result = null)
|
||||
{
|
||||
Step = step;
|
||||
PropertyId = propertyId;
|
||||
Status = status;
|
||||
CurrentIndex = currentIndex;
|
||||
TotalCount = totalCount;
|
||||
IsCompleted = isCompleted;
|
||||
Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Processor
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Twin Agent의 자동 설정 프로세서입니다.
|
||||
/// Auto 버튼 클릭 시 순차적으로 네트워크 설정을 진행합니다.
|
||||
@@ -17,14 +270,121 @@ namespace UVC.Entity.Processors
|
||||
|
||||
private readonly Entity _entity;
|
||||
|
||||
/// <summary>완료된 단계 상태 (PropertyId -> Value). 복원용.</summary>
|
||||
private readonly Dictionary<string, string> _stepStates = new Dictionary<string, string>();
|
||||
/// <summary>완료된 단계 상태 (PropertyId -> Value). PropertyItem 복원용.</summary>
|
||||
private readonly Dictionary<string, string> _stepStates = new();
|
||||
|
||||
/// <summary>전체 메시지 이력 (Entity별 터미널 로그 복원용)</summary>
|
||||
private readonly List<(string PropertyId, string Value)> _messageHistory = new();
|
||||
|
||||
/// <summary>현재 진행 중인 PropertyId (취소 시 사용)</summary>
|
||||
private string _currentPropertyId;
|
||||
|
||||
public bool HasSavedState => _stepStates.Count > 0;
|
||||
|
||||
/// <summary>전체 메시지 이력 (읽기 전용)</summary>
|
||||
public IReadOnlyList<(string PropertyId, string Value)> MessageHistory => _messageHistory;
|
||||
|
||||
/// <summary>전체 파이프라인이 중지 없이 완료되었는지 여부 (Speed까지 완료해야 true)</summary>
|
||||
public bool IsFullPipelineCompleted { get; private set; }
|
||||
|
||||
/// <summary>SubStep 진행 시 콜백 (진행도 정보 포함)</summary>
|
||||
public event Action<StepProgressInfo> onSubStepChanged;
|
||||
|
||||
/// <summary>MainStep의 마지막 SubStep 진입 시 콜백</summary>
|
||||
public event Action<StepProgressInfo> onLastSubStepEntered;
|
||||
|
||||
/// <summary>MainStep 완료 시 콜백 (step, completionStatus, result)</summary>
|
||||
public event Action<AutoStep, string, CompletionResult> onStepCompleted;
|
||||
|
||||
#region Step Definitions
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// 파이프라인 정의: MainStep → [ SubSteps... ] → Completion
|
||||
// 매직 스트링 없음. 순서 = 배열 인덱스.
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
private static readonly StepDefinition[] StepDefinitions =
|
||||
{
|
||||
// 0. Entity 정보 수집
|
||||
new(AutoStep.GetEntityInfo, new[]
|
||||
{
|
||||
SubStepId.ReadEntityMetaData,
|
||||
SubStepId.ReadEntityDefaultData,
|
||||
SubStepId.ReadEntityDetailInfo,
|
||||
SubStepId.ReadEntityStatus,
|
||||
SubStepId.Coalescing,
|
||||
SubStepId.GenerateGeneralInfo,
|
||||
}),
|
||||
|
||||
// 1. 네트워크 정보 추출
|
||||
new(AutoStep.ExtractNetworkInfo, new[]
|
||||
{
|
||||
SubStepId.Processing,
|
||||
SubStepId.Perusing,
|
||||
SubStepId.Inferring,
|
||||
}),
|
||||
|
||||
// 2. 연결
|
||||
new(AutoStep.Connecting, new[]
|
||||
{
|
||||
SubStepId.Crunching,
|
||||
SubStepId.Sussing,
|
||||
}),
|
||||
|
||||
// 3. 서버 탐색 (OctopusHub 75% / NotFound 25%)
|
||||
new(AutoStep.Server, new[]
|
||||
{
|
||||
SubStepId.Spelunking,
|
||||
SubStepId.Inferring,
|
||||
SubStepId.GenerateServerList,
|
||||
SubStepId.Forming,
|
||||
SubStepId.TargetServer,
|
||||
},
|
||||
new WeightedCompletion(CompletionId.OctopusHub, 0.75f),
|
||||
new WeightedCompletion(CompletionId.NotFound, 0.25f, CompletionResult.Error)
|
||||
),
|
||||
|
||||
// 4. 포트 스캔
|
||||
new(AutoStep.Port, new[]
|
||||
{
|
||||
SubStepId.Scanning,
|
||||
SubStepId.PortNumber,
|
||||
}),
|
||||
|
||||
// 5. 프로토콜 결정 (API 75% / MQTT 25%)
|
||||
new(AutoStep.Protocol, new[]
|
||||
{
|
||||
SubStepId.Shimmying,
|
||||
SubStepId.Transmuting,
|
||||
SubStepId.Perusing,
|
||||
SubStepId.ProtocolType,
|
||||
},
|
||||
new WeightedCompletion(CompletionId.API, 0.75f),
|
||||
new WeightedCompletion(CompletionId.MQTT, 0.25f)
|
||||
),
|
||||
|
||||
// 6. 서버 상태 확인 (Done 85% / Busy 10% / Error 5%)
|
||||
new(AutoStep.ServerStatusCheck, new[]
|
||||
{
|
||||
SubStepId.ServerStatus,
|
||||
},
|
||||
new WeightedCompletion(CompletionId.Done, 0.85f),
|
||||
new WeightedCompletion(CompletionId.Busy, 0.10f, CompletionResult.Warning),
|
||||
new WeightedCompletion(CompletionId.Error, 0.05f, CompletionResult.Error)
|
||||
),
|
||||
|
||||
// 7. 속도 측정 (5~120ms 80% / "300ms~" 20%)
|
||||
new(AutoStep.Speed, new[]
|
||||
{
|
||||
SubStepId.Speed,
|
||||
},
|
||||
new WeightedCompletion(0.80f, () => $"{UnityEngine.Random.Range(5, 121)}ms"),
|
||||
new WeightedCompletion(0.20f, () => "300ms~")
|
||||
),
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
public TwinAgentAutoProcessor(Entity entity, Action onInit = null) : base(onInit)
|
||||
{
|
||||
_entity = entity;
|
||||
@@ -47,19 +407,31 @@ namespace UVC.Entity.Processors
|
||||
protected override async UniTask Processing(CancellationToken ct)
|
||||
{
|
||||
Debug.Log("[TwinAgentAutoProcessor] Auto 프로세스 시작");
|
||||
IsFullPipelineCompleted = false;
|
||||
|
||||
await RunReadEntity(ct);
|
||||
await RunConnection(ct);
|
||||
foreach (var def in StepDefinitions)
|
||||
{
|
||||
Debug.Log($"[TwinAgentAutoProcessor] Step {(int)def.Step}: {def.Step}");
|
||||
bool shouldContinue = await RunStep(def, ct);
|
||||
if (!shouldContinue)
|
||||
{
|
||||
Debug.Log($"[TwinAgentAutoProcessor] 프로세스 중지 ({def.Step})");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("[TwinAgentAutoProcessor] Auto 프로세스 완료");
|
||||
IsFullPipelineCompleted = true;
|
||||
Debug.Log("[TwinAgentAutoProcessor] Auto 프로세스 완료 (전체 파이프라인 완료)");
|
||||
}
|
||||
|
||||
protected override void ProcessCancel()
|
||||
{
|
||||
if (_currentPropertyId != null)
|
||||
{
|
||||
SendMessage(_currentPropertyId, "Canceled");
|
||||
_stepStates[_currentPropertyId] = "Canceled";
|
||||
var canceledText = StepConfig.GetCompletionText(CompletionId.Canceled);
|
||||
SendMessage(_currentPropertyId, canceledText);
|
||||
_messageHistory.Add((_currentPropertyId, canceledText));
|
||||
_stepStates[_currentPropertyId] = canceledText;
|
||||
Debug.Log($"[TwinAgentAutoProcessor] 취소 (항목: {_currentPropertyId})");
|
||||
}
|
||||
}
|
||||
@@ -67,7 +439,9 @@ namespace UVC.Entity.Processors
|
||||
protected override void ProcessReset()
|
||||
{
|
||||
_stepStates.Clear();
|
||||
_messageHistory.Clear();
|
||||
_currentPropertyId = null;
|
||||
IsFullPipelineCompleted = false;
|
||||
Debug.Log("[TwinAgentAutoProcessor] 초기화 완료");
|
||||
}
|
||||
|
||||
@@ -78,76 +452,50 @@ namespace UVC.Entity.Processors
|
||||
return "Ready";
|
||||
}
|
||||
|
||||
#region 0. Read Entity
|
||||
|
||||
private async UniTask RunReadEntity(CancellationToken ct)
|
||||
{
|
||||
await RunStep("get_entity_info_status",
|
||||
new[] {
|
||||
"Read Entity Meta Data",
|
||||
"Read Entity Default Data",
|
||||
"Read Entity Detail Info",
|
||||
"Read Entity Status",
|
||||
"Coalescing",
|
||||
"Generate General Info",
|
||||
"Done" }, ct);
|
||||
|
||||
await RunStep("extract_network_info_status",
|
||||
new[] {
|
||||
"Processing",
|
||||
"Perusing",
|
||||
"Inferring",
|
||||
"Done" }, ct);
|
||||
|
||||
await RunStep("connecting_status",
|
||||
new[] {
|
||||
"Crunching",
|
||||
"Sussing",
|
||||
"Done" }, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 1. Connection
|
||||
|
||||
private async UniTask RunConnection(CancellationToken ct)
|
||||
{
|
||||
await RunStep("server_status",
|
||||
new[] { "Spelunking", "Inferring", "Generate Server List", "Forming", "Target Server", "Done" }, ct);
|
||||
|
||||
await RunStep("port_status",
|
||||
new[] { "Scanning", "Port Number", "Done" }, ct);
|
||||
|
||||
await RunStep("protocol_status",
|
||||
new[] { "Shimmying", "Transmuting", "Perusing", "Protocol Type", "Done" }, ct);
|
||||
|
||||
await RunStep("server_status_check",
|
||||
new[] { "ServerStatus", "Done" }, ct);
|
||||
|
||||
await RunStep("speed_status",
|
||||
new[] { "Speed", "Done" }, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper
|
||||
|
||||
private async UniTask RunStep(string propertyId, string[] statuses, CancellationToken ct)
|
||||
/// <summary>
|
||||
/// 단계를 실행합니다. 다음 단계를 계속 진행해야 하면 true, 중지해야 하면 false를 반환합니다.
|
||||
/// </summary>
|
||||
private async UniTask<bool> RunStep(StepDefinition def, CancellationToken ct)
|
||||
{
|
||||
_currentPropertyId = propertyId;
|
||||
_currentPropertyId = def.PropertyId;
|
||||
|
||||
foreach (var status in statuses)
|
||||
// SubSteps 순차 진행
|
||||
for (int i = 0; i < def.SubSteps.Length; i++)
|
||||
{
|
||||
SendMessage(propertyId, status);
|
||||
_stepStates[propertyId] = status;
|
||||
Debug.Log($"[TwinAgentAutoProcessor] {propertyId}: {status}");
|
||||
var status = StepConfig.GetSubStepText(def.SubSteps[i]);
|
||||
SendMessage(def.PropertyId, status);
|
||||
_messageHistory.Add((def.PropertyId, status));
|
||||
_stepStates[def.PropertyId] = status;
|
||||
Debug.Log($"[TwinAgentAutoProcessor] {def.PropertyId}: {status}");
|
||||
|
||||
if (status != "Done")
|
||||
{
|
||||
int delayMs = (int)(UnityEngine.Random.Range(1f, 2f) * 1000);
|
||||
await UniTask.Delay(delayMs, cancellationToken: ct);
|
||||
}
|
||||
var progressInfo = new StepProgressInfo(
|
||||
def.Step, def.PropertyId, status, i, def.TotalCount, false);
|
||||
onSubStepChanged?.Invoke(progressInfo);
|
||||
|
||||
if (i == def.SubSteps.Length - 1)
|
||||
onLastSubStepEntered?.Invoke(progressInfo);
|
||||
|
||||
int delayMs = (int)(UnityEngine.Random.Range(0.5f, 1.5f) * 1000);
|
||||
await UniTask.Delay(delayMs, cancellationToken: ct);
|
||||
}
|
||||
|
||||
// 완료 상태 선택 (가중치 기반) 및 전송
|
||||
var completion = def.SelectCompletion();
|
||||
var completionStatus = completion.Status;
|
||||
SendMessage(def.PropertyId, completionStatus);
|
||||
_messageHistory.Add((def.PropertyId, completionStatus));
|
||||
_stepStates[def.PropertyId] = completionStatus;
|
||||
Debug.Log($"[TwinAgentAutoProcessor] {def.PropertyId}: {completionStatus} (완료)");
|
||||
|
||||
onSubStepChanged?.Invoke(new StepProgressInfo(
|
||||
def.Step, def.PropertyId, completionStatus,
|
||||
def.SubSteps.Length, def.TotalCount, true, completion.Result));
|
||||
|
||||
onStepCompleted?.Invoke(def.Step, completionStatus, completion.Result);
|
||||
|
||||
return !completion.StopsProcess;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
|
||||
// 1. object_name 속성 추가 (수정 가능, 그룹 없이 개별)
|
||||
var nameProperty = new StringProperty("object_name", "Name",
|
||||
stageObject.GameObject != null ? stageObject.GameObject.name : "Unknown")
|
||||
stageObject.gameObject != null ? stageObject.gameObject.name : "Unknown")
|
||||
{
|
||||
IsReadOnly = false,
|
||||
Order = orderIndex++
|
||||
@@ -87,9 +87,9 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
entries.Add(nameProperty);
|
||||
|
||||
// 2. Transform 그룹 추가
|
||||
if (stageObject.GameObject != null)
|
||||
if (stageObject.gameObject != null)
|
||||
{
|
||||
var transform = stageObject.GameObject.transform;
|
||||
var transform = stageObject.gameObject.transform;
|
||||
var transformGroup = new PropertyGroup("transform", "Transform", order: orderIndex++);
|
||||
transformGroup.AddItems(new IPropertyItem[]
|
||||
{
|
||||
@@ -240,9 +240,11 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st
|
||||
Debug.Log("[EntityPropertyAdapter] TwinAgentAutoProcessor 생성 및 등록");
|
||||
}
|
||||
|
||||
// 이벤트 바인딩 (기존 구독 정리 후 최신 propertyDict로 재구독)
|
||||
processor.ClearBindings();
|
||||
processor.onMessage += (propertyId, value) =>
|
||||
// 이벤트 바인딩 (이전 스코프 구독만 정리 후 최신 propertyDict로 재구독)
|
||||
processor.ClearScopedBindings();
|
||||
|
||||
// UI 핸들러를 변수에 저장 (스코프 기반 정리용)
|
||||
Action<string, string> messageHandler = (propertyId, value) =>
|
||||
{
|
||||
if (!propertyDict.TryGetValue(propertyId, out var item))
|
||||
return;
|
||||
@@ -262,7 +264,7 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st
|
||||
|
||||
item.SetValue(value);
|
||||
};
|
||||
processor.onReset += () =>
|
||||
Action resetHandler = () =>
|
||||
{
|
||||
foreach (var kvp in propertyDict)
|
||||
{
|
||||
@@ -273,70 +275,15 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st
|
||||
}
|
||||
};
|
||||
// 백그라운드 완료/취소 시 버튼 텍스트 갱신
|
||||
processor.onComplete += () => { autoButton.ButtonText = "Run"; };
|
||||
processor.onCancel += () => { autoButton.ButtonText = "Run"; };
|
||||
Action completeHandler = () => { autoButton.ButtonText = "Run"; };
|
||||
Action cancelHandler = () => { autoButton.ButtonText = "Run"; };
|
||||
|
||||
// TerminalView 바인딩 (터미널 스타일 로그)
|
||||
if (terminalView != null)
|
||||
{
|
||||
// propertyId → 표시명 매핑
|
||||
var stepNames = new Dictionary<string, string>
|
||||
{
|
||||
["get_entity_info_status"] = "Get Entity Info",
|
||||
["extract_network_info_status"] = "Extract Network Info",
|
||||
["connecting_status"] = "Connecting",
|
||||
["server_status"] = "Server",
|
||||
["port_status"] = "Port",
|
||||
["protocol_status"] = "Protocol",
|
||||
["server_status_check"] = "Status Check",
|
||||
["speed_status"] = "Speed"
|
||||
};
|
||||
processor.onMessage += messageHandler;
|
||||
processor.onReset += resetHandler;
|
||||
processor.onComplete += completeHandler;
|
||||
processor.onCancel += cancelHandler;
|
||||
|
||||
var tv = terminalView; // 클로저 캡처용 로컬 변수
|
||||
|
||||
processor.onStart += () =>
|
||||
{
|
||||
tv.AddLog("> Starting Twin Agent Auto Process...", Color.cyan);
|
||||
};
|
||||
|
||||
processor.onMessage += (propId, value) =>
|
||||
{
|
||||
// ProcessorId 자체의 상태 메시지는 무시 (onStart/onComplete에서 처리)
|
||||
if (propId == processor.ProcessorId) return;
|
||||
|
||||
string stepName = stepNames.TryGetValue(propId, out var name) ? name : propId;
|
||||
|
||||
if (value == "Done")
|
||||
{
|
||||
tv.AddLog($"> [{stepName}] Done", Color.green);
|
||||
}
|
||||
else if (value == "Canceled")
|
||||
{
|
||||
tv.AddLog($"> [{stepName}] Canceled", new Color(1f, 0.647f, 0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
tv.AddLog($"> [{stepName}] {value}");
|
||||
}
|
||||
};
|
||||
|
||||
processor.onComplete += () =>
|
||||
{
|
||||
tv.AddLog("> Process completed successfully", Color.green);
|
||||
};
|
||||
|
||||
processor.onCancel += () =>
|
||||
{
|
||||
tv.AddLog("> Process canceled", new Color(1f, 0.647f, 0f));
|
||||
};
|
||||
|
||||
processor.onReset += () =>
|
||||
{
|
||||
tv.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
// 저장된 상태 복원
|
||||
// PropertyItem UI 상태 복원 (터미널 핸들러 구독 전에 수행하여 터미널에 중복 기록 방지)
|
||||
if (processor.HasSavedState)
|
||||
{
|
||||
processor.RestoreState();
|
||||
@@ -347,6 +294,112 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st
|
||||
autoButton.ButtonText = "Run";
|
||||
}
|
||||
|
||||
// TerminalView 바인딩 (터미널 스타일 로그)
|
||||
// propertyId → 표시명 매핑
|
||||
var stepNames = new Dictionary<string, string>
|
||||
{
|
||||
["get_entity_info_status"] = "Get Entity Info",
|
||||
["extract_network_info_status"] = "Extract Network Info",
|
||||
["connecting_status"] = "Connecting",
|
||||
["server_status"] = "Server",
|
||||
["port_status"] = "Port",
|
||||
["protocol_status"] = "Protocol",
|
||||
["server_status_check"] = "Status Check",
|
||||
["speed_status"] = "Speed"
|
||||
};
|
||||
|
||||
Action? startTerminal = null;
|
||||
Action<string, string>? messageTerminal = null;
|
||||
Action? completeTerminal = null;
|
||||
Action? cancelTerminal = null;
|
||||
Action? resetTerminal = null;
|
||||
|
||||
if (terminalView != null)
|
||||
{
|
||||
var tv = terminalView; // 클로저 캡처용 로컬 변수
|
||||
|
||||
startTerminal = () =>
|
||||
{
|
||||
tv.AddLog("> Starting Twin Agent Auto Process...", Color.cyan);
|
||||
};
|
||||
|
||||
messageTerminal = (propId, value) =>
|
||||
{
|
||||
// ProcessorId 자체의 상태 메시지는 무시 (onStart/onComplete에서 처리)
|
||||
if (propId == processor.ProcessorId) return;
|
||||
|
||||
string stepName = stepNames.TryGetValue(propId, out var name) ? name : propId;
|
||||
|
||||
if (value == "Done")
|
||||
tv.AddLog($"> [{stepName}] Done", Color.green);
|
||||
else if (value == "Canceled")
|
||||
tv.AddLog($"> [{stepName}] Canceled", new Color(1f, 0.647f, 0f));
|
||||
else
|
||||
tv.AddLog($"> [{stepName}] {value}");
|
||||
};
|
||||
|
||||
completeTerminal = () =>
|
||||
{
|
||||
tv.AddLog("> Process completed successfully", Color.green);
|
||||
};
|
||||
|
||||
cancelTerminal = () =>
|
||||
{
|
||||
tv.AddLog("> Process canceled", new Color(1f, 0.647f, 0f));
|
||||
};
|
||||
|
||||
resetTerminal = () =>
|
||||
{
|
||||
tv.Clear();
|
||||
};
|
||||
|
||||
processor.onStart += startTerminal;
|
||||
processor.onMessage += messageTerminal;
|
||||
processor.onComplete += completeTerminal;
|
||||
processor.onCancel += cancelTerminal;
|
||||
processor.onReset += resetTerminal;
|
||||
|
||||
// 터미널 로그 복원: MessageHistory에서 Entity별 이력 재생
|
||||
tv.Clear();
|
||||
if (processor.HasSavedState)
|
||||
{
|
||||
tv.AddLog("> Starting Twin Agent Auto Process...", Color.cyan);
|
||||
|
||||
foreach (var (propId, value) in processor.MessageHistory)
|
||||
{
|
||||
if (propId == processor.ProcessorId) continue;
|
||||
string stepName = stepNames.TryGetValue(propId, out var sn) ? sn : propId;
|
||||
|
||||
if (value == "Done")
|
||||
tv.AddLog($"> [{stepName}] Done", Color.green);
|
||||
else if (value == "Canceled")
|
||||
tv.AddLog($"> [{stepName}] Canceled", new Color(1f, 0.647f, 0f));
|
||||
else
|
||||
tv.AddLog($"> [{stepName}] {value}");
|
||||
}
|
||||
|
||||
// 완료/취소 상태에 따라 터미널 종료 메시지 추가
|
||||
if (processor.IsCompleted)
|
||||
tv.AddLog("> Process completed successfully", Color.green);
|
||||
else if (!processor.IsRunning)
|
||||
tv.AddLog("> Process canceled", new Color(1f, 0.647f, 0f));
|
||||
}
|
||||
}
|
||||
|
||||
// 스코프 정리 액션 등록 (다음 ClearScopedBindings 호출 시 이 핸들러들만 해제)
|
||||
processor.SetScopedCleanup(() =>
|
||||
{
|
||||
processor.onMessage -= messageHandler;
|
||||
processor.onReset -= resetHandler;
|
||||
processor.onComplete -= completeHandler;
|
||||
processor.onCancel -= cancelHandler;
|
||||
if (startTerminal != null) processor.onStart -= startTerminal;
|
||||
if (messageTerminal != null) processor.onMessage -= messageTerminal;
|
||||
if (completeTerminal != null) processor.onComplete -= completeTerminal;
|
||||
if (cancelTerminal != null) processor.onCancel -= cancelTerminal;
|
||||
if (resetTerminal != null) processor.onReset -= resetTerminal;
|
||||
});
|
||||
|
||||
// Auto 버튼 클릭 시 Run/Cancel 토글
|
||||
autoButton.Clicked += async () =>
|
||||
{
|
||||
@@ -441,11 +494,32 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st
|
||||
if (processor.HasSavedState)
|
||||
{
|
||||
processor.RestoreState();
|
||||
|
||||
// 터미널 로그: MessageHistory에서 Entity별 이력 재생
|
||||
terminalView?.Clear();
|
||||
terminalView?.AddLog("> Starting Twin Agent Auto Process...", Color.cyan);
|
||||
foreach (var (propId, value) in processor.MessageHistory)
|
||||
{
|
||||
if (propId == processor.ProcessorId) continue;
|
||||
string sn = stepNames.TryGetValue(propId, out var n) ? n : propId;
|
||||
if (value == "Done")
|
||||
terminalView?.AddLog($"> [{sn}] Done", Color.green);
|
||||
else if (value == "Canceled")
|
||||
terminalView?.AddLog($"> [{sn}] Canceled", new Color(1f, 0.647f, 0f));
|
||||
else
|
||||
terminalView?.AddLog($"> [{sn}] {value}");
|
||||
}
|
||||
if (processor.IsCompleted)
|
||||
terminalView?.AddLog("> Process completed successfully", Color.green);
|
||||
else if (!processor.IsRunning)
|
||||
terminalView?.AddLog("> Process canceled", new Color(1f, 0.647f, 0f));
|
||||
|
||||
autoButton.ButtonText = processor.IsRunning ? "Cancel" : "Run";
|
||||
Debug.Log("[EntityPropertyAdapter] Twin Agent로 전환 → 저장된 UI 상태 복원");
|
||||
}
|
||||
else
|
||||
{
|
||||
terminalView?.Clear();
|
||||
Debug.Log("[EntityPropertyAdapter] Twin Agent로 전환 → 초기 상태");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,11 +713,12 @@ public void Open(UVC.Entity.Entity entity)
|
||||
// Clear()는 _currentEntity를 null로 만들믰로 미리 저장해둔 previousEntity 사용
|
||||
Clear();
|
||||
|
||||
// 2. 이전 엔티티의 프로세서는 취소하지 않음 (백그라운드에서 계속 실행)
|
||||
// 다시 해당 엔티티를 선택하면 진행 상황이 복원됨
|
||||
// 2. 이전 엔티티의 UI 바인딩 해제 (프로세서는 백그라운드에서 계속 실행)
|
||||
// 공유 TerminalView에 이전 Entity의 로그가 침범하지 않도록 scoped bindings 해제
|
||||
if (previousEntity != null)
|
||||
{
|
||||
Debug.Log($"[PropertyWindow] 이전 엔티티 ({previousEntity.Name})의 프로세서는 백그라운드에서 계속 실행");
|
||||
previousEntity.ClearAllProcessorScopedBindings();
|
||||
Debug.Log($"[PropertyWindow] 이전 엔티티 ({previousEntity.Name})의 UI 바인딩 해제 (프로세서는 계속 실행)");
|
||||
}
|
||||
|
||||
// 3. 새 엔티티 설정
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,11 @@ namespace OCTOPUS_TWIN
|
||||
public bool IsRouteActive => routeSequence != null && routeSequence.IsActive();
|
||||
private Sequence routeSequence;
|
||||
|
||||
private CameraRoute currentRoute;
|
||||
public bool IsRouteRunning { get; private set; }
|
||||
public Action onRouteComplete;
|
||||
public CameraRoute route;
|
||||
|
||||
public bool IsClickUI
|
||||
{
|
||||
get
|
||||
@@ -150,6 +155,10 @@ namespace OCTOPUS_TWIN
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Q))
|
||||
{
|
||||
SetRoute(route);
|
||||
}
|
||||
//UI <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ī<><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||||
if (IsClickUI || IsOnTheUI)
|
||||
return;
|
||||
@@ -232,11 +241,11 @@ namespace OCTOPUS_TWIN
|
||||
currentAzimuth += input.mouseX * rotateSpeed;
|
||||
currentAzimuth %= 360;
|
||||
|
||||
//if(viewMode == ViewMode.PerspectiveView)
|
||||
//{
|
||||
// currentElevation -= input.mouseY * rotateSpeed;
|
||||
// currentElevation = Mathf.Clamp(currentElevation, minElevation, maxElevation);
|
||||
//}
|
||||
if(viewMode == ViewMode.PerspectiveView)
|
||||
{
|
||||
currentElevation -= input.mouseY * rotateSpeed;
|
||||
currentElevation = Mathf.Clamp(currentElevation, minElevation, maxElevation);
|
||||
}
|
||||
isRotateOperation = true;
|
||||
}
|
||||
|
||||
@@ -354,71 +363,122 @@ namespace OCTOPUS_TWIN
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public void SetRoute(CameraRoute route)
|
||||
{
|
||||
StopRoute();
|
||||
currentRoute = route;
|
||||
|
||||
var points = currentRoute.movePoints;
|
||||
if (points == null || points.Length == 0) return;
|
||||
|
||||
IsRouteRunning = true;
|
||||
Enable = false;
|
||||
|
||||
routeSequence = DOTween.Sequence();
|
||||
camera.transform.position = points[0].transform.position;
|
||||
camera.transform.rotation = points[0].transform.rotation;
|
||||
|
||||
for (int i = 0; i < route.PointCount; i++)
|
||||
if (points.Length == 1)
|
||||
{
|
||||
var (position, point) = route.GetWaypoint(i);
|
||||
|
||||
float elev = point != null ? point.elevation : currentElevation;
|
||||
float azi = point != null ? point.azimuth : currentAzimuth;
|
||||
float dist = point != null ? point.distance : currentDistance;
|
||||
float dur = point != null ? point.duration : 0.5f;
|
||||
float wait = point != null ? point.waitTime : 0f;
|
||||
|
||||
routeSequence.Append(
|
||||
DOTween.To(() => nextPosition, x => nextPosition = x, position, dur));
|
||||
routeSequence.Join(
|
||||
DOTween.To(() => currentElevation, x => currentElevation = x, elev, dur));
|
||||
routeSequence.Join(
|
||||
DOTween.To(() => currentAzimuth, x => currentAzimuth = x, azi, dur));
|
||||
routeSequence.Join(
|
||||
DOTween.To(() => currentDistance, x => currentDistance = x, dist, dur));
|
||||
|
||||
if (wait > 0f)
|
||||
routeSequence.AppendInterval(wait);
|
||||
CompleteRoute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (route.loop)
|
||||
routeSequence.SetLoops(-1, LoopType.Restart);
|
||||
int segCount = points.Length - 1;
|
||||
float[] segDurations = new float[segCount];
|
||||
float totalDuration = 0f;
|
||||
for (int i = 0; i < segCount; i++)
|
||||
{
|
||||
float dist = Vector3.Distance(points[i].transform.position, points[i + 1].transform.position);
|
||||
float spd = points[i + 1].speed > 0f ? points[i + 1].speed : moveSpeed;
|
||||
segDurations[i] = spd > 0f ? dist / spd : 1f;
|
||||
totalDuration += segDurations[i];
|
||||
}
|
||||
|
||||
routeSequence.OnUpdate(LastPositioning);
|
||||
routeSequence.OnKill(() => Enable = true);
|
||||
float[] segEnd = new float[segCount];
|
||||
float acc = 0f;
|
||||
for (int i = 0; i < segCount; i++)
|
||||
{
|
||||
acc += segDurations[i];
|
||||
segEnd[i] = acc / totalDuration;
|
||||
}
|
||||
|
||||
Vector3[] pos = new Vector3[points.Length];
|
||||
Quaternion[] rot = new Quaternion[points.Length];
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
pos[i] = points[i].transform.position;
|
||||
rot[i] = points[i].transform.rotation;
|
||||
}
|
||||
|
||||
routeSequence = DOTween.Sequence();
|
||||
routeSequence.Append(
|
||||
DOVirtual.Float(0f, 1f, totalDuration, t =>
|
||||
{
|
||||
int seg = segCount - 1;
|
||||
for (int i = 0; i < segEnd.Length; i++)
|
||||
{
|
||||
if (t <= segEnd[i]) { seg = i; break; }
|
||||
}
|
||||
|
||||
float start = seg > 0 ? segEnd[seg - 1] : 0f;
|
||||
float localT = Mathf.InverseLerp(start, segEnd[seg], t);
|
||||
|
||||
int idx = seg + 1;
|
||||
Vector3 p0 = (idx >= 2) ? pos[idx - 2] : pos[idx - 1];
|
||||
Vector3 p1 = pos[idx - 1];
|
||||
Vector3 p2 = pos[idx];
|
||||
Vector3 p3 = (idx + 1 < pos.Length) ? pos[idx + 1] : pos[idx];
|
||||
|
||||
camera.transform.position = CatmullRom(p0, p1, p2, p3, localT);
|
||||
camera.transform.rotation = Quaternion.Slerp(rot[idx - 1], rot[idx], localT);
|
||||
}).SetEase(Ease.Linear));
|
||||
|
||||
routeSequence.OnComplete(CompleteRoute);
|
||||
}
|
||||
|
||||
private static Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
||||
{
|
||||
float t2 = t * t;
|
||||
float t3 = t2 * t;
|
||||
return 0.5f * (
|
||||
2f * p1 +
|
||||
(-p0 + p2) * t +
|
||||
(2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
|
||||
(-p0 + 3f * p1 - 3f * p2 + p3) * t3
|
||||
);
|
||||
}
|
||||
|
||||
public void StopRoute()
|
||||
{
|
||||
if (routeSequence != null && routeSequence.IsActive())
|
||||
{
|
||||
routeSequence.Kill();
|
||||
routeSequence = null;
|
||||
}
|
||||
if (!IsRouteRunning) return;
|
||||
routeSequence?.Kill();
|
||||
routeSequence = null;
|
||||
currentRoute = null;
|
||||
IsRouteRunning = false;
|
||||
SyncFromCamera();
|
||||
Enable = true;
|
||||
}
|
||||
|
||||
public void AnimateToState(Vector3 pivotPosition, Vector3 eulerAngles, float distance, float duration = 0.4f)
|
||||
private void CompleteRoute()
|
||||
{
|
||||
// <20>ִϸ<D6B4><CFB8>̼<EFBFBD> <20>߿<EFBFBD><DFBF><EFBFBD> <20><><EFBFBD>콺 <20>Է<EFBFBD> <20><>Ȱ<EFBFBD><C8B0>ȭ
|
||||
Enable = false;
|
||||
|
||||
// DoTween<65><6E> <20><><EFBFBD><EFBFBD>Ͽ<EFBFBD> <20><>Ʈ<EFBFBD>ѷ<EFBFBD><D1B7><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε巴<CEB5><E5B7B4> <20><><EFBFBD><EFBFBD>
|
||||
DOTween.To(() => nextPosition, x => nextPosition = x, pivotPosition, duration);
|
||||
DOTween.To(() => currentElevation, x => currentElevation = x, eulerAngles.x, duration);
|
||||
DOTween.To(() => currentAzimuth, x => currentAzimuth = x, eulerAngles.y, duration);
|
||||
DOTween.To(() => currentDistance, x => currentDistance = x, distance, duration)
|
||||
.OnComplete(() => {
|
||||
// <20>ִϸ<D6B4><CFB8>̼<EFBFBD><CCBC><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>콺 <20>Է<EFBFBD><D4B7><EFBFBD> <20>ٽ<EFBFBD> Ȱ<><C8B0>ȭ
|
||||
Enable = true;
|
||||
});
|
||||
routeSequence = null;
|
||||
currentRoute = null;
|
||||
IsRouteRunning = false;
|
||||
SyncFromCamera();
|
||||
Enable = true;
|
||||
onRouteComplete?.Invoke();
|
||||
}
|
||||
|
||||
//public bool IsOperation()
|
||||
//{
|
||||
// if(is)
|
||||
//}
|
||||
private void SyncFromCamera()
|
||||
{
|
||||
var euler = camera.transform.eulerAngles;
|
||||
currentElevation = euler.x;
|
||||
currentAzimuth = euler.y;
|
||||
|
||||
var offset = Quaternion.Euler(euler.x, euler.y, 0f) * new Vector3(0, 0, -currentDistance);
|
||||
nextPosition = camera.transform.position - offset;
|
||||
cameraPivot.transform.position = nextPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ Material:
|
||||
m_LightmapFlags: 6
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: 2100
|
||||
m_CustomRenderQueue: 3000
|
||||
stringTagMap:
|
||||
RenderType: Transparent
|
||||
disabledShaderPasses:
|
||||
|
||||
Reference in New Issue
Block a user