2026.06.24
This commit is contained in:
@@ -1,31 +1,158 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 전체 인벤토리 UI를 갱신하는 스크립트입니다.
|
||||
/// InventoryManager의 변경 이벤트를 받아 각 InventorySlotUI에 전달합니다.
|
||||
/// 기능: 패널 열기/닫기, 슬롯 갱신, 획득 팝업, 부족 안내 메시지, 확인창, 상세보기, 획득 로그, 기억의 조각 진행도, 간단한 손목/타겟 추적.
|
||||
/// InventoryUI가 붙은 오브젝트는 항상 켜두고, 실제 보이는 inventoryPanel만 켜고 끄는 구조를 권장합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class InventoryUI : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private InventoryManager inventoryManager;
|
||||
[Tooltip("실제로 보이거나 숨겨질 자식 패널입니다. InventoryUI가 붙은 자기 자신을 넣지 않는 것을 권장합니다.")]
|
||||
[SerializeField] private GameObject inventoryPanel;
|
||||
[SerializeField] private InventorySlotUI[] slots;
|
||||
|
||||
[Header("Settings")]
|
||||
[Header("Panel Settings")]
|
||||
[SerializeField] private bool autoFindManager = true;
|
||||
[SerializeField] private bool autoFindPanelChild = true;
|
||||
[SerializeField] private string autoPanelChildName = "InventoryPanel";
|
||||
[SerializeField] private bool visibleOnStart = false;
|
||||
[SerializeField] private bool refreshOnEnable = true;
|
||||
[SerializeField] private bool showNewBadgeOnIncrease = true;
|
||||
[SerializeField] private bool preventDisablingSelf = true;
|
||||
|
||||
[Header("Filter")]
|
||||
[SerializeField] private InventoryItemCategory currentFilter = InventoryItemCategory.All;
|
||||
[SerializeField] private bool hideSlotsOutsideFilter = false;
|
||||
|
||||
[Header("Acquisition Popup")]
|
||||
[SerializeField] private GameObject acquisitionPopupPanel;
|
||||
[SerializeField] private CanvasGroup acquisitionPopupCanvasGroup;
|
||||
[SerializeField] private Image acquisitionIconImage;
|
||||
[SerializeField] private TMP_Text acquisitionText;
|
||||
[SerializeField] private float acquisitionPopupTime = 1.4f;
|
||||
[SerializeField] private string acquisitionFormat = "{0} x{1} 획득!";
|
||||
|
||||
[Header("Message Popup")]
|
||||
[SerializeField] private GameObject messagePanel;
|
||||
[SerializeField] private CanvasGroup messageCanvasGroup;
|
||||
[SerializeField] private TMP_Text messageText;
|
||||
[SerializeField] private float messageShowTime = 1.5f;
|
||||
|
||||
[Header("Confirmation UI")]
|
||||
[SerializeField] private GameObject confirmationPanel;
|
||||
[SerializeField] private TMP_Text confirmationTitleText;
|
||||
[SerializeField] private TMP_Text confirmationBodyText;
|
||||
[SerializeField] private Button confirmationYesButton;
|
||||
[SerializeField] private Button confirmationNoButton;
|
||||
|
||||
[Header("Detail UI")]
|
||||
[SerializeField] private GameObject detailPanel;
|
||||
[SerializeField] private Image detailIconImage;
|
||||
[SerializeField] private TMP_Text detailTitleText;
|
||||
[SerializeField] private TMP_Text detailDescriptionText;
|
||||
[SerializeField] private TMP_Text detailCountText;
|
||||
[SerializeField] private TMP_Text detailGoalText;
|
||||
[SerializeField] private Button detailUseButton;
|
||||
|
||||
[Header("Recent Log UI")]
|
||||
[SerializeField] private TMP_Text[] recentLogTexts;
|
||||
|
||||
[Header("Memory Piece UI")]
|
||||
[SerializeField] private TMP_Text memoryProgressText;
|
||||
[SerializeField] private Slider memoryProgressSlider;
|
||||
[SerializeField] private string memoryProgressFormat = "기억의 조각 {0} / {1}";
|
||||
|
||||
[Header("Audio")]
|
||||
[SerializeField] private AudioSource uiAudioSource;
|
||||
[SerializeField] private AudioClip defaultAcquisitionClip;
|
||||
[SerializeField] private AudioClip defaultUseClip;
|
||||
[SerializeField] private AudioClip defaultErrorClip;
|
||||
|
||||
[Header("Optional Follow Target / Wrist UI")]
|
||||
[SerializeField] private bool followTarget = false;
|
||||
[SerializeField] private Transform targetToFollow;
|
||||
[SerializeField] private Vector3 localPositionOffset = new Vector3(0f, 0.08f, 0.12f);
|
||||
[SerializeField] private Vector3 localEulerOffset = Vector3.zero;
|
||||
[SerializeField] private bool faceMainCamera = false;
|
||||
|
||||
[Header("Editor Test Toggle")]
|
||||
[SerializeField] private bool enableKeyboardToggleForTesting = false;
|
||||
[SerializeField] private KeyCode keyboardToggleKey = KeyCode.I;
|
||||
|
||||
private bool subscribed;
|
||||
private Coroutine acquisitionRoutine;
|
||||
private Coroutine messageRoutine;
|
||||
private InventoryItemType pendingUseItemType;
|
||||
private int pendingUseAmount = 1;
|
||||
private bool hasPendingUse;
|
||||
private InventoryItemType currentDetailItemType;
|
||||
private bool hasDetailItem;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (inventoryPanel == null)
|
||||
inventoryPanel = gameObject;
|
||||
if (inventoryManager == null && InventoryManager.Instance != null)
|
||||
inventoryManager = InventoryManager.Instance;
|
||||
|
||||
if (inventoryManager == null && autoFindManager)
|
||||
inventoryManager = FindFirstObjectByType<InventoryManager>();
|
||||
|
||||
if (inventoryPanel == null && autoFindPanelChild)
|
||||
{
|
||||
Transform panelTransform = transform.Find(autoPanelChildName);
|
||||
if (panelTransform != null)
|
||||
inventoryPanel = panelTransform.gameObject;
|
||||
}
|
||||
|
||||
if (uiAudioSource == null)
|
||||
uiAudioSource = GetComponent<AudioSource>();
|
||||
|
||||
if (acquisitionPopupPanel != null)
|
||||
acquisitionPopupPanel.SetActive(false);
|
||||
|
||||
if (messagePanel != null)
|
||||
messagePanel.SetActive(false);
|
||||
|
||||
if (confirmationPanel != null)
|
||||
confirmationPanel.SetActive(false);
|
||||
|
||||
if (detailPanel != null)
|
||||
detailPanel.SetActive(false);
|
||||
|
||||
if (confirmationYesButton != null)
|
||||
{
|
||||
confirmationYesButton.onClick.RemoveListener(ConfirmPendingUse);
|
||||
confirmationYesButton.onClick.AddListener(ConfirmPendingUse);
|
||||
}
|
||||
|
||||
if (confirmationNoButton != null)
|
||||
{
|
||||
confirmationNoButton.onClick.RemoveListener(CancelPendingUse);
|
||||
confirmationNoButton.onClick.AddListener(CancelPendingUse);
|
||||
}
|
||||
|
||||
if (detailUseButton != null)
|
||||
{
|
||||
detailUseButton.onClick.RemoveListener(UseCurrentDetailItem);
|
||||
detailUseButton.onClick.AddListener(UseCurrentDetailItem);
|
||||
}
|
||||
|
||||
ApplyItemDataToSlots();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetVisible(visibleOnStart);
|
||||
RefreshAllSlots();
|
||||
RefreshRecentLogs();
|
||||
RefreshMemoryProgress();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -41,18 +168,54 @@ private void OnDisable()
|
||||
Unsubscribe();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (enableKeyboardToggleForTesting && Input.GetKeyDown(keyboardToggleKey))
|
||||
ToggleVisible();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!followTarget || targetToFollow == null)
|
||||
return;
|
||||
|
||||
transform.position = targetToFollow.TransformPoint(localPositionOffset);
|
||||
transform.rotation = targetToFollow.rotation * Quaternion.Euler(localEulerOffset);
|
||||
|
||||
if (faceMainCamera && Camera.main != null)
|
||||
{
|
||||
Vector3 direction = transform.position - Camera.main.transform.position;
|
||||
if (direction.sqrMagnitude > 0.0001f)
|
||||
transform.rotation = Quaternion.LookRotation(direction.normalized, Vector3.up);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
if (inventoryPanel != null)
|
||||
inventoryPanel.SetActive(visible);
|
||||
if (inventoryPanel == null)
|
||||
return;
|
||||
|
||||
if (preventDisablingSelf && inventoryPanel == gameObject)
|
||||
{
|
||||
Debug.LogWarning("[InventoryUI] InventoryPanel에 자기 자신이 들어가 있습니다. InventoryRoot는 항상 켜두고 자식 InventoryPanel만 연결하세요.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryPanel.SetActive(visible);
|
||||
|
||||
if (visible)
|
||||
RefreshAllSlots();
|
||||
}
|
||||
|
||||
public void ToggleVisible()
|
||||
{
|
||||
if (inventoryPanel != null)
|
||||
inventoryPanel.SetActive(!inventoryPanel.activeSelf);
|
||||
SetVisible(!inventoryPanel.activeSelf);
|
||||
}
|
||||
|
||||
public void ShowInventory() => SetVisible(true);
|
||||
public void HideInventory() => SetVisible(false);
|
||||
|
||||
public void SetManager(InventoryManager manager)
|
||||
{
|
||||
if (inventoryManager == manager)
|
||||
@@ -61,9 +224,22 @@ public void SetManager(InventoryManager manager)
|
||||
Unsubscribe();
|
||||
inventoryManager = manager;
|
||||
Subscribe();
|
||||
ApplyItemDataToSlots();
|
||||
RefreshAllSlots();
|
||||
}
|
||||
|
||||
public void SetFilterAll() => SetFilter(InventoryItemCategory.All);
|
||||
public void SetFilterConsumable() => SetFilter(InventoryItemCategory.Consumable);
|
||||
public void SetFilterQuest() => SetFilter(InventoryItemCategory.Quest);
|
||||
public void SetFilterKeyItem() => SetFilter(InventoryItemCategory.KeyItem);
|
||||
public void SetFilterMaterial() => SetFilter(InventoryItemCategory.Material);
|
||||
|
||||
public void SetFilter(InventoryItemCategory category)
|
||||
{
|
||||
currentFilter = category;
|
||||
ApplyFilterToSlots();
|
||||
}
|
||||
|
||||
public void RefreshAllSlots()
|
||||
{
|
||||
if (inventoryManager == null || slots == null)
|
||||
@@ -75,9 +251,16 @@ public void RefreshAllSlots()
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
InventoryItemDefinition definition = inventoryManager.GetDefinition(slot.ItemType);
|
||||
slot.SetInventoryUI(this);
|
||||
slot.SetDefinition(definition);
|
||||
|
||||
int count = inventoryManager.GetItemCount(slot.ItemType);
|
||||
slot.SetCount(count, false);
|
||||
int maxCount = inventoryManager.GetMaxCount(slot.ItemType);
|
||||
slot.SetCount(count, false, maxCount);
|
||||
}
|
||||
|
||||
ApplyFilterToSlots();
|
||||
}
|
||||
|
||||
private void RefreshSlot(InventoryItemType itemType, int count)
|
||||
@@ -85,13 +268,53 @@ private void RefreshSlot(InventoryItemType itemType, int count)
|
||||
if (slots == null)
|
||||
return;
|
||||
|
||||
int maxCount = inventoryManager != null ? inventoryManager.GetMaxCount(itemType) : 0;
|
||||
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
InventorySlotUI slot = slots[i];
|
||||
if (slot == null || slot.ItemType != itemType)
|
||||
continue;
|
||||
|
||||
slot.SetCount(count, showNewBadgeOnIncrease);
|
||||
slot.SetCount(count, showNewBadgeOnIncrease, maxCount);
|
||||
}
|
||||
|
||||
if (hasDetailItem && currentDetailItemType == itemType)
|
||||
ShowItemDetail(itemType);
|
||||
}
|
||||
|
||||
private void ApplyItemDataToSlots()
|
||||
{
|
||||
if (inventoryManager == null || slots == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
InventorySlotUI slot = slots[i];
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
slot.SetInventoryUI(this);
|
||||
slot.SetDefinition(inventoryManager.GetDefinition(slot.ItemType));
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilterToSlots()
|
||||
{
|
||||
if (slots == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
InventorySlotUI slot = slots[i];
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
bool visible = currentFilter == InventoryItemCategory.All || slot.Category == currentFilter;
|
||||
if (hideSlotsOutsideFilter)
|
||||
slot.gameObject.SetActive(visible);
|
||||
else
|
||||
slot.SetFilteredOut(!visible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +324,11 @@ private void Subscribe()
|
||||
return;
|
||||
|
||||
inventoryManager.ItemCountChanged += RefreshSlot;
|
||||
inventoryManager.ItemAdded += HandleItemAdded;
|
||||
inventoryManager.ItemUsed += HandleItemUsed;
|
||||
inventoryManager.MessageRequested += ShowMessage;
|
||||
inventoryManager.LogAdded += HandleLogAdded;
|
||||
inventoryManager.MemoryPieceProgressChanged += HandleMemoryPieceProgressChanged;
|
||||
subscribed = true;
|
||||
}
|
||||
|
||||
@@ -110,6 +338,306 @@ private void Unsubscribe()
|
||||
return;
|
||||
|
||||
inventoryManager.ItemCountChanged -= RefreshSlot;
|
||||
inventoryManager.ItemAdded -= HandleItemAdded;
|
||||
inventoryManager.ItemUsed -= HandleItemUsed;
|
||||
inventoryManager.MessageRequested -= ShowMessage;
|
||||
inventoryManager.LogAdded -= HandleLogAdded;
|
||||
inventoryManager.MemoryPieceProgressChanged -= HandleMemoryPieceProgressChanged;
|
||||
subscribed = false;
|
||||
}
|
||||
|
||||
private void HandleItemAdded(InventoryItemType itemType, int addedAmount, int totalCount)
|
||||
{
|
||||
ShowAcquisitionPopup(itemType, addedAmount);
|
||||
}
|
||||
|
||||
private void HandleItemUsed(InventoryItemType itemType, int count)
|
||||
{
|
||||
AudioClip clip = inventoryManager != null ? inventoryManager.GetUseClip(itemType) : null;
|
||||
PlayUIClip(clip != null ? clip : defaultUseClip);
|
||||
}
|
||||
|
||||
private void HandleLogAdded(InventoryLogEntry entry)
|
||||
{
|
||||
RefreshRecentLogs();
|
||||
}
|
||||
|
||||
private void HandleMemoryPieceProgressChanged(int current, int target)
|
||||
{
|
||||
RefreshMemoryProgress(current, target);
|
||||
}
|
||||
|
||||
public void ShowAcquisitionPopup(InventoryItemType itemType, int amount)
|
||||
{
|
||||
if (inventoryManager == null)
|
||||
return;
|
||||
|
||||
string displayName = inventoryManager.GetDisplayName(itemType);
|
||||
Sprite icon = inventoryManager.GetIcon(itemType);
|
||||
|
||||
if (acquisitionIconImage != null)
|
||||
{
|
||||
acquisitionIconImage.sprite = icon;
|
||||
acquisitionIconImage.enabled = icon != null;
|
||||
}
|
||||
|
||||
if (acquisitionText != null)
|
||||
acquisitionText.text = string.Format(acquisitionFormat, displayName, Mathf.Max(1, amount));
|
||||
|
||||
AudioClip clip = inventoryManager.GetAcquisitionClip(itemType);
|
||||
PlayUIClip(clip != null ? clip : defaultAcquisitionClip);
|
||||
|
||||
if (acquisitionRoutine != null)
|
||||
StopCoroutine(acquisitionRoutine);
|
||||
|
||||
acquisitionRoutine = StartCoroutine(ShowPanelRoutine(acquisitionPopupPanel, acquisitionPopupCanvasGroup, acquisitionPopupTime));
|
||||
}
|
||||
|
||||
public void ShowMessage(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return;
|
||||
|
||||
if (messageText != null)
|
||||
messageText.text = message;
|
||||
|
||||
if (messageRoutine != null)
|
||||
StopCoroutine(messageRoutine);
|
||||
|
||||
messageRoutine = StartCoroutine(ShowPanelRoutine(messagePanel, messageCanvasGroup, messageShowTime));
|
||||
}
|
||||
|
||||
private IEnumerator ShowPanelRoutine(GameObject panel, CanvasGroup canvasGroup, float showTime)
|
||||
{
|
||||
if (panel == null)
|
||||
yield break;
|
||||
|
||||
panel.SetActive(true);
|
||||
|
||||
if (canvasGroup != null)
|
||||
canvasGroup.alpha = 1f;
|
||||
|
||||
yield return new WaitForSecondsRealtime(Mathf.Max(0.05f, showTime));
|
||||
|
||||
if (canvasGroup != null)
|
||||
canvasGroup.alpha = 0f;
|
||||
|
||||
panel.SetActive(false);
|
||||
}
|
||||
|
||||
public void RequestUseItem(InventoryItemType itemType)
|
||||
{
|
||||
RequestUseItem(itemType, 1);
|
||||
}
|
||||
|
||||
public void RequestUseItem(InventoryItemType itemType, int amount)
|
||||
{
|
||||
if (inventoryManager == null)
|
||||
return;
|
||||
|
||||
if (!inventoryManager.IsUsable(itemType))
|
||||
{
|
||||
ShowMessage($"{inventoryManager.GetDisplayName(itemType)}은(는) 지금 사용할 수 없습니다.");
|
||||
PlayUIClip(defaultErrorClip);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inventoryManager.HasItem(itemType, amount))
|
||||
{
|
||||
ShowMessage(inventoryManager.GetInsufficientMessage(itemType, amount));
|
||||
PlayUIClip(defaultErrorClip);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inventoryManager.RequiresUseConfirmation(itemType))
|
||||
{
|
||||
pendingUseItemType = itemType;
|
||||
pendingUseAmount = Mathf.Max(1, amount);
|
||||
ShowConfirmation(itemType, amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool result = inventoryManager.UseItem(itemType, amount);
|
||||
if (!result)
|
||||
PlayUIClip(defaultErrorClip);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowConfirmation(InventoryItemType itemType, int amount)
|
||||
{
|
||||
pendingUseItemType = itemType;
|
||||
pendingUseAmount = Mathf.Max(1, amount);
|
||||
hasPendingUse = true;
|
||||
|
||||
if (confirmationPanel == null)
|
||||
{
|
||||
ConfirmPendingUse();
|
||||
return;
|
||||
}
|
||||
|
||||
string displayName = inventoryManager != null ? inventoryManager.GetDisplayName(itemType) : itemType.ToString();
|
||||
|
||||
if (confirmationTitleText != null)
|
||||
confirmationTitleText.text = "아이템 사용";
|
||||
|
||||
if (confirmationBodyText != null)
|
||||
confirmationBodyText.text = $"{displayName}을(를) 사용하시겠습니까?";
|
||||
|
||||
confirmationPanel.SetActive(true);
|
||||
}
|
||||
|
||||
public void ConfirmPendingUse()
|
||||
{
|
||||
if (confirmationPanel != null)
|
||||
confirmationPanel.SetActive(false);
|
||||
|
||||
if (!hasPendingUse)
|
||||
return;
|
||||
|
||||
InventoryItemType itemType = pendingUseItemType;
|
||||
int amount = Mathf.Max(1, pendingUseAmount);
|
||||
hasPendingUse = false;
|
||||
|
||||
if (inventoryManager != null)
|
||||
{
|
||||
bool result = inventoryManager.UseItem(itemType, amount);
|
||||
if (!result)
|
||||
PlayUIClip(defaultErrorClip);
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelPendingUse()
|
||||
{
|
||||
hasPendingUse = false;
|
||||
|
||||
if (confirmationPanel != null)
|
||||
confirmationPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowItemDetail(InventoryItemType itemType)
|
||||
{
|
||||
hasDetailItem = true;
|
||||
currentDetailItemType = itemType;
|
||||
|
||||
if (inventoryManager == null || detailPanel == null)
|
||||
return;
|
||||
|
||||
InventoryItemDefinition definition = inventoryManager.GetDefinition(itemType);
|
||||
int count = inventoryManager.GetItemCount(itemType);
|
||||
int maxCount = inventoryManager.GetMaxCount(itemType);
|
||||
Sprite icon = inventoryManager.GetIcon(itemType);
|
||||
|
||||
if (detailIconImage != null)
|
||||
{
|
||||
detailIconImage.sprite = icon;
|
||||
detailIconImage.enabled = icon != null;
|
||||
}
|
||||
|
||||
if (detailTitleText != null)
|
||||
detailTitleText.text = inventoryManager.GetDisplayName(itemType);
|
||||
|
||||
if (detailDescriptionText != null)
|
||||
detailDescriptionText.text = inventoryManager.GetDescription(itemType);
|
||||
|
||||
if (detailCountText != null)
|
||||
detailCountText.text = maxCount > 0 ? $"보유: x{count} / {maxCount}" : $"보유: x{count}";
|
||||
|
||||
if (detailGoalText != null)
|
||||
detailGoalText.text = inventoryManager.GetGoalHint(itemType);
|
||||
|
||||
if (detailUseButton != null)
|
||||
{
|
||||
bool usable = definition != null && definition.usable && count > 0;
|
||||
detailUseButton.gameObject.SetActive(definition != null && definition.usable);
|
||||
detailUseButton.interactable = usable;
|
||||
}
|
||||
|
||||
detailPanel.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideItemDetail()
|
||||
{
|
||||
hasDetailItem = false;
|
||||
if (detailPanel != null)
|
||||
detailPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public void UseCurrentDetailItem()
|
||||
{
|
||||
if (!hasDetailItem)
|
||||
return;
|
||||
|
||||
RequestUseItem(currentDetailItemType, 1);
|
||||
}
|
||||
|
||||
private void RefreshRecentLogs()
|
||||
{
|
||||
if (recentLogTexts == null || recentLogTexts.Length == 0 || inventoryManager == null)
|
||||
return;
|
||||
|
||||
IReadOnlyList<InventoryLogEntry> logs = inventoryManager.GetRecentLogs();
|
||||
|
||||
for (int i = 0; i < recentLogTexts.Length; i++)
|
||||
{
|
||||
if (recentLogTexts[i] == null)
|
||||
continue;
|
||||
|
||||
recentLogTexts[i].text = i < logs.Count && logs[i] != null ? logs[i].ToString() : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshMemoryProgress()
|
||||
{
|
||||
if (inventoryManager == null)
|
||||
return;
|
||||
|
||||
RefreshMemoryProgress(inventoryManager.GetItemCount(inventoryManager.MemoryPieceItemType), inventoryManager.MemoryPieceTargetCount);
|
||||
}
|
||||
|
||||
private void RefreshMemoryProgress(int current, int target)
|
||||
{
|
||||
target = Mathf.Max(1, target);
|
||||
current = Mathf.Clamp(current, 0, target);
|
||||
|
||||
if (memoryProgressText != null)
|
||||
memoryProgressText.text = string.Format(memoryProgressFormat, current, target);
|
||||
|
||||
if (memoryProgressSlider != null)
|
||||
{
|
||||
memoryProgressSlider.minValue = 0f;
|
||||
memoryProgressSlider.maxValue = target;
|
||||
memoryProgressSlider.value = current;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayUIClip(AudioClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return;
|
||||
|
||||
if (uiAudioSource != null)
|
||||
uiAudioSource.PlayOneShot(clip);
|
||||
else
|
||||
AudioSource.PlayClipAtPoint(clip, transform.position);
|
||||
}
|
||||
|
||||
public void CheckVRUISetup()
|
||||
{
|
||||
Canvas canvas = GetComponentInParent<Canvas>();
|
||||
if (canvas == null)
|
||||
{
|
||||
Debug.LogWarning("[InventoryUI] Canvas가 없습니다.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (canvas.renderMode != RenderMode.WorldSpace)
|
||||
Debug.LogWarning("[InventoryUI] VR UI에서는 Canvas Render Mode를 World Space로 권장합니다.", canvas);
|
||||
|
||||
if (EventSystem.current == null)
|
||||
Debug.LogWarning("[InventoryUI] EventSystem이 없습니다. VR 포인터 UI가 동작하지 않을 수 있습니다.", this);
|
||||
|
||||
GraphicRaycaster graphicRaycaster = canvas.GetComponent<GraphicRaycaster>();
|
||||
if (graphicRaycaster == null)
|
||||
Debug.LogWarning("[InventoryUI] Canvas에 GraphicRaycaster 또는 Tracked Device Graphic Raycaster가 필요합니다.", canvas);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user