- BackgroundSceneLoaderWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - PropBrowserWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - StreamingleCommon.uss: 브라우저 공통 스타일 추가 (그리드/리스트/뷰토글/액션바/상태바) - excludeFromWeb 상태 새로고침 시 보존 수정 - 삭제된 배경 리소스 정리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1231 lines
43 KiB
C#
1231 lines
43 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.Rendering;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Text;
|
|
|
|
public class AssetBundleLoaderWindow : EditorWindow
|
|
{
|
|
private string bundlePath = "";
|
|
private AssetBundle loadedBundle;
|
|
private string[] assetNames;
|
|
private Vector2 scrollPosition;
|
|
private Dictionary<string, Object> loadedAssets = new Dictionary<string, Object>();
|
|
private Dictionary<string, bool> assetFoldouts = new Dictionary<string, bool>();
|
|
private string searchFilter = "";
|
|
private string fileHeaderInfo = "";
|
|
private int headerOffset = 0;
|
|
private bool showAdvanced = false;
|
|
private string tempExtractPath = "";
|
|
private List<string> zipEntries = new List<string>();
|
|
private bool isWarudoFormat = false;
|
|
|
|
// 멀티 번들 지원
|
|
private List<AssetBundle> additionalBundles = new List<AssetBundle>();
|
|
private string sceneBundlePath = "";
|
|
private AssetBundle sceneBundle;
|
|
|
|
[MenuItem("Tools/Utilities/AssetBundle Loader")]
|
|
public static void ShowWindow()
|
|
{
|
|
var window = GetWindow<AssetBundleLoaderWindow>("AssetBundle Loader");
|
|
window.minSize = new Vector2(400, 300);
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
GUILayout.Label("AssetBundle 테스트 로더", EditorStyles.boldLabel);
|
|
GUILayout.Space(5);
|
|
|
|
DrawBundlePathSection();
|
|
GUILayout.Space(10);
|
|
DrawLoadUnloadButtons();
|
|
GUILayout.Space(10);
|
|
|
|
if (loadedBundle != null)
|
|
{
|
|
DrawBundleInfo();
|
|
GUILayout.Space(5);
|
|
DrawSearchFilter();
|
|
GUILayout.Space(5);
|
|
DrawAssetList();
|
|
}
|
|
}
|
|
|
|
private void DrawBundlePathSection()
|
|
{
|
|
// Warudo 파일 원클릭 로드
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField("Warudo 원클릭", GUILayout.Width(85));
|
|
if (GUILayout.Button(".warudo 파일 열기", GUILayout.Height(24)))
|
|
{
|
|
string selectedPath = EditorUtility.OpenFilePanel("Warudo 파일 선택", "", "warudo");
|
|
if (!string.IsNullOrEmpty(selectedPath))
|
|
{
|
|
LoadWarudoFileOneClick(selectedPath);
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
GUILayout.Space(5);
|
|
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
|
|
GUILayout.Space(5);
|
|
|
|
// 리소스 번들 (sharedassets)
|
|
EditorGUILayout.BeginHorizontal();
|
|
bundlePath = EditorGUILayout.TextField("리소스 번들", bundlePath);
|
|
if (GUILayout.Button("찾기", GUILayout.Width(50)))
|
|
{
|
|
string selectedPath = EditorUtility.OpenFilePanel("리소스 번들 선택 (sharedassets)", "", "bin");
|
|
if (!string.IsNullOrEmpty(selectedPath))
|
|
{
|
|
bundlePath = selectedPath;
|
|
AnalyzeFileHeader();
|
|
AutoDetectSceneBundle();
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
// 씬 번들 (sceneassets)
|
|
EditorGUILayout.BeginHorizontal();
|
|
sceneBundlePath = EditorGUILayout.TextField("씬 번들", sceneBundlePath);
|
|
if (GUILayout.Button("찾기", GUILayout.Width(50)))
|
|
{
|
|
string selectedPath = EditorUtility.OpenFilePanel("씬 번들 선택 (sceneassets)", "", "bin");
|
|
if (!string.IsNullOrEmpty(selectedPath))
|
|
{
|
|
sceneBundlePath = selectedPath;
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
// 드래그 앤 드롭 지원
|
|
var dropArea = GUILayoutUtility.GetLastRect();
|
|
var evt = Event.current;
|
|
if (evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform)
|
|
{
|
|
if (dropArea.Contains(evt.mousePosition))
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
|
if (evt.type == EventType.DragPerform)
|
|
{
|
|
DragAndDrop.AcceptDrag();
|
|
if (DragAndDrop.paths.Length > 0)
|
|
{
|
|
bundlePath = DragAndDrop.paths[0];
|
|
AnalyzeFileHeader();
|
|
}
|
|
}
|
|
evt.Use();
|
|
}
|
|
}
|
|
|
|
// 고급 옵션
|
|
showAdvanced = EditorGUILayout.Foldout(showAdvanced, "고급 옵션");
|
|
if (showAdvanced)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
headerOffset = EditorGUILayout.IntField("헤더 오프셋 (bytes)", headerOffset);
|
|
|
|
if (!string.IsNullOrEmpty(bundlePath) && File.Exists(bundlePath))
|
|
{
|
|
if (GUILayout.Button("파일 헤더 분석"))
|
|
{
|
|
AnalyzeFileHeader();
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(fileHeaderInfo))
|
|
{
|
|
EditorGUILayout.HelpBox(fileHeaderInfo, MessageType.None);
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
|
|
private void DrawLoadUnloadButtons()
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
GUI.enabled = loadedBundle == null && !string.IsNullOrEmpty(bundlePath);
|
|
if (GUILayout.Button("번들 로드", GUILayout.Height(30)))
|
|
{
|
|
LoadBundle();
|
|
}
|
|
|
|
GUI.enabled = loadedBundle != null;
|
|
if (GUILayout.Button("번들 언로드", GUILayout.Height(30)))
|
|
{
|
|
UnloadBundle();
|
|
}
|
|
|
|
GUI.enabled = true;
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawBundleInfo()
|
|
{
|
|
// 씬 경로 확인 (리소스 번들 + 씬 번들)
|
|
List<string> allScenePaths = new List<string>();
|
|
|
|
string[] resourceScenes = loadedBundle.GetAllScenePaths();
|
|
if (resourceScenes != null) allScenePaths.AddRange(resourceScenes);
|
|
|
|
if (sceneBundle != null)
|
|
{
|
|
string[] sceneScenes = sceneBundle.GetAllScenePaths();
|
|
if (sceneScenes != null) allScenePaths.AddRange(sceneScenes);
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine($"리소스 번들: {Path.GetFileName(bundlePath)}");
|
|
sb.AppendLine($"에셋 개수: {(assetNames != null ? assetNames.Length : 0)}개");
|
|
|
|
if (sceneBundle != null)
|
|
{
|
|
sb.AppendLine($"씬 번들: {Path.GetFileName(sceneBundlePath)} (로드됨)");
|
|
}
|
|
|
|
if (allScenePaths.Count > 0)
|
|
{
|
|
sb.AppendLine($"씬 개수: {allScenePaths.Count}개");
|
|
}
|
|
|
|
EditorGUILayout.HelpBox(sb.ToString().TrimEnd(), MessageType.Info);
|
|
|
|
// 씬이 있으면 씬 로드 버튼 표시
|
|
if (allScenePaths.Count > 0)
|
|
{
|
|
GUILayout.Label("씬 목록", EditorStyles.boldLabel);
|
|
foreach (var scenePath in allScenePaths)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField(scenePath);
|
|
if (GUILayout.Button("씬 로드", GUILayout.Width(70)))
|
|
{
|
|
LoadSceneFromBundle(scenePath);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
GUILayout.Space(5);
|
|
}
|
|
|
|
// 빠른 액션 버튼들
|
|
EditorGUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("모든 GameObject 씬에 추가", GUILayout.Height(25)))
|
|
{
|
|
LoadAllGameObjectsToScene();
|
|
}
|
|
if (GUILayout.Button("GLB/Prefab만 추가", GUILayout.Height(25)))
|
|
{
|
|
LoadMainAssetsToScene();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void AutoDetectSceneBundle()
|
|
{
|
|
if (string.IsNullOrEmpty(bundlePath)) return;
|
|
|
|
string dir = Path.GetDirectoryName(bundlePath);
|
|
string baseName = Path.GetFileNameWithoutExtension(bundlePath);
|
|
|
|
// sharedassets -> sceneassets 자동 매핑
|
|
if (baseName.ToLower().Contains("sharedassets"))
|
|
{
|
|
string sceneFile = Path.Combine(dir, "sceneassets.bin");
|
|
if (File.Exists(sceneFile))
|
|
{
|
|
sceneBundlePath = sceneFile;
|
|
Debug.Log($"[AssetBundle Loader] 씬 번들 자동 감지: {sceneBundlePath}");
|
|
}
|
|
}
|
|
else if (baseName.ToLower().Contains("sceneassets"))
|
|
{
|
|
// 반대로 sceneassets를 선택한 경우
|
|
string sharedFile = Path.Combine(dir, "sharedassets.bin");
|
|
if (File.Exists(sharedFile))
|
|
{
|
|
sceneBundlePath = bundlePath;
|
|
bundlePath = sharedFile;
|
|
Debug.Log($"[AssetBundle Loader] 리소스/씬 번들 자동 스왑");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadWarudoFileOneClick(string warudoPath)
|
|
{
|
|
Debug.Log($"[AssetBundle Loader] Warudo 파일 원클릭 로드: {warudoPath}");
|
|
|
|
// 기존 번들 언로드
|
|
UnloadBundle();
|
|
|
|
try
|
|
{
|
|
// 파일 읽기
|
|
byte[] fileData = File.ReadAllBytes(warudoPath);
|
|
|
|
// UMOD 헤더 확인
|
|
if (fileData.Length < 16 || Encoding.ASCII.GetString(fileData, 0, 4) != "UMOD")
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "올바른 Warudo 파일이 아닙니다.", "확인");
|
|
return;
|
|
}
|
|
|
|
// ZIP 시그니처 찾기
|
|
byte[] pkSig = { 0x50, 0x4B, 0x03, 0x04 };
|
|
int zipOffset = -1;
|
|
for (int i = 0; i < System.Math.Min(64, fileData.Length - 4); i++)
|
|
{
|
|
if (fileData[i] == pkSig[0] && fileData[i + 1] == pkSig[1] &&
|
|
fileData[i + 2] == pkSig[2] && fileData[i + 3] == pkSig[3])
|
|
{
|
|
zipOffset = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (zipOffset < 0)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "ZIP 데이터를 찾을 수 없습니다.", "확인");
|
|
return;
|
|
}
|
|
|
|
// 임시 폴더 생성
|
|
tempExtractPath = Path.Combine(Path.GetTempPath(),
|
|
"WarudoExtract_" + Path.GetFileNameWithoutExtension(warudoPath) + "_" +
|
|
System.Guid.NewGuid().ToString("N").Substring(0, 6));
|
|
Directory.CreateDirectory(tempExtractPath);
|
|
|
|
// ZIP 추출
|
|
using (var zipStream = new MemoryStream(fileData, zipOffset, fileData.Length - zipOffset))
|
|
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Read))
|
|
{
|
|
Debug.Log($"[AssetBundle Loader] ZIP 엔트리 {archive.Entries.Count}개 발견");
|
|
|
|
foreach (var entry in archive.Entries)
|
|
{
|
|
if (string.IsNullOrEmpty(entry.Name)) continue;
|
|
|
|
string destPath = Path.Combine(tempExtractPath, entry.FullName);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(destPath));
|
|
entry.ExtractToFile(destPath, true);
|
|
|
|
Debug.Log($" - 추출: {entry.FullName} ({entry.Length:N0} bytes)");
|
|
}
|
|
}
|
|
|
|
// sharedassets.bin, sceneassets.bin 찾기
|
|
string sharedAssetsPath = Path.Combine(tempExtractPath, "sharedassets.bin");
|
|
string sceneAssetsPath = Path.Combine(tempExtractPath, "sceneassets.bin");
|
|
|
|
if (!File.Exists(sharedAssetsPath))
|
|
{
|
|
// 다른 이름의 번들 파일 찾기
|
|
foreach (var file in Directory.GetFiles(tempExtractPath, "*.bin"))
|
|
{
|
|
byte[] header = new byte[16];
|
|
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
|
|
{
|
|
fs.Read(header, 0, header.Length);
|
|
}
|
|
|
|
string sig = Encoding.ASCII.GetString(header, 0, 7);
|
|
if (sig == "UnityFS")
|
|
{
|
|
if (Path.GetFileName(file).ToLower().Contains("scene"))
|
|
sceneAssetsPath = file;
|
|
else
|
|
sharedAssetsPath = file;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 번들 경로 설정
|
|
if (File.Exists(sharedAssetsPath))
|
|
{
|
|
bundlePath = sharedAssetsPath;
|
|
}
|
|
if (File.Exists(sceneAssetsPath))
|
|
{
|
|
sceneBundlePath = sceneAssetsPath;
|
|
}
|
|
|
|
Debug.Log($"[AssetBundle Loader] 리소스 번들: {bundlePath}");
|
|
Debug.Log($"[AssetBundle Loader] 씬 번들: {sceneBundlePath}");
|
|
|
|
// 번들 로드
|
|
if (!string.IsNullOrEmpty(bundlePath) && File.Exists(bundlePath))
|
|
{
|
|
LoadBundle();
|
|
}
|
|
else
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "AssetBundle을 찾을 수 없습니다.", "확인");
|
|
}
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", $"Warudo 파일 로드 실패:\n{e.Message}", "확인");
|
|
Debug.LogError($"[AssetBundle Loader] 오류: {e}");
|
|
}
|
|
|
|
Repaint();
|
|
}
|
|
|
|
private void LoadSceneFromBundle(string scenePath)
|
|
{
|
|
try
|
|
{
|
|
// Additive로 씬 로드
|
|
SceneManager.LoadScene(scenePath, LoadSceneMode.Additive);
|
|
Debug.Log($"[AssetBundle Loader] 씬 로드 완료: {scenePath}");
|
|
|
|
// 로드된 씬 처리
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
// 로드된 씬의 모든 루트 오브젝트 활성화
|
|
ActivateLoadedSceneObjects(scenePath);
|
|
|
|
// 라이트 세팅 복사할지 물어보기
|
|
if (EditorUtility.DisplayDialog("라이트 세팅",
|
|
"로드된 씬의 라이트 세팅을 현재 씬에 적용하시겠습니까?\n\n" +
|
|
"(Skybox, Ambient Light, Fog, Reflection 등)",
|
|
"적용", "건너뛰기"))
|
|
{
|
|
CopyLightingFromLoadedScene(scenePath);
|
|
}
|
|
};
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
Debug.LogError($"[AssetBundle Loader] 씬 로드 실패: {e.Message}");
|
|
EditorUtility.DisplayDialog("씬 로드 실패",
|
|
$"씬을 로드할 수 없습니다.\n\n{e.Message}", "확인");
|
|
}
|
|
}
|
|
|
|
private void ActivateLoadedSceneObjects(string scenePath)
|
|
{
|
|
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
|
|
|
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
if (scene.name == sceneName || scene.path == scenePath)
|
|
{
|
|
GameObject[] rootObjects = scene.GetRootGameObjects();
|
|
|
|
foreach (var obj in rootObjects)
|
|
{
|
|
// Environment 오브젝트만 활성화
|
|
if (obj.name == "Environment" && !obj.activeSelf)
|
|
{
|
|
obj.SetActive(true);
|
|
Debug.Log($"[AssetBundle Loader] Environment 활성화됨");
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CopyLightingFromLoadedScene(string scenePath)
|
|
{
|
|
// 로드된 씬 찾기
|
|
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
|
|
Scene loadedScene = default;
|
|
|
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
if (scene.name == sceneName || scene.path == scenePath)
|
|
{
|
|
loadedScene = scene;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!loadedScene.IsValid())
|
|
{
|
|
Debug.LogWarning("[AssetBundle Loader] 로드된 씬을 찾을 수 없습니다.");
|
|
return;
|
|
}
|
|
|
|
// 활성 씬 임시 변경해서 RenderSettings 복사
|
|
Scene originalActiveScene = SceneManager.GetActiveScene();
|
|
SceneManager.SetActiveScene(loadedScene);
|
|
|
|
// 현재 RenderSettings 저장
|
|
var settings = new LightingSettingsData();
|
|
settings.CaptureFromRenderSettings();
|
|
|
|
// 원래 씬으로 복원
|
|
SceneManager.SetActiveScene(originalActiveScene);
|
|
|
|
// 저장한 설정 적용
|
|
settings.ApplyToRenderSettings();
|
|
|
|
Debug.Log("[AssetBundle Loader] 라이트 세팅 복사 완료!");
|
|
Debug.Log($" - Skybox: {RenderSettings.skybox?.name ?? "None"}");
|
|
Debug.Log($" - Ambient Mode: {RenderSettings.ambientMode}");
|
|
Debug.Log($" - Ambient Color: {RenderSettings.ambientLight}");
|
|
Debug.Log($" - Fog: {RenderSettings.fog}");
|
|
}
|
|
|
|
// 라이트 세팅 데이터 저장용 클래스
|
|
private class LightingSettingsData
|
|
{
|
|
// Skybox
|
|
public Material skybox;
|
|
|
|
// Ambient
|
|
public AmbientMode ambientMode;
|
|
public Color ambientLight;
|
|
public Color ambientSkyColor;
|
|
public Color ambientEquatorColor;
|
|
public Color ambientGroundColor;
|
|
public float ambientIntensity;
|
|
|
|
// Fog
|
|
public bool fog;
|
|
public FogMode fogMode;
|
|
public Color fogColor;
|
|
public float fogDensity;
|
|
public float fogStartDistance;
|
|
public float fogEndDistance;
|
|
|
|
// Reflection
|
|
public DefaultReflectionMode defaultReflectionMode;
|
|
public int defaultReflectionResolution;
|
|
public Cubemap customReflection;
|
|
public float reflectionIntensity;
|
|
public int reflectionBounces;
|
|
|
|
// Halo/Flare
|
|
public float haloStrength;
|
|
public float flareFadeSpeed;
|
|
public float flareStrength;
|
|
|
|
// Sun
|
|
public Light sun;
|
|
|
|
public void CaptureFromRenderSettings()
|
|
{
|
|
// Skybox
|
|
skybox = RenderSettings.skybox;
|
|
|
|
// Ambient
|
|
ambientMode = RenderSettings.ambientMode;
|
|
ambientLight = RenderSettings.ambientLight;
|
|
ambientSkyColor = RenderSettings.ambientSkyColor;
|
|
ambientEquatorColor = RenderSettings.ambientEquatorColor;
|
|
ambientGroundColor = RenderSettings.ambientGroundColor;
|
|
ambientIntensity = RenderSettings.ambientIntensity;
|
|
|
|
// Fog
|
|
fog = RenderSettings.fog;
|
|
fogMode = RenderSettings.fogMode;
|
|
fogColor = RenderSettings.fogColor;
|
|
fogDensity = RenderSettings.fogDensity;
|
|
fogStartDistance = RenderSettings.fogStartDistance;
|
|
fogEndDistance = RenderSettings.fogEndDistance;
|
|
|
|
// Reflection
|
|
defaultReflectionMode = RenderSettings.defaultReflectionMode;
|
|
defaultReflectionResolution = RenderSettings.defaultReflectionResolution;
|
|
customReflection = RenderSettings.customReflection;
|
|
reflectionIntensity = RenderSettings.reflectionIntensity;
|
|
reflectionBounces = RenderSettings.reflectionBounces;
|
|
|
|
// Halo/Flare
|
|
haloStrength = RenderSettings.haloStrength;
|
|
flareFadeSpeed = RenderSettings.flareFadeSpeed;
|
|
flareStrength = RenderSettings.flareStrength;
|
|
|
|
// Sun
|
|
sun = RenderSettings.sun;
|
|
}
|
|
|
|
public void ApplyToRenderSettings()
|
|
{
|
|
// Skybox
|
|
RenderSettings.skybox = skybox;
|
|
|
|
// Ambient
|
|
RenderSettings.ambientMode = ambientMode;
|
|
RenderSettings.ambientLight = ambientLight;
|
|
RenderSettings.ambientSkyColor = ambientSkyColor;
|
|
RenderSettings.ambientEquatorColor = ambientEquatorColor;
|
|
RenderSettings.ambientGroundColor = ambientGroundColor;
|
|
RenderSettings.ambientIntensity = ambientIntensity;
|
|
|
|
// Fog
|
|
RenderSettings.fog = fog;
|
|
RenderSettings.fogMode = fogMode;
|
|
RenderSettings.fogColor = fogColor;
|
|
RenderSettings.fogDensity = fogDensity;
|
|
RenderSettings.fogStartDistance = fogStartDistance;
|
|
RenderSettings.fogEndDistance = fogEndDistance;
|
|
|
|
// Reflection
|
|
RenderSettings.defaultReflectionMode = defaultReflectionMode;
|
|
RenderSettings.defaultReflectionResolution = defaultReflectionResolution;
|
|
RenderSettings.customReflection = customReflection;
|
|
RenderSettings.reflectionIntensity = reflectionIntensity;
|
|
RenderSettings.reflectionBounces = reflectionBounces;
|
|
|
|
// Halo/Flare
|
|
RenderSettings.haloStrength = haloStrength;
|
|
RenderSettings.flareFadeSpeed = flareFadeSpeed;
|
|
RenderSettings.flareStrength = flareStrength;
|
|
|
|
// Sun
|
|
RenderSettings.sun = sun;
|
|
}
|
|
}
|
|
|
|
private void LoadAllGameObjectsToScene()
|
|
{
|
|
if (loadedBundle == null || assetNames == null) return;
|
|
|
|
int count = 0;
|
|
GameObject rootParent = new GameObject($"[Bundle] {Path.GetFileNameWithoutExtension(bundlePath)}");
|
|
Undo.RegisterCreatedObjectUndo(rootParent, "Load All Assets from Bundle");
|
|
|
|
foreach (var assetName in assetNames)
|
|
{
|
|
try
|
|
{
|
|
var asset = loadedBundle.LoadAsset<GameObject>(assetName);
|
|
if (asset != null)
|
|
{
|
|
var instance = Instantiate(asset);
|
|
instance.name = asset.name;
|
|
instance.transform.SetParent(rootParent.transform);
|
|
Undo.RegisterCreatedObjectUndo(instance, "Load Asset from Bundle");
|
|
count++;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
Selection.activeGameObject = rootParent;
|
|
Debug.Log($"[AssetBundle Loader] {count}개 GameObject 씬에 추가됨");
|
|
}
|
|
|
|
private void LoadMainAssetsToScene()
|
|
{
|
|
if (loadedBundle == null || assetNames == null) return;
|
|
|
|
int count = 0;
|
|
GameObject rootParent = new GameObject($"[Bundle] {Path.GetFileNameWithoutExtension(bundlePath)}");
|
|
Undo.RegisterCreatedObjectUndo(rootParent, "Load Main Assets from Bundle");
|
|
|
|
foreach (var assetName in assetNames)
|
|
{
|
|
// GLB, prefab 파일만 필터링
|
|
string ext = Path.GetExtension(assetName).ToLower();
|
|
if (ext != ".glb" && ext != ".gltf" && ext != ".prefab" && ext != ".fbx")
|
|
continue;
|
|
|
|
// 하위 머티리얼/텍스처 제외
|
|
if (assetName.Contains(".materials/") || assetName.Contains(".textures/"))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
var asset = loadedBundle.LoadAsset<GameObject>(assetName);
|
|
if (asset != null)
|
|
{
|
|
var instance = Instantiate(asset);
|
|
instance.name = asset.name;
|
|
instance.transform.SetParent(rootParent.transform);
|
|
Undo.RegisterCreatedObjectUndo(instance, "Load Asset from Bundle");
|
|
count++;
|
|
Debug.Log($"[AssetBundle Loader] 로드: {assetName}");
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
Selection.activeGameObject = rootParent;
|
|
Debug.Log($"[AssetBundle Loader] {count}개 메인 에셋 씬에 추가됨");
|
|
}
|
|
|
|
private void DrawSearchFilter()
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.Label("검색:", GUILayout.Width(40));
|
|
searchFilter = EditorGUILayout.TextField(searchFilter);
|
|
if (GUILayout.Button("X", GUILayout.Width(20)))
|
|
{
|
|
searchFilter = "";
|
|
GUI.FocusControl(null);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawAssetList()
|
|
{
|
|
GUILayout.Label("에셋 목록", EditorStyles.boldLabel);
|
|
|
|
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
|
|
|
if (assetNames != null)
|
|
{
|
|
foreach (var assetName in assetNames)
|
|
{
|
|
// 검색 필터 적용
|
|
if (!string.IsNullOrEmpty(searchFilter) &&
|
|
!assetName.ToLower().Contains(searchFilter.ToLower()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
DrawAssetItem(assetName);
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
private void DrawAssetItem(string assetName)
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
// 폴드아웃 토글
|
|
if (!assetFoldouts.ContainsKey(assetName))
|
|
assetFoldouts[assetName] = false;
|
|
|
|
assetFoldouts[assetName] = EditorGUILayout.Foldout(assetFoldouts[assetName], assetName, true);
|
|
|
|
// 로드 버튼
|
|
bool isLoaded = loadedAssets.ContainsKey(assetName) && loadedAssets[assetName] != null;
|
|
if (GUILayout.Button(isLoaded ? "로드됨" : "로드", GUILayout.Width(60)))
|
|
{
|
|
if (!isLoaded)
|
|
{
|
|
LoadAsset(assetName);
|
|
}
|
|
}
|
|
|
|
// 씬에 추가 버튼 (GameObject인 경우)
|
|
if (isLoaded && loadedAssets[assetName] is GameObject)
|
|
{
|
|
if (GUILayout.Button("씬에 추가", GUILayout.Width(70)))
|
|
{
|
|
InstantiateAsset(assetName);
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
// 폴드아웃 내용
|
|
if (assetFoldouts[assetName] && isLoaded)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
var asset = loadedAssets[assetName];
|
|
EditorGUILayout.ObjectField("에셋", asset, asset.GetType(), false);
|
|
EditorGUILayout.LabelField("타입", asset.GetType().Name);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void AnalyzeFileHeader()
|
|
{
|
|
if (!File.Exists(bundlePath))
|
|
{
|
|
fileHeaderInfo = "파일을 찾을 수 없습니다.";
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
byte[] headerBytes = new byte[256];
|
|
using (var fs = new FileStream(bundlePath, FileMode.Open, FileAccess.Read))
|
|
{
|
|
fs.Read(headerBytes, 0, headerBytes.Length);
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine($"파일 크기: {new FileInfo(bundlePath).Length:N0} bytes");
|
|
|
|
// 첫 32바이트를 16진수로 표시
|
|
sb.Append("Header (hex): ");
|
|
for (int i = 0; i < 32 && i < headerBytes.Length; i++)
|
|
{
|
|
sb.Append(headerBytes[i].ToString("X2") + " ");
|
|
}
|
|
sb.AppendLine();
|
|
|
|
// ASCII로 표시 (출력 가능한 문자만)
|
|
sb.Append("Header (ascii): ");
|
|
for (int i = 0; i < 32 && i < headerBytes.Length; i++)
|
|
{
|
|
char c = (char)headerBytes[i];
|
|
sb.Append(char.IsControl(c) ? '.' : c);
|
|
}
|
|
sb.AppendLine();
|
|
|
|
// UMod/Warudo 포맷 체크 (UMOD 헤더 + PK ZIP)
|
|
isWarudoFormat = CheckWarudoFormat(headerBytes);
|
|
if (isWarudoFormat)
|
|
{
|
|
sb.AppendLine("포맷: UMod/Warudo (.warudo)");
|
|
sb.AppendLine("ZIP 압축된 AssetBundle 컨테이너입니다.");
|
|
|
|
// ZIP 내용물 미리보기
|
|
try
|
|
{
|
|
zipEntries.Clear();
|
|
int zipOffset = FindZipSignature(headerBytes);
|
|
if (zipOffset >= 0)
|
|
{
|
|
sb.AppendLine($"ZIP 시작 위치: offset {zipOffset}");
|
|
using (var fs = new FileStream(bundlePath, FileMode.Open, FileAccess.Read))
|
|
{
|
|
fs.Seek(zipOffset, SeekOrigin.Begin);
|
|
using (var zipStream = new MemoryStream())
|
|
{
|
|
fs.CopyTo(zipStream);
|
|
zipStream.Seek(0, SeekOrigin.Begin);
|
|
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Read))
|
|
{
|
|
sb.AppendLine($"ZIP 엔트리 ({archive.Entries.Count}개):");
|
|
foreach (var entry in archive.Entries)
|
|
{
|
|
zipEntries.Add(entry.FullName);
|
|
sb.AppendLine($" - {entry.FullName} ({entry.Length:N0} bytes)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
sb.AppendLine($"ZIP 분석 오류: {ex.Message}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Unity AssetBundle 시그니처 검색
|
|
int signatureOffset = FindUnitySignature(headerBytes);
|
|
if (signatureOffset >= 0)
|
|
{
|
|
sb.AppendLine($"Unity 시그니처 발견 위치: offset {signatureOffset}");
|
|
headerOffset = signatureOffset;
|
|
}
|
|
else
|
|
{
|
|
// 파일 전체에서 검색 (처음 64KB만)
|
|
byte[] searchBytes = new byte[65536];
|
|
using (var fs = new FileStream(bundlePath, FileMode.Open, FileAccess.Read))
|
|
{
|
|
int bytesRead = fs.Read(searchBytes, 0, searchBytes.Length);
|
|
signatureOffset = FindUnitySignature(searchBytes, bytesRead);
|
|
if (signatureOffset >= 0)
|
|
{
|
|
sb.AppendLine($"Unity 시그니처 발견 위치: offset {signatureOffset}");
|
|
headerOffset = signatureOffset;
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("Unity AssetBundle 시그니처를 찾을 수 없습니다.");
|
|
sb.AppendLine("이 파일은 암호화되었거나 다른 포맷일 수 있습니다.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileHeaderInfo = sb.ToString();
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
fileHeaderInfo = $"헤더 분석 오류: {e.Message}";
|
|
}
|
|
|
|
Repaint();
|
|
}
|
|
|
|
private bool CheckWarudoFormat(byte[] header)
|
|
{
|
|
// UMOD 시그니처 체크
|
|
if (header.Length >= 4)
|
|
{
|
|
string sig = Encoding.ASCII.GetString(header, 0, 4);
|
|
if (sig == "UMOD")
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private int FindZipSignature(byte[] data)
|
|
{
|
|
// PK 시그니처 (0x50, 0x4B, 0x03, 0x04)
|
|
byte[] pkSig = { 0x50, 0x4B, 0x03, 0x04 };
|
|
for (int i = 0; i <= data.Length - pkSig.Length; i++)
|
|
{
|
|
bool match = true;
|
|
for (int j = 0; j < pkSig.Length; j++)
|
|
{
|
|
if (data[i + j] != pkSig[j])
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private int FindUnitySignature(byte[] data, int length = -1)
|
|
{
|
|
if (length < 0) length = data.Length;
|
|
|
|
// Unity AssetBundle 시그니처: "UnityFS", "UnityRaw", "UnityWeb"
|
|
string[] signatures = { "UnityFS", "UnityRaw", "UnityWeb" };
|
|
|
|
foreach (var sig in signatures)
|
|
{
|
|
byte[] sigBytes = Encoding.ASCII.GetBytes(sig);
|
|
for (int i = 0; i <= length - sigBytes.Length; i++)
|
|
{
|
|
bool match = true;
|
|
for (int j = 0; j < sigBytes.Length; j++)
|
|
{
|
|
if (data[i + j] != sigBytes[j])
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match) return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private void LoadBundle()
|
|
{
|
|
if (!File.Exists(bundlePath))
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "파일을 찾을 수 없습니다: " + bundlePath, "확인");
|
|
return;
|
|
}
|
|
|
|
// 먼저 헤더 분석
|
|
if (string.IsNullOrEmpty(fileHeaderInfo))
|
|
{
|
|
AnalyzeFileHeader();
|
|
}
|
|
|
|
try
|
|
{
|
|
// UMod/Warudo 포맷인 경우 ZIP에서 추출
|
|
if (isWarudoFormat)
|
|
{
|
|
LoadWarudoBundle();
|
|
return;
|
|
}
|
|
|
|
// 방법 1: 직접 로드
|
|
if (headerOffset == 0)
|
|
{
|
|
loadedBundle = AssetBundle.LoadFromFile(bundlePath);
|
|
}
|
|
|
|
// 방법 2: 오프셋이 있으면 메모리에서 로드
|
|
if (loadedBundle == null && headerOffset > 0)
|
|
{
|
|
Debug.Log($"[AssetBundle Loader] 오프셋 {headerOffset}에서 로드 시도...");
|
|
byte[] allBytes = File.ReadAllBytes(bundlePath);
|
|
int dataLength = allBytes.Length - headerOffset;
|
|
byte[] bundleBytes = new byte[dataLength];
|
|
System.Array.Copy(allBytes, headerOffset, bundleBytes, 0, dataLength);
|
|
loadedBundle = AssetBundle.LoadFromMemory(bundleBytes);
|
|
}
|
|
|
|
// 방법 3: 직접 로드 실패 시 시그니처 자동 검색
|
|
if (loadedBundle == null && headerOffset == 0)
|
|
{
|
|
Debug.Log("[AssetBundle Loader] 직접 로드 실패, 시그니처 검색 중...");
|
|
AnalyzeFileHeader();
|
|
|
|
if (headerOffset > 0)
|
|
{
|
|
byte[] allBytes = File.ReadAllBytes(bundlePath);
|
|
int dataLength = allBytes.Length - headerOffset;
|
|
byte[] bundleBytes = new byte[dataLength];
|
|
System.Array.Copy(allBytes, headerOffset, bundleBytes, 0, dataLength);
|
|
loadedBundle = AssetBundle.LoadFromMemory(bundleBytes);
|
|
}
|
|
}
|
|
|
|
if (loadedBundle == null)
|
|
{
|
|
EditorUtility.DisplayDialog("로드 실패",
|
|
"AssetBundle 로드 실패.\n\n" +
|
|
"가능한 원인:\n" +
|
|
"- 다른 Unity 버전으로 빌드됨\n" +
|
|
"- 암호화된 번들\n" +
|
|
"- 지원하지 않는 포맷\n\n" +
|
|
"'고급 옵션'에서 파일 헤더를 확인하세요.",
|
|
"확인");
|
|
return;
|
|
}
|
|
|
|
assetNames = loadedBundle.GetAllAssetNames();
|
|
loadedAssets.Clear();
|
|
assetFoldouts.Clear();
|
|
|
|
Debug.Log($"[AssetBundle Loader] 리소스 번들 로드 완료: {bundlePath}");
|
|
Debug.Log($"[AssetBundle Loader] 에셋 {assetNames.Length}개 발견");
|
|
|
|
// 씬 번들도 로드
|
|
LoadSceneBundleIfExists();
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "번들 로드 중 오류 발생: " + e.Message, "확인");
|
|
Debug.LogError($"[AssetBundle Loader] 오류: {e}");
|
|
}
|
|
}
|
|
|
|
private void LoadSceneBundleIfExists()
|
|
{
|
|
if (string.IsNullOrEmpty(sceneBundlePath) || !File.Exists(sceneBundlePath))
|
|
return;
|
|
|
|
try
|
|
{
|
|
sceneBundle = AssetBundle.LoadFromFile(sceneBundlePath);
|
|
if (sceneBundle != null)
|
|
{
|
|
string[] scenePaths = sceneBundle.GetAllScenePaths();
|
|
Debug.Log($"[AssetBundle Loader] 씬 번들 로드 완료: {sceneBundlePath}");
|
|
Debug.Log($"[AssetBundle Loader] 씬 {scenePaths.Length}개 발견");
|
|
|
|
foreach (var path in scenePaths)
|
|
{
|
|
Debug.Log($" - {path}");
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
Debug.LogWarning($"[AssetBundle Loader] 씬 번들 로드 실패: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private void LoadWarudoBundle()
|
|
{
|
|
Debug.Log("[AssetBundle Loader] UMod/Warudo 포맷 감지, ZIP에서 추출 중...");
|
|
|
|
try
|
|
{
|
|
// UMOD 헤더 뒤의 ZIP 오프셋 찾기
|
|
byte[] headerBytes = new byte[64];
|
|
using (var fs = new FileStream(bundlePath, FileMode.Open, FileAccess.Read))
|
|
{
|
|
fs.Read(headerBytes, 0, headerBytes.Length);
|
|
}
|
|
|
|
int zipOffset = FindZipSignature(headerBytes);
|
|
if (zipOffset < 0)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "ZIP 시그니처를 찾을 수 없습니다.", "확인");
|
|
return;
|
|
}
|
|
|
|
// 임시 폴더에 추출
|
|
tempExtractPath = Path.Combine(Path.GetTempPath(), "WarudoExtract_" + System.Guid.NewGuid().ToString("N").Substring(0, 8));
|
|
Directory.CreateDirectory(tempExtractPath);
|
|
|
|
using (var fs = new FileStream(bundlePath, FileMode.Open, FileAccess.Read))
|
|
{
|
|
fs.Seek(zipOffset, SeekOrigin.Begin);
|
|
using (var zipStream = new MemoryStream())
|
|
{
|
|
fs.CopyTo(zipStream);
|
|
zipStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Read))
|
|
{
|
|
string assetBundleEntry = null;
|
|
|
|
foreach (var entry in archive.Entries)
|
|
{
|
|
string destPath = Path.Combine(tempExtractPath, entry.FullName);
|
|
|
|
// 디렉토리면 생성
|
|
if (string.IsNullOrEmpty(entry.Name))
|
|
{
|
|
Directory.CreateDirectory(destPath);
|
|
continue;
|
|
}
|
|
|
|
// 파일 추출
|
|
Directory.CreateDirectory(Path.GetDirectoryName(destPath));
|
|
entry.ExtractToFile(destPath, true);
|
|
|
|
Debug.Log($"[AssetBundle Loader] 추출: {entry.FullName}");
|
|
|
|
// AssetBundle 찾기 (modinfo.dat, .dll 제외)
|
|
string ext = Path.GetExtension(entry.Name).ToLower();
|
|
if (ext != ".dat" && ext != ".dll" && ext != ".json" && ext != ".xml" && ext != ".txt")
|
|
{
|
|
// Unity 시그니처 확인
|
|
if (File.Exists(destPath))
|
|
{
|
|
byte[] firstBytes = new byte[16];
|
|
using (var checkFs = new FileStream(destPath, FileMode.Open, FileAccess.Read))
|
|
{
|
|
checkFs.Read(firstBytes, 0, firstBytes.Length);
|
|
}
|
|
if (FindUnitySignature(firstBytes) >= 0)
|
|
{
|
|
assetBundleEntry = destPath;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// AssetBundle 로드
|
|
if (!string.IsNullOrEmpty(assetBundleEntry))
|
|
{
|
|
Debug.Log($"[AssetBundle Loader] AssetBundle 발견: {assetBundleEntry}");
|
|
loadedBundle = AssetBundle.LoadFromFile(assetBundleEntry);
|
|
|
|
if (loadedBundle != null)
|
|
{
|
|
assetNames = loadedBundle.GetAllAssetNames();
|
|
loadedAssets.Clear();
|
|
assetFoldouts.Clear();
|
|
|
|
Debug.Log($"[AssetBundle Loader] Warudo 번들 로드 완료!");
|
|
Debug.Log($"[AssetBundle Loader] 에셋 {assetNames.Length}개 발견");
|
|
}
|
|
else
|
|
{
|
|
EditorUtility.DisplayDialog("로드 실패",
|
|
"AssetBundle 로드 실패.\n\n" +
|
|
"이 번들은 다른 Unity 버전(2021.3.18f1)으로 빌드되었습니다.\n" +
|
|
"현재 프로젝트의 Unity 버전과 호환되지 않을 수 있습니다.",
|
|
"확인");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorUtility.DisplayDialog("오류",
|
|
"ZIP 내에서 AssetBundle을 찾을 수 없습니다.\n\n" +
|
|
"추출된 파일들:\n" + string.Join("\n", zipEntries),
|
|
"확인");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", $"Warudo 번들 로드 오류:\n{e.Message}", "확인");
|
|
Debug.LogError($"[AssetBundle Loader] Warudo 로드 오류: {e}");
|
|
}
|
|
}
|
|
|
|
private void UnloadBundle()
|
|
{
|
|
// 씬 번들 먼저 언로드
|
|
if (sceneBundle != null)
|
|
{
|
|
sceneBundle.Unload(true);
|
|
sceneBundle = null;
|
|
Debug.Log("[AssetBundle Loader] 씬 번들 언로드 완료");
|
|
}
|
|
|
|
// 리소스 번들 언로드
|
|
if (loadedBundle != null)
|
|
{
|
|
loadedBundle.Unload(true);
|
|
loadedBundle = null;
|
|
assetNames = null;
|
|
loadedAssets.Clear();
|
|
assetFoldouts.Clear();
|
|
Debug.Log("[AssetBundle Loader] 리소스 번들 언로드 완료");
|
|
}
|
|
|
|
// 추가 번들들 언로드
|
|
foreach (var bundle in additionalBundles)
|
|
{
|
|
if (bundle != null) bundle.Unload(true);
|
|
}
|
|
additionalBundles.Clear();
|
|
|
|
// 임시 추출 폴더 정리
|
|
if (!string.IsNullOrEmpty(tempExtractPath) && Directory.Exists(tempExtractPath))
|
|
{
|
|
try
|
|
{
|
|
Directory.Delete(tempExtractPath, true);
|
|
Debug.Log("[AssetBundle Loader] 임시 폴더 정리 완료");
|
|
}
|
|
catch { }
|
|
tempExtractPath = "";
|
|
}
|
|
|
|
isWarudoFormat = false;
|
|
zipEntries.Clear();
|
|
fileHeaderInfo = "";
|
|
}
|
|
|
|
private void LoadAsset(string assetName)
|
|
{
|
|
if (loadedBundle == null) return;
|
|
|
|
try
|
|
{
|
|
var asset = loadedBundle.LoadAsset(assetName);
|
|
if (asset != null)
|
|
{
|
|
loadedAssets[assetName] = asset;
|
|
Debug.Log($"[AssetBundle Loader] 에셋 로드: {assetName} ({asset.GetType().Name})");
|
|
}
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
Debug.LogError($"[AssetBundle Loader] 에셋 로드 실패: {assetName}\n{e}");
|
|
}
|
|
}
|
|
|
|
private void InstantiateAsset(string assetName)
|
|
{
|
|
if (!loadedAssets.ContainsKey(assetName)) return;
|
|
|
|
var asset = loadedAssets[assetName] as GameObject;
|
|
if (asset == null) return;
|
|
|
|
var instance = Instantiate(asset);
|
|
instance.name = asset.name;
|
|
Undo.RegisterCreatedObjectUndo(instance, "Instantiate Asset from Bundle");
|
|
Selection.activeGameObject = instance;
|
|
|
|
Debug.Log($"[AssetBundle Loader] 씬에 추가: {asset.name}");
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
UnloadBundle();
|
|
}
|
|
}
|