Files
WhaleAdventure_VR/Assets/My project/Inventory/Scripts/InventoryUILayoutController.cs
2026-06-24 17:13:40 +09:00

2243 lines
89 KiB
C#

using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public enum InventoryRootUIMode
{
TestMode,
PlayMode,
BuildMode,
HiddenMode,
LayoutOnly
}
public enum InventoryUIAnchorPreset
{
Custom,
MiddleCenter,
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
StretchAll
}
[Serializable]
public class InventoryControlledRectTarget
{
[Header("Target")]
public string name;
public RectTransform rectTransform;
public GameObject activeTarget;
[Header("Rect Layout")]
public bool applyRect = true;
public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter;
public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f);
public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f);
public Vector2 customPivot = new Vector2(0.5f, 0.5f);
public Vector2 size = new Vector2(100f, 100f);
public Vector2 anchoredPosition = Vector2.zero;
[Header("Transform")]
public bool resetScaleToOne = true;
public bool applyLocalScale = false;
public Vector3 localScale = Vector3.one;
public bool applyLocalRotation = false;
public Vector3 localEulerAngles = Vector3.zero;
public bool applySiblingIndex = false;
public int siblingIndex = 0;
[Header("Active By Mode")]
public bool controlActiveState = false;
public bool activeInTestMode = true;
public bool activeInPlayMode = true;
public bool activeInBuildMode = true;
public bool activeInHiddenMode = false;
[Header("CanvasGroup")]
public bool applyCanvasGroup = false;
public bool addCanvasGroupIfMissing = true;
[Range(0f, 1f)] public float canvasAlpha = 1f;
public bool canvasInteractable = false;
public bool canvasBlocksRaycasts = false;
[Header("Raycast")]
public bool applySelfImageRaycast = false;
public bool selfImageRaycastTarget = false;
public bool applyChildImageRaycasts = false;
public bool childImageRaycastTarget = false;
public bool preserveButtonImageRaycasts = true;
public bool applyChildTextRaycasts = false;
public bool childTextRaycastTarget = false;
}
[Serializable]
public class InventoryControlledTextTarget
{
[Header("Target")]
public string name;
public TMP_Text text;
[Header("Rect Layout")]
public bool applyRect = false;
public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter;
public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f);
public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f);
public Vector2 customPivot = new Vector2(0.5f, 0.5f);
public Vector2 size = new Vector2(100f, 30f);
public Vector2 anchoredPosition = Vector2.zero;
[Header("Text Settings")]
public bool applyFontSize = false;
public float fontSize = 20f;
public bool applyAlignment = false;
public TextAlignmentOptions alignment = TextAlignmentOptions.Center;
public bool applyColor = false;
public Color color = Color.white;
public bool applyRaycastTarget = true;
public bool raycastTarget = false;
public bool resetScaleToOne = true;
[Header("Outline / Shadow")]
public bool applyOutline = false;
public bool addOutlineIfMissing = true;
public Color outlineColor = new Color(0f, 0.75f, 1f, 0.8f);
public Vector2 outlineEffectDistance = new Vector2(1f, -1f);
public bool outlineUseGraphicAlpha = true;
public bool applyShadow = false;
public bool addShadowIfMissing = true;
public Color shadowColor = new Color(0f, 0f, 0f, 0.65f);
public Vector2 shadowEffectDistance = new Vector2(2f, -2f);
public bool shadowUseGraphicAlpha = true;
}
[Serializable]
public class InventoryControlledImageTarget
{
[Header("Target")]
public string name;
public Image image;
[Header("Rect Layout")]
public bool applyRect = false;
public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter;
public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f);
public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f);
public Vector2 customPivot = new Vector2(0.5f, 0.5f);
public Vector2 size = new Vector2(64f, 64f);
public Vector2 anchoredPosition = Vector2.zero;
[Header("Image Settings")]
public bool applyColor = false;
public Color color = Color.white;
public bool applySprite = false;
public Sprite sprite;
public bool applyRaycastTarget = true;
public bool raycastTarget = false;
public bool resetScaleToOne = true;
[Header("Outline / Shadow")]
public bool applyOutline = false;
public bool addOutlineIfMissing = true;
public Color outlineColor = new Color(0f, 0.75f, 1f, 0.8f);
public Vector2 outlineEffectDistance = new Vector2(1f, -1f);
public bool outlineUseGraphicAlpha = true;
public bool applyShadow = false;
public bool addShadowIfMissing = true;
public Color shadowColor = new Color(0f, 0f, 0f, 0.55f);
public Vector2 shadowEffectDistance = new Vector2(2f, -2f);
public bool shadowUseGraphicAlpha = true;
}
[Serializable]
public class InventoryControlledButtonTarget
{
[Header("Target")]
public string name;
public Button button;
[Header("Rect Layout")]
public bool applyRect = false;
public InventoryUIAnchorPreset anchorPreset = InventoryUIAnchorPreset.MiddleCenter;
public Vector2 customAnchorMin = new Vector2(0.5f, 0.5f);
public Vector2 customAnchorMax = new Vector2(0.5f, 0.5f);
public Vector2 customPivot = new Vector2(0.5f, 0.5f);
public Vector2 size = new Vector2(100f, 35f);
public Vector2 anchoredPosition = Vector2.zero;
[Header("Button Settings")]
public bool applyInteractable = false;
public bool interactable = true;
public bool applyImageRaycastTarget = true;
public bool imageRaycastTarget = true;
public bool disableChildTextRaycasts = true;
public bool resetScaleToOne = true;
[Header("Button Colors")]
public bool applyButtonColors = false;
public Color normalColor = new Color(0.07f, 0.18f, 0.30f, 0.90f);
public Color highlightedColor = new Color(0.12f, 0.42f, 0.70f, 1f);
public Color pressedColor = new Color(0.04f, 0.12f, 0.20f, 1f);
public Color selectedColor = new Color(0.15f, 0.50f, 0.85f, 1f);
public Color disabledColor = new Color(0.20f, 0.20f, 0.20f, 0.45f);
public float colorMultiplier = 1f;
public float fadeDuration = 0.1f;
[Header("Outline / Shadow")]
public bool applyOutline = false;
public bool addOutlineIfMissing = true;
public Color outlineColor = new Color(0f, 0.75f, 1f, 0.8f);
public Vector2 outlineEffectDistance = new Vector2(1f, -1f);
public bool outlineUseGraphicAlpha = true;
public bool applyShadow = false;
public bool addShadowIfMissing = true;
public Color shadowColor = new Color(0f, 0f, 0f, 0.55f);
public Vector2 shadowEffectDistance = new Vector2(2f, -2f);
public bool shadowUseGraphicAlpha = true;
}
[Serializable]
public class InventoryLayoutPadding
{
public int left;
public int right;
public int top;
public int bottom;
public InventoryLayoutPadding() { }
public InventoryLayoutPadding(int left, int right, int top, int bottom)
{
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
}
public RectOffset ToRectOffset()
{
return new RectOffset(left, right, top, bottom);
}
}
[Serializable]
public class InventoryControlledGridLayoutTarget
{
[Header("Target")]
public string name;
public GridLayoutGroup grid;
[Header("Grid Settings")]
public bool applyGrid = true;
public Vector2 cellSize = new Vector2(100f, 100f);
public Vector2 spacing = Vector2.zero;
public TextAnchor childAlignment = TextAnchor.MiddleCenter;
public GridLayoutGroup.Constraint constraint = GridLayoutGroup.Constraint.FixedColumnCount;
public int constraintCount = 5;
public InventoryLayoutPadding padding;
}
/// <summary>
/// InventoryRoot 아래 UI를 한 곳에서 정리하는 컨트롤러입니다.
/// - 기본 인벤토리 패널들의 위치/크기/배치를 제어합니다.
/// - Inspector에 직접 연결한 Rect/Text/Image/Button/Grid 대상도 제어합니다.
/// - 없는 CanvasGroup, Image, GridLayoutGroup, AudioSource 등을 자동으로 붙일 수 있습니다.
/// - Text 내용 자체는 바꾸지 않습니다. 내용 변경은 InventoryUI가 담당하게 두는 것을 권장합니다.
/// </summary>
[ExecuteAlways]
[DisallowMultipleComponent]
public class InventoryUILayoutController : MonoBehaviour
{
[Header("Root / Canvas")]
[SerializeField] private bool configureRootCanvas = true;
[SerializeField] private Vector2 rootCanvasSize = new Vector2(800f, 500f);
[SerializeField] private bool applyRootWorldScale = false;
[SerializeField] private Vector3 rootWorldScale = new Vector3(0.0015f, 0.0015f, 0.0015f);
[SerializeField] private bool addGraphicRaycasterIfMissing = true;
[SerializeField] private bool addTrackedDeviceGraphicRaycasterIfAvailable = true;
[Header("Known UI References")]
[SerializeField] private RectTransform inventoryPanel;
[SerializeField] private RectTransform titleText;
[SerializeField] private RectTransform closeButton;
[SerializeField] private RectTransform categoryButtons;
[SerializeField] private RectTransform memoryProgressArea;
[SerializeField] private RectTransform slotContainer;
[SerializeField] private RectTransform recentLogArea;
[SerializeField] private RectTransform debugPanel;
[SerializeField] private RectTransform quickUseButtons;
[Header("Popup / Floating Panels")]
[SerializeField] private RectTransform tooltipPanel;
[SerializeField] private RectTransform acquisitionPopupPanel;
[SerializeField] private RectTransform messagePanel;
[SerializeField] private RectTransform confirmationPanel;
[SerializeField] private RectTransform detailPanel;
[Header("Inventory Scripts")]
[SerializeField] private InventoryUI inventoryUI;
[SerializeField] private InventorySlotUI[] slots;
[Header("Auto Find Names")]
[SerializeField] private string inventoryPanelName = "InventoryPanel";
[SerializeField] private string titleTextName = "TitleText";
[SerializeField] private string closeButtonName = "CloseButton";
[SerializeField] private string categoryButtonsName = "CategoryButtons";
[SerializeField] private string memoryProgressAreaName = "MemoryProgressArea";
[SerializeField] private string slotContainerName = "SlotContainer";
[SerializeField] private string recentLogAreaName = "RecentLogArea";
[SerializeField] private string debugPanelName = "DebugPanel";
[SerializeField] private string quickUseButtonsName = "QuickUseButtons";
[SerializeField] private string tooltipPanelName = "TooltipPanel";
[SerializeField] private string acquisitionPopupPanelName = "AcquisitionPopupPanel";
[SerializeField] private string messagePanelName = "MessagePanel";
[SerializeField] private string confirmationPanelName = "ConfirmationPanel";
[SerializeField] private string detailPanelName = "DetailPanel";
[Header("Apply Settings")]
[SerializeField] private bool autoFindReferences = true;
[SerializeField] private bool autoAddMissingComponents = true;
[SerializeField] private bool applyKnownLayout = true;
[SerializeField] private bool applyCustomControlledTargets = true;
[SerializeField] private bool resetChildScaleToOne = true;
[SerializeField] private bool applyLayoutOnAwake = true;
[SerializeField] private bool applyModeOnAwake = true;
[Tooltip("InventoryUI.Start()의 visibleOnStart 처리 이후에 모드를 한 번 더 적용해 패널 ON/OFF 충돌을 줄입니다.")]
[SerializeField] private bool applyModeAfterOneFrame = true;
[SerializeField] private bool applyInEditor = true;
[SerializeField] private InventoryRootUIMode initialMode = InventoryRootUIMode.TestMode;
[Header("Known Panel Layout")]
[SerializeField] private Vector2 inventoryPanelSize = new Vector2(760f, 460f);
[SerializeField] private Vector2 inventoryPanelPos = Vector2.zero;
[SerializeField] private Vector2 titleTextSize = new Vector2(360f, 50f);
[SerializeField] private Vector2 titleTextPos = new Vector2(0f, -18f);
[SerializeField] private Vector2 closeButtonSize = new Vector2(70f, 38f);
[SerializeField] private Vector2 closeButtonPos = new Vector2(-18f, -18f);
[SerializeField] private Vector2 categoryButtonsSize = new Vector2(620f, 45f);
[SerializeField] private Vector2 categoryButtonsPos = new Vector2(0f, -75f);
[SerializeField] private Vector2 memoryProgressAreaSize = new Vector2(620f, 55f);
[SerializeField] private Vector2 memoryProgressAreaPos = new Vector2(0f, -125f);
[SerializeField] private Vector2 slotContainerSize = new Vector2(500f, 145f);
[SerializeField] private Vector2 slotContainerPos = new Vector2(-80f, 20f);
[SerializeField] private Vector2 recentLogSize = new Vector2(340f, 110f);
[SerializeField] private Vector2 recentLogPos = new Vector2(25f, 25f);
[SerializeField] private Vector2 debugPanelSize = new Vector2(300f, 110f);
[SerializeField] private Vector2 debugPanelPos = new Vector2(-25f, 25f);
[SerializeField] private Vector2 quickUseButtonsSize = new Vector2(130f, 160f);
[SerializeField] private Vector2 quickUseButtonsPos = new Vector2(-20f, -35f);
[Header("Known Popup / Detail Layout")]
[SerializeField] private Vector2 tooltipPanelSize = new Vector2(520f, 120f);
[SerializeField] private Vector2 tooltipPanelPos = new Vector2(0f, -15f);
[SerializeField] private Vector2 acquisitionPopupSize = new Vector2(360f, 70f);
[SerializeField] private Vector2 acquisitionPopupPos = new Vector2(0f, 40f);
[SerializeField] private Vector2 messagePanelSize = new Vector2(460f, 60f);
[SerializeField] private Vector2 messagePanelPos = new Vector2(0f, -85f);
[SerializeField] private Vector2 confirmationPanelSize = new Vector2(420f, 220f);
[SerializeField] private Vector2 confirmationPanelPos = Vector2.zero;
[SerializeField] private Vector2 detailPanelSize = new Vector2(260f, 340f);
[SerializeField] private Vector2 detailPanelPos = new Vector2(-25f, 0f);
[Header("Known LayoutGroup Defaults")]
[SerializeField] private Vector2 slotCellSize = new Vector2(85f, 120f);
[SerializeField] private Vector2 slotSpacing = new Vector2(8f, 0f);
[SerializeField] private int slotColumnCount = 5;
[SerializeField] private Vector2 debugButtonCellSize = new Vector2(85f, 28f);
[SerializeField] private Vector2 debugButtonSpacing = new Vector2(5f, 5f);
[SerializeField] private int debugButtonColumnCount = 3;
[SerializeField] private Vector2 quickUseButtonCellSize = new Vector2(115f, 26f);
[SerializeField] private Vector2 quickUseButtonSpacing = new Vector2(0f, 5f);
[Header("Known Slot Child Layout")]
[SerializeField] private bool arrangeSlotChildren = true;
[SerializeField] private Vector2 slotIconSize = new Vector2(64f, 64f);
[SerializeField] private Vector2 slotIconPos = new Vector2(0f, 18f);
[SerializeField] private Vector2 slotCountTextSize = new Vector2(100f, 28f);
[SerializeField] private Vector2 slotCountTextPos = new Vector2(0f, 8f);
[SerializeField] private Vector2 slotNewBadgeSize = new Vector2(52f, 24f);
[SerializeField] private Vector2 slotNewBadgePos = new Vector2(-4f, -4f);
[Header("Mode Active States")]
[SerializeField] private bool testShowInventoryPanel = true;
[SerializeField] private bool testShowDebugPanel = true;
[SerializeField] private bool testShowRecentLogArea = true;
[SerializeField] private bool testShowCategoryButtons = true;
[SerializeField] private bool testShowMemoryProgressArea = true;
[SerializeField] private bool testShowQuickUseButtons = false;
[SerializeField] private bool playShowInventoryPanel = false;
[SerializeField] private bool playShowDebugPanel = false;
[SerializeField] private bool playShowRecentLogArea = true;
[SerializeField] private bool playShowCategoryButtons = true;
[SerializeField] private bool playShowMemoryProgressArea = true;
[SerializeField] private bool playShowQuickUseButtons = false;
[SerializeField] private bool buildShowInventoryPanel = false;
[SerializeField] private bool buildShowDebugPanel = false;
[SerializeField] private bool buildShowRecentLogArea = true;
[SerializeField] private bool buildShowCategoryButtons = true;
[SerializeField] private bool buildShowMemoryProgressArea = true;
[SerializeField] private bool buildShowQuickUseButtons = false;
[Header("Custom Inspector-Controlled Targets")]
[Tooltip("패널, 영역, 빈 오브젝트 등 RectTransform 기준으로 제어할 대상입니다.")]
[SerializeField] private List<InventoryControlledRectTarget> controlledRects = new List<InventoryControlledRectTarget>();
[Tooltip("패널 자식 TMP_Text의 위치, 크기, 폰트 크기, 정렬, Raycast를 제어합니다. Text 내용은 바꾸지 않습니다.")]
[SerializeField] private List<InventoryControlledTextTarget> controlledTexts = new List<InventoryControlledTextTarget>();
[Tooltip("패널 자식 Image의 위치, 크기, 색상, Raycast를 제어합니다. Sprite는 Apply Sprite를 켰을 때만 바꿉니다.")]
[SerializeField] private List<InventoryControlledImageTarget> controlledImages = new List<InventoryControlledImageTarget>();
[Tooltip("패널 자식 Button의 위치, 크기, Interactable, Raycast를 제어합니다.")]
[SerializeField] private List<InventoryControlledButtonTarget> controlledButtons = new List<InventoryControlledButtonTarget>();
[Tooltip("SlotContainer, DebugPanel 같은 GridLayoutGroup의 Cell Size, Spacing, Count를 제어합니다.")]
[SerializeField] private List<InventoryControlledGridLayoutTarget> controlledGridLayouts = new List<InventoryControlledGridLayoutTarget>();
[Header("Basic Visual Theme")]
[SerializeField] private bool applyBasicVisualTheme = true;
[SerializeField] private bool styleKnownPanels = true;
[SerializeField] private bool styleKnownButtons = true;
[SerializeField] private bool styleKnownTexts = true;
[SerializeField] private bool styleKnownSlots = true;
[SerializeField] private bool addOutlineToKnownGraphics = true;
[SerializeField] private bool addShadowToKnownTexts = true;
[Header("Theme Colors - Ocean VR")]
[SerializeField] private Color mainPanelColor = new Color(0.03f, 0.08f, 0.15f, 0.88f);
[SerializeField] private Color subPanelColor = new Color(0.04f, 0.14f, 0.24f, 0.82f);
[SerializeField] private Color popupPanelColor = new Color(0.02f, 0.10f, 0.18f, 0.92f);
[SerializeField] private Color slotColor = new Color(0.05f, 0.16f, 0.26f, 0.90f);
[SerializeField] private Color slotImportantGlowColor = new Color(1.0f, 0.72f, 0.20f, 0.38f);
[SerializeField] private Color filteredOverlayColor = new Color(0f, 0f, 0f, 0.55f);
[SerializeField] private Color newBadgeColor = new Color(1.0f, 0.82f, 0.12f, 0.95f);
[SerializeField] private Color mainTextColor = new Color(0.95f, 0.92f, 0.82f, 1f);
[SerializeField] private Color accentTextColor = new Color(1.0f, 0.78f, 0.35f, 1f);
[SerializeField] private Color mutedTextColor = new Color(0.70f, 0.86f, 0.95f, 1f);
[SerializeField] private Color outlineColor = new Color(0.18f, 0.76f, 1f, 0.65f);
[SerializeField] private Color goldOutlineColor = new Color(1.0f, 0.72f, 0.20f, 0.80f);
[SerializeField] private Color shadowColor = new Color(0f, 0f, 0f, 0.62f);
[Header("Theme Button Colors")]
[SerializeField] private Color buttonNormalColor = new Color(0.06f, 0.20f, 0.33f, 0.95f);
[SerializeField] private Color buttonHighlightedColor = new Color(0.12f, 0.44f, 0.74f, 1f);
[SerializeField] private Color buttonPressedColor = new Color(0.03f, 0.12f, 0.22f, 1f);
[SerializeField] private Color buttonSelectedColor = new Color(0.16f, 0.50f, 0.82f, 1f);
[SerializeField] private Color buttonDisabledColor = new Color(0.20f, 0.22f, 0.24f, 0.45f);
[SerializeField] private Color debugButtonColor = new Color(0.07f, 0.22f, 0.32f, 0.95f);
[SerializeField] private Color dangerButtonColor = new Color(0.45f, 0.10f, 0.12f, 0.95f);
[Header("Popup Groups")]
[SerializeField] private GameObject[] extraPopupPanelsToHide;
[Header("Raycast / Component Fix")]
[SerializeField] private bool fixRaycastTargets = true;
[SerializeField] private bool fixCanvasGroups = true;
[SerializeField] private bool disableTextRaycasts = true;
[SerializeField] private bool disableNonInteractiveImageRaycasts = true;
[SerializeField] private bool setSliderInteractableFalse = true;
[SerializeField] private bool setSlotHoverSafeValues = true;
[SerializeField] private bool slotUseHoverScale = true;
[SerializeField] private float slotHoverScale = 1.03f;
[SerializeField] private bool slotBringToFrontOnHover = false;
private bool isApplying;
private void Reset()
{
AutoFindReferences();
}
private void Awake()
{
if (!Application.isPlaying)
return;
if (autoFindReferences)
AutoFindReferences();
if (autoAddMissingComponents)
AutoSetupMissingComponents();
if (applyLayoutOnAwake)
ApplyFullInventoryRootLayout();
if (applyModeOnAwake)
ApplyInitialMode();
}
private System.Collections.IEnumerator Start()
{
if (!Application.isPlaying || !applyModeAfterOneFrame)
yield break;
// InventoryUI.Start()의 visibleOnStart 적용이 끝난 뒤 모드를 다시 적용합니다.
yield return null;
ApplyInitialMode();
}
#if UNITY_EDITOR
private void OnValidate()
{
if (!applyInEditor)
return;
EditorApplication.delayCall -= DelayedApplyInEditor;
EditorApplication.delayCall += DelayedApplyInEditor;
}
private void DelayedApplyInEditor()
{
if (this == null || isApplying)
return;
ApplyFullInventoryRootLayout();
}
#endif
[ContextMenu("Inventory UI/Apply Full Inventory Root Layout")]
public void ApplyFullInventoryRootLayout()
{
if (isApplying)
return;
isApplying = true;
if (autoFindReferences)
AutoFindReferences();
if (autoAddMissingComponents)
AutoSetupMissingComponents();
if (configureRootCanvas)
ConfigureRootCanvas();
if (applyKnownLayout)
ApplyKnownLayout();
if (arrangeSlotChildren)
ArrangeSlotChildren();
if (applyCustomControlledTargets)
ApplyCustomTargets();
if (applyBasicVisualTheme)
ApplyBasicVisualTheme();
if (fixRaycastTargets)
FixAllRaycastTargets();
isApplying = false;
}
[ContextMenu("Inventory UI/Apply Custom Inspector Targets Only")]
public void ApplyCustomTargets()
{
ApplyControlledRectTargets();
ApplyControlledTextTargets();
ApplyControlledImageTargets();
ApplyControlledButtonTargets();
ApplyControlledGridLayoutTargets();
}
[ContextMenu("Inventory UI/Auto Find References")]
public void AutoFindReferences()
{
inventoryPanel = FindRectIfNull(inventoryPanel, inventoryPanelName);
titleText = FindRectIfNull(titleText, titleTextName);
closeButton = FindRectIfNull(closeButton, closeButtonName);
categoryButtons = FindRectIfNull(categoryButtons, categoryButtonsName);
memoryProgressArea = FindRectIfNull(memoryProgressArea, memoryProgressAreaName);
slotContainer = FindRectIfNull(slotContainer, slotContainerName);
recentLogArea = FindRectIfNull(recentLogArea, recentLogAreaName);
debugPanel = FindRectIfNull(debugPanel, debugPanelName);
quickUseButtons = FindRectIfNull(quickUseButtons, quickUseButtonsName);
tooltipPanel = FindRectIfNull(tooltipPanel, tooltipPanelName);
acquisitionPopupPanel = FindRectIfNull(acquisitionPopupPanel, acquisitionPopupPanelName);
messagePanel = FindRectIfNull(messagePanel, messagePanelName);
confirmationPanel = FindRectIfNull(confirmationPanel, confirmationPanelName);
detailPanel = FindRectIfNull(detailPanel, detailPanelName);
if (inventoryUI == null)
inventoryUI = GetComponent<InventoryUI>();
if (slots == null || slots.Length == 0)
slots = GetComponentsInChildren<InventorySlotUI>(true);
}
[ContextMenu("Inventory UI/Auto Setup Missing Components")]
public void AutoSetupMissingComponents()
{
Canvas canvas = GetOrAdd<Canvas>(gameObject);
canvas.renderMode = RenderMode.WorldSpace;
GetOrAdd<CanvasScaler>(gameObject);
if (addGraphicRaycasterIfMissing)
GetOrAdd<GraphicRaycaster>(gameObject);
if (addTrackedDeviceGraphicRaycasterIfAvailable)
TryAddComponentByTypeName(gameObject, "UnityEngine.XR.Interaction.Toolkit.UI.TrackedDeviceGraphicRaycaster");
GetOrAdd<AudioSource>(gameObject).playOnAwake = false;
AddCanvasGroupIfNeeded(tooltipPanel, false, false);
AddCanvasGroupIfNeeded(acquisitionPopupPanel, false, false);
AddCanvasGroupIfNeeded(messagePanel, false, false);
AddCanvasGroupIfNeeded(confirmationPanel, true, true);
AddCanvasGroupIfNeeded(detailPanel, true, true);
if (slotContainer != null)
GetOrAdd<GridLayoutGroup>(slotContainer.gameObject);
if (debugPanel != null)
GetOrAdd<GridLayoutGroup>(debugPanel.gameObject);
if (quickUseButtons != null && quickUseButtons.GetComponent<LayoutGroup>() == null)
quickUseButtons.gameObject.AddComponent<VerticalLayoutGroup>();
}
[ContextMenu("Inventory UI/Add Default Known Targets To Inspector Lists")]
public void AddDefaultKnownTargetsToInspectorLists()
{
AutoFindReferences();
AddControlledRect("InventoryPanel", inventoryPanel, null, InventoryUIAnchorPreset.MiddleCenter, inventoryPanelSize, inventoryPanelPos, false, true, true, true, false);
AddControlledRect("TitleText", titleText, null, InventoryUIAnchorPreset.TopCenter, titleTextSize, titleTextPos, false, true, true, true, false);
AddControlledRect("CloseButton", closeButton, null, InventoryUIAnchorPreset.TopRight, closeButtonSize, closeButtonPos, false, true, true, true, false);
AddControlledRect("CategoryButtons", categoryButtons, null, InventoryUIAnchorPreset.TopCenter, categoryButtonsSize, categoryButtonsPos, true, testShowCategoryButtons, playShowCategoryButtons, buildShowCategoryButtons, false);
AddControlledRect("MemoryProgressArea", memoryProgressArea, null, InventoryUIAnchorPreset.TopCenter, memoryProgressAreaSize, memoryProgressAreaPos, true, testShowMemoryProgressArea, playShowMemoryProgressArea, buildShowMemoryProgressArea, false);
AddControlledRect("SlotContainer", slotContainer, null, InventoryUIAnchorPreset.MiddleCenter, slotContainerSize, slotContainerPos, false, true, true, true, false);
AddControlledRect("RecentLogArea", recentLogArea, null, InventoryUIAnchorPreset.BottomLeft, recentLogSize, recentLogPos, true, testShowRecentLogArea, playShowRecentLogArea, buildShowRecentLogArea, false);
AddControlledRect("DebugPanel", debugPanel, null, InventoryUIAnchorPreset.BottomRight, debugPanelSize, debugPanelPos, true, testShowDebugPanel, playShowDebugPanel, buildShowDebugPanel, false);
AddControlledRect("QuickUseButtons", quickUseButtons, null, InventoryUIAnchorPreset.MiddleRight, quickUseButtonsSize, quickUseButtonsPos, true, testShowQuickUseButtons, playShowQuickUseButtons, buildShowQuickUseButtons, false);
AddControlledRect("TooltipPanel", tooltipPanel, null, InventoryUIAnchorPreset.BottomCenter, tooltipPanelSize, tooltipPanelPos, true, false, false, false, false);
AddControlledRect("AcquisitionPopupPanel", acquisitionPopupPanel, null, InventoryUIAnchorPreset.TopCenter, acquisitionPopupSize, acquisitionPopupPos, true, false, false, false, false);
AddControlledRect("MessagePanel", messagePanel, null, InventoryUIAnchorPreset.BottomCenter, messagePanelSize, messagePanelPos, true, false, false, false, false);
AddControlledRect("ConfirmationPanel", confirmationPanel, null, InventoryUIAnchorPreset.MiddleCenter, confirmationPanelSize, confirmationPanelPos, true, false, false, false, false);
AddControlledRect("DetailPanel", detailPanel, null, InventoryUIAnchorPreset.MiddleRight, detailPanelSize, detailPanelPos, true, false, false, false, false);
AddGrid("SlotContainer Grid", slotContainer != null ? slotContainer.GetComponent<GridLayoutGroup>() : null, slotCellSize, slotSpacing, GridLayoutGroup.Constraint.FixedColumnCount, slotColumnCount);
AddGrid("DebugPanel Grid", debugPanel != null ? debugPanel.GetComponent<GridLayoutGroup>() : null, debugButtonCellSize, debugButtonSpacing, GridLayoutGroup.Constraint.FixedColumnCount, debugButtonColumnCount);
AddChildrenOfPanelsToInspectorLists();
}
[ContextMenu("Inventory UI/Add Current Panel Children Texts Images Buttons")]
public void AddChildrenOfPanelsToInspectorLists()
{
RectTransform[] panels = new[] { inventoryPanel, tooltipPanel, acquisitionPopupPanel, messagePanel, confirmationPanel, detailPanel, recentLogArea, debugPanel };
foreach (RectTransform panel in panels)
{
if (panel == null)
continue;
foreach (TMP_Text text in panel.GetComponentsInChildren<TMP_Text>(true))
AddText(text.name, text);
foreach (Image image in panel.GetComponentsInChildren<Image>(true))
AddImage(image.name, image);
foreach (Button button in panel.GetComponentsInChildren<Button>(true))
AddButton(button.name, button);
}
}
[ContextMenu("Inventory UI/Clear Custom Inspector Lists")]
public void ClearCustomInspectorLists()
{
controlledRects.Clear();
controlledTexts.Clear();
controlledImages.Clear();
controlledButtons.Clear();
controlledGridLayouts.Clear();
}
/// <summary>
/// Scene View/RectTransform에서 직접 옮긴 현재 위치와 크기를 컨트롤러 값으로 저장합니다.
/// 이후 Apply Full Inventory Root Layout을 실행해도 캡처한 위치가 유지됩니다.
/// </summary>
[ContextMenu("Inventory UI/Capture Current Layout")]
public void CaptureCurrentLayout()
{
if (autoFindReferences)
AutoFindReferences();
CaptureKnownLayoutValues();
CaptureCustomTargetLayoutValues();
CaptureKnownGridLayoutValues();
MarkControllerDirty();
Debug.Log("[InventoryUILayoutController] 현재 UI 위치/크기/배치 값을 컨트롤러에 저장했습니다.", this);
}
[ContextMenu("Inventory UI/Capture Current Known Layout")]
public void CaptureCurrentKnownLayout()
{
if (autoFindReferences)
AutoFindReferences();
CaptureKnownLayoutValues();
CaptureKnownGridLayoutValues();
MarkControllerDirty();
Debug.Log("[InventoryUILayoutController] Known UI References의 현재 위치/크기를 저장했습니다.", this);
}
[ContextMenu("Inventory UI/Capture Current Custom Targets")]
public void CaptureCurrentCustomTargets()
{
CaptureCustomTargetLayoutValues();
MarkControllerDirty();
Debug.Log("[InventoryUILayoutController] Custom Inspector-Controlled Targets의 현재 위치/크기를 저장했습니다.", this);
}
[ContextMenu("Inventory UI/Capture Known Layout Into Controlled Rects")]
public void CaptureKnownLayoutIntoControlledRects()
{
if (autoFindReferences)
AutoFindReferences();
CaptureOrAddControlledRect("InventoryPanel", inventoryPanel, false, true, true, true, false);
CaptureOrAddControlledRect("TitleText", titleText, false, true, true, true, false);
CaptureOrAddControlledRect("CloseButton", closeButton, false, true, true, true, false);
CaptureOrAddControlledRect("CategoryButtons", categoryButtons, true, testShowCategoryButtons, playShowCategoryButtons, buildShowCategoryButtons, false);
CaptureOrAddControlledRect("MemoryProgressArea", memoryProgressArea, true, testShowMemoryProgressArea, playShowMemoryProgressArea, buildShowMemoryProgressArea, false);
CaptureOrAddControlledRect("SlotContainer", slotContainer, false, true, true, true, false);
CaptureOrAddControlledRect("RecentLogArea", recentLogArea, true, testShowRecentLogArea, playShowRecentLogArea, buildShowRecentLogArea, false);
CaptureOrAddControlledRect("DebugPanel", debugPanel, true, testShowDebugPanel, playShowDebugPanel, buildShowDebugPanel, false);
CaptureOrAddControlledRect("QuickUseButtons", quickUseButtons, true, testShowQuickUseButtons, playShowQuickUseButtons, buildShowQuickUseButtons, false);
CaptureOrAddControlledRect("TooltipPanel", tooltipPanel, true, false, false, false, false);
CaptureOrAddControlledRect("AcquisitionPopupPanel", acquisitionPopupPanel, true, false, false, false, false);
CaptureOrAddControlledRect("MessagePanel", messagePanel, true, false, false, false, false);
CaptureOrAddControlledRect("ConfirmationPanel", confirmationPanel, true, false, false, false, false);
CaptureOrAddControlledRect("DetailPanel", detailPanel, true, false, false, false, false);
MarkControllerDirty();
Debug.Log("[InventoryUILayoutController] 현재 Known UI 위치를 Controlled Rects에 저장했습니다.", this);
}
private void CaptureKnownLayoutValues()
{
RectTransform rootRect = GetComponent<RectTransform>();
if (rootRect != null)
{
rootCanvasSize = rootRect.sizeDelta;
rootWorldScale = rootRect.localScale;
}
CaptureKnownRect(inventoryPanel, ref inventoryPanelSize, ref inventoryPanelPos);
CaptureKnownRect(titleText, ref titleTextSize, ref titleTextPos);
CaptureKnownRect(closeButton, ref closeButtonSize, ref closeButtonPos);
CaptureKnownRect(categoryButtons, ref categoryButtonsSize, ref categoryButtonsPos);
CaptureKnownRect(memoryProgressArea, ref memoryProgressAreaSize, ref memoryProgressAreaPos);
CaptureKnownRect(slotContainer, ref slotContainerSize, ref slotContainerPos);
CaptureKnownRect(recentLogArea, ref recentLogSize, ref recentLogPos);
CaptureKnownRect(debugPanel, ref debugPanelSize, ref debugPanelPos);
CaptureKnownRect(quickUseButtons, ref quickUseButtonsSize, ref quickUseButtonsPos);
CaptureKnownRect(tooltipPanel, ref tooltipPanelSize, ref tooltipPanelPos);
CaptureKnownRect(acquisitionPopupPanel, ref acquisitionPopupSize, ref acquisitionPopupPos);
CaptureKnownRect(messagePanel, ref messagePanelSize, ref messagePanelPos);
CaptureKnownRect(confirmationPanel, ref confirmationPanelSize, ref confirmationPanelPos);
CaptureKnownRect(detailPanel, ref detailPanelSize, ref detailPanelPos);
}
private void CaptureKnownRect(RectTransform rect, ref Vector2 sizeField, ref Vector2 posField)
{
if (rect == null)
return;
sizeField = rect.sizeDelta;
posField = rect.anchoredPosition;
}
private void CaptureCustomTargetLayoutValues()
{
if (controlledRects != null)
{
foreach (InventoryControlledRectTarget target in controlledRects)
{
if (target == null || target.rectTransform == null)
continue;
CaptureRectData(target.rectTransform, target);
}
}
if (controlledTexts != null)
{
foreach (InventoryControlledTextTarget target in controlledTexts)
{
if (target == null || target.text == null)
continue;
CaptureRectData(target.text.rectTransform, target);
}
}
if (controlledImages != null)
{
foreach (InventoryControlledImageTarget target in controlledImages)
{
if (target == null || target.image == null)
continue;
CaptureRectData(target.image.rectTransform, target);
}
}
if (controlledButtons != null)
{
foreach (InventoryControlledButtonTarget target in controlledButtons)
{
if (target == null || target.button == null)
continue;
CaptureRectData(target.button.GetComponent<RectTransform>(), target);
}
}
if (controlledGridLayouts != null)
{
foreach (InventoryControlledGridLayoutTarget target in controlledGridLayouts)
{
if (target == null || target.grid == null)
continue;
CaptureGridData(target.grid, target);
}
}
}
private void CaptureKnownGridLayoutValues()
{
GridLayoutGroup slotGrid = slotContainer != null ? slotContainer.GetComponent<GridLayoutGroup>() : null;
if (slotGrid != null)
{
slotCellSize = slotGrid.cellSize;
slotSpacing = slotGrid.spacing;
slotColumnCount = Mathf.Max(1, slotGrid.constraintCount);
}
GridLayoutGroup debugGrid = debugPanel != null ? debugPanel.GetComponent<GridLayoutGroup>() : null;
if (debugGrid != null)
{
debugButtonCellSize = debugGrid.cellSize;
debugButtonSpacing = debugGrid.spacing;
debugButtonColumnCount = Mathf.Max(1, debugGrid.constraintCount);
}
GridLayoutGroup quickUseGrid = quickUseButtons != null ? quickUseButtons.GetComponent<GridLayoutGroup>() : null;
if (quickUseGrid != null)
{
quickUseButtonCellSize = quickUseGrid.cellSize;
quickUseButtonSpacing = quickUseGrid.spacing;
}
else if (quickUseButtons != null)
{
VerticalLayoutGroup vertical = quickUseButtons.GetComponent<VerticalLayoutGroup>();
if (vertical != null)
quickUseButtonSpacing = new Vector2(quickUseButtonSpacing.x, vertical.spacing);
}
}
private void CaptureRectData(RectTransform rect, InventoryControlledRectTarget target)
{
if (rect == null || target == null)
return;
target.applyRect = true;
target.anchorPreset = DetectAnchorPreset(rect);
target.customAnchorMin = rect.anchorMin;
target.customAnchorMax = rect.anchorMax;
target.customPivot = rect.pivot;
target.size = rect.sizeDelta;
target.anchoredPosition = rect.anchoredPosition;
target.localScale = rect.localScale;
target.localEulerAngles = rect.localEulerAngles;
target.siblingIndex = rect.GetSiblingIndex();
}
private void CaptureRectData(RectTransform rect, InventoryControlledTextTarget target)
{
if (rect == null || target == null)
return;
target.applyRect = true;
target.anchorPreset = DetectAnchorPreset(rect);
target.customAnchorMin = rect.anchorMin;
target.customAnchorMax = rect.anchorMax;
target.customPivot = rect.pivot;
target.size = rect.sizeDelta;
target.anchoredPosition = rect.anchoredPosition;
}
private void CaptureRectData(RectTransform rect, InventoryControlledImageTarget target)
{
if (rect == null || target == null)
return;
target.applyRect = true;
target.anchorPreset = DetectAnchorPreset(rect);
target.customAnchorMin = rect.anchorMin;
target.customAnchorMax = rect.anchorMax;
target.customPivot = rect.pivot;
target.size = rect.sizeDelta;
target.anchoredPosition = rect.anchoredPosition;
}
private void CaptureRectData(RectTransform rect, InventoryControlledButtonTarget target)
{
if (rect == null || target == null)
return;
target.applyRect = true;
target.anchorPreset = DetectAnchorPreset(rect);
target.customAnchorMin = rect.anchorMin;
target.customAnchorMax = rect.anchorMax;
target.customPivot = rect.pivot;
target.size = rect.sizeDelta;
target.anchoredPosition = rect.anchoredPosition;
}
private void CaptureGridData(GridLayoutGroup grid, InventoryControlledGridLayoutTarget target)
{
if (grid == null || target == null)
return;
target.applyGrid = true;
target.cellSize = grid.cellSize;
target.spacing = grid.spacing;
target.childAlignment = grid.childAlignment;
target.constraint = grid.constraint;
target.constraintCount = Mathf.Max(1, grid.constraintCount);
target.padding = new InventoryLayoutPadding(grid.padding.left, grid.padding.right, grid.padding.top, grid.padding.bottom);
}
private void CaptureOrAddControlledRect(string targetName, RectTransform rect, bool controlActive, bool testActive, bool playActive, bool buildActive, bool hiddenActive)
{
if (rect == null)
return;
if (controlledRects == null)
controlledRects = new List<InventoryControlledRectTarget>();
InventoryControlledRectTarget target = FindControlledRect(rect);
if (target == null)
{
target = new InventoryControlledRectTarget();
controlledRects.Add(target);
}
target.name = targetName;
target.rectTransform = rect;
target.activeTarget = rect.gameObject;
target.controlActiveState = controlActive;
target.activeInTestMode = testActive;
target.activeInPlayMode = playActive;
target.activeInBuildMode = buildActive;
target.activeInHiddenMode = hiddenActive;
CaptureRectData(rect, target);
}
private InventoryControlledRectTarget FindControlledRect(RectTransform rect)
{
if (rect == null || controlledRects == null)
return null;
foreach (InventoryControlledRectTarget target in controlledRects)
{
if (target != null && target.rectTransform == rect)
return target;
}
return null;
}
private InventoryUIAnchorPreset DetectAnchorPreset(RectTransform rect)
{
if (rect == null)
return InventoryUIAnchorPreset.MiddleCenter;
if (Approximately(rect.anchorMin, Vector2.zero) && Approximately(rect.anchorMax, Vector2.one))
return InventoryUIAnchorPreset.StretchAll;
if (!Approximately(rect.anchorMin, rect.anchorMax))
return InventoryUIAnchorPreset.Custom;
Vector2 anchor = rect.anchorMin;
Vector2 pivot = rect.pivot;
if (Approximately(anchor, new Vector2(0.5f, 0.5f)) && Approximately(pivot, new Vector2(0.5f, 0.5f))) return InventoryUIAnchorPreset.MiddleCenter;
if (Approximately(anchor, new Vector2(0f, 1f)) && Approximately(pivot, new Vector2(0f, 1f))) return InventoryUIAnchorPreset.TopLeft;
if (Approximately(anchor, new Vector2(0.5f, 1f)) && Approximately(pivot, new Vector2(0.5f, 1f))) return InventoryUIAnchorPreset.TopCenter;
if (Approximately(anchor, new Vector2(1f, 1f)) && Approximately(pivot, new Vector2(1f, 1f))) return InventoryUIAnchorPreset.TopRight;
if (Approximately(anchor, new Vector2(0f, 0.5f)) && Approximately(pivot, new Vector2(0f, 0.5f))) return InventoryUIAnchorPreset.MiddleLeft;
if (Approximately(anchor, new Vector2(1f, 0.5f)) && Approximately(pivot, new Vector2(1f, 0.5f))) return InventoryUIAnchorPreset.MiddleRight;
if (Approximately(anchor, new Vector2(0f, 0f)) && Approximately(pivot, new Vector2(0f, 0f))) return InventoryUIAnchorPreset.BottomLeft;
if (Approximately(anchor, new Vector2(0.5f, 0f)) && Approximately(pivot, new Vector2(0.5f, 0f))) return InventoryUIAnchorPreset.BottomCenter;
if (Approximately(anchor, new Vector2(1f, 0f)) && Approximately(pivot, new Vector2(1f, 0f))) return InventoryUIAnchorPreset.BottomRight;
return InventoryUIAnchorPreset.Custom;
}
private bool Approximately(Vector2 a, Vector2 b)
{
return Mathf.Abs(a.x - b.x) <= 0.0001f && Mathf.Abs(a.y - b.y) <= 0.0001f;
}
private void MarkControllerDirty()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
EditorUtility.SetDirty(this);
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene);
}
#endif
}
private void ConfigureRootCanvas()
{
RectTransform rootRect = GetComponent<RectTransform>();
if (rootRect != null)
{
rootRect.sizeDelta = rootCanvasSize;
if (applyRootWorldScale)
rootRect.localScale = rootWorldScale;
}
Canvas canvas = GetComponent<Canvas>();
if (canvas != null)
canvas.renderMode = RenderMode.WorldSpace;
}
private void ApplyKnownLayout()
{
ApplyRect(inventoryPanel, InventoryUIAnchorPreset.MiddleCenter, inventoryPanelSize, inventoryPanelPos);
ApplyRect(titleText, InventoryUIAnchorPreset.TopCenter, titleTextSize, titleTextPos);
ApplyRect(closeButton, InventoryUIAnchorPreset.TopRight, closeButtonSize, closeButtonPos);
ApplyRect(categoryButtons, InventoryUIAnchorPreset.TopCenter, categoryButtonsSize, categoryButtonsPos);
ApplyRect(memoryProgressArea, InventoryUIAnchorPreset.TopCenter, memoryProgressAreaSize, memoryProgressAreaPos);
ApplyRect(slotContainer, InventoryUIAnchorPreset.MiddleCenter, slotContainerSize, slotContainerPos);
ApplyRect(recentLogArea, InventoryUIAnchorPreset.BottomLeft, recentLogSize, recentLogPos);
ApplyRect(debugPanel, InventoryUIAnchorPreset.BottomRight, debugPanelSize, debugPanelPos);
ApplyRect(quickUseButtons, InventoryUIAnchorPreset.MiddleRight, quickUseButtonsSize, quickUseButtonsPos);
ApplyRect(tooltipPanel, InventoryUIAnchorPreset.BottomCenter, tooltipPanelSize, tooltipPanelPos);
ApplyRect(acquisitionPopupPanel, InventoryUIAnchorPreset.TopCenter, acquisitionPopupSize, acquisitionPopupPos);
ApplyRect(messagePanel, InventoryUIAnchorPreset.BottomCenter, messagePanelSize, messagePanelPos);
ApplyRect(confirmationPanel, InventoryUIAnchorPreset.MiddleCenter, confirmationPanelSize, confirmationPanelPos);
ApplyRect(detailPanel, InventoryUIAnchorPreset.MiddleRight, detailPanelSize, detailPanelPos);
ConfigureKnownLayoutGroups();
}
private void ConfigureKnownLayoutGroups()
{
if (slotContainer != null)
{
GridLayoutGroup grid = GetOrAdd<GridLayoutGroup>(slotContainer.gameObject);
ConfigureGrid(grid, slotCellSize, slotSpacing, GridLayoutGroup.Constraint.FixedColumnCount, slotColumnCount, TextAnchor.MiddleCenter, new InventoryLayoutPadding(0, 0, 0, 0));
}
if (debugPanel != null)
{
GridLayoutGroup grid = GetOrAdd<GridLayoutGroup>(debugPanel.gameObject);
ConfigureGrid(grid, debugButtonCellSize, debugButtonSpacing, GridLayoutGroup.Constraint.FixedColumnCount, debugButtonColumnCount, TextAnchor.MiddleCenter, new InventoryLayoutPadding(0, 0, 0, 0));
}
ConfigureQuickUseButtonsLayout();
}
private void ConfigureQuickUseButtonsLayout()
{
if (quickUseButtons == null)
return;
// Unity UI는 한 GameObject에 LayoutGroup을 하나만 붙일 수 있습니다.
// QuickUseButtons에 이미 GridLayoutGroup이 있으면 VerticalLayoutGroup을 추가하지 않고 GridLayoutGroup을 그대로 설정합니다.
VerticalLayoutGroup vertical = quickUseButtons.GetComponent<VerticalLayoutGroup>();
if (vertical != null)
{
vertical.childAlignment = TextAnchor.MiddleCenter;
vertical.spacing = quickUseButtonSpacing.y;
vertical.childControlWidth = true;
vertical.childControlHeight = true;
vertical.childForceExpandWidth = false;
vertical.childForceExpandHeight = false;
return;
}
GridLayoutGroup grid = quickUseButtons.GetComponent<GridLayoutGroup>();
if (grid != null)
{
ConfigureGrid(grid, quickUseButtonCellSize, quickUseButtonSpacing, GridLayoutGroup.Constraint.FixedColumnCount, 1, TextAnchor.MiddleCenter, new InventoryLayoutPadding(0, 0, 0, 0));
return;
}
LayoutGroup existingLayoutGroup = quickUseButtons.GetComponent<LayoutGroup>();
if (existingLayoutGroup != null)
{
Debug.LogWarning($"[InventoryUILayoutController] QuickUseButtons에 지원하지 않는 LayoutGroup({existingLayoutGroup.GetType().Name})이 이미 있습니다. 자동 배치를 건너뜁니다.", quickUseButtons);
return;
}
VerticalLayoutGroup added = quickUseButtons.gameObject.AddComponent<VerticalLayoutGroup>();
if (added == null)
return;
added.childAlignment = TextAnchor.MiddleCenter;
added.spacing = quickUseButtonSpacing.y;
added.childControlWidth = true;
added.childControlHeight = true;
added.childForceExpandWidth = false;
added.childForceExpandHeight = false;
}
private void ArrangeSlotChildren()
{
if (slots == null || slots.Length == 0)
slots = GetComponentsInChildren<InventorySlotUI>(true);
foreach (InventorySlotUI slot in slots)
{
if (slot == null)
continue;
RectTransform slotRect = slot.GetComponent<RectTransform>();
if (slotRect != null && resetChildScaleToOne)
slotRect.localScale = Vector3.one;
RectTransform icon = FindChildRect(slot.transform, "Icon");
RectTransform count = FindChildRect(slot.transform, "CountText");
RectTransform newBadge = FindChildRect(slot.transform, "NewBadge");
RectTransform importantGlow = FindChildRect(slot.transform, "ImportantGlow");
RectTransform filteredOverlay = FindChildRect(slot.transform, "FilteredOutOverlay");
ApplyRect(icon, InventoryUIAnchorPreset.MiddleCenter, slotIconSize, slotIconPos);
ApplyRect(count, InventoryUIAnchorPreset.BottomCenter, slotCountTextSize, slotCountTextPos);
ApplyRect(newBadge, InventoryUIAnchorPreset.TopRight, slotNewBadgeSize, slotNewBadgePos);
ApplyStretch(importantGlow, new Vector2(-4f, -4f), new Vector2(4f, 4f));
ApplyStretch(filteredOverlay, Vector2.zero, Vector2.zero);
}
}
private void ApplyControlledRectTargets()
{
if (controlledRects == null)
return;
foreach (InventoryControlledRectTarget target in controlledRects)
{
if (target == null)
continue;
RectTransform rect = target.rectTransform;
if (rect == null)
continue;
if (target.applyRect)
ApplyRect(rect, target.anchorPreset, target.size, target.anchoredPosition, target.customAnchorMin, target.customAnchorMax, target.customPivot);
if (target.resetScaleToOne)
rect.localScale = Vector3.one;
if (target.applyLocalScale)
rect.localScale = target.localScale;
if (target.applyLocalRotation)
rect.localEulerAngles = target.localEulerAngles;
if (target.applySiblingIndex)
rect.SetSiblingIndex(Mathf.Max(0, target.siblingIndex));
if (target.applyCanvasGroup)
{
CanvasGroup group = target.addCanvasGroupIfMissing ? GetOrAdd<CanvasGroup>(rect.gameObject) : rect.GetComponent<CanvasGroup>();
if (group != null)
{
group.alpha = target.canvasAlpha;
group.interactable = target.canvasInteractable;
group.blocksRaycasts = target.canvasBlocksRaycasts;
}
}
if (target.applySelfImageRaycast)
{
Image image = rect.GetComponent<Image>();
if (image != null)
image.raycastTarget = target.selfImageRaycastTarget;
}
if (target.applyChildImageRaycasts)
{
foreach (Image image in rect.GetComponentsInChildren<Image>(true))
{
if (target.preserveButtonImageRaycasts && image.GetComponentInParent<Button>() != null)
continue;
image.raycastTarget = target.childImageRaycastTarget;
}
}
if (target.applyChildTextRaycasts)
{
foreach (TMP_Text text in rect.GetComponentsInChildren<TMP_Text>(true))
text.raycastTarget = target.childTextRaycastTarget;
}
}
}
private void ApplyControlledTextTargets()
{
if (controlledTexts == null)
return;
foreach (InventoryControlledTextTarget target in controlledTexts)
{
if (target == null || target.text == null)
continue;
RectTransform rect = target.text.rectTransform;
if (target.applyRect)
ApplyRect(rect, target.anchorPreset, target.size, target.anchoredPosition, target.customAnchorMin, target.customAnchorMax, target.customPivot);
if (target.resetScaleToOne && rect != null)
rect.localScale = Vector3.one;
if (target.applyFontSize)
target.text.fontSize = target.fontSize;
if (target.applyAlignment)
target.text.alignment = target.alignment;
if (target.applyColor)
target.text.color = target.color;
if (target.applyRaycastTarget)
target.text.raycastTarget = target.raycastTarget;
if (target.applyOutline)
ApplyOutline(target.text.gameObject, target.outlineColor, target.outlineEffectDistance, target.outlineUseGraphicAlpha, target.addOutlineIfMissing);
if (target.applyShadow)
ApplyShadow(target.text.gameObject, target.shadowColor, target.shadowEffectDistance, target.shadowUseGraphicAlpha, target.addShadowIfMissing);
}
}
private void ApplyControlledImageTargets()
{
if (controlledImages == null)
return;
foreach (InventoryControlledImageTarget target in controlledImages)
{
if (target == null || target.image == null)
continue;
RectTransform rect = target.image.rectTransform;
if (target.applyRect)
ApplyRect(rect, target.anchorPreset, target.size, target.anchoredPosition, target.customAnchorMin, target.customAnchorMax, target.customPivot);
if (target.resetScaleToOne && rect != null)
rect.localScale = Vector3.one;
if (target.applyColor)
target.image.color = target.color;
if (target.applySprite)
target.image.sprite = target.sprite;
if (target.applyRaycastTarget)
target.image.raycastTarget = target.raycastTarget;
if (target.applyOutline)
ApplyOutline(target.image.gameObject, target.outlineColor, target.outlineEffectDistance, target.outlineUseGraphicAlpha, target.addOutlineIfMissing);
if (target.applyShadow)
ApplyShadow(target.image.gameObject, target.shadowColor, target.shadowEffectDistance, target.shadowUseGraphicAlpha, target.addShadowIfMissing);
}
}
private void ApplyControlledButtonTargets()
{
if (controlledButtons == null)
return;
foreach (InventoryControlledButtonTarget target in controlledButtons)
{
if (target == null || target.button == null)
continue;
RectTransform rect = target.button.GetComponent<RectTransform>();
if (target.applyRect)
ApplyRect(rect, target.anchorPreset, target.size, target.anchoredPosition, target.customAnchorMin, target.customAnchorMax, target.customPivot);
if (target.resetScaleToOne && rect != null)
rect.localScale = Vector3.one;
if (target.applyInteractable)
target.button.interactable = target.interactable;
if (target.applyImageRaycastTarget)
{
Image image = target.button.GetComponent<Image>();
if (image != null)
image.raycastTarget = target.imageRaycastTarget;
}
if (target.applyButtonColors)
ApplyButtonColorBlock(target.button, target.normalColor, target.highlightedColor, target.pressedColor, target.selectedColor, target.disabledColor, target.colorMultiplier, target.fadeDuration);
if (target.disableChildTextRaycasts)
{
foreach (TMP_Text text in target.button.GetComponentsInChildren<TMP_Text>(true))
text.raycastTarget = false;
}
if (target.applyOutline)
ApplyOutline(target.button.gameObject, target.outlineColor, target.outlineEffectDistance, target.outlineUseGraphicAlpha, target.addOutlineIfMissing);
if (target.applyShadow)
ApplyShadow(target.button.gameObject, target.shadowColor, target.shadowEffectDistance, target.shadowUseGraphicAlpha, target.addShadowIfMissing);
}
}
private void ApplyControlledGridLayoutTargets()
{
if (controlledGridLayouts == null)
return;
foreach (InventoryControlledGridLayoutTarget target in controlledGridLayouts)
{
if (target == null || target.grid == null || !target.applyGrid)
continue;
ConfigureGrid(target.grid, target.cellSize, target.spacing, target.constraint, target.constraintCount, target.childAlignment, target.padding);
}
}
[ContextMenu("Inventory UI/Apply Basic Visual Theme")]
public void ApplyBasicVisualTheme()
{
if (styleKnownPanels)
ApplyKnownPanelTheme();
if (styleKnownButtons)
ApplyKnownButtonTheme();
if (styleKnownTexts)
ApplyKnownTextTheme();
if (styleKnownSlots)
ApplyKnownSlotTheme();
}
private void ApplyKnownPanelTheme()
{
ApplyImageColor(inventoryPanel, mainPanelColor, false);
ApplyImageColor(recentLogArea, subPanelColor, false);
ApplyImageColor(debugPanel, subPanelColor, false);
ApplyImageColor(tooltipPanel, popupPanelColor, false);
ApplyImageColor(acquisitionPopupPanel, popupPanelColor, false);
ApplyImageColor(messagePanel, popupPanelColor, false);
ApplyImageColor(confirmationPanel, popupPanelColor, true);
ApplyImageColor(detailPanel, popupPanelColor, true);
ApplyImageColor(memoryProgressArea, new Color(subPanelColor.r, subPanelColor.g, subPanelColor.b, Mathf.Min(subPanelColor.a, 0.55f)), false);
if (addOutlineToKnownGraphics)
{
ApplyOutline(inventoryPanel != null ? inventoryPanel.gameObject : null, outlineColor, new Vector2(2f, -2f), true, true);
ApplyOutline(recentLogArea != null ? recentLogArea.gameObject : null, outlineColor, new Vector2(1f, -1f), true, true);
ApplyOutline(debugPanel != null ? debugPanel.gameObject : null, outlineColor, new Vector2(1f, -1f), true, true);
ApplyOutline(detailPanel != null ? detailPanel.gameObject : null, goldOutlineColor, new Vector2(1.5f, -1.5f), true, true);
ApplyOutline(confirmationPanel != null ? confirmationPanel.gameObject : null, goldOutlineColor, new Vector2(1.5f, -1.5f), true, true);
}
}
private void ApplyKnownButtonTheme()
{
ApplyButtonTheme(closeButton != null ? closeButton.GetComponent<Button>() : null, buttonNormalColor, false);
ApplyButtonsIn(categoryButtons, buttonNormalColor, false);
ApplyButtonsIn(detailPanel, buttonNormalColor, false);
ApplyButtonsIn(confirmationPanel, buttonNormalColor, false);
ApplyButtonsIn(quickUseButtons, buttonNormalColor, false);
ApplyButtonsIn(debugPanel, debugButtonColor, true);
}
private void ApplyButtonsIn(RectTransform root, Color baseColor, bool allowDangerColor)
{
if (root == null)
return;
Button[] buttons = root.GetComponentsInChildren<Button>(true);
foreach (Button button in buttons)
ApplyButtonTheme(button, baseColor, allowDangerColor);
}
private void ApplyButtonTheme(Button button, Color baseColor, bool allowDangerColor)
{
if (button == null)
return;
Color chosenNormal = baseColor;
if (allowDangerColor && IsDangerButton(button))
chosenNormal = dangerButtonColor;
ColorBlock colors = button.colors;
colors.normalColor = chosenNormal;
colors.highlightedColor = buttonHighlightedColor;
colors.pressedColor = buttonPressedColor;
colors.selectedColor = buttonSelectedColor;
colors.disabledColor = buttonDisabledColor;
colors.colorMultiplier = 1f;
colors.fadeDuration = 0.1f;
button.colors = colors;
Image image = button.GetComponent<Image>();
if (image != null)
{
image.color = chosenNormal;
image.raycastTarget = true;
}
if (addOutlineToKnownGraphics)
ApplyOutline(button.gameObject, outlineColor, new Vector2(1f, -1f), true, true);
TMP_Text[] texts = button.GetComponentsInChildren<TMP_Text>(true);
foreach (TMP_Text text in texts)
{
text.color = mainTextColor;
text.raycastTarget = false;
if (addShadowToKnownTexts)
ApplyShadow(text.gameObject, shadowColor, new Vector2(1.5f, -1.5f), true, true);
}
}
private bool IsDangerButton(Button button)
{
if (button == null)
return false;
string lowerName = button.name.ToLowerInvariant();
if (lowerName.Contains("delete") || lowerName.Contains("reset") || lowerName.Contains("clear"))
return true;
TMP_Text[] texts = button.GetComponentsInChildren<TMP_Text>(true);
foreach (TMP_Text text in texts)
{
if (text == null || string.IsNullOrEmpty(text.text))
continue;
if (text.text.Contains("삭제") || text.text.Contains("초기화") || text.text.Contains("저장삭제"))
return true;
}
return false;
}
private void ApplyKnownTextTheme()
{
TMP_Text[] texts = GetComponentsInChildren<TMP_Text>(true);
foreach (TMP_Text text in texts)
{
if (text == null)
continue;
text.raycastTarget = false;
text.color = IsAccentText(text) ? accentTextColor : mainTextColor;
if (IsMutedText(text))
text.color = mutedTextColor;
if (addShadowToKnownTexts)
ApplyShadow(text.gameObject, shadowColor, new Vector2(1.5f, -1.5f), true, true);
}
}
private bool IsAccentText(TMP_Text text)
{
if (text == null)
return false;
string lowerName = text.name.ToLowerInvariant();
return text.rectTransform == titleText || lowerName.Contains("title") || lowerName.Contains("goal") || lowerName.Contains("new");
}
private bool IsMutedText(TMP_Text text)
{
if (text == null)
return false;
string lowerName = text.name.ToLowerInvariant();
return lowerName.Contains("description") || lowerName.Contains("desc") || lowerName.Contains("count") || lowerName.Contains("log");
}
private void ApplyKnownSlotTheme()
{
if (slots == null || slots.Length == 0)
slots = GetComponentsInChildren<InventorySlotUI>(true);
foreach (InventorySlotUI slot in slots)
{
if (slot == null)
continue;
Image slotImage = slot.GetComponent<Image>();
if (slotImage != null)
{
slotImage.color = slotColor;
slotImage.raycastTarget = true;
}
if (addOutlineToKnownGraphics)
ApplyOutline(slot.gameObject, outlineColor, new Vector2(1f, -1f), true, true);
foreach (Transform child in slot.GetComponentsInChildren<Transform>(true))
{
if (child == null || child == slot.transform)
continue;
string lowerName = child.name.ToLowerInvariant();
Image image = child.GetComponent<Image>();
TMP_Text text = child.GetComponent<TMP_Text>();
if (image != null)
{
if (lowerName.Contains("glow") || lowerName.Contains("important"))
image.color = slotImportantGlowColor;
else if (lowerName.Contains("new"))
image.color = newBadgeColor;
else if (lowerName.Contains("overlay") || lowerName.Contains("filter"))
image.color = filteredOverlayColor;
image.raycastTarget = false;
}
if (text != null)
{
text.color = lowerName.Contains("new") ? new Color(0.08f, 0.06f, 0.02f, 1f) : mainTextColor;
text.raycastTarget = false;
if (addShadowToKnownTexts)
ApplyShadow(text.gameObject, shadowColor, new Vector2(1.2f, -1.2f), true, true);
}
}
}
}
[ContextMenu("Inventory UI/Apply Initial Mode")]
public void ApplyInitialMode()
{
ApplyMode(initialMode);
}
[ContextMenu("Inventory UI/Apply Test Mode")]
public void ApplyTestMode() => ApplyMode(InventoryRootUIMode.TestMode);
[ContextMenu("Inventory UI/Apply Play Mode")]
public void ApplyPlayMode() => ApplyMode(InventoryRootUIMode.PlayMode);
[ContextMenu("Inventory UI/Apply Build Mode")]
public void ApplyBuildMode() => ApplyMode(InventoryRootUIMode.BuildMode);
[ContextMenu("Inventory UI/Apply Hidden Mode")]
public void ApplyHiddenMode() => ApplyMode(InventoryRootUIMode.HiddenMode);
public void ApplyMode(InventoryRootUIMode mode)
{
if (mode == InventoryRootUIMode.LayoutOnly)
return;
switch (mode)
{
case InventoryRootUIMode.TestMode:
SetActive(inventoryPanel, testShowInventoryPanel);
SetActive(debugPanel, testShowDebugPanel);
SetActive(recentLogArea, testShowRecentLogArea);
SetActive(categoryButtons, testShowCategoryButtons);
SetActive(memoryProgressArea, testShowMemoryProgressArea);
SetActive(quickUseButtons, testShowQuickUseButtons);
HideAllPopups();
break;
case InventoryRootUIMode.PlayMode:
SetActive(inventoryPanel, playShowInventoryPanel);
SetActive(debugPanel, playShowDebugPanel);
SetActive(recentLogArea, playShowRecentLogArea);
SetActive(categoryButtons, playShowCategoryButtons);
SetActive(memoryProgressArea, playShowMemoryProgressArea);
SetActive(quickUseButtons, playShowQuickUseButtons);
HideAllPopups();
break;
case InventoryRootUIMode.BuildMode:
SetActive(inventoryPanel, buildShowInventoryPanel);
SetActive(debugPanel, buildShowDebugPanel);
SetActive(recentLogArea, buildShowRecentLogArea);
SetActive(categoryButtons, buildShowCategoryButtons);
SetActive(memoryProgressArea, buildShowMemoryProgressArea);
SetActive(quickUseButtons, buildShowQuickUseButtons);
HideAllPopups();
break;
case InventoryRootUIMode.HiddenMode:
SetActive(inventoryPanel, false);
SetActive(debugPanel, false);
SetActive(recentLogArea, false);
SetActive(categoryButtons, false);
SetActive(memoryProgressArea, false);
SetActive(quickUseButtons, false);
HideAllPopups();
break;
}
ApplyControlledActiveStates(mode);
}
private void ApplyControlledActiveStates(InventoryRootUIMode mode)
{
if (controlledRects == null)
return;
foreach (InventoryControlledRectTarget target in controlledRects)
{
if (target == null || !target.controlActiveState)
continue;
GameObject go = target.activeTarget != null ? target.activeTarget : target.rectTransform != null ? target.rectTransform.gameObject : null;
if (go == null)
continue;
bool active = true;
switch (mode)
{
case InventoryRootUIMode.TestMode:
active = target.activeInTestMode;
break;
case InventoryRootUIMode.PlayMode:
active = target.activeInPlayMode;
break;
case InventoryRootUIMode.BuildMode:
active = target.activeInBuildMode;
break;
case InventoryRootUIMode.HiddenMode:
active = target.activeInHiddenMode;
break;
}
go.SetActive(active);
}
}
[ContextMenu("Inventory UI/Hide All Popups")]
public void HideAllPopups()
{
SetActive(tooltipPanel, false);
SetActive(acquisitionPopupPanel, false);
SetActive(messagePanel, false);
SetActive(confirmationPanel, false);
SetActive(detailPanel, false);
if (extraPopupPanelsToHide != null)
{
foreach (GameObject panel in extraPopupPanelsToHide)
{
if (panel != null)
panel.SetActive(false);
}
}
}
[ContextMenu("Inventory UI/Show Inventory Panel")]
public void ShowInventoryPanel() => SetActive(inventoryPanel, true);
[ContextMenu("Inventory UI/Hide Inventory Panel")]
public void HideInventoryPanel() => SetActive(inventoryPanel, false);
[ContextMenu("Inventory UI/Show Debug Panel")]
public void ShowDebugPanel() => SetActive(debugPanel, true);
[ContextMenu("Inventory UI/Hide Debug Panel")]
public void HideDebugPanel() => SetActive(debugPanel, false);
[ContextMenu("Inventory UI/Toggle Debug Panel")]
public void ToggleDebugPanel()
{
if (debugPanel != null)
debugPanel.gameObject.SetActive(!debugPanel.gameObject.activeSelf);
}
[ContextMenu("Inventory UI/Check Setup")]
public void CheckSetup()
{
if (inventoryUI == null)
Debug.LogWarning("[InventoryUILayoutController] InventoryUI가 연결되어 있지 않습니다.", this);
if (inventoryPanel == null)
Debug.LogWarning("[InventoryUILayoutController] InventoryPanel이 연결되어 있지 않습니다.", this);
if (slots == null || slots.Length == 0)
Debug.LogWarning("[InventoryUILayoutController] Slots 배열이 비어 있습니다.", this);
if (tooltipPanel != null)
{
CanvasGroup group = tooltipPanel.GetComponent<CanvasGroup>();
if (group != null && group.blocksRaycasts)
Debug.LogWarning("[InventoryUILayoutController] TooltipPanel CanvasGroup Blocks Raycasts가 켜져 있습니다. 슬롯 Hover 흔들림 원인이 될 수 있습니다.", tooltipPanel);
}
Slider[] sliders = GetComponentsInChildren<Slider>(true);
foreach (Slider slider in sliders)
{
if (slider != null && slider.interactable)
Debug.LogWarning($"[InventoryUILayoutController] Slider '{slider.name}'의 Interactable이 켜져 있습니다. 진행도 표시용이면 꺼두는 것을 권장합니다.", slider);
}
if (debugPanel != null && buildShowDebugPanel)
Debug.LogWarning("[InventoryUILayoutController] BuildMode에서도 DebugPanel이 보이도록 설정되어 있습니다.", debugPanel);
}
[ContextMenu("Inventory UI/Fix All Raycast Targets")]
public void FixAllRaycastTargets()
{
if (disableTextRaycasts)
{
TMP_Text[] texts = GetComponentsInChildren<TMP_Text>(true);
foreach (TMP_Text text in texts)
text.raycastTarget = false;
}
if (disableNonInteractiveImageRaycasts)
{
Image[] images = GetComponentsInChildren<Image>(true);
foreach (Image image in images)
{
if (image == null)
continue;
Button buttonOnSameObject = image.GetComponent<Button>();
if (buttonOnSameObject != null)
{
image.raycastTarget = true;
continue;
}
InventorySlotUI slotOnSameObject = image.GetComponent<InventorySlotUI>();
if (slotOnSameObject != null)
{
image.raycastTarget = true;
continue;
}
image.raycastTarget = false;
}
}
if (fixCanvasGroups)
{
ConfigurePassiveCanvasGroup(tooltipPanel);
ConfigurePassiveCanvasGroup(acquisitionPopupPanel);
ConfigurePassiveCanvasGroup(messagePanel);
ConfigureInteractiveCanvasGroup(confirmationPanel);
ConfigureInteractiveCanvasGroup(detailPanel);
}
if (setSliderInteractableFalse)
FixSlidersForDisplayOnly();
if (setSlotHoverSafeValues)
ApplySlotHoverSettings();
}
private void ApplyImageColor(RectTransform rect, Color color, bool raycastTarget)
{
if (rect == null)
return;
Image image = rect.GetComponent<Image>();
if (image == null && autoAddMissingComponents)
image = rect.gameObject.AddComponent<Image>();
if (image == null)
return;
image.color = color;
image.raycastTarget = raycastTarget;
}
private void ApplyButtonColorBlock(Button button, Color normal, Color highlighted, Color pressed, Color selected, Color disabled, float multiplier, float fade)
{
if (button == null)
return;
ColorBlock colors = button.colors;
colors.normalColor = normal;
colors.highlightedColor = highlighted;
colors.pressedColor = pressed;
colors.selectedColor = selected;
colors.disabledColor = disabled;
colors.colorMultiplier = Mathf.Max(0f, multiplier);
colors.fadeDuration = Mathf.Max(0f, fade);
button.colors = colors;
Image image = button.GetComponent<Image>();
if (image != null)
image.color = normal;
}
private void ApplyOutline(GameObject target, Color color, Vector2 effectDistance, bool useGraphicAlpha, bool addIfMissing)
{
if (target == null)
return;
Outline outline = target.GetComponent<Outline>();
if (outline == null && addIfMissing)
outline = target.AddComponent<Outline>();
if (outline == null)
return;
outline.effectColor = color;
outline.effectDistance = effectDistance;
outline.useGraphicAlpha = useGraphicAlpha;
}
private void ApplyShadow(GameObject target, Color color, Vector2 effectDistance, bool useGraphicAlpha, bool addIfMissing)
{
if (target == null)
return;
Shadow shadow = GetPlainShadow(target);
if (shadow == null && addIfMissing)
shadow = target.AddComponent<Shadow>();
if (shadow == null)
return;
shadow.effectColor = color;
shadow.effectDistance = effectDistance;
shadow.useGraphicAlpha = useGraphicAlpha;
}
private Shadow GetPlainShadow(GameObject target)
{
if (target == null)
return null;
Shadow[] shadows = target.GetComponents<Shadow>();
foreach (Shadow shadow in shadows)
{
if (shadow != null && shadow.GetType() == typeof(Shadow))
return shadow;
}
return null;
}
private void FixSlidersForDisplayOnly()
{
Slider[] sliders = GetComponentsInChildren<Slider>(true);
foreach (Slider slider in sliders)
{
if (slider == null)
continue;
slider.interactable = false;
foreach (Graphic graphic in slider.GetComponentsInChildren<Graphic>(true))
{
if (graphic != null)
graphic.raycastTarget = false;
}
}
}
private void ApplySlotHoverSettings()
{
if (slots == null || slots.Length == 0)
slots = GetComponentsInChildren<InventorySlotUI>(true);
foreach (InventorySlotUI slot in slots)
{
if (slot == null)
continue;
slot.SetHoverSettings(slotUseHoverScale, slotHoverScale, slotBringToFrontOnHover);
}
}
private void ApplyRect(RectTransform rect, InventoryUIAnchorPreset preset, Vector2 size, Vector2 pos)
{
ApplyRect(rect, preset, size, pos, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f));
}
private void ApplyRect(RectTransform rect, InventoryUIAnchorPreset preset, Vector2 size, Vector2 pos, Vector2 customMin, Vector2 customMax, Vector2 customPivot)
{
if (rect == null)
return;
SetAnchorPreset(rect, preset, customMin, customMax, customPivot);
if (preset == InventoryUIAnchorPreset.StretchAll)
{
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
}
else
{
rect.sizeDelta = size;
rect.anchoredPosition = pos;
}
if (resetChildScaleToOne)
rect.localScale = Vector3.one;
}
private void ApplyStretch(RectTransform rect, Vector2 offsetMin, Vector2 offsetMax)
{
if (rect == null)
return;
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.pivot = new Vector2(0.5f, 0.5f);
rect.offsetMin = offsetMin;
rect.offsetMax = offsetMax;
if (resetChildScaleToOne)
rect.localScale = Vector3.one;
}
private void SetAnchorPreset(RectTransform rect, InventoryUIAnchorPreset preset, Vector2 customMin, Vector2 customMax, Vector2 customPivot)
{
switch (preset)
{
case InventoryUIAnchorPreset.TopLeft:
rect.anchorMin = rect.anchorMax = new Vector2(0f, 1f);
rect.pivot = new Vector2(0f, 1f);
break;
case InventoryUIAnchorPreset.TopCenter:
rect.anchorMin = rect.anchorMax = new Vector2(0.5f, 1f);
rect.pivot = new Vector2(0.5f, 1f);
break;
case InventoryUIAnchorPreset.TopRight:
rect.anchorMin = rect.anchorMax = new Vector2(1f, 1f);
rect.pivot = new Vector2(1f, 1f);
break;
case InventoryUIAnchorPreset.MiddleLeft:
rect.anchorMin = rect.anchorMax = new Vector2(0f, 0.5f);
rect.pivot = new Vector2(0f, 0.5f);
break;
case InventoryUIAnchorPreset.MiddleRight:
rect.anchorMin = rect.anchorMax = new Vector2(1f, 0.5f);
rect.pivot = new Vector2(1f, 0.5f);
break;
case InventoryUIAnchorPreset.BottomLeft:
rect.anchorMin = rect.anchorMax = new Vector2(0f, 0f);
rect.pivot = new Vector2(0f, 0f);
break;
case InventoryUIAnchorPreset.BottomCenter:
rect.anchorMin = rect.anchorMax = new Vector2(0.5f, 0f);
rect.pivot = new Vector2(0.5f, 0f);
break;
case InventoryUIAnchorPreset.BottomRight:
rect.anchorMin = rect.anchorMax = new Vector2(1f, 0f);
rect.pivot = new Vector2(1f, 0f);
break;
case InventoryUIAnchorPreset.StretchAll:
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.pivot = new Vector2(0.5f, 0.5f);
break;
case InventoryUIAnchorPreset.Custom:
rect.anchorMin = customMin;
rect.anchorMax = customMax;
rect.pivot = customPivot;
break;
default:
rect.anchorMin = rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
break;
}
}
private void ConfigureGrid(GridLayoutGroup grid, Vector2 cell, Vector2 spacingValue, GridLayoutGroup.Constraint constraint, int count, TextAnchor alignment, InventoryLayoutPadding padding)
{
if (grid == null)
return;
grid.cellSize = cell;
grid.spacing = spacingValue;
grid.constraint = constraint;
grid.constraintCount = Mathf.Max(1, count);
grid.childAlignment = alignment;
grid.padding = padding != null ? padding.ToRectOffset() : new RectOffset(0, 0, 0, 0);
}
private CanvasGroup AddCanvasGroupIfNeeded(RectTransform rect, bool interactable, bool blocksRaycasts)
{
if (rect == null)
return null;
CanvasGroup group = GetOrAdd<CanvasGroup>(rect.gameObject);
group.interactable = interactable;
group.blocksRaycasts = blocksRaycasts;
return group;
}
private void ConfigurePassiveCanvasGroup(RectTransform rect)
{
CanvasGroup group = AddCanvasGroupIfNeeded(rect, false, false);
if (group != null)
group.alpha = 1f;
}
private void ConfigureInteractiveCanvasGroup(RectTransform rect)
{
CanvasGroup group = AddCanvasGroupIfNeeded(rect, true, true);
if (group != null)
group.alpha = 1f;
}
private T GetOrAdd<T>(GameObject target) where T : Component
{
if (target == null)
return null;
T component = target.GetComponent<T>();
if (component == null)
component = target.AddComponent<T>();
return component;
}
private void TryAddComponentByTypeName(GameObject target, string typeName)
{
if (target == null || string.IsNullOrWhiteSpace(typeName))
return;
Type type = Type.GetType(typeName);
if (type == null)
{
foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
if (type == null || !typeof(Component).IsAssignableFrom(type))
return;
if (target.GetComponent(type) == null)
target.AddComponent(type);
}
private RectTransform FindRectIfNull(RectTransform current, string objectName)
{
if (current != null)
return current;
Transform found = FindDeepChild(transform, objectName);
return found != null ? found.GetComponent<RectTransform>() : null;
}
private RectTransform FindChildRect(Transform parent, string objectName)
{
Transform found = FindDeepChild(parent, objectName);
return found != null ? found.GetComponent<RectTransform>() : null;
}
private Transform FindDeepChild(Transform parent, string objectName)
{
if (parent == null || string.IsNullOrWhiteSpace(objectName))
return null;
foreach (Transform child in parent)
{
if (child.name == objectName)
return child;
Transform result = FindDeepChild(child, objectName);
if (result != null)
return result;
}
return null;
}
private void SetActive(RectTransform rect, bool active)
{
if (rect != null)
rect.gameObject.SetActive(active);
}
private void AddControlledRect(string targetName, RectTransform rect, GameObject activeObject, InventoryUIAnchorPreset preset, Vector2 size, Vector2 pos, bool controlActive, bool testActive, bool playActive, bool buildActive, bool hiddenActive)
{
if (rect == null || ContainsRectTarget(rect))
return;
controlledRects.Add(new InventoryControlledRectTarget
{
name = targetName,
rectTransform = rect,
activeTarget = activeObject != null ? activeObject : rect.gameObject,
applyRect = true,
anchorPreset = preset,
size = size,
anchoredPosition = pos,
controlActiveState = controlActive,
activeInTestMode = testActive,
activeInPlayMode = playActive,
activeInBuildMode = buildActive,
activeInHiddenMode = hiddenActive,
resetScaleToOne = true
});
}
private void AddText(string targetName, TMP_Text text)
{
if (text == null || ContainsTextTarget(text))
return;
controlledTexts.Add(new InventoryControlledTextTarget
{
name = targetName,
text = text,
applyRect = false,
applyRaycastTarget = true,
raycastTarget = false,
resetScaleToOne = true
});
}
private void AddImage(string targetName, Image image)
{
if (image == null || ContainsImageTarget(image))
return;
bool shouldRaycast = image.GetComponent<Button>() != null || image.GetComponent<InventorySlotUI>() != null;
controlledImages.Add(new InventoryControlledImageTarget
{
name = targetName,
image = image,
applyRect = false,
applyRaycastTarget = true,
raycastTarget = shouldRaycast,
resetScaleToOne = true
});
}
private void AddButton(string targetName, Button button)
{
if (button == null || ContainsButtonTarget(button))
return;
controlledButtons.Add(new InventoryControlledButtonTarget
{
name = targetName,
button = button,
applyRect = false,
applyInteractable = false,
applyImageRaycastTarget = true,
imageRaycastTarget = true,
disableChildTextRaycasts = true,
resetScaleToOne = true
});
}
private void AddGrid(string targetName, GridLayoutGroup grid, Vector2 cellSize, Vector2 spacing, GridLayoutGroup.Constraint constraint, int count)
{
if (grid == null || ContainsGridTarget(grid))
return;
controlledGridLayouts.Add(new InventoryControlledGridLayoutTarget
{
name = targetName,
grid = grid,
applyGrid = true,
cellSize = cellSize,
spacing = spacing,
constraint = constraint,
constraintCount = count,
childAlignment = TextAnchor.MiddleCenter,
padding = new InventoryLayoutPadding(0, 0, 0, 0)
});
}
private bool ContainsRectTarget(RectTransform rect)
{
foreach (InventoryControlledRectTarget target in controlledRects)
{
if (target != null && target.rectTransform == rect)
return true;
}
return false;
}
private bool ContainsTextTarget(TMP_Text text)
{
foreach (InventoryControlledTextTarget target in controlledTexts)
{
if (target != null && target.text == text)
return true;
}
return false;
}
private bool ContainsImageTarget(Image image)
{
foreach (InventoryControlledImageTarget target in controlledImages)
{
if (target != null && target.image == image)
return true;
}
return false;
}
private bool ContainsButtonTarget(Button button)
{
foreach (InventoryControlledButtonTarget target in controlledButtons)
{
if (target != null && target.button == button)
return true;
}
return false;
}
private bool ContainsGridTarget(GridLayoutGroup grid)
{
foreach (InventoryControlledGridLayoutTarget target in controlledGridLayouts)
{
if (target != null && target.grid == grid)
return true;
}
return false;
}
private void SetPrivateFieldIfExists(object target, string fieldName, object value)
{
if (target == null || string.IsNullOrWhiteSpace(fieldName))
return;
System.Reflection.FieldInfo field = target.GetType().GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
if (field != null)
field.SetValue(target, value);
}
}