so 싱글톤 추가

This commit is contained in:
NTG 2025-08-19 13:51:42 +09:00
parent b8eec075ab
commit 9d57bcb040
33 changed files with 185 additions and 72 deletions

View File

@ -10,22 +10,22 @@ public class GameController : Singleton<GameController>, IManager, IGameFlowHand
{ {
[SerializeField] private AssetReference _gameData; [SerializeField] private AssetReference _gameData;
public GameData GameData { get; private set; } public GameData GetGameData() => GameData.Instance;
public GameState GameState { get; private set; } public GameState GetGameState() => GameState.Instance;
private List<FlowController> _gameFlowControllers = new(); private List<FlowController> _gameFlowControllers = new();
private static readonly List<Type> GameFlowControllerTypes = new(); private static readonly List<Type> GameFlowControllerTypes = new();
public void PreInit() public void PreInit()
{ {
LoadOrCreateRestaurantState(); CreateGameState();
RegisterFlowHandler(); RegisterFlowHandler();
} }
public async Task Init() public async Task Init()
{ {
await LoadData(); await LoadData();
await GameData.LoadData(); await GetGameData().LoadData();
await InitializeAllFlowControllers(); await InitializeAllFlowControllers();
} }
@ -33,10 +33,9 @@ public void PostInit()
{ {
} }
private void LoadOrCreateRestaurantState() private void CreateGameState()
{ {
// TODO : Load states from saved files. if none, create them. GameState.CreateScriptSingleton();
GameState = ScriptableObject.CreateInstance<GameState>();
} }
private void RegisterFlowHandler() private void RegisterFlowHandler()
@ -49,7 +48,7 @@ private async Task InitializeAllFlowControllers()
// Create controllers and initialize them // Create controllers and initialize them
foreach (var gameFlowControllerType in GameFlowControllerTypes) foreach (var gameFlowControllerType in GameFlowControllerTypes)
{ {
// create new controllers from restaurantFlowControllerType // create new controllers from gameFlowControllerType
var newController = ScriptableObject.CreateInstance(gameFlowControllerType); var newController = ScriptableObject.CreateInstance(gameFlowControllerType);
var newFlowController = newController as FlowController; var newFlowController = newController as FlowController;
_gameFlowControllers.Add(newFlowController); _gameFlowControllers.Add(newFlowController);
@ -64,14 +63,6 @@ private async Task InitializeAllFlowControllers()
private async Task LoadData() private async Task LoadData()
{ {
var gameDataHandle = _gameData.LoadAssetAsync<GameData>();
await gameDataHandle.Task;
GameData = gameDataHandle.Result;
Debug.Assert(GameData != null, "GameData is null");
await Task.CompletedTask; await Task.CompletedTask;
} }

View File

@ -5,7 +5,7 @@
namespace DDD namespace DDD
{ {
[CreateAssetMenu(fileName = "GameData", menuName = "GameData/GameData")] [CreateAssetMenu(fileName = "GameData", menuName = "GameData/GameData")]
public class GameData : ScriptableObject public class GameData : ScriptSingleton<GameData>
{ {
[SerializeField] private AssetReference _gameLocalizationData; [SerializeField] private AssetReference _gameLocalizationData;

View File

@ -97,7 +97,7 @@ public string GetString(string key)
var entryRef = key; var entryRef = key;
var locale = LocalizationSettings.SelectedLocale; var locale = LocalizationSettings.SelectedLocale;
VariablesGroupAsset variables = GameController.Instance.GameData.LocalizationData.SmartStringVariableGroup; VariablesGroupAsset variables = GameData.Instance.LocalizationData.SmartStringVariableGroup;
if (variables != null) if (variables != null)
{ {
_singleArgBuffer.Clear(); _singleArgBuffer.Clear();

View File

@ -50,8 +50,8 @@ public void PreInit() { }
public async Task Init() public async Task Init()
{ {
var gameLevelStateSo = GameController.Instance.GameState.LevelState; var gameLevelStateSo = GameState.Instance.LevelState;
var restaurantStateSo = RestaurantController.Instance.RestaurantState.ManagementState; var restaurantStateSo = RestaurantState.Instance.ManagementState;
// 예시: day 초기 세팅 (없으면 생성, 타입 다르면 교체) // 예시: day 초기 세팅 (없으면 생성, 타입 다르면 교체)
Set(_smartStringKeys[smartStringKey.Day], gameLevelStateSo.Level); Set(_smartStringKeys[smartStringKey.Day], gameLevelStateSo.Level);
@ -71,7 +71,7 @@ public void PostInit()
EventBus.Register<SmartVariablesDirtyEvent>(this); EventBus.Register<SmartVariablesDirtyEvent>(this);
} }
private RestaurantManagementState GetRestaurantState() => RestaurantController.Instance.RestaurantState.ManagementState; private RestaurantManagementState GetRestaurantState() => RestaurantState.Instance.ManagementState;
public void Invoke(SmartVariablesDirtyEvent evt) public void Invoke(SmartVariablesDirtyEvent evt)
{ {
@ -113,7 +113,7 @@ public void RefreshChecklistTargets()
public void RefreshDay() public void RefreshDay()
{ {
var gameLevelStateSo = GameController.Instance.GameState.LevelState; var gameLevelStateSo = GameState.Instance.LevelState;
Set(_smartStringKeys[smartStringKey.Day], gameLevelStateSo.Level); Set(_smartStringKeys[smartStringKey.Day], gameLevelStateSo.Level);
} }
@ -179,7 +179,7 @@ public void SetEnum<TEnum>(string key, TEnum value) where TEnum : struct
return null; return null;
} }
var smartStringVariableGroup = GameController.Instance.GameData.LocalizationData.SmartStringVariableGroup; var smartStringVariableGroup = GameData.Instance.LocalizationData.SmartStringVariableGroup;
if (smartStringVariableGroup.TryGetValue(key, out var existing)) if (smartStringVariableGroup.TryGetValue(key, out var existing))
{ {

View File

@ -3,7 +3,7 @@
namespace DDD namespace DDD
{ {
public class GameState : ScriptableObject public class GameState : ScriptSingleton<GameState>
{ {
[SerializeField] private AssetReference _gameLevelState; [SerializeField] private AssetReference _gameLevelState;

View File

@ -29,7 +29,7 @@ public class ChecklistView : MonoBehaviour, IEventHandler<TodayMenuAddedEvent>,
public void Initalize() public void Initalize()
{ {
restaurantManagementStateSo = RestaurantController.Instance.RestaurantState.ManagementState; restaurantManagementStateSo = RestaurantState.Instance.ManagementState;
_checklistDatas = new List<ChecklistData>(3); _checklistDatas = new List<ChecklistData>(3);
_checklistDatas = GetComponentsInChildren<ChecklistData>().ToList(); _checklistDatas = GetComponentsInChildren<ChecklistData>().ToList();

View File

@ -40,7 +40,7 @@ public void Setup(ItemSlotUi ui, ItemViewModel model)
public RuntimeAnimatorController GetAnimatorController() public RuntimeAnimatorController GetAnimatorController()
{ {
return RestaurantController.Instance.RestaurantData.ManagementData.InventorySlotUiAnimatorController; return RestaurantData.Instance.ManagementData.InventorySlotUiAnimatorController;
} }
public void OnInventoryChanged(ItemSlotUi ui) public void OnInventoryChanged(ItemSlotUi ui)

View File

@ -38,8 +38,8 @@ private void OnDisable()
public void Initialize() public void Initialize()
{ {
restaurantManagementStateSo = RestaurantController.Instance.RestaurantState.ManagementState; restaurantManagementStateSo = RestaurantState.Instance.ManagementState;
restaurantManagementDataSo = RestaurantController.Instance.RestaurantData.ManagementData; restaurantManagementDataSo = RestaurantData.Instance.ManagementData;
Debug.Assert(restaurantManagementDataSo != null, "_todayMenuDataSo != null"); Debug.Assert(restaurantManagementDataSo != null, "_todayMenuDataSo != null");
Clear(); Clear();

View File

@ -49,7 +49,7 @@ private void OnDisable()
public void Initialize() public void Initialize()
{ {
restaurantManagementDataSo = RestaurantController.Instance.RestaurantData.ManagementData; restaurantManagementDataSo = RestaurantData.Instance.ManagementData;
} }
public void Invoke(ItemSlotSelectedEvent evt) public void Invoke(ItemSlotSelectedEvent evt)

View File

@ -60,7 +60,7 @@ private void UpdateHoldProgress()
private void ProcessCompleteBatchAction() private void ProcessCompleteBatchAction()
{ {
if (RestaurantController.Instance.RestaurantState.ManagementState.GetChecklistStates().Any(state => state == false)) if (RestaurantState.Instance.ManagementState.GetChecklistStates().Any(state => state == false))
{ {
ShowChecklistFailedPopup(); ShowChecklistFailedPopup();
} }

View File

@ -10,7 +10,7 @@ public void OnAdded(ItemSlotUi itemSlotUi)
if (inventorySlotUiStrategy.CanCrafting(itemSlotUi)) if (inventorySlotUiStrategy.CanCrafting(itemSlotUi))
{ {
RestaurantController.Instance.RestaurantState.ManagementState.TryAddTodayMenu(itemSlotUi.Model); RestaurantState.Instance.ManagementState.TryAddTodayMenu(itemSlotUi.Model);
} }
else else
{ {
@ -25,7 +25,7 @@ public void OnRemoved(ItemSlotUi itemSlotUi)
{ {
if (itemSlotUi.Strategy is InventorySlotUiStrategy) return; if (itemSlotUi.Strategy is InventorySlotUiStrategy) return;
RestaurantController.Instance.RestaurantState.ManagementState.TryRemoveTodayMenu(itemSlotUi.Model); RestaurantState.Instance.ManagementState.TryRemoveTodayMenu(itemSlotUi.Model);
} }
} }
} }

View File

@ -35,7 +35,7 @@ public void Setup(ItemSlotUi ui, ItemViewModel model)
} }
string markSpriteKey = null; string markSpriteKey = null;
if (RestaurantController.Instance.RestaurantState.ManagementState.IsCookwareMatched(ui.Model.Id)) if (RestaurantState.Instance.ManagementState.IsCookwareMatched(ui.Model.Id))
{ {
markSpriteKey = SpriteConstants.CheckYesSpriteKey; markSpriteKey = SpriteConstants.CheckYesSpriteKey;
} }
@ -51,7 +51,7 @@ public void Setup(ItemSlotUi ui, ItemViewModel model)
public RuntimeAnimatorController GetAnimatorController() public RuntimeAnimatorController GetAnimatorController()
{ {
return RestaurantController.Instance.RestaurantData.ManagementData.TodayMenuSlotUiAnimatorController; return RestaurantData.Instance.ManagementData.TodayMenuSlotUiAnimatorController;
} }
} }
} }

View File

@ -23,8 +23,8 @@ private void OnDestroy()
public void Initialize() public void Initialize()
{ {
restaurantManagementStateSo = RestaurantController.Instance.RestaurantState.ManagementState; restaurantManagementStateSo = RestaurantState.Instance.ManagementState;
restaurantManagementDataSo = RestaurantController.Instance.RestaurantData.ManagementData; restaurantManagementDataSo = RestaurantData.Instance.ManagementData;
foreach (Transform child in _todayFoodContent) foreach (Transform child in _todayFoodContent)
{ {

View File

@ -10,7 +10,7 @@ public void OnAdded(ItemSlotUi itemSlotUi)
if (inventorySlotUiStrategy.CanCrafting(itemSlotUi)) if (inventorySlotUiStrategy.CanCrafting(itemSlotUi))
{ {
RestaurantController.Instance.RestaurantState.ManagementState.TryAddTodayCookware(itemSlotUi.Model); RestaurantState.Instance.ManagementState.TryAddTodayCookware(itemSlotUi.Model);
} }
else else
{ {
@ -25,7 +25,7 @@ public void OnRemoved(ItemSlotUi itemSlotUi)
{ {
if (itemSlotUi.Strategy is InventorySlotUiStrategy) return; if (itemSlotUi.Strategy is InventorySlotUiStrategy) return;
RestaurantController.Instance.RestaurantState.ManagementState.TryRemoveTodayCookware(itemSlotUi.Model); RestaurantState.Instance.ManagementState.TryRemoveTodayCookware(itemSlotUi.Model);
} }
} }
} }

View File

@ -18,7 +18,7 @@ public void Setup(ItemSlotUi ui, ItemViewModel model)
} }
string markSpriteKey = null; string markSpriteKey = null;
if (RestaurantController.Instance.RestaurantState.ManagementState.IsTodayMenuMatched(ui.Model.Id)) if (RestaurantState.Instance.ManagementState.IsTodayMenuMatched(ui.Model.Id))
{ {
markSpriteKey = SpriteConstants.CheckYesSpriteKey; markSpriteKey = SpriteConstants.CheckYesSpriteKey;
} }
@ -34,7 +34,7 @@ public void Setup(ItemSlotUi ui, ItemViewModel model)
public RuntimeAnimatorController GetAnimatorController() public RuntimeAnimatorController GetAnimatorController()
{ {
return RestaurantController.Instance.RestaurantData.ManagementData.TodayMenuSlotUiAnimatorController; return RestaurantData.Instance.ManagementData.TodayMenuSlotUiAnimatorController;
} }
} }
} }

View File

@ -23,8 +23,8 @@ private void OnDestroy()
public void Initialize() public void Initialize()
{ {
restaurantManagementStateSo = RestaurantController.Instance.RestaurantState.ManagementState; restaurantManagementStateSo = RestaurantState.Instance.ManagementState;
restaurantManagementDataSo = RestaurantController.Instance.RestaurantData.ManagementData; restaurantManagementDataSo = RestaurantData.Instance.ManagementData;
foreach (Transform child in _todayWorkerContent) foreach (Transform child in _todayWorkerContent)
{ {

View File

@ -26,7 +26,7 @@ public void Setup(ItemSlotUi ui, ItemViewModel model)
public RuntimeAnimatorController GetAnimatorController() public RuntimeAnimatorController GetAnimatorController()
{ {
return RestaurantController.Instance.RestaurantData.ManagementData.TodayMenuSlotUiAnimatorController; return RestaurantData.Instance.ManagementData.TodayMenuSlotUiAnimatorController;
} }
} }
} }

View File

@ -9,7 +9,7 @@ public class RestaurantPlayerInput : MonoBehaviour
private void Start() private void Start()
{ {
_playerDataSo = RestaurantController.Instance.RestaurantData.PlayerData; _playerDataSo = RestaurantData.Instance.PlayerData;
_playerDataSo.OpenManagementUiAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.OpenManagementUi)); _playerDataSo.OpenManagementUiAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.OpenManagementUi));
_playerDataSo.OpenManagementUiAction.performed += OnOpenManagementUi; _playerDataSo.OpenManagementUiAction.performed += OnOpenManagementUi;

View File

@ -17,7 +17,7 @@ protected override void Start()
private Task Initialize() private Task Initialize()
{ {
_restaurantPlayerDataSo = RestaurantController.Instance.RestaurantData.PlayerData; _restaurantPlayerDataSo = RestaurantData.Instance.PlayerData;
Debug.Assert(_restaurantPlayerDataSo != null, "_restaurantPlayerDataSo is null"); Debug.Assert(_restaurantPlayerDataSo != null, "_restaurantPlayerDataSo is null");
_restaurantPlayerDataSo!.InteractAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Interact)); _restaurantPlayerDataSo!.InteractAction = InputManager.Instance.GetAction(InputActionMaps.Restaurant, nameof(RestaurantActions.Interact));

View File

@ -81,7 +81,7 @@ private System.Threading.Tasks.Task InitializePlayerData()
{ {
try try
{ {
_playerDataSo = RestaurantController.Instance.RestaurantData.PlayerData; _playerDataSo = RestaurantData.Instance.PlayerData;
SubscribeToInputEvents(); SubscribeToInputEvents();
_isInitialized = true; _isInitialized = true;
} }

View File

@ -13,7 +13,7 @@ public override Task InitializeController()
public override Task InitializeState() public override Task InitializeState()
{ {
_environmentState = RestaurantController.Instance.RestaurantState.EnvironmentState; _environmentState = RestaurantState.Instance.EnvironmentState;
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -13,7 +13,7 @@ public override Task InitializeController()
public override Task InitializeState() public override Task InitializeState()
{ {
// Load default asset // Load default asset
RestaurantController.Instance.RestaurantState.ManagementState.InitializeReadyForRestaurant(); RestaurantState.Instance.ManagementState.InitializeReadyForRestaurant();
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -8,14 +8,13 @@ public class RestaurantRunController : FlowController
RestaurantCustomerState _restaurantCustomerStateSo; RestaurantCustomerState _restaurantCustomerStateSo;
public override Task InitializeController() public override Task InitializeController()
{ {
_restaurantCustomerStateSo = RestaurantController.Instance.RestaurantState.CustomerState; _restaurantCustomerStateSo = RestaurantState.Instance.CustomerState;
return Task.CompletedTask; return Task.CompletedTask;
} }
public override Task InitializeState() public override Task InitializeState()
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
public override async Task OnReadyNewFlow(GameFlowState newFlowState) public override async Task OnReadyNewFlow(GameFlowState newFlowState)

View File

@ -15,7 +15,7 @@ public override Task RunFlowTask()
{ {
// TODO : Base prefab from EnvironmentDataSo // TODO : Base prefab from EnvironmentDataSo
var props = RestaurantController.Instance.RestaurantState.EnvironmentState.Props; var props = RestaurantState.Instance.EnvironmentState.Props;
foreach (var prop in props) foreach (var prop in props)
{ {
// TODO : Instantiate and Initialize // TODO : Instantiate and Initialize

View File

@ -20,7 +20,7 @@ public override Task RunFlowTask()
return Task.CompletedTask; return Task.CompletedTask;
} }
var playerPrefab = RestaurantController.Instance.RestaurantData.PlayerData.PlayerPrefab; var playerPrefab = RestaurantData.Instance.PlayerData.PlayerPrefab;
if (playerPrefab == null) if (playerPrefab == null)
{ {
Debug.LogError("PlayerPrefab이 설정되지 않았습니다!"); Debug.LogError("PlayerPrefab이 설정되지 않았습니다!");

View File

@ -10,8 +10,8 @@ public class RestaurantController : Singleton<RestaurantController>, IManager, I
{ {
[SerializeField] private AssetReference _restaurantData; [SerializeField] private AssetReference _restaurantData;
public RestaurantData RestaurantData { get; private set; } public RestaurantData GetRestaurantData() => RestaurantData.Instance;
public RestaurantState RestaurantState { get; private set; } public RestaurantState GetRestaurantState() => RestaurantState.Instance;
private List<FlowController> _restaurantFlowControllers = new(); private List<FlowController> _restaurantFlowControllers = new();
@ -27,14 +27,14 @@ public class RestaurantController : Singleton<RestaurantController>, IManager, I
public void PreInit() public void PreInit()
{ {
LoadOrCreateRestaurantState(); CreateRestaurantState();
RegisterFlowHandler(); RegisterFlowHandler();
} }
public async Task Init() public async Task Init()
{ {
await LoadData(); await LoadData();
await RestaurantData.LoadData(); await GetRestaurantData().LoadData();
await InitializeAllFlowControllers(); await InitializeAllFlowControllers();
} }
@ -42,10 +42,9 @@ public void PostInit()
{ {
} }
private void LoadOrCreateRestaurantState() private void CreateRestaurantState()
{ {
// TODO : Load states from saved files. if none, create them. RestaurantState.CreateScriptSingleton();
RestaurantState = ScriptableObject.CreateInstance<RestaurantState>();
} }
private void RegisterFlowHandler() private void RegisterFlowHandler()
@ -73,13 +72,7 @@ private async Task InitializeAllFlowControllers()
private async Task LoadData() private async Task LoadData()
{ {
var restaurantDataHandle = _restaurantData.LoadAssetAsync<RestaurantData>(); await Task.CompletedTask;
await restaurantDataHandle.Task;
RestaurantData = restaurantDataHandle.Result;
Debug.Assert(RestaurantData != null, "RestaurantData is null");
} }
public async Task OnReadyNewFlow(GameFlowState newFlowState) public async Task OnReadyNewFlow(GameFlowState newFlowState)

View File

@ -5,7 +5,7 @@
namespace DDD namespace DDD
{ {
[CreateAssetMenu(fileName = "RestaurantData", menuName = "RestaurantData/RestaurantData", order = 0)] [CreateAssetMenu(fileName = "RestaurantData", menuName = "RestaurantData/RestaurantData", order = 0)]
public class RestaurantData : ScriptableObject public class RestaurantData : ScriptSingleton<RestaurantData>
{ {
[SerializeField] private AssetReference _restaurantPlayerData; [SerializeField] private AssetReference _restaurantPlayerData;
[SerializeField] private AssetReference _restaurantManagementData; [SerializeField] private AssetReference _restaurantManagementData;

View File

@ -15,7 +15,7 @@ public bool ExecuteInteraction(IInteractor interactor, IInteractable interactabl
private RestaurantManagementState GetManagementState() private RestaurantManagementState GetManagementState()
{ {
return RestaurantController.Instance.RestaurantState.ManagementState; return RestaurantState.Instance.ManagementState;
} }
public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payloadSo = null) public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null, ScriptableObject payloadSo = null)

View File

@ -51,7 +51,7 @@ private async Task InitializeRunRestaurant()
{ {
_iCustomerFactory = new CustomerFactory(); _iCustomerFactory = new CustomerFactory();
var currentGameLevel = GameController.Instance.GameState.LevelState.Level; var currentGameLevel = GameState.Instance.LevelState.Level;
if (_levelDataSo == null) if (_levelDataSo == null)
{ {
_levelDataSo = DataManager.Instance.GetDataSo<LevelDataSo>(); _levelDataSo = DataManager.Instance.GetDataSo<LevelDataSo>();

View File

@ -86,7 +86,7 @@ public bool IsOpenable()
public RestaurantManagementData GetManagementData() public RestaurantManagementData GetManagementData()
{ {
return RestaurantController.Instance.RestaurantData.ManagementData; return RestaurantData.Instance.ManagementData;
} }
public bool TryAddTodayMenu(ItemViewModel model) public bool TryAddTodayMenu(ItemViewModel model)

View File

@ -2,7 +2,7 @@
namespace DDD namespace DDD
{ {
public class RestaurantState : ScriptableObject public class RestaurantState : ScriptSingleton<RestaurantState>
{ {
public RestaurantManagementState ManagementState { get; private set; } public RestaurantManagementState ManagementState { get; private set; }
public RestaurantRunState RunState { get; private set; } public RestaurantRunState RunState { get; private set; }

View File

@ -0,0 +1,128 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace DDD
{
/// <summary>
/// Addressables를 통해 ScriptableObject 에셋을 로드하여 싱글톤으로 제공하는 베이스 클래스.
/// - 첫 접근 시 Addressables에서 타입명(네임스페이스 제외)을 키로 동기 로드합니다.
/// - 로드에 실패하면 예외를 발생합니다. (새로 생성하지 않습니다)
/// - 이미 로드된 경우 캐시된 인스턴스를 반환합니다.
/// </summary>
/// <typeparam name="T">구현 타입</typeparam>
public abstract class ScriptSingleton<T> : ScriptableObject where T : ScriptSingleton<T>
{
#region Fields
[CanBeNull]
private static T _instance;
[NotNull]
private static readonly object _lock = new();
private static bool _isQuitting;
#endregion
#region Properties
[NotNull]
public static T Instance
{
get
{
if (_instance != null)
return _instance;
if (_isQuitting)
throw new InvalidOperationException($"애플리케이션 종료 중에는 '{typeof(T).Name}' 인스턴스를 로드할 수 없습니다.");
lock (_lock)
{
// 이중 체크 락킹 패턴
if (_instance != null)
return _instance;
try
{
var key = ResolveAddressKey();
var handle = Addressables.LoadAssetAsync<T>(key);
// 동기 로드: 메인 스레드에서 호출할 것을 권장합니다.
var loaded = handle.WaitForCompletion();
if (handle.Status != AsyncOperationStatus.Succeeded || loaded == null)
{
throw new InvalidOperationException($"Addressables 로드 실패: 타입='{typeof(T).Name}', key='{key}'");
}
_instance = loaded;
_instance.hideFlags = HideFlags.DontUnloadUnusedAsset;
_instance.OnInstanceLoaded();
return _instance;
}
catch (Exception)
{
throw;
}
}
}
}
#endregion
#region Methods
/// <summary>
/// 새로운 인스턴스를 생성하고 싱글톤으로 등록합니다.
/// Addressables에 등록되지 않은 경우 사용합니다.
/// </summary>
public static T CreateScriptSingleton()
{
if (_instance != null)
{
Debug.LogWarning($"[ScriptSingleton] {typeof(T).Name} 인스턴스가 이미 존재합니다. 기존 인스턴스를 반환합니다.");
return _instance;
}
lock (_lock)
{
if (_instance != null)
return _instance;
var newInstance = ScriptableObject.CreateInstance<T>();
_instance = newInstance;
_instance.hideFlags = HideFlags.DontUnloadUnusedAsset;
_instance.OnInstanceLoaded();
Debug.Log($"[ScriptSingleton] {typeof(T).Name} 인스턴스를 생성하고 싱글톤으로 등록했습니다.");
return _instance;
}
}
/// <summary>
/// 사용자 정의 초기화 훅. 인스턴스가 로드된 뒤 1회 호출됩니다.
/// </summary>
protected virtual void OnInstanceLoaded() { }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
private static void RegisterQuitHandler()
{
Application.quitting -= OnApplicationQuitting;
Application.quitting += OnApplicationQuitting;
}
private static void OnApplicationQuitting()
{
_isQuitting = true;
}
/// <summary>
/// Address Key를 해석합니다. 요구사항에 따라 타입명(네임스페이스 제외) 그대로를 사용합니다.
/// </summary>
private static string ResolveAddressKey()
{
return typeof(T).Name;
}
#endregion
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0544b64d4ef0a744dbd9ee6bcf4ecc00