TopMenu 개발 완료
This commit is contained in:
@@ -152,7 +152,7 @@ namespace UVC.UI.Menu
|
||||
view.ClearMenuItems();
|
||||
// View에 현재 모델 데이터를 기반으로 메뉴 UI를 생성하도록 요청
|
||||
// view.MenuContainer는 TopMenuView에서 메뉴 UI 요소들이 배치될 부모 Transform을 가리킵니다.
|
||||
view.CreateMenuItems(model.MenuItems, view.MenuContainer);
|
||||
view.CreateMenuItems(model.MenuItems, view.menuContainer);
|
||||
|
||||
// View에서 발생하는 메뉴 아이템 클릭 이벤트에 대한 핸들러 등록
|
||||
view.OnMenuItemClicked += HandleMenuItemClicked;
|
||||
@@ -220,6 +220,10 @@ namespace UVC.UI.Menu
|
||||
new MenuItemData("edit_undo", "menu_edit_undo", new DebugLogCommand("실행 취소 선택됨 (Command 실행)")),
|
||||
new MenuItemData("edit_redo", "menu_edit_redo", new DebugLogCommand("다시 실행 선택됨 (Command 실행)")),
|
||||
MenuItemData.CreateSeparator("edit_sep1"), // 구분선
|
||||
new MenuItemData("file_save", "menu_file_save", command: new DebugLogCommand("저장 선택됨 (Command 실행)") , subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
new MenuItemData("file_save_as", "menu_file_save_as", new DebugLogCommand("다른 이름으로 저장 선택됨 (Command 실행)"))
|
||||
}),
|
||||
new MenuItemData("preferences", "menu_preferences", new DebugLogCommand("환경설정 선택됨 (Command 실행)"))
|
||||
}));
|
||||
|
||||
@@ -227,7 +231,11 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
// 각 언어 메뉴 아이템에 ChangeLanguageCommand를 연결하여 언어 변경 기능 수행
|
||||
new MenuItemData("lang_ko", "menu_lang_korean", new ChangeLanguageCommand("ko-KR"), commandParameter: "ko-KR"),
|
||||
new MenuItemData("lang_en", "menu_lang_english", new ChangeLanguageCommand("en-US"))
|
||||
new MenuItemData("lang_en", "menu_lang_english", new ChangeLanguageCommand("en-US")),
|
||||
new MenuItemData("file_save", "menu_file_save", command: new DebugLogCommand("저장 선택됨 (Command 실행)") , subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
new MenuItemData("file_save_as", "menu_file_save_as", new DebugLogCommand("다른 이름으로 저장 선택됨 (Command 실행)"))
|
||||
}),
|
||||
// 필요에 따라 다른 언어들도 추가 가능
|
||||
}));
|
||||
}
|
||||
@@ -245,7 +253,7 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
if (!clickedItemData.IsEnabled)
|
||||
{
|
||||
Debug.Log($"비활성화된 메뉴 아이템 클릭 시도: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayNameKey})");
|
||||
Debug.Log($"비활성화된 메뉴 아이템 클릭 시도: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayName})");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -253,12 +261,12 @@ namespace UVC.UI.Menu
|
||||
if (!clickedItemData.IsEnabled)
|
||||
{
|
||||
// 비활성화된 아이템 클릭 시 로그 (디버깅 목적)
|
||||
ULog.Debug($"비활성화된 메뉴 아이템 클릭 시도: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayNameKey})");
|
||||
ULog.Debug($"비활성화된 메뉴 아이템 클릭 시도: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayName})");
|
||||
return; // 비활성화된 아이템은 명령을 실행하지 않음
|
||||
}
|
||||
|
||||
// 클릭된 메뉴 아이템 정보 로그 (디버깅 목적)
|
||||
ULog.Debug($"메뉴 아이템 클릭됨: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayNameKey})");
|
||||
ULog.Debug($"메뉴 아이템 클릭됨: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayName})");
|
||||
|
||||
// 메뉴 아이템에 연결된 Command가 있다면 실행
|
||||
// Command가 null일 수 있으므로 null 조건부 연산자(?.) 사용
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UVC.UI.Commands;
|
||||
|
||||
@@ -19,7 +21,7 @@ namespace UVC.UI.Menu
|
||||
/// UI에 표시될 메뉴 아이템의 이름입니다. 다국어 키도 가능합니다.
|
||||
/// 이 키를 사용하여 실제 표시될 텍스트를 가져옵니다.
|
||||
/// </summary>
|
||||
public string DisplayNameKey { get; private set; }
|
||||
public string DisplayName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템이 선택되었을 때 실행될 명령입니다.
|
||||
@@ -53,6 +55,26 @@ namespace UVC.UI.Menu
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
private int depth = 0; // 메뉴 아이템의 깊이 (하위 메뉴의 레벨)
|
||||
/// <summary>
|
||||
/// 계층 구조에서 현재 개체의 깊이를 가져옵니다.
|
||||
/// </summary>
|
||||
public int Depth
|
||||
{
|
||||
get { return depth; } // 외부에서 접근할 수 있도록 프로퍼티로 노출
|
||||
internal set { depth = value; }
|
||||
}
|
||||
|
||||
private MenuItemData? parent = null;
|
||||
/// <summary>
|
||||
/// 현재 메뉴 항목의 부모 메뉴 항목을 가져옵니다.
|
||||
/// </summary>
|
||||
public MenuItemData? Parent
|
||||
{
|
||||
get { return parent; } // 외부에서 접근할 수 있도록 프로퍼티로 노출
|
||||
internal set { parent = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `MenuItemData` 클래스의 새 인스턴스를 초기화합니다.
|
||||
/// </summary>
|
||||
@@ -63,15 +85,38 @@ namespace UVC.UI.Menu
|
||||
/// <param name="subMenuItems">하위 메뉴 아이템 목록 (선택 사항).</param>
|
||||
/// <param name="isSeparator">구분선 여부 (선택 사항, 기본값: false).</param>
|
||||
/// <param name="isEnabled">활성화 상태 (선택 사항, 기본값: true).</param>
|
||||
public MenuItemData(string itemId, string displayName, ICommand command = null, object commandParameter = null, List<MenuItemData> subMenuItems = null, bool isSeparator = false, bool isEnabled = true)
|
||||
public MenuItemData(string itemId, string displayName, ICommand command = null, object commandParameter = null, List<MenuItemData> subMenuItems = null, bool isSeparator = false, bool isEnabled = true, int depth = 0)
|
||||
{
|
||||
ItemId = itemId;
|
||||
DisplayNameKey = displayName;
|
||||
DisplayName = displayName;
|
||||
Command = command;
|
||||
CommandParameter = commandParameter; // 파라미터 저장
|
||||
SubMenuItems = subMenuItems ?? new List<MenuItemData>(); // null인 경우 빈 리스트로 초기화
|
||||
IsSeparator = isSeparator;
|
||||
IsEnabled = isEnabled;
|
||||
this.depth = depth; // 메뉴 아이템의 깊이 설정
|
||||
|
||||
SetupDepthAndParent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 하위 메뉴 항목의 깊이와 부모 관계를 구성합니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 하위 메뉴 항목 컬렉션을 반복하며 깊이와
|
||||
/// 부모 속성을 설정합니다. 깊이는 현재 항목의 깊이에 따라 증가하고, 부모는
|
||||
/// 현재 항목으로 설정됩니다. 하위 메뉴 항목에 자체 하위 메뉴 항목이 포함된 경우, 이 메서드는 재귀적으로 호출되어
|
||||
/// 깊이와 부모 관계를 구성합니다.</remarks>
|
||||
protected void SetupDepthAndParent()
|
||||
{
|
||||
for (int i = 0; i < SubMenuItems.Count; i++)
|
||||
{
|
||||
SubMenuItems[i].Depth = this.depth + 1; // 하위 메뉴 아이템의 깊이를 현재 아이템의 깊이 + 1로 설정
|
||||
SubMenuItems[i].Parent = this; // 하위 메뉴 아이템의 부모를 현재 아이템으로 설정
|
||||
if (SubMenuItems[i].SubMenuItems.Count > 0)
|
||||
{
|
||||
SubMenuItems[i].SetupDepthAndParent(); // 재귀적으로 하위 메뉴 아이템의 깊이 설정
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,6 +127,8 @@ namespace UVC.UI.Menu
|
||||
public void AddSubMenuItem(MenuItemData subItem)
|
||||
{
|
||||
if (IsSeparator) return; // 구분선에는 하위 메뉴를 추가할 수 없음
|
||||
subItem.Depth = this.depth + 1; // 하위 메뉴 아이템의 깊이를 현재 아이템의 깊이 + 1로 설정
|
||||
subItem.Parent = this; // 하위 메뉴 아이템의 부모를 현재 아이템으로 설정
|
||||
SubMenuItems.Add(subItem);
|
||||
}
|
||||
|
||||
@@ -92,9 +139,20 @@ namespace UVC.UI.Menu
|
||||
/// <returns>구분선 역할을 하는 새로운 `MenuItemData` 객체입니다.</returns>
|
||||
public static MenuItemData CreateSeparator(string itemId = null)
|
||||
{
|
||||
// 구분선은 특정 동작이나 표시 이름이 필요 없으므로, displayNameKey는 비워두고 command는 null로 설정합니다.
|
||||
// 구분선은 특정 동작이나 표시 이름이 필요 없으므로, displayName는 비워두고 command는 null로 설정합니다.
|
||||
return new MenuItemData(itemId ?? $"separator_{Guid.NewGuid()}", string.Empty, null, null, null, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 메뉴 항목에 연관된 하위 메뉴 항목이 있는지 여부를 확인합니다.
|
||||
/// </summary>
|
||||
/// <param name="ItemId">확인할 메뉴 항목의 고유 식별자입니다.</param>
|
||||
/// 지정된 메뉴 항목에 대한 하위 메뉴 항목이 있으면 <see langword="true"/>를 반환하고, 그렇지 않으면 <see langword="false"/>를 반환합니다. </returns>
|
||||
public bool HasSubMenuItems(string ItemId)
|
||||
{
|
||||
return SubMenuItems.Exists(item => item.ItemId == ItemId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TopMenuModel
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Extension;
|
||||
using UVC.Locale; // 다국어 처리 네임스페이스
|
||||
using UVC.Log;
|
||||
|
||||
namespace UVC.UI.Menu
|
||||
{
|
||||
@@ -22,16 +25,6 @@ namespace UVC.UI.Menu
|
||||
/// public class CustomTopMenuView : TopMenuView
|
||||
/// {
|
||||
/// /// <summary>
|
||||
/// /// 커스텀 메뉴 아이템 프리팹의 Resources 폴더 내 경로입니다.
|
||||
/// /// </summary>
|
||||
/// protected override string MenuItemPrefabPath => "Prefabs/UI/Menu/CustomMenuItem"; // 다른 프리팹 경로로 변경
|
||||
///
|
||||
/// /// <summary>
|
||||
/// /// 커스텀 하위 메뉴 아이템 프리팹의 Resources 폴더 내 경로입니다.
|
||||
/// /// </summary>
|
||||
/// protected override string SubMenuItemPrefabPath => "Prefabs/UI/Menu/CustomSubMenuItem"; // 다른 프리팹 경로로 변경
|
||||
///
|
||||
/// /// <summary>
|
||||
/// /// MonoBehaviour의 Awake 메시지입니다.
|
||||
/// /// 부모 클래스의 Awake를 호출한 후, 추가적인 커스텀 초기화 로직을 수행합니다.
|
||||
/// /// </summary>
|
||||
@@ -43,34 +36,6 @@ namespace UVC.UI.Menu
|
||||
///
|
||||
/// // 여기에 CustomTopMenuView만의 추가적인 초기화 코드를 작성할 수 있습니다.
|
||||
/// ULog.Debug("CustomTopMenuView Awake 실행됨. 커스텀 프리팹 경로가 사용됩니다.");
|
||||
///
|
||||
/// // 예: 특정 조건에 따라 메뉴 아이템 간 간격을 변경
|
||||
/// // this.menuItemSpace = new Vector2(40, 5);
|
||||
/// }
|
||||
///
|
||||
/// /// <summary>
|
||||
/// /// UI 요소(메뉴 아이템 또는 구분선)의 위치와 크기를 설정합니다.
|
||||
/// /// 이 예제에서는 기본 레이아웃 로직을 그대로 사용하지만,
|
||||
/// /// 필요에 따라 완전히 새로운 레이아웃 로직을 구현할 수 있습니다.
|
||||
/// /// 예를 들어, 1차 메뉴 아이템들을 수직으로 정렬하도록 변경할 수 있습니다.
|
||||
/// /// </summary>
|
||||
/// protected override void LayoutMenuItem(GameObject uiElement, MenuItemData itemData, int depth, int itemIndex, List<MenuItemData> siblingItems)
|
||||
/// {
|
||||
/// // 기본 레이아웃 로직을 그대로 사용하려면 base.LayoutMenuItem을 호출합니다.
|
||||
/// base.LayoutMenuItem(uiElement, itemData, depth, itemIndex, siblingItems);
|
||||
///
|
||||
/// // 여기에 추가적인 레이아웃 조정 로직을 넣을 수 있습니다.
|
||||
/// // 예: 특정 아이템 ID를 가진 메뉴만 특별한 스타일을 적용 (색상 변경 등 - 여기서는 RectTransform만 다룸)
|
||||
/// if (itemData.ItemId == "special_menu_item")
|
||||
/// {
|
||||
/// RectTransform rt = uiElement.GetComponent<RectTransform>();
|
||||
/// if (rt != null)
|
||||
/// {
|
||||
/// // 예시: 특정 아이템의 높이를 약간 늘림 (실제로는 크기 변경 시 다른 아이템 위치도 재조정 필요)
|
||||
/// // rt.sizeDelta = new Vector2(rt.sizeDelta.x, rt.sizeDelta.y + 10);
|
||||
/// ULog.Debug($"Special item '{itemData.ItemId}' layout customized.");
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// <summary>
|
||||
@@ -82,7 +47,7 @@ namespace UVC.UI.Menu
|
||||
/// /// </summary>
|
||||
/// protected override void ToggleSubMenuDisplay(MenuItemData itemData, GameObject menuItemInstance)
|
||||
/// {
|
||||
/// ULog.Debug($"Custom ToggleSubMenuDisplay: {itemData.DisplayNameKey} 클릭됨.");
|
||||
/// ULog.Debug($"Custom ToggleSubMenuDisplay: {itemData.DisplayName} 클릭됨.");
|
||||
/// base.ToggleSubMenuDisplay(itemData, menuItemInstance); // 부모의 원래 로직 실행
|
||||
/// }
|
||||
///
|
||||
@@ -93,70 +58,39 @@ namespace UVC.UI.Menu
|
||||
/// </example>
|
||||
public class TopMenuView : MonoBehaviour
|
||||
{
|
||||
// --- 프리팹 경로 및 UI 요소 이름 ---
|
||||
// 메뉴 아이템, 하위 메뉴 아이템, 구분선 프리팹의 Resources 폴더 내 경로입니다.
|
||||
// 상속 클래스에서 이 경로들을 변경하여 다른 프리팹을 사용할 수 있습니다.
|
||||
protected string menuItemPrefabPath = "Prefabs/UI/Menu/MenuItem";
|
||||
protected string subMenuItemPrefabPath = "Prefabs/UI/Menu/SubMenuItem";
|
||||
protected string menuSeparatorPrefabPath = "Prefabs/UI/Menu/MenuSeparator";
|
||||
[Header("UI References")]
|
||||
[Tooltip("메인 메뉴 아이템을 위한 프리팹입니다.")]
|
||||
[SerializeField] public GameObject menuItemPrefab;
|
||||
|
||||
// 메뉴 UI 요소들을 찾기 위한 이름들입니다.
|
||||
// 프리팹의 계층 구조나 이름이 다를 경우, 상속 클래스에서 이 값들을 변경해야 합니다.
|
||||
protected string menuContainerName = "TopMenu"; // 최상위 메뉴 아이템들이 배치될 부모 GameObject의 이름
|
||||
protected string subMenuContainerName = "SubMenuContainer"; // 하위 메뉴 아이템들이 배치될 부모 GameObject의 이름 (각 메뉴 아이템 내부에 존재)
|
||||
protected string subMenuArrowName = "SubMenuArrow"; // 하위 메뉴가 있음을 나타내는 화살표 UI GameObject의 이름 (각 메뉴 아이템 내부에 존재)
|
||||
[Tooltip("하위 메뉴 아이템을 위한 프리팹입니다.")]
|
||||
[SerializeField] public GameObject subMenuItemPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// 기본 메뉴 아이템 프리팹의 Resources 폴더 내 경로입니다.
|
||||
/// 상속 클래스에서 오버라이드하여 다른 경로를 지정할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual string MenuItemPrefabPath => menuItemPrefabPath;
|
||||
/// <summary>
|
||||
/// 하위 메뉴 아이템 프리팹의 Resources 폴더 내 경로입니다.
|
||||
/// 상속 클래스에서 오버라이드하여 다른 경로를 지정할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual string SubMenuItemPrefabPath => subMenuItemPrefabPath;
|
||||
/// <summary>
|
||||
/// 메뉴 구분선 프리팹의 Resources 폴더 내 경로입니다.
|
||||
/// 상속 클래스에서 오버라이드하여 다른 경로를 지정할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual string MenuSeparatorPrefabPath => menuSeparatorPrefabPath;
|
||||
/// <summary>
|
||||
/// 최상위 메뉴 아이템들이 배치될 컨테이너 GameObject의 이름입니다.
|
||||
/// </summary>
|
||||
protected virtual string MenuContainerName => menuContainerName;
|
||||
/// <summary>
|
||||
/// 하위 메뉴 아이템들이 배치될 컨테이너 GameObject의 이름입니다. (각 메뉴 아이템 프리팹 내부에 있어야 함)
|
||||
/// </summary>
|
||||
protected virtual string SubMenuContainerName => subMenuContainerName;
|
||||
/// <summary>
|
||||
/// 하위 메뉴 존재를 나타내는 화살표 UI GameObject의 이름입니다. (각 메뉴 아이템 프리팹 내부에 있어야 함)
|
||||
/// </summary>
|
||||
protected virtual string SubMenuArrowName => subMenuArrowName;
|
||||
[Tooltip("메뉴 구분선을 위한 프리팹입니다. (선택 사항)")]
|
||||
[SerializeField] public GameObject menuSeparatorPrefab;
|
||||
|
||||
// --- 로드된 프리팹 및 UI 참조 ---
|
||||
protected GameObject menuItemPrefab; // 로드된 기본 메뉴 아이템 프리팹
|
||||
protected GameObject subMenuItemPrefab; // 로드된 하위 메뉴 아이템 프리팹
|
||||
protected GameObject menuSeparatorPrefab; // 로드된 메뉴 구분선 프리팹
|
||||
protected Transform menuContainer; // 메뉴 아이템들이 실제로 배치될 부모 Transform
|
||||
[Tooltip("최상위 메뉴 아이템들이 배치될 부모 Transform입니다.")]
|
||||
[SerializeField] public Transform menuContainer;
|
||||
|
||||
// --- UI 레이아웃 관련 설정 값 ---
|
||||
// 이 값들은 프리팹에서 자동으로 읽어오려고 시도하지만, 실패 시 또는 커스텀 로직에서 사용될 수 있습니다.
|
||||
// 상속 클래스에서 이 값들을 직접 변경하거나, Awake()에서 프리팹으로부터 다시 읽어오도록 수정할 수 있습니다.
|
||||
protected Vector2 menuItemInitPosition = new Vector2(0, 0); // 1depth 메뉴 아이템의 초기 위치 (부모 컨테이너 기준)
|
||||
protected Vector2 menuItemSize = new Vector2(100, 30); // 기본 메뉴 아이템의 크기
|
||||
protected Vector2 subMenuItemSize = new Vector2(100, 30); // 하위 메뉴 아이템의 크기
|
||||
protected Vector2 menuItemSeparatorSize = new Vector2(100, 1); // 메뉴 구분선의 크기
|
||||
protected Vector4 menuItemSeparatorMargin = new Vector4(0, 4, 0, 4); // 구분선의 좌, 상, 우, 하 마진 (주로 상하 마진 사용)
|
||||
protected Vector2 menuItemSpace = new Vector2(10, 2); // 메뉴 아이템 간의 간격 (수평, 수직)
|
||||
protected Vector2 menuDepthSpace = new Vector2(5, 10); // 1차 메뉴 아이템과 그 하위 메뉴 컨테이너 간의 간격 (수평, 수직)
|
||||
protected Vector2 subMenuDepthSpace = new Vector2(5, 10); // 하위 메뉴 아이템과 그 하위 메뉴 컨테이너 간의 간격 (수평, 수직)
|
||||
protected Vector4 subContainerPadding = new Vector4(4, 10, 4, 10); // 하위 메뉴 컨테이너 내부의 좌, 상, 우, 하 패딩
|
||||
[Tooltip("하위 메뉴 아이템들이 배치될 부모 Transform입니다.")]
|
||||
[SerializeField] public Transform subMenuContainer;
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템들이 배치되는 부모 Transform 입니다.
|
||||
/// </summary>
|
||||
public Transform MenuContainer { get => menuContainer; }
|
||||
[Header("UI Element Names")]
|
||||
|
||||
[Tooltip("하위 메뉴가 있음을 나타내는 화살표 UI GameObject의 이름입니다. (메뉴 아이템 프리팹 내부에 존재)")]
|
||||
[SerializeField] public string subMenuArrowName = "SubMenuArrow";
|
||||
|
||||
[Header("Layout Settings")]
|
||||
[Tooltip("1차 메뉴 아이템과 그 하위 메뉴 컨테이너 간의 간격 (수평, 수직)")]
|
||||
[SerializeField] public Vector2 menuDepthSpace = new Vector2(0, -5);
|
||||
|
||||
[Tooltip("하위 메뉴 아이템과 그 하위 메뉴 컨테이너 간의 간격 (수평, 수직)")]
|
||||
[SerializeField] public Vector2 subMenuDepthSpace = new Vector2(-5, 0);
|
||||
|
||||
[Tooltip("1 depth 메뉴를 넓이를 글자 크기로 줄일지 여부")]
|
||||
[SerializeField] public bool isShrinkMenuItemWidth = true;
|
||||
|
||||
[Tooltip("isShrinkMenuItemWidth가 활성화됐을 때, 텍스트 좌우에 추가될 여백입니다.")]
|
||||
[SerializeField] public float menuItemWidthPadding = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템이 클릭되었을 때 발생하는 이벤트입니다.
|
||||
@@ -167,6 +101,8 @@ namespace UVC.UI.Menu
|
||||
// --- 내부 상태 변수 ---
|
||||
// 생성된 메뉴 아이템 GameObject들을 관리하는 딕셔너리 (키: ItemId, 값: GameObject)
|
||||
protected Dictionary<string, GameObject> _menuItemObjects = new Dictionary<string, GameObject>();
|
||||
protected Dictionary<string, GameObject> _subMenuContainerObjects = new Dictionary<string, GameObject>();
|
||||
protected Dictionary<string, MenuItemData> _menuItemDataMap = new Dictionary<string, MenuItemData>();
|
||||
protected LocalizationManager _locManager; // 다국어 처리를 위한 LocalizationManager 인스턴스
|
||||
protected GameObject uiBlockerInstance; // 하위 메뉴가 열렸을 때 다른 UI 상호작용을 막기 위한 투명한 UI 요소
|
||||
protected bool isAnySubMenuOpen = false; // 하나 이상의 하위 메뉴가 열려있는지 여부
|
||||
@@ -184,67 +120,37 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
// LocalizationManager가 필수적인 경우, 여기서 에러를 발생시키거나 게임을 중단시킬 수 있습니다.
|
||||
// 여기서는 경고만 기록하고 진행합니다.
|
||||
ULog.Error("LocalizationManager 인스턴스를 찾을 수 없습니다. 메뉴 텍스트가 제대로 표시되지 않을 수 있습니다.", new InvalidOperationException("LocalizationManager 인스턴스를 찾을 수 없습니다."));
|
||||
Debug.LogError("LocalizationManager 인스턴스를 찾을 수 없습니다. 메뉴 텍스트가 제대로 표시되지 않을 수 있습니다.");
|
||||
}
|
||||
|
||||
// 메뉴 아이템 프리팹 로드 및 크기 정보 추출
|
||||
menuItemPrefab = Resources.Load<GameObject>(MenuItemPrefabPath);
|
||||
if (menuItemPrefab != null)
|
||||
// Inspector에서 할당된 참조 확인
|
||||
if (menuItemPrefab == null)
|
||||
{
|
||||
RectTransform rt = menuItemPrefab.GetComponent<RectTransform>();
|
||||
if (rt != null) menuItemSize = rt.sizeDelta;
|
||||
else ULog.Error($"메뉴 아이템 프리팹 '{MenuItemPrefabPath}'에 RectTransform 컴포넌트가 없습니다. 기본 크기 값을 사용합니다.");
|
||||
Debug.LogError("menuItemPrefab이 Inspector에서 할당되지 않았습니다.", this);
|
||||
}
|
||||
else
|
||||
if (subMenuItemPrefab == null)
|
||||
{
|
||||
ULog.Error($"메뉴 아이템 프리팹을 Resources 폴더에서 로드할 수 없습니다. 경로: {MenuItemPrefabPath}", new System.IO.FileNotFoundException($"Prefab not found at Resources/{MenuItemPrefabPath}"));
|
||||
Debug.LogError("subMenuItemPrefab이 Inspector에서 할당되지 않았습니다.", this);
|
||||
}
|
||||
|
||||
// 하위 메뉴 아이템 프리팹 로드 및 크기 정보 추출
|
||||
subMenuItemPrefab = Resources.Load<GameObject>(SubMenuItemPrefabPath);
|
||||
if (subMenuItemPrefab != null)
|
||||
{
|
||||
RectTransform rt = subMenuItemPrefab.GetComponent<RectTransform>();
|
||||
if (rt != null) subMenuItemSize = rt.sizeDelta;
|
||||
else ULog.Error($"서브 메뉴 아이템 프리팹 '{SubMenuItemPrefabPath}'에 RectTransform 컴포넌트가 없습니다. 기본 크기 값을 사용합니다.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Error($"서브 메뉴 아이템 프리팹을 Resources 폴더에서 로드할 수 없습니다. 경로: {SubMenuItemPrefabPath}", new System.IO.FileNotFoundException($"Prefab not found at Resources/{SubMenuItemPrefabPath}"));
|
||||
}
|
||||
|
||||
// 메뉴 구분선 프리팹 로드 및 크기 정보 추출
|
||||
menuSeparatorPrefab = Resources.Load<GameObject>(MenuSeparatorPrefabPath);
|
||||
if (menuSeparatorPrefab != null)
|
||||
{
|
||||
RectTransform rt = menuSeparatorPrefab.GetComponent<RectTransform>();
|
||||
if (rt != null) menuItemSeparatorSize = rt.sizeDelta;
|
||||
else ULog.Error($"메뉴 구분선 프리팹 '{MenuSeparatorPrefabPath}'에 RectTransform 컴포넌트가 없습니다. 기본 크기 값을 사용합니다.");
|
||||
}
|
||||
else
|
||||
if (menuSeparatorPrefab == null)
|
||||
{
|
||||
// 구분선은 선택 사항일 수 있으므로, 경고 수준으로 처리합니다.
|
||||
ULog.Warning($"메뉴 구분선 프리팹을 Resources 폴더에서 로드할 수 없습니다. 경로: {MenuSeparatorPrefabPath}. 구분선 기능이 작동하지 않습니다.");
|
||||
Debug.LogWarning("menuSeparatorPrefab이 Inspector에서 할당되지 않았습니다. 구분선 기능이 작동하지 않습니다.", this);
|
||||
}
|
||||
|
||||
// 메뉴 컨테이너 Transform 찾기
|
||||
Transform containerTransform = transform.Find(MenuContainerName);
|
||||
if (containerTransform != null)
|
||||
// 메뉴 컨테이너 확인
|
||||
if (menuContainer == null)
|
||||
{
|
||||
menuContainer = containerTransform;
|
||||
Debug.LogError("menuContainer가 Inspector에서 할당되지 않았습니다. Inspector에서 참조를 설정해주세요.", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// MenuContainerName으로 지정된 자식 GameObject를 찾지 못한 경우, 현재 GameObject의 Transform을 사용합니다.
|
||||
// 이는 TopMenuView GameObject 자체가 메뉴 아이템들의 직접적인 부모가 되는 경우를 의미합니다.
|
||||
ULog.Warning($"'{MenuContainerName}'이라는 이름의 자식 GameObject를 찾을 수 없어 현재 Transform을 menuContainer로 사용합니다. 의도된 동작이 아니라면 확인해주세요.");
|
||||
menuContainer = transform;
|
||||
}
|
||||
if (menuContainer == null)
|
||||
{
|
||||
// menuContainer를 어떤 방식으로도 설정할 수 없는 경우, 에러를 기록합니다.
|
||||
// 이는 메뉴 시스템이 정상적으로 작동할 수 없음을 의미합니다.
|
||||
ULog.Error("menuContainer를 설정할 수 없습니다. 메뉴 아이템이 표시되지 않습니다.", new InvalidOperationException("menuContainer could not be set."));
|
||||
// 메뉴 컨테이너에 LayoutGroup이 있는지 확인하고, 없다면 경고를 표시합니다.
|
||||
// 자동 배치를 위해 HorizontalLayoutGroup 또는 VerticalLayoutGroup이 필요합니다.
|
||||
if (menuContainer.GetComponent<LayoutGroup>() == null)
|
||||
{
|
||||
Debug.LogWarning($"menuContainer '{menuContainer.name}'에 LayoutGroup 컴포넌트가 없습니다. 메뉴 아이템이 자동으로 배치되지 않을 수 있습니다. Inspector에서 HorizontalLayoutGroup 또는 VerticalLayoutGroup을 추가해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
// UI 블로커 생성
|
||||
@@ -267,7 +173,7 @@ namespace UVC.UI.Menu
|
||||
if (blockerParent == null) // 부모를 찾지 못한 극단적인 경우, TopMenuView 자신을 부모로 설정 (권장되지 않음)
|
||||
{
|
||||
blockerParent = transform;
|
||||
ULog.Warning("TopMenuUIBlocker의 부모를 TopMenuView 자신으로 설정합니다. Canvas 하위에 배치하는 것을 권장합니다.");
|
||||
Debug.LogWarning("TopMenuUIBlocker의 부모를 TopMenuView 자신으로 설정합니다. Canvas 하위에 배치하는 것을 권장합니다.");
|
||||
}
|
||||
uiBlockerInstance.transform.SetParent(blockerParent, false);
|
||||
|
||||
@@ -299,9 +205,7 @@ namespace UVC.UI.Menu
|
||||
// menuContainer 바로 뒤에 오도록 (SiblingIndex가 작을수록 먼저 그려짐 - 뒤에 위치)
|
||||
// 블로커가 메뉴보다 먼저 그려져야 메뉴를 가리지 않으면서 메뉴 외 영역 클릭을 막을 수 있음.
|
||||
// 하지만 UI 이벤트는 앞쪽에 있는 것부터 받으므로, 블로커가 메뉴보다 뒤에 있어야 함.
|
||||
// 따라서, 블로커는 메뉴보다 낮은 SiblingIndex - 1를 가져야 함.
|
||||
// 여기서는 메뉴 컨테이너 바로 뒤에 오도록 설정 (블로커가 렌더링된 후 메뉴가 렌더링되어 메뉴를 덮지 않도록)
|
||||
// 혼동을 피하기 위해, 블로커는 메뉴 시스템 전체보다 뒤에 위치해야 클릭을 가로챌 수 있습니다.
|
||||
// 따라서, 블로커는 메뉴 시스템 전체보다 뒤에 위치해야 클릭을 가로챌 수 있습니다.
|
||||
// CreateUIBlocker 호출 시점에는 menuContainer의 sibling index가 확정되지 않았을 수 있으므로,
|
||||
// UpdateBlockerVisibility에서 최종적으로 조정하는 것이 더 안전할 수 있습니다.
|
||||
// 여기서는 일단 menuContainer의 뒤에 두도록 시도합니다.
|
||||
@@ -327,42 +231,35 @@ namespace UVC.UI.Menu
|
||||
/// <param name="items">생성할 메뉴 아이템들의 데이터 리스트입니다.</param>
|
||||
/// <param name="parentContainer">생성된 메뉴 아이템들이 자식으로 추가될 부모 Transform입니다.</param>
|
||||
/// <param name="depth">현재 메뉴의 깊이입니다. 최상위 메뉴는 0입니다.</param>
|
||||
/// <returns>생성된 메뉴 아이템들이 차지하는 전체 영역의 크기 (너비, 높이)를 반환합니다.</returns>
|
||||
public virtual Vector2 CreateMenuItems(List<MenuItemData> items, Transform parentContainer, int depth = 0)
|
||||
public virtual void CreateMenuItems(List<MenuItemData> items, Transform parentContainer, int depth = 0)
|
||||
{
|
||||
if (items == null || parentContainer == null)
|
||||
{
|
||||
ULog.Error("메뉴 아이템 생성에 필요한 'items' 또는 'parentContainer'가 null입니다.", new ArgumentNullException(items == null ? "items" : "parentContainer"));
|
||||
return Vector2.zero; // 오류 발생 시 크기 0 반환
|
||||
Debug.LogError("메뉴 아이템 생성에 필요한 'items' 또는 'parentContainer'가 null입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 현재 레벨(깊이)에서 메뉴 아이템들이 차지할 총 너비와 높이를 계산하기 위한 변수
|
||||
float currentLevelCalculatedWidth = 0;
|
||||
float currentLevelCalculatedHeight = 0;
|
||||
|
||||
// 아이템이 없고, 하위 메뉴를 그리는 경우가 아니라면 (depth > 0), 크기는 0
|
||||
if (items.Count == 0 && depth > 0)
|
||||
{
|
||||
return Vector2.zero;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
MenuItemData itemData = items[i];
|
||||
GameObject instanceToLayout = null; // 레이아웃을 적용할 GameObject (메뉴 아이템 또는 구분선)
|
||||
GameObject prefabToUse = null; // 인스턴스화할 프리팹
|
||||
_menuItemDataMap[itemData.ItemId] = itemData;
|
||||
GameObject prefabToUse = null;
|
||||
|
||||
if (itemData.IsSeparator) // 구분선 아이템인 경우
|
||||
{
|
||||
if (menuSeparatorPrefab == null)
|
||||
{
|
||||
ULog.Error("menuSeparatorPrefab이 할당되지 않았습니다. 구분선을 생성할 수 없습니다.", new InvalidOperationException("menuSeparatorPrefab is not loaded."));
|
||||
Debug.LogError("menuSeparatorPrefab이 할당되지 않았습니다. 구분선을 생성할 수 없습니다.");
|
||||
continue; // 다음 아이템으로 넘어감
|
||||
}
|
||||
GameObject separatorInstance = Instantiate(menuSeparatorPrefab, parentContainer);
|
||||
separatorInstance.name = $"Separator_{itemData.ItemId}_Depth{depth}";
|
||||
_menuItemObjects[itemData.ItemId] = separatorInstance; // 관리 목록에 추가
|
||||
instanceToLayout = separatorInstance;
|
||||
}
|
||||
else // 일반 메뉴 아이템인 경우
|
||||
{
|
||||
@@ -372,7 +269,7 @@ namespace UVC.UI.Menu
|
||||
prefabToUse = menuItemPrefab;
|
||||
if (prefabToUse == null)
|
||||
{
|
||||
ULog.Error("menuItemPrefab이 할당되지 않았습니다. 1차 메뉴 아이템을 생성할 수 없습니다.", new InvalidOperationException("menuItemPrefab is not loaded."));
|
||||
Debug.LogError("menuItemPrefab이 할당되지 않았습니다. 1차 메뉴 아이템을 생성할 수 없습니다.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -381,7 +278,7 @@ namespace UVC.UI.Menu
|
||||
prefabToUse = subMenuItemPrefab;
|
||||
if (prefabToUse == null)
|
||||
{
|
||||
ULog.Error("subMenuItemPrefab이 할당되지 않았습니다. 하위 메뉴 아이템을 생성할 수 없습니다.", new InvalidOperationException("subMenuItemPrefab is not loaded."));
|
||||
Debug.LogError("subMenuItemPrefab이 할당되지 않았습니다. 하위 메뉴 아이템을 생성할 수 없습니다.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -389,27 +286,42 @@ namespace UVC.UI.Menu
|
||||
GameObject menuItemInstance = Instantiate(prefabToUse, parentContainer);
|
||||
menuItemInstance.name = $"MenuItem_{itemData.ItemId}_Depth{depth}";
|
||||
_menuItemObjects[itemData.ItemId] = menuItemInstance; // 관리 목록에 추가
|
||||
instanceToLayout = menuItemInstance;
|
||||
|
||||
// 메뉴 아이템 텍스트 설정
|
||||
TextMeshProUGUI buttonText = menuItemInstance.GetComponentInChildren<TextMeshProUGUI>(true); // 비활성화된 자식도 검색
|
||||
// 메뉴 아이템 버튼 기능 설정
|
||||
Button button = menuItemInstance.GetComponent<Button>();
|
||||
|
||||
if (buttonText != null && !string.IsNullOrEmpty(itemData.DisplayNameKey))
|
||||
if (buttonText != null && !string.IsNullOrEmpty(itemData.DisplayName))
|
||||
{
|
||||
if (_locManager != null)
|
||||
{
|
||||
buttonText.text = _locManager.GetString(itemData.DisplayNameKey);
|
||||
buttonText.text = _locManager.GetString(itemData.DisplayName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// LocalizationManager가 없는 경우, 키 값을 그대로 표시 (개발 중 확인 용도)
|
||||
ULog.Warning($"LocalizationManager 인스턴스가 없어 메뉴 아이템 텍스트를 키 값으로 설정합니다: {itemData.DisplayNameKey}");
|
||||
buttonText.text = itemData.DisplayNameKey;
|
||||
Debug.LogWarning($"LocalizationManager 인스턴스가 없어 메뉴 아이템 텍스트를 키 값으로 설정합니다: {itemData.DisplayName}");
|
||||
buttonText.text = itemData.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
// isShrinkMenuItemWidth가 true일 때 1차 메뉴 아이템의 너비를 텍스트에 맞게 조절합니다.
|
||||
// 텍스트가 설정된 후에 이 로직을 실행해야 정확한 너비를 계산할 수 있습니다.
|
||||
if (depth == 0 && isShrinkMenuItemWidth && buttonText != null)
|
||||
{
|
||||
buttonText.ForceMeshUpdate(); // 텍스트 변경 후 메쉬 강제 업데이트 (정확한 크기 계산 위함)
|
||||
Vector2 textSize = buttonText.GetPreferredValues(buttonText.text); // 텍스트 내용에 따른 선호 크기 계산
|
||||
RectTransform rect = menuItemInstance.GetComponent<RectTransform>();
|
||||
textSize.x += menuItemWidthPadding; // 좌우 여백 추가
|
||||
if (rect != null) rect.sizeDelta = new Vector2(textSize.x, rect.rect.height);
|
||||
|
||||
// LayoutElement가 없으면 추가
|
||||
LayoutElement layoutElement = menuItemInstance.GetComponent<LayoutElement>() ?? menuItemInstance.AddComponent<LayoutElement>();
|
||||
|
||||
layoutElement.preferredWidth = buttonText.preferredWidth;
|
||||
}
|
||||
|
||||
if (button != null)
|
||||
{
|
||||
button.onClick.RemoveAllListeners(); // 기존 리스너 제거 (프리팹에 설정된 것이 있을 수 있으므로)
|
||||
@@ -421,7 +333,7 @@ namespace UVC.UI.Menu
|
||||
if (itemData.SubMenuItems != null && itemData.SubMenuItems.Count > 0)
|
||||
{
|
||||
// 하위 메뉴가 있으면 해당 하위 메뉴의 표시 상태를 토글
|
||||
ToggleSubMenuDisplay(itemData, menuItemInstance);
|
||||
ToggleSubMenuDisplay(itemData, menuItemInstance, depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -437,21 +349,27 @@ namespace UVC.UI.Menu
|
||||
if (itemData.SubMenuItems != null && itemData.SubMenuItems.Count > 0)
|
||||
{
|
||||
// 하위 메뉴 화살표 아이콘 표시
|
||||
Transform subMenuArrowTransform = menuItemInstance.transform.Find(SubMenuArrowName);
|
||||
Transform subMenuArrowTransform = menuItemInstance.transform.Find(subMenuArrowName);
|
||||
if (subMenuArrowTransform != null)
|
||||
{
|
||||
subMenuArrowTransform.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// 하위 메뉴 컨테이너 찾기 및 설정
|
||||
Transform subMenuContainerTransform = menuItemInstance.transform.Find(SubMenuContainerName);
|
||||
if (subMenuContainerTransform != null)
|
||||
// 하위 메뉴 컨테이너 생성 및 설정
|
||||
if (subMenuContainer != null)
|
||||
{
|
||||
RectTransform subMenuRect = subMenuContainerTransform.GetComponent<RectTransform>();
|
||||
GameObject subMenuContainerInstance = Instantiate(subMenuContainer.gameObject);
|
||||
subMenuContainerInstance.name = $"SubMenuContainer_{itemData.ItemId}";
|
||||
_subMenuContainerObjects[itemData.ItemId] = subMenuContainerInstance;
|
||||
|
||||
// 부모를 메뉴 아이템으로 설정하여 위치를 쉽게 계산
|
||||
subMenuContainerInstance.transform.SetParent(menuItemInstance.transform, false);
|
||||
|
||||
RectTransform subMenuRect = subMenuContainerInstance.GetComponent<RectTransform>();
|
||||
if (subMenuRect == null)
|
||||
{
|
||||
ULog.Warning($"{SubMenuContainerName} for '{menuItemInstance.name}' is missing RectTransform. Adding one.");
|
||||
subMenuRect = subMenuContainerTransform.gameObject.AddComponent<RectTransform>();
|
||||
Debug.LogWarning($"{subMenuContainer.name} for '{menuItemInstance.name}' is missing RectTransform. Adding one.");
|
||||
subMenuRect = subMenuContainerInstance.gameObject.AddComponent<RectTransform>();
|
||||
}
|
||||
|
||||
// 하위 메뉴 컨테이너의 RectTransform 기본 설정 (앵커, 피벗)
|
||||
@@ -459,219 +377,105 @@ namespace UVC.UI.Menu
|
||||
subMenuRect.anchorMax = new Vector2(0, 1); // 좌상단 기준
|
||||
subMenuRect.pivot = new Vector2(0, 1); // 좌상단 기준
|
||||
|
||||
// 하위 메뉴 위치 조정
|
||||
if (depth == 0) // 1차 메뉴의 하위 메뉴는 부모 아이템의 오른쪽 아래에 펼쳐짐
|
||||
RectTransform menuItemRect = menuItemInstance.GetComponent<RectTransform>();
|
||||
|
||||
|
||||
// 최종 부모 설정
|
||||
if (menuContainer != null && menuContainer.parent != null)
|
||||
{
|
||||
// 부모 메뉴 아이템의 너비만큼 오른쪽으로, 설정된 깊이 간격만큼 아래로 이동
|
||||
subMenuRect.anchoredPosition = new Vector2(menuItemSize.x - menuDepthSpace.x, -menuDepthSpace.y);
|
||||
subMenuContainerInstance.transform.SetParent(menuContainer.parent, true);
|
||||
}
|
||||
else // 2차 이상 메뉴의 하위 메뉴는 부모 아이템의 오른쪽 아래에 펼쳐짐
|
||||
else
|
||||
{
|
||||
// 부모 하위 메뉴 아이템의 너비만큼 오른쪽으로, 설정된 깊이 간격만큼 아래로 이동
|
||||
subMenuRect.anchoredPosition = new Vector2(subMenuItemSize.x - subMenuDepthSpace.x, -subMenuDepthSpace.y);
|
||||
Debug.LogWarning("menuContainer 또는 menuContainer.parent가 null이므로 하위 메뉴 컨테이너를 최상위로 설정합니다.");
|
||||
}
|
||||
|
||||
// 재귀 호출을 통해 하위 메뉴 아이템들 생성 및 배치
|
||||
Vector2 subContentSize = CreateMenuItems(itemData.SubMenuItems, subMenuContainerTransform, depth + 1);
|
||||
|
||||
// 하위 메뉴 컨테이너의 크기를 내용물(하위 메뉴 아이템들)과 패딩에 맞게 조절
|
||||
float containerWidth = subContentSize.x + subContainerPadding.x + subContainerPadding.z; // 좌우 패딩 추가
|
||||
float containerHeight = subContentSize.y + subContainerPadding.y + subContainerPadding.w; // 상하 패딩 추가
|
||||
subMenuRect.sizeDelta = new Vector2(containerWidth, containerHeight);
|
||||
CreateMenuItems(itemData.SubMenuItems, subMenuContainerInstance.transform, depth + 1);
|
||||
|
||||
// 하위 메뉴는 초기에 숨겨진 상태로 설정
|
||||
subMenuContainerTransform.gameObject.SetActive(false);
|
||||
subMenuContainerInstance.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ULog.Warning($"'{menuItemInstance.name}' 내부에 '{SubMenuContainerName}'를 찾을 수 없습니다. 하위 메뉴가 정상적으로 표시되지 않을 수 있습니다.");
|
||||
Debug.LogWarning($"'subMenuContainer' 프리팹이 Inspector에서 할당되지 않았습니다. 하위 메뉴가 정상적으로 표시되지 않을 수 있습니다.");
|
||||
}
|
||||
}
|
||||
else // 하위 메뉴가 없는 경우
|
||||
{
|
||||
// 하위 메뉴 화살표 아이콘 숨김 (프리팹에 기본적으로 활성화되어 있을 수 있으므로)
|
||||
Transform existingArrow = menuItemInstance.transform.Find(SubMenuArrowName);
|
||||
Transform existingArrow = menuItemInstance.transform.Find(subMenuArrowName);
|
||||
if (existingArrow != null)
|
||||
{
|
||||
existingArrow.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 생성된 메뉴 아이템(또는 구분선)의 레이아웃(위치, 크기) 설정
|
||||
if (instanceToLayout != null)
|
||||
{
|
||||
LayoutMenuItem(instanceToLayout, itemData, depth, i, items);
|
||||
}
|
||||
|
||||
// 현재 레벨의 전체 너비와 높이 누적 계산
|
||||
if (depth == 0) // 최상위 메뉴 (수평으로 아이템 나열)
|
||||
{
|
||||
if (i > 0) currentLevelCalculatedWidth += menuItemSpace.x; // 아이템 간 수평 간격 추가
|
||||
// 아이템 너비 추가 (구분선은 구분선 크기 + 좌우 마진, 일반 아이템은 menuItemSize.x)
|
||||
currentLevelCalculatedWidth += itemData.IsSeparator ? (menuItemSeparatorSize.x + menuItemSeparatorMargin.x + menuItemSeparatorMargin.z) : menuItemSize.x;
|
||||
// 현재 레벨의 최대 높이 갱신 (구분선은 구분선 높이 + 상하 마진, 일반 아이템은 menuItemSize.y)
|
||||
currentLevelCalculatedHeight = Mathf.Max(currentLevelCalculatedHeight, itemData.IsSeparator ? (menuItemSeparatorSize.y + menuItemSeparatorMargin.y + menuItemSeparatorMargin.w) : menuItemSize.y);
|
||||
}
|
||||
else // 하위 메뉴 (수직으로 아이템 나열)
|
||||
{
|
||||
if (i > 0) currentLevelCalculatedHeight += menuItemSpace.y; // 아이템 간 수직 간격 추가
|
||||
|
||||
if (itemData.IsSeparator) // 구분선인 경우
|
||||
{
|
||||
// 구분선 높이 + 상하 마진 추가
|
||||
currentLevelCalculatedHeight += menuItemSeparatorSize.y + menuItemSeparatorMargin.y + menuItemSeparatorMargin.w;
|
||||
}
|
||||
else // 일반 하위 메뉴 아이템인 경우
|
||||
{
|
||||
currentLevelCalculatedHeight += subMenuItemSize.y; // 하위 메뉴 아이템 높이 추가
|
||||
}
|
||||
// 현재 레벨의 최대 너비 갱신 (모든 하위 메뉴 아이템은 동일한 너비를 가진다고 가정)
|
||||
currentLevelCalculatedWidth = Mathf.Max(currentLevelCalculatedWidth, subMenuItemSize.x);
|
||||
}
|
||||
}
|
||||
return new Vector2(currentLevelCalculatedWidth, currentLevelCalculatedHeight);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UI 요소(메뉴 아이템 또는 구분선)의 위치와 크기를 설정합니다.
|
||||
/// 메뉴의 깊이(depth)와 아이템의 인덱스(itemIndex)에 따라 수평 또는 수직으로 배치합니다.
|
||||
/// </summary>
|
||||
/// <param name="uiElement">레이아웃을 적용할 메뉴 아이템 또는 구분선 GameObject입니다.</param>
|
||||
/// <param name="itemData">해당 UI 요소의 MenuItemData입니다.</param>
|
||||
/// <param name="depth">현재 메뉴의 깊이입니다.</param>
|
||||
/// <param name="itemIndex">현재 아이템의 부모 컨테이너 내에서의 인덱스입니다.</param>
|
||||
/// <param name="siblingItems">현재 아이템과 같은 레벨에 있는 모든 형제 아이템들의 리스트입니다.</param>
|
||||
protected virtual void LayoutMenuItem(GameObject uiElement, MenuItemData itemData, int depth, int itemIndex, List<MenuItemData> siblingItems)
|
||||
{
|
||||
RectTransform rectTransform = uiElement.GetComponent<RectTransform>();
|
||||
if (rectTransform == null)
|
||||
{
|
||||
ULog.Error($"GameObject '{uiElement.name}'에 RectTransform 컴포넌트가 없습니다. 레이아웃을 적용할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 모든 메뉴 아이템과 구분선의 앵커 및 피벗을 좌상단으로 통일 (계산 편의성)
|
||||
rectTransform.anchorMin = new Vector2(0, 1); // 좌상단
|
||||
rectTransform.anchorMax = new Vector2(0, 1); // 좌상단
|
||||
rectTransform.pivot = new Vector2(0, 1); // 좌상단
|
||||
|
||||
float currentX = 0; // 계산될 X 위치
|
||||
float currentY = 0; // 계산될 Y 위치
|
||||
|
||||
if (depth == 0) // 1차 깊이 메뉴 (최상위 메뉴, 수평 레이아웃)
|
||||
{
|
||||
// 초기 위치 설정 (menuContainer의 좌상단 기준)
|
||||
currentX = menuItemInitPosition.x;
|
||||
currentY = -menuItemInitPosition.y; // Y는 아래로 내려가므로 음수
|
||||
|
||||
// 현재 아이템 이전의 형제 아이템들의 너비와 간격을 누적하여 X 위치 계산
|
||||
for (int k = 0; k < itemIndex; k++)
|
||||
{
|
||||
MenuItemData sibling = siblingItems[k];
|
||||
if (sibling.IsSeparator)
|
||||
{
|
||||
// 구분선 너비 + 좌우 마진 + 아이템 간 간격
|
||||
currentX += menuItemSeparatorSize.x + menuItemSeparatorMargin.x + menuItemSeparatorMargin.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentX += menuItemSize.x; // 일반 메뉴 아이템 너비
|
||||
}
|
||||
currentX += menuItemSpace.x; // 아이템 간 수평 간격
|
||||
}
|
||||
|
||||
if (itemData.IsSeparator) // 현재 아이템이 구분선인 경우
|
||||
{
|
||||
rectTransform.sizeDelta = menuItemSeparatorSize; // 구분선 크기 설정
|
||||
// 구분선 위치에 좌측 마진 적용, Y 위치에 상단 마진 적용
|
||||
rectTransform.anchoredPosition = new Vector2(currentX + menuItemSeparatorMargin.x, currentY - menuItemSeparatorMargin.y);
|
||||
}
|
||||
else // 현재 아이템이 일반 메뉴 아이템인 경우
|
||||
{
|
||||
rectTransform.sizeDelta = menuItemSize; // 메뉴 아이템 크기 설정
|
||||
rectTransform.anchoredPosition = new Vector2(currentX, currentY); // 계산된 위치 설정
|
||||
}
|
||||
}
|
||||
else // 2차 깊이 이상 하위 메뉴 (수직 레이아웃)
|
||||
{
|
||||
// 초기 위치 설정 (부모 SubMenuContainer의 좌상단 기준, 패딩 적용)
|
||||
currentX = subContainerPadding.x; // 좌측 패딩
|
||||
currentY = -subContainerPadding.y; // 상단 패딩 (Y는 아래로 내려가므로 음수)
|
||||
|
||||
// 현재 아이템 이전의 형제 아이템들의 높이와 간격을 누적하여 Y 위치 계산
|
||||
for (int k = 0; k < itemIndex; k++)
|
||||
{
|
||||
MenuItemData sibling = siblingItems[k];
|
||||
if (sibling.IsSeparator)
|
||||
{
|
||||
// 구분선 상단 마진 + 구분선 높이 + 구분선 하단 마진 + 아이템 간 간격
|
||||
currentY -= (menuItemSeparatorMargin.y + menuItemSeparatorSize.y + menuItemSeparatorMargin.w);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentY -= subMenuItemSize.y; // 하위 메뉴 아이템 높이
|
||||
}
|
||||
currentY -= menuItemSpace.y; // 아이템 간 수직 간격
|
||||
}
|
||||
|
||||
if (itemData.IsSeparator) // 현재 아이템이 구분선인 경우
|
||||
{
|
||||
// 구분선 Y 위치에 추가로 자신의 상단 마진 적용
|
||||
currentY -= menuItemSeparatorMargin.y;
|
||||
rectTransform.sizeDelta = menuItemSeparatorSize; // 구분선 크기 설정
|
||||
// 구분선 X 위치에 좌측 마진 적용
|
||||
rectTransform.anchoredPosition = new Vector2(currentX + menuItemSeparatorMargin.x, currentY);
|
||||
}
|
||||
else // 현재 아이템이 일반 하위 메뉴 아이템인 경우
|
||||
{
|
||||
rectTransform.sizeDelta = subMenuItemSize; // 하위 메뉴 아이템 크기 설정
|
||||
rectTransform.anchoredPosition = new Vector2(currentX, currentY); // 계산된 위치 설정
|
||||
}
|
||||
}
|
||||
// 최종 계산된 anchoredPosition을 다시 한번 설정 (depth 0 일반 아이템의 경우 중복될 수 있으나 일관성을 위해)
|
||||
// rectTransform.anchoredPosition = new Vector2(currentX, currentY); // 이 줄은 depth 0 일반 아이템의 경우 이미 위에서 설정됨. 조건부로 하거나 위의 로직을 재검토.
|
||||
// 현재 로직에서는 각 분기 내에서 anchoredPosition을 설정하므로 이 줄은 불필요하거나,
|
||||
// depth 0 일반 아이템의 X 위치 계산 후 여기서 Y와 함께 최종 설정하는 방식으로 변경 가능.
|
||||
// 현재는 각 분기에서 설정하는 것이 명확해 보임.
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 특정 메뉴 아이템에 연결된 하위 메뉴의 표시 상태를 토글(열기/닫기)합니다.
|
||||
/// 하위 메뉴를 열 때, 같은 레벨의 다른 열려있는 하위 메뉴들은 닫습니다.
|
||||
/// </summary>
|
||||
/// <param name="itemData">토글할 하위 메뉴를 가진 부모 메뉴의 MenuItemData입니다.</param>
|
||||
/// <param name="menuItemInstance">부모 메뉴 아이템의 GameObject입니다.</param>
|
||||
protected virtual void ToggleSubMenuDisplay(MenuItemData itemData, GameObject menuItemInstance)
|
||||
/// <param name="depth">클릭된 메뉴 아이템의 깊이입니다.</param>
|
||||
protected virtual void ToggleSubMenuDisplay(MenuItemData itemData, GameObject menuItemInstance, int depth)
|
||||
{
|
||||
if (itemData.IsSeparator) return; // 구분선은 하위 메뉴를 가질 수 없음
|
||||
|
||||
// 메뉴 아이템 내부에서 SubMenuContainer 이름의 GameObject를 찾음
|
||||
Transform subMenuContainer = menuItemInstance.transform.Find(SubMenuContainerName);
|
||||
if (subMenuContainer != null)
|
||||
if (_subMenuContainerObjects.TryGetValue(itemData.ItemId, out GameObject subMenuContainerObject))
|
||||
{
|
||||
bool isActive = subMenuContainer.gameObject.activeSelf; // 현재 하위 메뉴의 활성화 상태
|
||||
bool isActive = subMenuContainerObject.activeSelf; // 현재 하위 메뉴의 활성화 상태
|
||||
bool becomingActive = !isActive; // 토글 후 활성화될 상태
|
||||
|
||||
if (becomingActive) // 하위 메뉴가 열리려고 하는 경우
|
||||
// 새로 메뉴를 여는 경우, 현재 메뉴와 관련된 다른 메뉴들을 먼저 닫습니다.
|
||||
if (becomingActive)
|
||||
{
|
||||
// 현재 클릭된 메뉴 아이템의 부모 컨테이너에서, 이 하위 메뉴를 제외한 다른 모든 하위 메뉴를 닫음
|
||||
CloseOtherSubMenusInParent(menuItemInstance.transform.parent, subMenuContainer);
|
||||
// 클릭된 아이템의 부모 컨테이너(같은 레벨의 메뉴 아이템들이 있는 곳)를 기준으로
|
||||
// 현재 열려는 하위 메뉴(subMenuContainerObject)를 제외한 다른 모든 하위 메뉴를 닫습니다.
|
||||
CloseOtherSubMenusInParent(itemData, menuItemInstance.transform.parent, subMenuContainerObject);
|
||||
|
||||
// 하위 메뉴를 포함하는 menuItemInstance를 그 부모 컨테이너 내에서 가장 앞으로 가져와서
|
||||
// 다른 형제 메뉴 아이템에 가려지지 않도록 합니다. (렌더링 순서 조정)
|
||||
menuItemInstance.transform.SetAsLastSibling();
|
||||
// 만약 1차 메뉴(depth 0)를 클릭해서 2차 메뉴를 여는 경우라면,
|
||||
// 현재 열려있는 모든 하위 메뉴를 닫아줍니다.
|
||||
// 이렇게 하면 다른 메뉴 가지(branch)에 열려있던 하위 메뉴들이 모두 닫힙니다.
|
||||
if (depth == 0)
|
||||
{
|
||||
CloseAllOpenSubMenus();
|
||||
}
|
||||
}
|
||||
else // 하위 메뉴가 닫히려고 하는 경우
|
||||
{
|
||||
// 닫으려는 메뉴의 모든 자식 메뉴들도 함께 닫습니다.
|
||||
CloseSubMenuAndDescendants(itemData);
|
||||
}
|
||||
|
||||
// 하위 메뉴 컨테이너의 활성화 상태를 토글
|
||||
subMenuContainer.gameObject.SetActive(becomingActive);
|
||||
subMenuContainerObject.SetActive(becomingActive);
|
||||
|
||||
if (becomingActive) subMenuContainerObject.GetComponent<LayoutGroup>()?.FitToChildren(); // 하위 메뉴 컨테이너의 크기를 자식 아이템에 맞게 조정
|
||||
|
||||
// isAnySubMenuOpen 상태 및 UI 블로커 업데이트
|
||||
if (becomingActive)
|
||||
{
|
||||
isAnySubMenuOpen = true; // 하나라도 열리면 true
|
||||
|
||||
RectTransform menuItemRect = menuItemInstance.GetComponent<RectTransform>();
|
||||
RectTransform menuItemRectparent = menuItemInstance.transform.parent?.GetComponent<RectTransform>();
|
||||
RectTransform subMenuRect = subMenuContainerObject.transform?.GetComponent<RectTransform>();
|
||||
if (menuItemRect != null && subMenuRect != null && menuItemRectparent != null)
|
||||
{
|
||||
// 하위 메뉴 위치 조정
|
||||
if (depth == 0) // 1차 메뉴의 하위 메뉴는 부모 아이템의 아래쪽에 펼쳐짐
|
||||
{
|
||||
subMenuRect.anchoredPosition = new Vector2(menuItemRect.anchoredPosition.x + menuDepthSpace.x, -menuItemRect.rect.height + menuDepthSpace.y);
|
||||
}
|
||||
else // 2차 이상 메뉴의 하위 메뉴는 부모 아이템의 오른쪽에 펼쳐짐
|
||||
{
|
||||
subMenuRect.anchoredPosition = new Vector2(menuItemRectparent.anchoredPosition.x + menuItemRectparent.rect.width + subMenuDepthSpace.x, menuItemRectparent.anchoredPosition.y + menuItemRect.anchoredPosition.y + subMenuDepthSpace.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -682,35 +486,71 @@ namespace UVC.UI.Menu
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 메뉴 아이템의 모든 하위 메뉴들을 재귀적으로 닫습니다.
|
||||
/// </summary>
|
||||
/// <param name="parentItemData">하위 메뉴들을 닫을 부모 메뉴의 MenuItemData입니다.</param>
|
||||
protected virtual void CloseSubMenuAndDescendants(MenuItemData parentItemData)
|
||||
{
|
||||
if (parentItemData?.SubMenuItems == null) return;
|
||||
|
||||
foreach (var subItem in parentItemData.SubMenuItems)
|
||||
{
|
||||
if (subItem.IsSeparator) continue;
|
||||
|
||||
if (_subMenuContainerObjects.TryGetValue(subItem.ItemId, out GameObject subContainer))
|
||||
{
|
||||
if (subContainer != null && subContainer.activeSelf)
|
||||
{
|
||||
subContainer.SetActive(false);
|
||||
// 이 하위 메뉴의 자식들도 재귀적으로 닫습니다.
|
||||
CloseSubMenuAndDescendants(subItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 부모 컨테이너 내에서, 지정된 하위 메뉴(subMenuToExclude)를 제외한 모든 다른 열려있는 하위 메뉴들을 닫습니다.
|
||||
/// 이는 한 번에 하나의 하위 메뉴만 열려 있도록 보장하는 데 사용됩니다.
|
||||
/// </summary>
|
||||
/// <param name="itemData">토글할 하위 메뉴를 가진 메뉴의 MenuItemData입니다.</param>
|
||||
/// <param name="currentMenuItemsParent">하위 메뉴들을 검사할 부모 Transform입니다. (예: TopMenu 또는 다른 SubMenuContainer)</param>
|
||||
/// <param name="subMenuToExclude">닫지 않고 유지할 특정 하위 메뉴의 Transform입니다. (보통 새로 열리려는 하위 메뉴)</param>
|
||||
protected virtual void CloseOtherSubMenusInParent(Transform currentMenuItemsParent, Transform subMenuToExclude)
|
||||
/// <param name="subMenuToExclude">닫지 않고 유지할 특정 하위 메뉴의 GameObject입니다. (보통 새로 열리려는 하위 메뉴)</param>
|
||||
protected virtual void CloseOtherSubMenusInParent(MenuItemData itemData, Transform currentMenuItemsParent, GameObject subMenuToExclude)
|
||||
{
|
||||
if (currentMenuItemsParent == null)
|
||||
{
|
||||
ULog.Warning("CloseOtherSubMenusInParent 호출 시 currentMenuItemsParent가 null입니다. 다른 하위 메뉴를 닫을 수 없습니다.");
|
||||
Debug.LogWarning("CloseOtherSubMenusInParent 호출 시 currentMenuItemsParent가 null입니다. 다른 하위 메뉴를 닫을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 부모 컨테이너의 모든 자식 메뉴 아이템들을 순회
|
||||
for (int i = 0; i < currentMenuItemsParent.childCount; i++)
|
||||
Debug.Log($"CloseOtherSubMenusInParent 호출: 현재 부모 - {currentMenuItemsParent.name}, 제외할 하위 메뉴 - {subMenuToExclude?.name}");
|
||||
|
||||
string parentName = currentMenuItemsParent.name + "_";
|
||||
|
||||
// 현재 부모 컨테이너 내에서 열려있는 하위 메뉴들을 찾습니다.
|
||||
MenuItemData? parentItemData = itemData.Parent;
|
||||
|
||||
if (parentItemData != null)
|
||||
{
|
||||
Transform siblingMenuItemTransform = currentMenuItemsParent.GetChild(i);
|
||||
// ToList()를 사용하여 반복 중 컬렉션 수정 문제를 방지합니다.
|
||||
var openSubMenus = _subMenuContainerObjects.Where(pair => pair.Value != null &&
|
||||
pair.Value.activeSelf && pair.Value != subMenuToExclude && parentItemData.HasSubMenuItems(pair.Key)).ToList();
|
||||
|
||||
// 자기 자신이거나, 제외할 하위 메뉴의 부모 메뉴 아이템인 경우는 건너뜀
|
||||
if (siblingMenuItemTransform == null || siblingMenuItemTransform == subMenuToExclude?.parent) continue;
|
||||
|
||||
// 형제 메뉴 아이템 내부에서 SubMenuContainer를 찾음
|
||||
Transform potentialSubMenu = siblingMenuItemTransform.Find(SubMenuContainerName);
|
||||
|
||||
// 찾은 SubMenuContainer가 존재하고, 제외할 하위 메뉴가 아니며, 현재 활성화 상태라면 비활성화시킴
|
||||
if (potentialSubMenu != null && potentialSubMenu != subMenuToExclude && potentialSubMenu.gameObject.activeSelf)
|
||||
foreach (var pair in openSubMenus)
|
||||
{
|
||||
potentialSubMenu.gameObject.SetActive(false);
|
||||
string itemId = pair.Key;
|
||||
GameObject containerToClose = pair.Value;
|
||||
|
||||
// 닫아야 할 형제 하위 메뉴를 찾았습니다.
|
||||
containerToClose.SetActive(false);
|
||||
|
||||
// 이 하위 메뉴의 자손 메뉴들도 모두 재귀적으로 닫습니다.
|
||||
if (_menuItemDataMap.TryGetValue(itemId, out MenuItemData itemDataToClose))
|
||||
{
|
||||
CloseSubMenuAndDescendants(itemDataToClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,18 +563,12 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
bool anyActuallyClosed = false; // 실제로 닫힌 하위 메뉴가 있었는지 추적
|
||||
|
||||
// _menuItemObjects에 저장된 모든 메뉴 아이템 GameObject들을 순회
|
||||
foreach (GameObject menuItemGO in _menuItemObjects.Values)
|
||||
foreach (GameObject subMenuContainer in _subMenuContainerObjects.Values)
|
||||
{
|
||||
if (menuItemGO == null) continue; // null 체크
|
||||
|
||||
// 각 메뉴 아이템에서 SubMenuContainer를 찾아 비활성화
|
||||
// 구분선 아이템은 SubMenuContainer가 없으므로 Find 결과가 null이 되어 자동으로 처리됨.
|
||||
Transform subMenuContainer = menuItemGO.transform.Find(SubMenuContainerName);
|
||||
if (subMenuContainer != null && subMenuContainer.gameObject.activeSelf)
|
||||
if (subMenuContainer != null && subMenuContainer.activeSelf)
|
||||
{
|
||||
subMenuContainer.gameObject.SetActive(false);
|
||||
anyActuallyClosed = true; // 하나라도 닫았으면 true
|
||||
subMenuContainer.SetActive(false);
|
||||
anyActuallyClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,12 +586,9 @@ namespace UVC.UI.Menu
|
||||
protected virtual void CheckIfAnySubMenuRemainsOpen()
|
||||
{
|
||||
isAnySubMenuOpen = false; // 일단 false로 가정
|
||||
foreach (GameObject menuItemGO in _menuItemObjects.Values)
|
||||
foreach (GameObject subMenuGO in _subMenuContainerObjects.Values)
|
||||
{
|
||||
if (menuItemGO == null) continue;
|
||||
|
||||
Transform subMenuContainer = menuItemGO.transform.Find(SubMenuContainerName);
|
||||
if (subMenuContainer != null && subMenuContainer.gameObject.activeSelf)
|
||||
if (subMenuGO != null && subMenuGO.activeSelf)
|
||||
{
|
||||
isAnySubMenuOpen = true; // 하나라도 열려있으면 true로 설정하고 반복 종료
|
||||
break;
|
||||
@@ -784,33 +615,20 @@ namespace UVC.UI.Menu
|
||||
Transform blockerParent = uiBlockerInstance.transform.parent;
|
||||
if (menuContainer != null && menuContainer.parent == blockerParent)
|
||||
{
|
||||
// menuContainer 바로 뒤에 오도록 (SiblingIndex가 작을수록 먼저 그려짐 - 뒤에 위치)
|
||||
// 블로커가 메뉴보다 먼저 그려져야 메뉴를 가리지 않으면서 메뉴 외 영역 클릭을 막을 수 있음.
|
||||
// 하지만 UI 이벤트는 앞쪽에 있는 것부터 받으므로, 블로커가 메뉴보다 뒤에 있어야 함.
|
||||
// 따라서, 블로커는 메뉴보다 낮은 SiblingIndex - 1를 가져야 함.
|
||||
// 여기서는 메뉴 컨테이너 바로 뒤에 오도록 설정 (블로커가 렌더링된 후 메뉴가 렌더링되어 메뉴를 덮지 않도록)
|
||||
// 혼동을 피하기 위해, 블로커는 메뉴 시스템 전체보다 뒤에 위치해야 클릭을 가로챌 수 있습니다.
|
||||
// CreateUIBlocker 호출 시점에는 menuContainer의 sibling index가 확정되지 않았을 수 있으므로,
|
||||
// UpdateBlockerVisibility에서 최종적으로 조정하는 것이 더 안전할 수 있습니다.
|
||||
// 여기서는 일단 menuContainer의 뒤에 두도록 시도합니다.
|
||||
uiBlockerInstance.transform.SetSiblingIndex(menuContainer.GetSiblingIndex() - 1);
|
||||
}
|
||||
else if (transform.parent == blockerParent)
|
||||
{
|
||||
// TopMenuView 자체가 blockerParent의 자식인 경우, TopMenuView 뒤에 블로커를 둡니다.
|
||||
uiBlockerInstance.transform.SetSiblingIndex(transform.GetSiblingIndex() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 그 외의 경우, 블로커를 부모의 마지막 자식으로 보내 UI 요소들 중 가장 앞에 오도록 합니다.
|
||||
// 이렇게 하면 다른 UI 요소들의 클릭을 막을 수 있습니다.
|
||||
uiBlockerInstance.transform.SetAsLastSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 화면에 표시된 모든 메뉴 아이템들을 제거하고 관련 내부 데이터도 초기화합니다.
|
||||
/// </summary>
|
||||
@@ -825,21 +643,23 @@ namespace UVC.UI.Menu
|
||||
}
|
||||
_menuItemObjects.Clear();
|
||||
|
||||
foreach (var pair in _subMenuContainerObjects)
|
||||
{
|
||||
if (pair.Value != null) Destroy(pair.Value);
|
||||
}
|
||||
_subMenuContainerObjects.Clear();
|
||||
_menuItemDataMap.Clear();
|
||||
|
||||
// menuContainer의 모든 자식 GameObject들을 직접 파괴합니다.
|
||||
// 이는 _menuItemObjects에 등록되지 않은 자식 요소(예: 동적으로 추가된 다른 요소)도 제거합니다.
|
||||
if (menuContainer != null)
|
||||
{
|
||||
foreach (Transform child in menuContainer)
|
||||
{
|
||||
// 자식 GameObject를 직접 파괴합니다.
|
||||
// Instantiate로 생성된 객체들이므로 Destroy로 제거하는 것이 맞습니다.
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
// menuContainer 자체를 파괴하는 것이 아니라 그 내용물만 비우는 것입니다.
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 제공된 MenuItemData 리스트를 기반으로 화면에 표시된 모든 메뉴 아이템들의 텍스트를 업데이트합니다.
|
||||
/// 주로 언어 변경 시 호출됩니다.
|
||||
@@ -849,18 +669,18 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
if (_locManager == null)
|
||||
{
|
||||
ULog.Warning("LocalizationManager가 없어 메뉴 텍스트를 업데이트할 수 없습니다.");
|
||||
Debug.LogWarning("LocalizationManager가 없어 메뉴 텍스트를 업데이트할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
// 재귀 함수를 호출하여 모든 메뉴 아이템(하위 메뉴 포함)의 텍스트를 업데이트합니다.
|
||||
UpdateMenuTextsRecursive(items);
|
||||
UpdateMenuTextsRecursive(items, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 재귀적으로 메뉴 아이템들의 텍스트를 업데이트하는 내부 도우미 함수입니다.
|
||||
/// 재귀적으로 메뉴 아이템들의 텍스트를 업데이트하고, 필요에 따라 너비를 조절하는 내부 도우미 함수입니다.
|
||||
/// </summary>
|
||||
/// <param name="items">텍스트를 업데이트할 메뉴 아이템들의 데이터 리스트입니다.</param>
|
||||
protected virtual void UpdateMenuTextsRecursive(List<MenuItemData> items)
|
||||
/// <param name="depth">현재 메뉴의 깊이입니다.</param>
|
||||
protected virtual void UpdateMenuTextsRecursive(List<MenuItemData> items, int depth = 0)
|
||||
{
|
||||
if (items == null) return;
|
||||
|
||||
@@ -868,21 +688,26 @@ namespace UVC.UI.Menu
|
||||
{
|
||||
if (itemData.IsSeparator) continue; // 구분선은 텍스트가 없음
|
||||
|
||||
// _menuItemObjects에서 해당 아이템의 GameObject를 찾음
|
||||
if (_menuItemObjects.TryGetValue(itemData.ItemId, out GameObject menuItemInstance))
|
||||
{
|
||||
// GameObject에서 TextMeshProUGUI 컴포넌트를 찾아 텍스트 업데이트
|
||||
TextMeshProUGUI buttonText = menuItemInstance.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
if (buttonText != null && !string.IsNullOrEmpty(itemData.DisplayNameKey))
|
||||
if (buttonText != null && !string.IsNullOrEmpty(itemData.DisplayName))
|
||||
{
|
||||
buttonText.text = _locManager.GetString(itemData.DisplayNameKey);
|
||||
buttonText.text = _locManager.GetString(itemData.DisplayName);
|
||||
|
||||
// isShrinkMenuItemWidth가 true일 때 1차 메뉴 아이템의 너비를 텍스트에 맞게 조절
|
||||
if (depth == 0 && isShrinkMenuItemWidth)
|
||||
{
|
||||
LayoutElement layoutElement = menuItemInstance.GetComponent<LayoutElement>() ?? menuItemInstance.AddComponent<LayoutElement>();
|
||||
// 텍스트의 preferredWidth를 기반으로 선호 너비 설정 (좌우 여백 포함)
|
||||
layoutElement.preferredWidth = buttonText.preferredWidth + menuItemWidthPadding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 하위 메뉴가 있으면 재귀적으로 호출하여 하위 메뉴 아이템들의 텍스트도 업데이트
|
||||
if (itemData.SubMenuItems != null && itemData.SubMenuItems.Count > 0)
|
||||
{
|
||||
UpdateMenuTextsRecursive(itemData.SubMenuItems);
|
||||
UpdateMenuTextsRecursive(itemData.SubMenuItems, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user