633 lines
23 KiB
C#
633 lines
23 KiB
C#
#nullable enable
|
|
using System.Collections.Generic;
|
|
using Cysharp.Threading.Tasks;
|
|
using EPOOutline;
|
|
using UnityEngine;
|
|
using UVC.Core;
|
|
using UVC.GLTF;
|
|
using UVC.Studio.Config;
|
|
using UVC.Studio.Manager;
|
|
using UVC.UI.List.Accordion;
|
|
using UVC.UI.Tab;
|
|
using UVC.UI.Window;
|
|
using UnityEngine.EventSystems;
|
|
using UVC.Util;
|
|
|
|
namespace UVC.Studio.Tab
|
|
{
|
|
public class StudioSideTabBarAccordion : MonoBehaviour, ITabContent
|
|
{
|
|
[SerializeField]
|
|
private AccordionWindow? accordionWindow;
|
|
|
|
[SerializeField]
|
|
private Transform? stageRoot;
|
|
|
|
[SerializeField]
|
|
private Camera? mainCamera;
|
|
|
|
[SerializeField]
|
|
private LayerMask groundLayerMask = ~0;
|
|
|
|
[SerializeField]
|
|
private float defaultPlacementHeight = 0f;
|
|
|
|
private Library? library;
|
|
private StageObjectManager? stageObjectManager;
|
|
|
|
private bool isInitialized = false;
|
|
|
|
/// <summary>
|
|
/// GLTF 모델 캐시 (gltf 경로 -> 로드된 원본 GameObject)
|
|
/// </summary>
|
|
private readonly Dictionary<string, GameObject> gltfCache = new();
|
|
|
|
/// <summary>
|
|
/// 현재 로딩 중인 GLTF 경로 목록 (중복 로딩 방지)
|
|
/// </summary>
|
|
private readonly HashSet<string> loadingPaths = new();
|
|
|
|
/// <summary>
|
|
/// 현재 드래그 중인 3D 프리뷰 객체
|
|
/// </summary>
|
|
private GameObject? dragPreview;
|
|
|
|
/// <summary>
|
|
/// 현재 드래그 중인 장비 정보
|
|
/// </summary>
|
|
private EquipmentItem? draggingEquipment;
|
|
|
|
private GameObject? stage;
|
|
|
|
private void Awake()
|
|
{
|
|
if (accordionWindow == null)
|
|
{
|
|
accordionWindow = GetComponentInChildren<AccordionWindow>();
|
|
}
|
|
|
|
if (accordionWindow == null)
|
|
{
|
|
Debug.LogError("StudioSideTabBarAccordion component is not assigned or found in Children.");
|
|
return;
|
|
}
|
|
|
|
if (mainCamera == null)
|
|
{
|
|
mainCamera = Camera.main;
|
|
}
|
|
|
|
InjectorAppContext.Instance.Injector.RegisterInstance<StudioSideTabBarAccordion>(this, ServiceLifetime.Scene);
|
|
}
|
|
|
|
public async void Start()
|
|
{
|
|
// 동적으로 로드되는 Prefab이므로 [Inject]가 자동으로 동작하지 않음
|
|
// InjectorAppContext에서 직접 Library를 가져옴
|
|
if (!InjectorAppContext.Instance.IsInitialized)
|
|
{
|
|
await InjectorAppContext.Instance.WaitForInitializationAsync();
|
|
}
|
|
|
|
library = InjectorAppContext.Instance.Get<Library>();
|
|
stageObjectManager = InjectorAppContext.Instance.Get<StageObjectManager>();
|
|
stage = StudioSceneMain.Instance != null ? StudioSceneMain.Instance.Stage : null;
|
|
stageRoot = stage != null ? stage.transform : null;
|
|
|
|
SetupData();
|
|
SetupEvents();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이벤트 핸들러 설정
|
|
/// </summary>
|
|
private void SetupEvents()
|
|
{
|
|
if (accordionWindow != null)
|
|
{
|
|
accordionWindow.AccordionList.OnGridItemBeginDrag += OnGridItemBeginDragHandler;
|
|
accordionWindow.AccordionList.OnGridItemDrag += OnGridItemDragHandler;
|
|
accordionWindow.AccordionList.OnGridItemEndDrag += OnGridItemEndDragHandler;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 시작 핸들러 - 3D 프리뷰 생성
|
|
/// </summary>
|
|
private void OnGridItemBeginDragHandler(AccordionGridItemData itemData, Vector2 screenPosition)
|
|
{
|
|
if (itemData.Data is EquipmentItem equipment)
|
|
{
|
|
draggingEquipment = equipment;
|
|
CreateDragPreviewAsync(equipment, screenPosition).Forget();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 중 핸들러 - 3D 프리뷰 위치 업데이트
|
|
/// </summary>
|
|
private void OnGridItemDragHandler(AccordionGridItemData itemData, Vector2 screenPosition)
|
|
{
|
|
if (dragPreview != null)
|
|
{
|
|
UpdatePreviewPosition(screenPosition);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 종료 핸들러 - 프리뷰를 실제 객체로 전환
|
|
/// </summary>
|
|
private void OnGridItemEndDragHandler(AccordionGridItemData itemData, Vector2 screenPosition)
|
|
{
|
|
// UI 위에서 드래그가 끝났으면 프리뷰 제거
|
|
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
|
|
{
|
|
CancelDragPreview();
|
|
return;
|
|
}
|
|
|
|
if (dragPreview != null && draggingEquipment != null && stageObjectManager != null)
|
|
{
|
|
// 최종 위치 계산
|
|
var worldPosition = ScreenToWorldPosition(screenPosition);
|
|
|
|
// 프리뷰를 실제 객체로 전환
|
|
dragPreview.transform.position = worldPosition;
|
|
|
|
// 프리뷰 머티리얼을 원래대로 복원
|
|
RestorePreviewMaterials(dragPreview);
|
|
|
|
// Collider 추가 (Raycast로 클릭 감지용)
|
|
AddCollidersToModel(dragPreview);
|
|
|
|
// Outlinable 컴포넌트 추가 (선택 시 아웃라인 표시용)
|
|
AddOutlinableComponent(dragPreview);
|
|
|
|
// StageObjectManager에 등록
|
|
stageObjectManager.Register(draggingEquipment, dragPreview);
|
|
|
|
Debug.Log($"[StudioSideTabBarAccordion] Placed object at: {worldPosition}");
|
|
|
|
// 프리뷰 참조 해제 (파괴하지 않음 - 실제 객체로 사용)
|
|
dragPreview = null;
|
|
draggingEquipment = null;
|
|
}
|
|
else
|
|
{
|
|
// 드래그 취소 또는 실패 시 프리뷰 제거
|
|
CancelDragPreview();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 프리뷰 생성
|
|
/// </summary>
|
|
private async UniTaskVoid CreateDragPreviewAsync(EquipmentItem equipment, Vector2 screenPosition)
|
|
{
|
|
if (library == null) return;
|
|
|
|
var gltfPath = library.LibraryPath + equipment.gltf;
|
|
Debug.Log($"[StudioSideTabBarAccordion] Creating drag preview: {equipment.label}");
|
|
|
|
// 캐시에서 모델 가져오기 또는 로드
|
|
var cachedModel = await GetOrLoadModelAsync(gltfPath);
|
|
if (cachedModel == null)
|
|
{
|
|
Debug.LogError($"Failed to load model for preview: {gltfPath}");
|
|
return;
|
|
}
|
|
|
|
// 드래그 중 취소되었는지 확인
|
|
if (draggingEquipment != equipment)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 프리뷰 인스턴스 생성
|
|
var parent = stageRoot != null ? stageRoot : null;
|
|
dragPreview = Instantiate(cachedModel, parent);
|
|
dragPreview.layer = LayerMask.NameToLayer("Model");
|
|
dragPreview.SetActive(true);
|
|
dragPreview.name = $"{equipment.label}_Preview";
|
|
|
|
// 프리뷰 머티리얼 설정 (반투명)
|
|
ApplyPreviewMaterials(dragPreview);
|
|
|
|
// 초기 위치 설정
|
|
UpdatePreviewPosition(screenPosition);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 프리뷰 위치 업데이트
|
|
/// </summary>
|
|
private void UpdatePreviewPosition(Vector2 screenPosition)
|
|
{
|
|
if (dragPreview == null) return;
|
|
|
|
var worldPosition = ScreenToWorldPosition(screenPosition);
|
|
dragPreview.transform.position = worldPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스크린 좌표를 월드 좌표로 변환
|
|
/// </summary>
|
|
private Vector3 ScreenToWorldPosition(Vector2 screenPosition)
|
|
{
|
|
if (mainCamera == null)
|
|
{
|
|
mainCamera = Camera.main;
|
|
if (mainCamera == null)
|
|
{
|
|
return new Vector3(0, defaultPlacementHeight, 0);
|
|
}
|
|
}
|
|
|
|
// 레이캐스트로 바닥 위치 찾기
|
|
var ray = mainCamera.ScreenPointToRay(screenPosition);
|
|
if (Physics.Raycast(ray, out var hit, 1000f, groundLayerMask))
|
|
{
|
|
return hit.point;
|
|
}
|
|
|
|
// 바닥이 없으면 고정 높이의 평면과 교차점 계산
|
|
var plane = new Plane(Vector3.up, new Vector3(0, defaultPlacementHeight, 0));
|
|
if (plane.Raycast(ray, out var distance))
|
|
{
|
|
return ray.GetPoint(distance);
|
|
}
|
|
|
|
// 기본값
|
|
return new Vector3(0, defaultPlacementHeight, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 프리뷰 머티리얼 적용 (반투명)
|
|
/// </summary>
|
|
private void ApplyPreviewMaterials(GameObject obj)
|
|
{
|
|
var renderers = obj.GetComponentsInChildren<Renderer>();
|
|
foreach (var renderer in renderers)
|
|
{
|
|
var materials = renderer.materials;
|
|
for (int i = 0; i < materials.Length; i++)
|
|
{
|
|
var mat = materials[i];
|
|
// 원본 색상 저장 (나중에 복원용)
|
|
var originalColor = mat.HasProperty("_Color") ? mat.color : Color.white;
|
|
|
|
// 반투명 설정
|
|
mat.SetFloat("_Mode", 3); // Transparent mode
|
|
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
|
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
|
mat.SetInt("_ZWrite", 0);
|
|
mat.DisableKeyword("_ALPHATEST_ON");
|
|
mat.EnableKeyword("_ALPHABLEND_ON");
|
|
mat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
|
mat.renderQueue = 3000;
|
|
|
|
// 색상을 반투명으로 설정
|
|
if (mat.HasProperty("_Color"))
|
|
{
|
|
mat.color = new Color(originalColor.r, originalColor.g, originalColor.b, 0.5f);
|
|
}
|
|
}
|
|
renderer.materials = materials;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 프리뷰 머티리얼 복원 (불투명)
|
|
/// </summary>
|
|
private void RestorePreviewMaterials(GameObject obj)
|
|
{
|
|
var renderers = obj.GetComponentsInChildren<Renderer>();
|
|
foreach (var renderer in renderers)
|
|
{
|
|
var materials = renderer.materials;
|
|
for (int i = 0; i < materials.Length; i++)
|
|
{
|
|
var mat = materials[i];
|
|
|
|
// 불투명 설정 복원
|
|
mat.SetFloat("_Mode", 0); // Opaque mode
|
|
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
|
|
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
|
|
mat.SetInt("_ZWrite", 1);
|
|
mat.DisableKeyword("_ALPHATEST_ON");
|
|
mat.DisableKeyword("_ALPHABLEND_ON");
|
|
mat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
|
mat.renderQueue = -1;
|
|
|
|
// 색상을 불투명으로 복원
|
|
if (mat.HasProperty("_Color"))
|
|
{
|
|
var color = mat.color;
|
|
mat.color = new Color(color.r, color.g, color.b, 1f);
|
|
}
|
|
}
|
|
renderer.materials = materials;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 프리뷰 취소
|
|
/// </summary>
|
|
private void CancelDragPreview()
|
|
{
|
|
if (dragPreview != null)
|
|
{
|
|
Destroy(dragPreview);
|
|
dragPreview = null;
|
|
}
|
|
draggingEquipment = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모델에 Collider를 추가하고 레이어를 설정합니다 (Raycast로 클릭 감지용)
|
|
/// </summary>
|
|
private void AddCollidersToModel(GameObject obj)
|
|
{
|
|
if (obj == null) return;
|
|
|
|
int modelLayer = LayerMask.NameToLayer("Model");
|
|
int colliderCount = 0;
|
|
|
|
// 모든 자식 객체의 레이어를 Model로 설정
|
|
var allTransforms = obj.GetComponentsInChildren<Transform>(true);
|
|
foreach (var t in allTransforms)
|
|
{
|
|
t.gameObject.layer = modelLayer;
|
|
}
|
|
|
|
// MeshFilter가 있는 모든 자식에 MeshCollider 추가
|
|
var meshFilters = obj.GetComponentsInChildren<MeshFilter>();
|
|
foreach (var meshFilter in meshFilters)
|
|
{
|
|
// 이미 Collider가 있으면 스킵
|
|
if (meshFilter.GetComponent<Collider>() != null) continue;
|
|
|
|
// Mesh가 없으면 스킵
|
|
if (meshFilter.sharedMesh == null) continue;
|
|
|
|
// MeshCollider 추가
|
|
var meshCollider = meshFilter.gameObject.AddComponent<MeshCollider>();
|
|
meshCollider.sharedMesh = meshFilter.sharedMesh;
|
|
meshCollider.convex = false; // 정확한 충돌 감지를 위해 convex=false
|
|
colliderCount++;
|
|
}
|
|
|
|
Debug.Log($"[StudioSideTabBarAccordion] Added {colliderCount} MeshColliders to: {obj.name} (total meshes: {meshFilters.Length})");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outlinable 컴포넌트를 추가합니다 (선택 시 아웃라인 표시용)
|
|
/// </summary>
|
|
private void AddOutlinableComponent(GameObject obj)
|
|
{
|
|
if (obj == null) return;
|
|
|
|
// 이미 Outlinable이 있는지 확인
|
|
var existingOutlinable = obj.GetComponent<Outlinable>();
|
|
if (existingOutlinable == null)
|
|
{
|
|
existingOutlinable = obj.GetComponentInChildren<Outlinable>();
|
|
}
|
|
|
|
if (existingOutlinable != null)
|
|
{
|
|
// 이미 있으면 비활성화만
|
|
existingOutlinable.enabled = false;
|
|
return;
|
|
}
|
|
|
|
// Outlinable 추가
|
|
var outlinable = obj.AddComponent<Outlinable>();
|
|
outlinable.enabled = false; // 기본적으로 비활성화 (선택 시 활성화)
|
|
|
|
// DrawingMode를 Normal로 설정
|
|
outlinable.DrawingMode = OutlinableDrawingMode.Normal;
|
|
|
|
// RenderStyle을 FrontBack으로 설정 (앞면과 뒷면 모두 아웃라인)
|
|
outlinable.RenderStyle = RenderStyle.Single;
|
|
|
|
|
|
// Front 아웃라인 설정 (보이는 부분)
|
|
outlinable.OutlineParameters.Enabled = true;
|
|
outlinable.OutlineParameters.Color = ColorUtil.FromHex("#0041A2");//new Color(1f, 0.5f, 0f, 1f); // 주황색
|
|
outlinable.OutlineParameters.DilateShift = 1.0f;
|
|
outlinable.OutlineParameters.BlurShift = 1.0f;
|
|
|
|
// Renderer가 있는 자식들을 OutlineTarget으로 등록
|
|
var renderers = obj.GetComponentsInChildren<Renderer>();
|
|
int totalTargets = 0;
|
|
foreach (var renderer in renderers)
|
|
{
|
|
// Material이 여러 개인 경우 각 submesh에 대해 별도의 OutlineTarget 생성
|
|
int materialCount = renderer.sharedMaterials.Length;
|
|
for (int submeshIndex = 0; submeshIndex < materialCount; submeshIndex++)
|
|
{
|
|
var target = new OutlineTarget(renderer, submeshIndex)
|
|
{
|
|
CullMode = UnityEngine.Rendering.CullMode.Back // 양면 렌더링으로 Outline 연결
|
|
};
|
|
outlinable.AddTarget(target);
|
|
totalTargets++;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[StudioSideTabBarAccordion] Added Outlinable to: {obj.name} with {totalTargets} targets (from {renderers.Length} renderers)");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 캐시에서 모델을 가져오거나 새로 로드합니다
|
|
/// </summary>
|
|
private async UniTask<GameObject?> GetOrLoadModelAsync(string gltfPath)
|
|
{
|
|
// 캐시에 있으면 반환
|
|
if (gltfCache.TryGetValue(gltfPath, out var cached))
|
|
{
|
|
Debug.Log($"[StudioSideTabBarAccordion] Cache hit: {gltfPath}");
|
|
return cached;
|
|
}
|
|
|
|
// 이미 로딩 중이면 완료될 때까지 대기
|
|
if (loadingPaths.Contains(gltfPath))
|
|
{
|
|
Debug.Log($"[StudioSideTabBarAccordion] Waiting for loading: {gltfPath}");
|
|
await UniTask.WaitUntil(() => !loadingPaths.Contains(gltfPath));
|
|
return gltfCache.TryGetValue(gltfPath, out var loadedModel) ? loadedModel : null;
|
|
}
|
|
|
|
// 로딩 시작
|
|
loadingPaths.Add(gltfPath);
|
|
Debug.Log($"[StudioSideTabBarAccordion] Loading model: {gltfPath}");
|
|
|
|
try
|
|
{
|
|
// 캐시용 숨겨진 부모 생성 (처음 한 번만)
|
|
var cacheParent = GetOrCreateCacheParent();
|
|
|
|
// GLTF 로드
|
|
var model = await GLTFImporter.ImportFromFile(gltfPath, cacheParent);
|
|
if (model != null)
|
|
{
|
|
// 캐시에 저장 및 비활성화 (원본은 숨김)
|
|
model.SetActive(false);
|
|
gltfCache[gltfPath] = model;
|
|
Debug.Log($"[StudioSideTabBarAccordion] Cached model: {gltfPath}");
|
|
}
|
|
return model;
|
|
}
|
|
finally
|
|
{
|
|
loadingPaths.Remove(gltfPath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 캐시 부모 객체를 가져오거나 생성합니다
|
|
/// </summary>
|
|
private Transform GetOrCreateCacheParent()
|
|
{
|
|
var cacheName = "_GLTFCache";
|
|
var cacheObj = GameObject.Find(cacheName);
|
|
if (cacheObj == null)
|
|
{
|
|
cacheObj = new GameObject(cacheName);
|
|
cacheObj.SetActive(false);
|
|
DontDestroyOnLoad(cacheObj);
|
|
}
|
|
return cacheObj.transform;
|
|
}
|
|
|
|
public async void SetupData()
|
|
{
|
|
Debug.Log("StudioSideTabBarAccordion: SetupData called");
|
|
if (!isInitialized)
|
|
{
|
|
isInitialized = true;
|
|
|
|
Debug.Log($"StudioSideTabBarAccordion: Setting up data library != null:{library != null}");
|
|
if (library != null)
|
|
{
|
|
var data = new AccordionData();
|
|
var sec1 = new AccordionSectionData
|
|
{
|
|
Title = "Stacker Crane",
|
|
IsExpanded = true,
|
|
LayoutType = AccordionItemLayoutType.Grid
|
|
};
|
|
library.StakerCraneData.list.ForEach(stackerCrane =>
|
|
{
|
|
sec1.Items.Add(new AccordionGridItemData { Caption = stackerCrane.label, Image = library.LibraryPath + stackerCrane.image, Data = stackerCrane });
|
|
});
|
|
data.Sections.Add(sec1);
|
|
var sec2 = new AccordionSectionData
|
|
{
|
|
Title = "AGV",
|
|
IsExpanded = true,
|
|
LayoutType = AccordionItemLayoutType.Grid
|
|
};
|
|
library.AGVData.list.ForEach(agv =>
|
|
{
|
|
sec2.Items.Add(new AccordionGridItemData { Caption = agv.label, Image = library.LibraryPath + agv.image, Data = agv });
|
|
});
|
|
data.Sections.Add(sec2);
|
|
var sec3 = new AccordionSectionData
|
|
{
|
|
Title = "Ract",
|
|
IsExpanded = true,
|
|
LayoutType = AccordionItemLayoutType.Grid
|
|
};
|
|
library.RackSingleData.list.ForEach(ract =>
|
|
{
|
|
sec3.Items.Add(new AccordionGridItemData { Caption = ract.label, Image = library.LibraryPath + ract.image, Data = ract });
|
|
});
|
|
data.Sections.Add(sec3);
|
|
accordionWindow?.SetData(data);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Library is not injected.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 탭 콘텐츠에 데이터를 전달합니다.
|
|
/// </summary>
|
|
/// <param name="data">전달할 데이터 객체</param>
|
|
public void SetContentData(object? data)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 탭 전환 시 데이터가 있는 경우 전달 되는 데이터. SetContentData 이후 호출 됨
|
|
/// </summary>
|
|
/// <param name="data">전달할 데이터 객체</param>
|
|
public void UpdateContentData(object? data)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 닫힐 때 실행되는 로직을 처리합니다.
|
|
/// </summary>
|
|
/// <returns>비동기 닫기 작업을 나타내는 <see cref="UniTask"/>입니다.</returns>
|
|
public UniTask OnCloseAsync()
|
|
{
|
|
Debug.Log("StudioSideTabBarAccordion: OnClose called");
|
|
return UniTask.CompletedTask;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
// 이벤트 해제
|
|
if (accordionWindow != null)
|
|
{
|
|
accordionWindow.AccordionList.OnGridItemBeginDrag -= OnGridItemBeginDragHandler;
|
|
accordionWindow.AccordionList.OnGridItemDrag -= OnGridItemDragHandler;
|
|
accordionWindow.AccordionList.OnGridItemEndDrag -= OnGridItemEndDragHandler;
|
|
}
|
|
|
|
// 드래그 프리뷰 정리
|
|
CancelDragPreview();
|
|
|
|
// 캐시 정리
|
|
ClearCache();
|
|
}
|
|
|
|
/// <summary>
|
|
/// GLTF 캐시를 정리합니다
|
|
/// </summary>
|
|
public void ClearCache()
|
|
{
|
|
foreach (var kvp in gltfCache)
|
|
{
|
|
if (kvp.Value != null)
|
|
{
|
|
Destroy(kvp.Value);
|
|
}
|
|
}
|
|
gltfCache.Clear();
|
|
loadingPaths.Clear();
|
|
Debug.Log("[StudioSideTabBarAccordion] Cache cleared");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 경로의 캐시를 제거합니다
|
|
/// </summary>
|
|
public void RemoveFromCache(string gltfPath)
|
|
{
|
|
if (gltfCache.TryGetValue(gltfPath, out var cached))
|
|
{
|
|
if (cached != null)
|
|
{
|
|
Destroy(cached);
|
|
}
|
|
gltfCache.Remove(gltfPath);
|
|
Debug.Log($"[StudioSideTabBarAccordion] Removed from cache: {gltfPath}");
|
|
}
|
|
}
|
|
}
|
|
}
|