490 lines
24 KiB
C#
490 lines
24 KiB
C#
using System.Collections.Generic;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UVC.Extension;
|
|
using UVC.Locale;
|
|
using UVC.UI.Tooltip;
|
|
|
|
namespace UVC.UI.Toolbar
|
|
{
|
|
public class ToolbarView : MonoBehaviour
|
|
{
|
|
protected ToolbarModel ToolbarModel { get; private set; }
|
|
|
|
// 이제 이 프리팹들은 ToolbarView GameObject의 Inspector에서 직접 할당해야 합니다.
|
|
public GameObject standardButtonPrefab;
|
|
public GameObject toggleButtonPrefab;
|
|
public GameObject radioButtonPrefab;
|
|
public GameObject expandableButtonPrefab;
|
|
public GameObject separatorPrefab;
|
|
public GameObject subMenuPanelPrefab;
|
|
|
|
public Transform toolbarContainer;
|
|
public LayoutGroup layoutGroup;
|
|
// UI 요소와 모델을 매핑하여 상태 업데이트 시 사용
|
|
protected Dictionary<ToolbarButtonBase, GameObject> _modelToGameObjectMap = new Dictionary<ToolbarButtonBase, GameObject>();
|
|
|
|
// Canvas 참조 (Inspector에서 할당하거나 GetComponentInParent 등으로 찾기)
|
|
protected Canvas rootCanvas;
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
|
|
if (toolbarContainer == null)
|
|
{
|
|
toolbarContainer = GetComponent<Transform>();
|
|
}
|
|
if (toolbarContainer == null)
|
|
{
|
|
toolbarContainer = GetComponentInChildren<Transform>();
|
|
}
|
|
|
|
if (layoutGroup == null)
|
|
{
|
|
layoutGroup = toolbarContainer.gameObject.GetComponent<VerticalLayoutGroup>();
|
|
}
|
|
if (layoutGroup == null)
|
|
{
|
|
layoutGroup = toolbarContainer.gameObject.AddComponent<VerticalLayoutGroup>();
|
|
}
|
|
|
|
|
|
// TooltipVisualManager 초기화
|
|
// rootCanvas가 Inspector에서 할당되지 않았다면 여기서 찾아야 합니다.
|
|
if (rootCanvas == null)
|
|
{
|
|
rootCanvas = GetComponentInParent<Canvas>();
|
|
if (rootCanvas == null)
|
|
{
|
|
// 최상위 Canvas를 찾기 위한 더 강력한 방법 (예: FindObjectOfType<Canvas>() 후 필터링)
|
|
// 또는 씬에 하나의 메인 Canvas만 있다는 가정 하에 동작
|
|
Canvas[] canvases = FindObjectsByType<Canvas>(FindObjectsSortMode.InstanceID);
|
|
foreach (Canvas c in canvases)
|
|
{
|
|
if (c.isRootCanvas)
|
|
{
|
|
rootCanvas = c;
|
|
break;
|
|
}
|
|
}
|
|
if (rootCanvas == null && canvases.Length > 0) rootCanvas = canvases[0]; // 최후의 수단
|
|
}
|
|
}
|
|
|
|
if (rootCanvas != null)
|
|
{
|
|
// defaultParent는 툴팁이 생성될 위치입니다.
|
|
// rootCanvas.transform을 사용하면 Canvas 직속 자식으로 생성됩니다.
|
|
// 또는 툴팁 전용 레이어를 두고 싶다면 해당 Transform을 사용합니다.
|
|
TooltipManager.Instance.Initialize(rootCanvas.transform, rootCanvas);
|
|
}
|
|
else
|
|
{
|
|
if (rootCanvas == null) Debug.LogError("ToolbarView: rootCanvas를 찾을 수 없어 TooltipVisualManager를 초기화할 수 없습니다.");
|
|
}
|
|
}
|
|
|
|
public virtual void Initialize(ToolbarModel toolbarModel)
|
|
{
|
|
ToolbarModel = toolbarModel;
|
|
|
|
if (toolbarContainer == null)
|
|
{
|
|
Debug.LogError("toolbarContainer가 할당되지 않았습니다.");
|
|
return;
|
|
}
|
|
|
|
if (layoutGroup == null)
|
|
{
|
|
Debug.LogError("layoutGroup이 할당되지 않았습니다.");
|
|
return;
|
|
}
|
|
|
|
RenderToolbar();
|
|
}
|
|
|
|
protected virtual void ClearToolbar()
|
|
{
|
|
foreach (var pair in _modelToGameObjectMap)
|
|
{
|
|
if (pair.Key != null)
|
|
{
|
|
pair.Key.OnStateChanged -= () => UpdateItemVisuals(pair.Key); // 이벤트 구독 해제
|
|
if (pair.Key is ToolbarToggleButton toggleButton)
|
|
{
|
|
toggleButton.OnToggleStateChanged -= (isSelected) => UpdateToggleVisuals(toggleButton, isSelected);
|
|
}
|
|
}
|
|
if (pair.Value != null)
|
|
{
|
|
// TooltipHandler 이벤트 구독 해제 (필요 시)
|
|
TooltipHandler handler = pair.Value.GetComponent<TooltipHandler>();
|
|
if (handler != null)
|
|
{
|
|
handler.OnPointerEnterAction = null;
|
|
handler.OnPointerExitAction = null;
|
|
}
|
|
Destroy(pair.Value);
|
|
}
|
|
}
|
|
_modelToGameObjectMap.Clear();
|
|
_toggleGroups.Clear(); // 토글 그룹도 정리
|
|
if (currentSubMenu != null) Destroy(currentSubMenu);
|
|
TooltipManager.Instance.HideTooltip(); // 툴바가 클리어될 때 툴팁도 숨김
|
|
}
|
|
|
|
protected void RenderToolbar()
|
|
{
|
|
ClearToolbar(); // 기존 UI 및 이벤트 구독 정리
|
|
|
|
if (ToolbarModel == null || ToolbarModel.Items == null) return;
|
|
|
|
foreach (var item in ToolbarModel.Items)
|
|
{
|
|
GameObject itemObj = null;
|
|
if (item is ToolbarSeparator)
|
|
{
|
|
itemObj = Instantiate(separatorPrefab, toolbarContainer);
|
|
}
|
|
else if (item is ToolbarButtonBase buttonModel) // 모든 버튼 타입의 기본 처리
|
|
{
|
|
// 적절한 프리팹 선택
|
|
if (buttonModel is ToolbarRadioButton) itemObj = Instantiate(radioButtonPrefab, toolbarContainer);
|
|
else if (buttonModel is ToolbarToggleButton) itemObj = Instantiate(toggleButtonPrefab, toolbarContainer);
|
|
else if (buttonModel is ToolbarExpandableButton) itemObj = Instantiate(expandableButtonPrefab, toolbarContainer);
|
|
else if (buttonModel is ToolbarStandardButton) itemObj = Instantiate(standardButtonPrefab, toolbarContainer);
|
|
// else // 다른 커스텀 버튼 타입이 있다면 추가
|
|
|
|
if (itemObj != null)
|
|
{
|
|
_modelToGameObjectMap[buttonModel] = itemObj;
|
|
buttonModel.OnStateChanged += () => UpdateItemVisuals(buttonModel); // 모델 상태 변경 시 UI 업데이트 구독
|
|
|
|
// 초기 UI 설정 및 이벤트 바인딩
|
|
SetupButtonVisualsAndInteractions(buttonModel, itemObj);
|
|
|
|
// 툴팁 핸들러 추가 및 설정
|
|
if (!string.IsNullOrEmpty(buttonModel.TooltipKey))
|
|
{
|
|
TooltipHandler tooltipHandler = itemObj.GetComponent<TooltipHandler>();
|
|
if (tooltipHandler == null) tooltipHandler = itemObj.AddComponent<TooltipHandler>();
|
|
|
|
tooltipHandler.Tooltip = buttonModel.TooltipKey;
|
|
tooltipHandler.OnPointerEnterAction = TooltipManager.Instance.HandlePointerEnter;
|
|
tooltipHandler.OnPointerExitAction = TooltipManager.Instance.HandlePointerExit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 모든 항목이 추가된 후 toolbarContainer의 높이를 layoutGroup의 내용에 맞게 조절합니다.
|
|
if (layoutGroup != null)
|
|
{
|
|
// layoutGroup 컴포넌트가 있는 RectTransform을 가져옵니다.
|
|
// 일반적으로 toolbarContainer의 RectTransform과 동일합니다.
|
|
RectTransform layoutRectTransform = layoutGroup.GetComponent<RectTransform>();
|
|
if (layoutRectTransform != null)
|
|
{
|
|
// 레이아웃을 즉시 재계산하도록 강제합니다.
|
|
// 이렇게 하면 preferredHeight가 현재 자식들을 기준으로 정확하게 계산됩니다.
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRectTransform);
|
|
|
|
// LayoutGroup에 의해 계산된 선호 높이를 가져옵니다.
|
|
float preferredHeight = LayoutUtility.GetPreferredHeight(layoutRectTransform);
|
|
|
|
// toolbarContainer (즉, layoutRectTransform)의 높이를 설정합니다.
|
|
// 이 코드는 toolbarContainer의 높이가 sizeDelta.y에 의해 제어된다고 가정합니다.
|
|
// 앵커가 수직으로 늘어나도록 설정된 경우, 이 방식이 원하는 대로 작동하지 않거나
|
|
// anchoredPosition 및 오프셋을 대신 조정해야 할 수 있습니다.
|
|
layoutRectTransform.sizeDelta = new Vector2(layoutRectTransform.sizeDelta.x, preferredHeight);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 버튼 모델과 게임 오브젝트를 받아 초기 시각적 요소 설정 및 UI 상호작용을 연결합니다.
|
|
protected void SetupButtonVisualsAndInteractions(ToolbarButtonBase model, GameObject itemObj)
|
|
{
|
|
// 공통 UI 요소 업데이트 (Text, Icon, Enabled)
|
|
UpdateCommonButtonVisuals(model, itemObj);
|
|
|
|
// 타입별 UI 요소 및 이벤트 설정
|
|
if (model is ToolbarRadioButton radioModel)
|
|
{
|
|
Toggle toggle = itemObj.GetComponent<Toggle>();
|
|
if (toggle != null)
|
|
{
|
|
ToggleGroup toggleGroup = GetOrCreateToggleGroup(radioModel.GroupName);
|
|
toggle.group = toggleGroup;
|
|
toggle.SetIsOnWithoutNotify(radioModel.IsSelected); // 초기 상태 설정 (이벤트 발생 방지)
|
|
toggle.onValueChanged.AddListener((isSelected) =>
|
|
{
|
|
// UI에서 사용자가 직접 토글한 경우 모델 업데이트
|
|
// 중요: 라디오 버튼은 그룹에 의해 선택이 관리되므로, isSelected가 true일 때만 모델 업데이트 요청
|
|
if (isSelected) radioModel.ExecuteClick(true); // 모델의 ExecuteClick -> RadioGroup.SetSelected 호출
|
|
});
|
|
// IsSelected 변경은 OnStateChanged를 통해 UpdateItemVisuals에서 처리되거나,
|
|
// 좀 더 명시적인 OnToggleStateChanged 이벤트를 사용할 수 있습니다.
|
|
radioModel.OnToggleStateChanged += (isSelected) => UpdateToggleVisuals(radioModel, isSelected);
|
|
}
|
|
}
|
|
else if (model is ToolbarToggleButton toggleModel)
|
|
{
|
|
Toggle toggle = itemObj.GetComponent<Toggle>();
|
|
if (toggle != null)
|
|
{
|
|
toggle.SetIsOnWithoutNotify(toggleModel.IsSelected);
|
|
toggle.onValueChanged.AddListener((isSelected) =>
|
|
{
|
|
toggleModel.ExecuteClick(); // 모델의 ExecuteClick이 IsSelected를 변경하고 OnStateChanged 호출
|
|
});
|
|
toggleModel.OnToggleStateChanged += (isSelected) => UpdateToggleVisuals(toggleModel, isSelected);
|
|
}
|
|
}
|
|
else if (model is ToolbarExpandableButton expandableModel)
|
|
{
|
|
Button uiButton = itemObj.GetComponent<Button>();
|
|
if (uiButton != null)
|
|
{
|
|
uiButton.onClick.AddListener(() =>
|
|
{
|
|
expandableModel.ExecuteClick();
|
|
ToggleSubMenu(expandableModel, itemObj); // itemObj 전달하여 위치 기준으로 삼기
|
|
});
|
|
}
|
|
}
|
|
else if (model is ToolbarStandardButton standardModel)
|
|
{
|
|
Button uiButton = itemObj.GetComponent<Button>();
|
|
if (uiButton != null)
|
|
{
|
|
uiButton.onClick.AddListener(() =>
|
|
{
|
|
standardModel.ExecuteClick(standardModel.Text);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// 모델의 상태가 변경되었을 때 호출되어 모든 관련 UI를 업데이트합니다.
|
|
protected void UpdateItemVisuals(ToolbarButtonBase model)
|
|
{
|
|
if (_modelToGameObjectMap.TryGetValue(model, out GameObject itemObj))
|
|
{
|
|
UpdateCommonButtonVisuals(model, itemObj); // 공통 부분 업데이트
|
|
|
|
// 타입별 특화된 부분 업데이트 (예: Toggle의 isOn 상태)
|
|
if (model is ToolbarToggleButton tb) // ToolbarRadioButton도 여기에 해당
|
|
{
|
|
Toggle toggle = itemObj.GetComponent<Toggle>();
|
|
if (toggle != null && toggle.isOn != tb.IsSelected) // UI와 모델 상태가 다를 때만 업데이트
|
|
{
|
|
toggle.SetIsOnWithoutNotify(tb.IsSelected);
|
|
}
|
|
}
|
|
// 다른 버튼 타입에 대한 추가적인 시각적 업데이트 로직
|
|
}
|
|
}
|
|
|
|
// 특정 토글 버튼/라디오 버튼의 IsSelected 상태가 모델에서 변경되었을 때 호출됩니다.
|
|
protected void UpdateToggleVisuals(ToolbarToggleButton model, bool isSelected)
|
|
{
|
|
if (_modelToGameObjectMap.TryGetValue(model, out GameObject itemObj))
|
|
{
|
|
Toggle toggle = itemObj.GetComponent<Toggle>();
|
|
if (toggle != null && toggle.isOn != isSelected)
|
|
{
|
|
toggle.SetIsOnWithoutNotify(isSelected);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 공통 버튼 시각적 요소(텍스트, 아이콘, 활성화 상태)를 업데이트합니다.
|
|
protected void UpdateCommonButtonVisuals(ToolbarButtonBase model, GameObject itemObj)
|
|
{
|
|
// 프리팹 구조에 대한 가정:
|
|
// - 텍스트는 TextMeshProUGUI 컴포넌트를 가진 자식 오브젝트에 표시됩니다.
|
|
// - 아이콘은 "Icon"이라는 이름의 자식 오브젝트에 Image 컴포넌트로 표시됩니다.
|
|
// 이러한 구조는 프로젝트의 프리팹 표준에 맞게 조정해야 합니다.
|
|
|
|
TextMeshProUGUI buttonTextComponent = itemObj.GetComponentInChildren<TextMeshProUGUI>(true);
|
|
if (buttonTextComponent != null)
|
|
{
|
|
if (LocalizationManager.Instance != null && !string.IsNullOrEmpty(model.Text))
|
|
{
|
|
// model.Text에는 이제 다국어 키가 저장되어 있습니다.
|
|
buttonTextComponent.text = LocalizationManager.Instance.GetString(model.Text);
|
|
}
|
|
else
|
|
{
|
|
// LocalizationManager가 없거나 Text(키)가 비어있는 경우, 키를 그대로 표시하거나 기본값 처리
|
|
buttonTextComponent.text = model.Text;
|
|
}
|
|
}
|
|
|
|
Image buttonIcon = itemObj.GetComponent<Image>();
|
|
|
|
if (buttonIcon != null)
|
|
{
|
|
// Toggle이 있는 경우, 아이콘의 활성화 상태를 토글의 선택 상태에 따라 변경
|
|
if (model is ToolbarToggleButton toolbarToggleButton)
|
|
{
|
|
buttonIcon.sprite = LoadSpriteFromResources(toolbarToggleButton.IsSelected ? toolbarToggleButton.IconSpritePath : toolbarToggleButton.OffIconSpritePath);
|
|
}
|
|
else if (model is ToolbarRadioButton radioButtonModel)
|
|
{
|
|
// 라디오 버튼의 아이콘은 선택 상태에 따라 다르게 설정할 수 있습니다.
|
|
buttonIcon.sprite = LoadSpriteFromResources(radioButtonModel.IsSelected ? model.IconSpritePath : radioButtonModel.OffIconSpritePath);
|
|
}
|
|
else
|
|
{
|
|
buttonIcon.sprite = LoadSpriteFromResources(model.IconSpritePath);
|
|
}
|
|
buttonIcon.gameObject.SetActive(model.IconSpritePath != null);
|
|
}
|
|
else
|
|
{
|
|
buttonIcon = itemObj.GetComponentInChildren<Image>();
|
|
if (buttonIcon != null)
|
|
{
|
|
// Toggle이 있는 경우, 아이콘의 활성화 상태를 토글의 선택 상태에 따라 변경
|
|
if (model is ToolbarToggleButton toolbarToggleButton)
|
|
{
|
|
buttonIcon.sprite = LoadSpriteFromResources(toolbarToggleButton.IsSelected ? toolbarToggleButton.IconSpritePath : toolbarToggleButton.OffIconSpritePath);
|
|
}
|
|
else if (model is ToolbarRadioButton radioButtonModel)
|
|
{
|
|
// 라디오 버튼의 아이콘은 선택 상태에 따라 다르게 설정할 수 있습니다.
|
|
buttonIcon.sprite = LoadSpriteFromResources(radioButtonModel.IsSelected ? model.IconSpritePath : radioButtonModel.OffIconSpritePath);
|
|
}
|
|
else
|
|
{
|
|
buttonIcon.sprite = LoadSpriteFromResources(model.IconSpritePath);
|
|
}
|
|
buttonIcon.gameObject.SetActive(model.IconSpritePath != null);
|
|
}
|
|
}
|
|
|
|
// 상호작용 가능 상태 업데이트
|
|
Selectable selectable = itemObj.GetComponent<Selectable>(); // Button, Toggle 등
|
|
if (selectable != null)
|
|
{
|
|
selectable.interactable = model.IsEnabled;
|
|
}
|
|
}
|
|
|
|
|
|
protected Dictionary<string, ToggleGroup> _toggleGroups = new Dictionary<string, ToggleGroup>();
|
|
protected ToggleGroup GetOrCreateToggleGroup(string groupName)
|
|
{
|
|
if (!_toggleGroups.TryGetValue(groupName, out ToggleGroup group))
|
|
{
|
|
GameObject groupObj = new GameObject($"ToggleGroup_{groupName}");
|
|
groupObj.transform.SetParent(toolbarContainer);
|
|
RectTransform groupRect = groupObj.AddComponent<RectTransform>();
|
|
groupRect.sizeDelta = new Vector2(0, 0); // 크기는 필요에 따라 조정
|
|
group = groupObj.AddComponent<ToggleGroup>();
|
|
group.allowSwitchOff = false; // 라디오 버튼 그룹은 일반적으로 하나는 선택되어 있도록 함
|
|
_toggleGroups.Add(groupName, group);
|
|
}
|
|
return group;
|
|
}
|
|
|
|
protected GameObject currentSubMenu = null;
|
|
// expandableButtonObj는 확장 메뉴의 위치를 잡기 위해 사용될 수 있습니다.
|
|
protected void ToggleSubMenu(ToolbarExpandableButton expandableButton, GameObject expandableButtonObj)
|
|
{
|
|
if (currentSubMenu != null)
|
|
{
|
|
Destroy(currentSubMenu);
|
|
currentSubMenu = null;
|
|
return;
|
|
}
|
|
|
|
if (subMenuPanelPrefab == null || expandableButton.SubButtons.Count == 0) return;
|
|
|
|
currentSubMenu = Instantiate(subMenuPanelPrefab, transform); // ToolbarView의 자식으로 생성 후 위치 조정
|
|
// 위치 조정 로직: expandableButtonObj의 위치를 기준으로 currentSubMenu의 RectTransform을 조정합니다.
|
|
Vector3 offset = new Vector3(expandableButtonObj.GetComponent<RectTransform>().rect.width, 0, 0);
|
|
currentSubMenu.transform.position = expandableButtonObj.transform.position + offset;
|
|
|
|
RectTransform panelRect = currentSubMenu.GetComponent<RectTransform>();
|
|
// 하위 메뉴 패널에 LayoutGroup이 있다면 자식 버튼들이 자동으로 정렬됩니다.
|
|
|
|
foreach (var subItemBase in expandableButton.SubButtons)
|
|
{
|
|
if (subItemBase is ToolbarButtonBase subItem) // 모든 하위 아이템은 ToolbarButtonBase라고 가정
|
|
{
|
|
// 하위 버튼도 적절한 프리팹을 사용해야 합니다. 여기서는 standardButtonPrefab을 예시로 사용합니다.
|
|
// 실제로는 subItem의 타입에 따라 다른 프리팹을 선택할 수 있습니다.
|
|
GameObject subButtonObj = Instantiate(standardButtonPrefab, panelRect); // 패널의 자식으로 생성
|
|
|
|
// 하위 버튼의 시각적 요소 설정 및 상호작용 연결
|
|
UpdateCommonButtonVisuals(subItem, subButtonObj); // 공통 시각 요소 업데이트
|
|
|
|
Button subUiButton = subButtonObj.GetComponent<Button>();
|
|
if (subUiButton != null)
|
|
{
|
|
subUiButton.interactable = subItem.IsEnabled; // 상호작용 상태 설정
|
|
subUiButton.onClick.AddListener(() =>
|
|
{
|
|
//expandableButton.SelectSubButton(subItem); // 모델 업데이트 및 주 버튼 외형 변경 요청
|
|
// 주 버튼 UI는 expandableButton의 OnStateChanged 이벤트에 의해 자동으로 업데이트됩니다.
|
|
subItem.ExecuteClick(subItem.Text); // 하위 버튼의 Command 실행
|
|
expandableButton.OnSubButtonSelected?.Invoke(subItem); // 주 버튼에 하위 버튼 선택 알림
|
|
|
|
Destroy(currentSubMenu);
|
|
currentSubMenu = null;
|
|
});
|
|
}
|
|
|
|
// 하위 버튼 툴팁 처리
|
|
if (!string.IsNullOrEmpty(subItem.TooltipKey))
|
|
{
|
|
TooltipHandler tooltipHandler = subButtonObj.GetComponent<TooltipHandler>();
|
|
if (tooltipHandler == null) tooltipHandler = subButtonObj.AddComponent<TooltipHandler>();
|
|
|
|
tooltipHandler.Tooltip = subItem.TooltipKey;
|
|
tooltipHandler.OnPointerEnterAction = TooltipManager.Instance.HandlePointerEnter;
|
|
tooltipHandler.OnPointerExitAction = TooltipManager.Instance.HandlePointerExit;
|
|
}
|
|
|
|
// 하위 버튼 모델의 OnStateChanged도 구독하여 하위 버튼 자체의 상태 변경(예: 텍스트)도 반영할 수 있습니다.
|
|
// subItem.OnStateChanged += () => UpdateCommonButtonVisuals(subItem, subButtonObj);
|
|
// _modelToGameObjectMap에 하위 버튼도 추가하여 ClearToolbar에서 정리되도록 해야 합니다. (선택적 확장)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 경로에서 Sprite를 로드합니다. Sprite 파일은 Resources 폴더 또는 그 하위 폴더에 있어야 합니다.
|
|
/// </summary>
|
|
/// <param name="spritePath">Resources 폴더 기준의 Sprite 경로입니다 (확장자 제외).</param>
|
|
/// <returns>로드된 Sprite 객체. 실패 시 null을 반환합니다.</returns>
|
|
protected Sprite LoadSpriteFromResources(string spritePath)
|
|
{
|
|
if (string.IsNullOrEmpty(spritePath))
|
|
{
|
|
Debug.LogWarning("LoadSpriteFromResources: spritePath가 null이거나 비어있습니다.");
|
|
return null;
|
|
}
|
|
Sprite loadedSprite = Resources.Load<Sprite>(spritePath);
|
|
if (loadedSprite == null)
|
|
{
|
|
Debug.LogError($"LoadSpriteFromResources: Resources 폴더에서 '{spritePath}' 경로의 Sprite를 로드할 수 없습니다.");
|
|
}
|
|
return loadedSprite;
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnDestroy()
|
|
{
|
|
// 씬 전환 등으로 ToolbarView가 파괴될 때 모든 이벤트 구독 해제
|
|
ClearToolbar();
|
|
TooltipManager.Instance.Dispose(); // 툴팁 인스턴스도 파괴
|
|
}
|
|
}
|
|
}
|