using System; using System.Collections; using TMPro; using UnityEngine; using UnityEngine.UI; [Serializable] public class FishingItemIconData { public FishingItemType itemType = FishingItemType.Fish; public Sprite icon; [TextArea] public string hintText; } public class FishingGaugeUI : MonoBehaviour { [Header("Auto Bind")] [SerializeField] private bool autoBindMissingReferences = true; [Header("Root / Main Panels")] [SerializeField] private GameObject backgroundPanel; [SerializeField] private GameObject counterPanel; [SerializeField] private GameObject itemSlotPanel; [SerializeField] private GameObject caughtItemPanel; [SerializeField] private GameObject controllerGuidePanel; [SerializeField] private GameObject memoryPieceNoticePanel; [SerializeField] private GameObject finalResultPanel; [SerializeField] private GameObject catchButton; [Header("Top Texts")] [SerializeField] private TMP_Text titleText; [SerializeField] private TMP_Text pondStateText; [SerializeField] private TMP_Text objectiveText; [Header("Counter Panel Texts")] [SerializeField] private TMP_Text catchCountText; [SerializeField] private TMP_Text successText; [SerializeField] private TMP_Text failText; [SerializeField] private TMP_Text cleanupProgressText; [Header("Cleanup Gauge")] [Tooltip("CleanupGauge/CleanupGaugeFill. Image Type = Filled, Fill Method = Horizontal, Fill Origin = Left")] [SerializeField] private Image cleanupGaugeFill; [SerializeField] private TMP_Text cleanupPercentText; [Header("Gauge UI")] [SerializeField] private RectTransform gaugeGroupRect; [SerializeField] private RectTransform pointerPivot; [Tooltip("SuccessZone. Image Type = Filled, Fill Method = Radial 360")] [SerializeField] private Image successZone; [Header("Simple Reel UI")] [Tooltip("릴 감기 진행도 게이지 루트입니다. 없으면 텍스트만 업데이트됩니다.")] [SerializeField] private GameObject reelProgressGaugeRoot; [Tooltip("릴 감기 진행도 Fill Image입니다. Image Type = Filled / Horizontal / Left 추천.")] [SerializeField] private Image reelProgressGaugeFill; [Tooltip("현재 줄 길이를 보여줄 텍스트입니다. CounterPanel 안에 LineLengthText를 추가해서 연결하세요.")] [SerializeField] private TMP_Text lineLengthText; [SerializeField] private string simpleReelObjectiveText = "릴을 감아 끌어올려라"; [SerializeField] private string simpleReelIdleText = "릴 손잡이를 잡고 돌려라"; [SerializeField] private string simpleReelReelingText = "좋다, 계속 감아라!"; [Header("Round Result UI")] [SerializeField] private TMP_Text resultText; [Header("Caught Item UI")] [SerializeField] private Image itemIcon; [SerializeField] private TMP_Text caughtItemText; [SerializeField] private TMP_Text itemHintText; [Header("Item Slot UI - Fish")] [SerializeField] private GameObject fishSlot; [SerializeField] private Image fishSlotBackground; [SerializeField] private Image fishIcon; [SerializeField] private TMP_Text fishCountText; [Header("Item Slot UI - Trash")] [SerializeField] private GameObject trashSlot; [SerializeField] private Image trashSlotBackground; [SerializeField] private Image trashIcon; [SerializeField] private TMP_Text trashCountText; [Header("Item Slot UI - Memory")] [SerializeField] private GameObject memorySlot; [SerializeField] private Image memorySlotBackground; [SerializeField] private Image memoryIcon; [SerializeField] private TMP_Text memoryPieceCountText; [Header("Item Slot UI - Optional Compass")] [SerializeField] private GameObject compassSlot; [SerializeField] private Image compassSlotBackground; [SerializeField] private Image compassIcon; [SerializeField] private TMP_Text compassCountText; [Header("Panel Canvas Groups")] [SerializeField] private CanvasGroup caughtItemCanvasGroup; [SerializeField] private CanvasGroup controllerGuideCanvasGroup; [SerializeField] private CanvasGroup memoryPieceNoticeCanvasGroup; [SerializeField] private CanvasGroup finalResultCanvasGroup; [Header("Effects")] [SerializeField] private FishingUIEffects effects; [SerializeField] private bool autoCreateEffectsComponent = true; [SerializeField] private bool animateCleanupGauge = true; [SerializeField] private float cleanupFillTime = 0.45f; [SerializeField] private float resultShowTime = 0.85f; [SerializeField] private float resultPopScale = 1.18f; [SerializeField] private float resultPopTime = 0.18f; [SerializeField] private float caughtItemShowTime = 1.6f; [SerializeField] private float panelFadeTime = 0.16f; [SerializeField] private float panelPopScale = 1.06f; [SerializeField] private float panelPopTime = 0.18f; [SerializeField] private float noticeShowTime = 2.2f; [SerializeField] private float finalPanelPopScale = 1.08f; [SerializeField] private float finalPanelPopTime = 0.22f; [SerializeField] private float gaugePopScale = 1.035f; [SerializeField] private float gaugePopTime = 0.16f; [SerializeField] private float missShakeStrength = 12f; [SerializeField] private float missShakeTime = 0.18f; [Header("Slot Visual Settings")] [Range(0f, 1f)] [SerializeField] private float emptyIconAlpha = 0.35f; [Range(0f, 1f)] [SerializeField] private float filledIconAlpha = 1f; [Range(0f, 1f)] [SerializeField] private float emptySlotAlpha = 0.55f; [Range(0f, 1f)] [SerializeField] private float filledSlotAlpha = 1f; [SerializeField] private float slotHighlightTime = 0.32f; [SerializeField] private float slotHighlightScale = 1.08f; [SerializeField] private Color slotHighlightColor = new Color(0.35f, 1f, 0.9f, 1f); [Header("Feedback UI")] [SerializeField] private Image feedbackFlash; [Range(0f, 1f)] [SerializeField] private float flashMaxAlpha = 0.25f; [SerializeField] private float flashTime = 0.16f; [Header("Notice UI")] [SerializeField] private TMP_Text noticeText; [Header("Final Result UI")] [SerializeField] private TMP_Text finalResultText; [SerializeField] private bool hideCounterOnFinalResult = true; [SerializeField] private bool hideCaughtItemOnFinalResult = true; [SerializeField] private bool hideGuideOnFinalResult = true; [Header("Final Result Only Mode")] [Tooltip("게임 클리어/실패 최종 결과가 뜰 때 진행 UI를 전부 숨기고 FinalResultPanel만 보이게 합니다.")] [SerializeField] private bool showOnlyFinalResult = true; [Tooltip("FinalResultPanel을 숨길 때 진행 UI를 다시 켤지 결정합니다. 클리어 후 UI를 완전히 닫는 구조면 Off로 두세요.")] [SerializeField] private bool restoreGameplayUIAfterFinalResult = true; [Tooltip("BackgroundPanel. 최종 결과창만 보이고 싶으면 자동 연결하거나 직접 연결하세요.")] [SerializeField] private GameObject gameplayBackgroundRoot; [Tooltip("CleanupGauge 루트 오브젝트입니다.")] [SerializeField] private GameObject cleanupGaugeRoot; [Tooltip("GaugeGroup 루트 오브젝트입니다.")] [SerializeField] private GameObject gaugeGroupRoot; [Header("Item Icons")] [SerializeField] private FishingItemIconData[] itemIcons; [Header("Zone Visual Correction")] [SerializeField] private float zoneVisualOffset = 0f; [Header("Guide / Debug UI")] [SerializeField] private bool showControllerGuideOnInitialize = true; [SerializeField] private bool showCatchButtonOnInitialize = true; [Header("Text Settings")] [SerializeField] private string titleDefaultText = "기묘한 낚시터"; [SerializeField] private string objectiveDirtyText = "쓰레기를 건져 연못을 맑게 하자"; [SerializeField] private string objectiveCleanText = "기억의 조각을 찾아보자"; [SerializeField] private string objectiveClearText = "기억의 조각을 획득했다"; [Header("Result Colors")] [SerializeField] private Color perfectColor = new Color(1f, 0.85f, 0.25f, 1f); [SerializeField] private Color goodColor = new Color(0.25f, 1f, 0.8f, 1f); [SerializeField] private Color missColor = new Color(1f, 0.3f, 0.2f, 1f); [SerializeField] private Color defaultResultColor = Color.white; private Coroutine resultRoutine; private Coroutine caughtItemRoutine; private Coroutine noticeRoutine; private bool initialized; private float lastCleanupFill = -1f; private void Awake() { if (autoBindMissingReferences) AutoBindMissingReferences(); EnsureEffectReferences(); InitializeFishingUI(); } private void OnDisable() { StopRunningRoutines(); } private void OnValidate() { cleanupFillTime = Mathf.Max(0f, cleanupFillTime); resultShowTime = Mathf.Max(0f, resultShowTime); resultPopScale = Mathf.Max(1f, resultPopScale); resultPopTime = Mathf.Max(0.01f, resultPopTime); caughtItemShowTime = Mathf.Max(0f, caughtItemShowTime); panelFadeTime = Mathf.Max(0f, panelFadeTime); panelPopScale = Mathf.Max(1f, panelPopScale); panelPopTime = Mathf.Max(0.01f, panelPopTime); noticeShowTime = Mathf.Max(0f, noticeShowTime); finalPanelPopScale = Mathf.Max(1f, finalPanelPopScale); finalPanelPopTime = Mathf.Max(0.01f, finalPanelPopTime); gaugePopScale = Mathf.Max(1f, gaugePopScale); gaugePopTime = Mathf.Max(0.01f, gaugePopTime); missShakeStrength = Mathf.Max(0f, missShakeStrength); missShakeTime = Mathf.Max(0.01f, missShakeTime); slotHighlightTime = Mathf.Max(0.01f, slotHighlightTime); slotHighlightScale = Mathf.Max(1f, slotHighlightScale); flashMaxAlpha = Mathf.Clamp01(flashMaxAlpha); flashTime = Mathf.Max(0.01f, flashTime); emptyIconAlpha = Mathf.Clamp01(emptyIconAlpha); filledIconAlpha = Mathf.Clamp01(filledIconAlpha); emptySlotAlpha = Mathf.Clamp01(emptySlotAlpha); filledSlotAlpha = Mathf.Clamp01(filledSlotAlpha); } private void EnsureEffectReferences() { if (effects == null) effects = GetComponent(); if (effects == null && autoCreateEffectsComponent) effects = gameObject.AddComponent(); if (effects == null) return; if (caughtItemCanvasGroup == null && caughtItemPanel != null) caughtItemCanvasGroup = effects.EnsureCanvasGroup(caughtItemPanel); if (controllerGuideCanvasGroup == null && controllerGuidePanel != null) controllerGuideCanvasGroup = effects.EnsureCanvasGroup(controllerGuidePanel); if (memoryPieceNoticeCanvasGroup == null && memoryPieceNoticePanel != null) memoryPieceNoticeCanvasGroup = effects.EnsureCanvasGroup(memoryPieceNoticePanel); if (finalResultCanvasGroup == null && finalResultPanel != null) finalResultCanvasGroup = effects.EnsureCanvasGroup(finalResultPanel); } [ContextMenu("Auto Bind UI References")] public void AutoBindMissingReferences() { if (backgroundPanel == null) backgroundPanel = FindGameObject("BackgroundPanel"); if (gameplayBackgroundRoot == null) gameplayBackgroundRoot = backgroundPanel; if (cleanupGaugeRoot == null) cleanupGaugeRoot = FindGameObject("CleanupGauge"); if (gaugeGroupRoot == null) gaugeGroupRoot = FindGameObject("GaugeGroup"); if (counterPanel == null) counterPanel = FindGameObject("CounterPanel"); if (itemSlotPanel == null) itemSlotPanel = FindGameObject("ItemSlotPanel"); if (caughtItemPanel == null) caughtItemPanel = FindGameObject("CaughtItemPanel"); if (controllerGuidePanel == null) controllerGuidePanel = FindGameObject("ControllerGuidePanel"); if (memoryPieceNoticePanel == null) memoryPieceNoticePanel = FindGameObject("MemoryPieceNoticePanel"); if (finalResultPanel == null) finalResultPanel = FindGameObject("FinalResultPanel"); if (catchButton == null) catchButton = FindGameObject("CatchButton", "DebugCatchButton"); if (titleText == null) titleText = FindComponentByName("TitleText"); if (pondStateText == null) pondStateText = FindComponentByName("PondStateText"); if (objectiveText == null) objectiveText = FindComponentByName("ObjectiveText"); if (catchCountText == null) catchCountText = FindComponentByName("CatchCountText", "SuccessText"); if (successText == null) successText = FindComponentByName("SuccessText"); if (failText == null) failText = FindComponentByName("FailText"); if (cleanupProgressText == null) cleanupProgressText = FindComponentByName("CleanupProgressText"); if (cleanupGaugeFill == null) cleanupGaugeFill = FindComponentByName("CleanupGaugeFill", "CleanupGaugeFillImage"); if (cleanupPercentText == null) cleanupPercentText = FindComponentByName("CleanupPercentText", "CleanupGaugePercentText"); if (gaugeGroupRect == null) gaugeGroupRect = FindComponentByName("GaugeGroup"); if (pointerPivot == null) pointerPivot = FindComponentByName("PointerPivot"); if (successZone == null) successZone = FindComponentByName("SuccessZone"); if (reelProgressGaugeRoot == null) reelProgressGaugeRoot = FindGameObject("ReelProgressGauge", "ReelProgressGroup", "ReelFightProgressGauge"); if (reelProgressGaugeFill == null) reelProgressGaugeFill = FindComponentByName("ReelProgressGaugeFill", "ReelProgressFill", "ReelFightProgressFill"); if (lineLengthText == null) lineLengthText = FindComponentByName("LineLengthText", "Line Length Text", "LineText"); if (resultText == null) resultText = FindComponentByName("ResultText"); if (itemIcon == null) itemIcon = FindComponentByName("ItemIcon"); if (caughtItemText == null) caughtItemText = FindComponentByName("CaughtItemText"); if (itemHintText == null) itemHintText = FindComponentByName("ItemHintText"); if (fishSlot == null) fishSlot = FindGameObject("FishSlot"); if (fishSlotBackground == null && fishSlot != null) fishSlotBackground = fishSlot.GetComponent(); if (fishIcon == null) fishIcon = FindComponentByName("FishIcon"); if (fishCountText == null) fishCountText = FindComponentByName("FishCountText"); if (trashSlot == null) trashSlot = FindGameObject("trash Slot", "TrashSlot"); if (trashSlotBackground == null && trashSlot != null) trashSlotBackground = trashSlot.GetComponent(); if (trashIcon == null) trashIcon = FindComponentByName("trashIcon", "TrashIcon"); if (trashCountText == null) trashCountText = FindComponentByName("trashCountText", "TrashCountText", "CleanupCountText"); if (memorySlot == null) memorySlot = FindGameObject("Memory Slot", "MemorySlot", "MemoryPieceSlot"); if (memorySlotBackground == null && memorySlot != null) memorySlotBackground = memorySlot.GetComponent(); if (memoryIcon == null) memoryIcon = FindComponentByName("Memory Icon", "MemoryIcon", "MemoryPieceIcon"); if (memoryPieceCountText == null) memoryPieceCountText = FindComponentByName("Memory CountText", "MemoryCountText", "MemoryPieceCountText"); if (compassSlot == null) compassSlot = FindGameObject("CompassSlot", "Compass Slot"); if (compassSlotBackground == null && compassSlot != null) compassSlotBackground = compassSlot.GetComponent(); if (compassIcon == null) compassIcon = FindComponentByName("CompassIcon", "Compass Icon"); if (compassCountText == null) compassCountText = FindComponentByName("CompassCountText", "Compass CountText"); if (feedbackFlash == null) feedbackFlash = FindComponentByName("FeedbackFlash"); if (finalResultText == null) finalResultText = FindComponentByName("FinalResultText"); if (noticeText == null) noticeText = FindComponentByName("NoticeText"); if (caughtItemCanvasGroup == null && caughtItemPanel != null) caughtItemCanvasGroup = caughtItemPanel.GetComponent(); if (controllerGuideCanvasGroup == null && controllerGuidePanel != null) controllerGuideCanvasGroup = controllerGuidePanel.GetComponent(); if (memoryPieceNoticeCanvasGroup == null && memoryPieceNoticePanel != null) memoryPieceNoticeCanvasGroup = memoryPieceNoticePanel.GetComponent(); if (finalResultCanvasGroup == null && finalResultPanel != null) finalResultCanvasGroup = finalResultPanel.GetComponent(); } public void InitializeFishingUI() { StopRunningRoutines(); EnsureEffectReferences(); SetGameplayUIVisible(true); initialized = false; if (titleText != null && !string.IsNullOrWhiteSpace(titleDefaultText)) titleText.text = titleDefaultText; ShowCounter(); SetItemSlotPanelVisible(true); SetControllerGuideVisible(showControllerGuideOnInitialize, true); SetCatchButtonVisible(showCatchButtonOnInitialize); HideRoundResult(); HideCaughtItem(true); HideNotice(true); HideFinalResult(true, true); SetFeedbackFlashVisible(false, Color.clear); UpdateInventoryUI(0, 0, 0, 0); lastCleanupFill = -1f; SetCleanupFill(0f, true); SetSimpleReelUIVisible(false); initialized = true; } private void StopRunningRoutines() { if (resultRoutine != null) StopCoroutine(resultRoutine); if (caughtItemRoutine != null) StopCoroutine(caughtItemRoutine); if (noticeRoutine != null) StopCoroutine(noticeRoutine); resultRoutine = null; caughtItemRoutine = null; noticeRoutine = null; if (effects != null) effects.StopAllEffects(); } public void SetPointerRotation(float angle) { if (pointerPivot == null) return; pointerPivot.localEulerAngles = new Vector3(0f, 0f, -angle); } public void SetZone(float centerAngle, float size) { SetZoneSize(size); SetZoneRotation(centerAngle, size); } public void SetZoneSize(float size) { if (successZone == null) return; successZone.fillAmount = Mathf.Clamp01(size / 360f); } public void SetZoneRotation(float centerAngle, float size) { if (successZone == null) return; float startAngle = centerAngle - size * 0.5f + zoneVisualOffset; successZone.rectTransform.localEulerAngles = new Vector3(0f, 0f, -startAngle); } public void UpdateCounter(int success, int successTarget, int fail, int failTarget) { ShowCounter(); if (catchCountText != null) catchCountText.text = $"낚기 {success}/{successTarget}"; if (successText != null && successText != catchCountText) successText.text = $"낚기 {success}/{successTarget}"; if (failText != null) failText.text = $"실패 {fail}/{failTarget}"; } public void SetTimingGaugeVisible(bool visible) { if (gaugeGroupRoot != null) gaugeGroupRoot.SetActive(visible); else if (gaugeGroupRect != null) gaugeGroupRect.gameObject.SetActive(visible); } public void SetSimpleReelUIVisible(bool visible) { if (reelProgressGaugeRoot != null) reelProgressGaugeRoot.SetActive(visible); } public void UpdateSimpleReelUI(float progress, float lineLength, bool isReeling) { ShowCounter(); progress = Mathf.Clamp01(progress); lineLength = Mathf.Max(0f, lineLength); int progressPercent = Mathf.RoundToInt(progress * 100f); if (catchCountText != null) catchCountText.text = $"끌어올리기 {progressPercent}%"; if (successText != null && successText != catchCountText) successText.text = $"끌어올리기 {progressPercent}%"; if (failText != null) failText.text = $"줄 길이 {lineLength:0.0}m"; if (lineLengthText != null) lineLengthText.text = $"줄 길이 {lineLength:0.0}m"; if (reelProgressGaugeFill != null) reelProgressGaugeFill.fillAmount = progress; if (objectiveText != null && !string.IsNullOrWhiteSpace(simpleReelObjectiveText)) objectiveText.text = simpleReelObjectiveText; if (resultText != null && resultText.gameObject.activeSelf) { if (isReeling && progress < 1f) resultText.text = simpleReelReelingText; else if (progress < 1f && !string.IsNullOrWhiteSpace(simpleReelIdleText)) resultText.text = simpleReelIdleText; } } public void UpdateFishingProgress(int cleanupItemCount, int cleanupTarget, bool pondCleaned, bool memoryPieceCollected, int totalCaughtItems) { cleanupTarget = Mathf.Max(1, cleanupTarget); int clampedCleanupCount = Mathf.Clamp(cleanupItemCount, 0, cleanupTarget); float progress = Mathf.Clamp01((float)clampedCleanupCount / cleanupTarget); if (cleanupProgressText != null) { cleanupProgressText.text = memoryPieceCollected ? $"정화 완료 {cleanupTarget}/{cleanupTarget}" : $"쓰레기 수거 {clampedCleanupCount}/{cleanupTarget}"; } SetCleanupFill(progress, !initialized || !animateCleanupGauge); if (cleanupPercentText != null) cleanupPercentText.text = $"{Mathf.RoundToInt(progress * 100f)}%"; if (pondStateText != null) { if (memoryPieceCollected) pondStateText.text = "기억의 조각 획득"; else if (pondCleaned) pondStateText.text = "연못이 맑아졌다!"; else if (progress >= 0.66f) pondStateText.text = "연못 상태: 거의 맑아짐"; else if (progress >= 0.33f) pondStateText.text = "연못 상태: 조금 맑아짐"; else pondStateText.text = "연못 상태: 탁함"; } if (objectiveText != null) { if (memoryPieceCollected) objectiveText.text = objectiveClearText; else if (pondCleaned) objectiveText.text = objectiveCleanText; else objectiveText.text = objectiveDirtyText; } } private void SetCleanupFill(float progress, bool instant) { if (cleanupGaugeFill == null) return; progress = Mathf.Clamp01(progress); if (!instant && Mathf.Abs(lastCleanupFill - progress) < 0.001f) return; lastCleanupFill = progress; if (instant || effects == null) cleanupGaugeFill.fillAmount = progress; else effects.FillImage(cleanupGaugeFill, progress, cleanupFillTime); } public void UpdateInventoryUI(int fishCount, int trashCount, int memoryPieceCount, int compassCount = 0) { fishCount = Mathf.Max(0, fishCount); trashCount = Mathf.Max(0, trashCount); memoryPieceCount = Mathf.Max(0, memoryPieceCount); compassCount = Mathf.Max(0, compassCount); if (fishCountText != null) fishCountText.text = $"생선\nx{fishCount}"; if (trashCountText != null) trashCountText.text = $"쓰레기\nx{trashCount}"; if (memoryPieceCountText != null) memoryPieceCountText.text = $"기억의 조각\nx{memoryPieceCount}"; if (compassCountText != null) compassCountText.text = $"나침반\nx{compassCount}"; ApplySlotState(fishSlot, fishSlotBackground, fishIcon, fishCount > 0); ApplySlotState(trashSlot, trashSlotBackground, trashIcon, trashCount > 0); ApplySlotState(memorySlot, memorySlotBackground, memoryIcon, memoryPieceCount > 0); ApplySlotState(compassSlot, compassSlotBackground, compassIcon, compassCount > 0); } public void HighlightSlotForItem(FishingItemType itemType) { GameObject targetSlot = GetSlotForItem(itemType); Image targetBackground = GetSlotBackgroundForItem(itemType); Image targetIcon = GetSlotIconForItem(itemType); if (targetSlot == null && targetBackground == null && targetIcon == null) return; if (effects != null) effects.HighlightSlot(targetBackground, targetIcon, targetSlot != null ? targetSlot.transform : null, slotHighlightColor, slotHighlightTime, slotHighlightScale); else StartCoroutine(SimpleSlotHighlightRoutine(targetBackground, targetIcon)); } private IEnumerator SimpleSlotHighlightRoutine(Image background, Image icon) { Color? originalBackground = null; Color? originalIcon = null; if (background != null) { originalBackground = background.color; Color c = background.color; c.a = 1f; background.color = c; } if (icon != null) { originalIcon = icon.color; Color c = icon.color; c.a = 1f; icon.color = c; } yield return new WaitForSeconds(slotHighlightTime); if (background != null && originalBackground.HasValue) background.color = originalBackground.Value; if (icon != null && originalIcon.HasValue) icon.color = originalIcon.Value; } private void ApplySlotState(GameObject slotRoot, Image slotBackground, Image icon, bool hasItem) { if (slotRoot != null) slotRoot.SetActive(true); SetImageAlpha(slotBackground, hasItem ? filledSlotAlpha : emptySlotAlpha); SetImageAlpha(icon, hasItem ? filledIconAlpha : emptyIconAlpha); } private void SetImageAlpha(Image image, float alpha) { if (image == null) return; Color color = image.color; color.a = Mathf.Clamp01(alpha); image.color = color; } private GameObject GetSlotForItem(FishingItemType itemType) { switch (itemType) { case FishingItemType.Fish: return fishSlot; case FishingItemType.Trash: case FishingItemType.Bottle: case FishingItemType.PlasticBag: return trashSlot; case FishingItemType.MemoryPiece: return memorySlot; case FishingItemType.OldCompass: return compassSlot; default: return null; } } private Image GetSlotBackgroundForItem(FishingItemType itemType) { switch (itemType) { case FishingItemType.Fish: return fishSlotBackground; case FishingItemType.Trash: case FishingItemType.Bottle: case FishingItemType.PlasticBag: return trashSlotBackground; case FishingItemType.MemoryPiece: return memorySlotBackground; case FishingItemType.OldCompass: return compassSlotBackground; default: return null; } } private Image GetSlotIconForItem(FishingItemType itemType) { switch (itemType) { case FishingItemType.Fish: return fishIcon; case FishingItemType.Trash: case FishingItemType.Bottle: case FishingItemType.PlasticBag: return trashIcon; case FishingItemType.MemoryPiece: return memoryIcon; case FishingItemType.OldCompass: return compassIcon; default: return null; } } public void ShowCounter() { if (counterPanel != null) counterPanel.SetActive(true); } public void HideCounter() { if (counterPanel != null) counterPanel.SetActive(false); } public void SetCounterVisible(bool visible) { if (visible) ShowCounter(); else HideCounter(); } public void SetItemSlotPanelVisible(bool visible) { if (itemSlotPanel != null) itemSlotPanel.SetActive(visible); } public void ShowResult(string text) { ShowResult(text, defaultResultColor); } public void ShowResult(string text, Color color) { if (resultText == null) return; if (resultRoutine != null) StopCoroutine(resultRoutine); resultRoutine = StartCoroutine(ResultRoutine(text, color)); } public void ShowPersistentResult(string text) { ShowPersistentResult(text, defaultResultColor); } public void ShowPersistentResult(string text, Color color) { if (resultText == null) return; if (resultRoutine != null) { StopCoroutine(resultRoutine); resultRoutine = null; } resultText.gameObject.SetActive(true); resultText.color = color; resultText.text = text; if (effects != null) effects.Pop(resultText.transform, resultPopScale, resultPopTime); } public void ShowResultForType(FishingGameManager.ResultType resultType) { switch (resultType) { case FishingGameManager.ResultType.Perfect: ShowResult("완벽!", perfectColor); if (effects != null && gaugeGroupRect != null) effects.Pop(gaugeGroupRect, gaugePopScale, gaugePopTime); break; case FishingGameManager.ResultType.Good: ShowResult("성공!", goodColor); if (effects != null && gaugeGroupRect != null) effects.Pop(gaugeGroupRect, gaugePopScale, gaugePopTime); break; case FishingGameManager.ResultType.Miss: ShowResult("실패!", missColor); if (effects != null && gaugeGroupRect != null) effects.Shake(gaugeGroupRect, missShakeStrength, missShakeTime); break; } } private IEnumerator ResultRoutine(string text, Color color) { resultText.gameObject.SetActive(true); resultText.color = color; resultText.text = text; if (effects != null) effects.Pop(resultText.transform, resultPopScale, resultPopTime); if (resultShowTime > 0f) yield return new WaitForSeconds(resultShowTime); else yield return null; resultText.gameObject.SetActive(false); resultRoutine = null; } public void ShowCaughtItem(string text) { ShowCaughtItem(FishingItemType.None, text, null); } public void ShowCaughtItem(FishingItemType itemType, string displayName, bool countsAsCleanupItem, string extraMessage = null) { string caughtMessage = GetCaughtMessage(itemType, displayName); string hintMessage = string.IsNullOrWhiteSpace(extraMessage) ? GetDefaultHint(itemType, countsAsCleanupItem) : extraMessage; ShowCaughtItem(itemType, caughtMessage, hintMessage); } private void ShowCaughtItem(FishingItemType itemType, string caughtMessage, string hintMessage) { if (caughtItemText == null && resultText != null) { ShowResult(caughtMessage); return; } if (caughtItemRoutine != null) StopCoroutine(caughtItemRoutine); caughtItemRoutine = StartCoroutine(CaughtItemRoutine(itemType, caughtMessage, hintMessage)); } private IEnumerator CaughtItemRoutine(FishingItemType itemType, string caughtMessage, string hintMessage) { if (caughtItemPanel != null) caughtItemPanel.SetActive(true); if (caughtItemCanvasGroup != null) { caughtItemCanvasGroup.alpha = 0f; caughtItemCanvasGroup.interactable = false; caughtItemCanvasGroup.blocksRaycasts = false; } if (caughtItemText != null) { caughtItemText.gameObject.SetActive(true); caughtItemText.text = caughtMessage; } ApplyItemIcon(itemType); if (itemHintText != null) { itemHintText.gameObject.SetActive(!string.IsNullOrWhiteSpace(hintMessage)); itemHintText.text = hintMessage ?? string.Empty; } if (effects != null) { effects.FadeCanvasGroup(caughtItemPanel, caughtItemCanvasGroup, true, panelFadeTime); if (caughtItemPanel != null) effects.Pop(caughtItemPanel.transform, panelPopScale, panelPopTime); } else if (caughtItemCanvasGroup != null) { caughtItemCanvasGroup.alpha = 1f; } if (caughtItemShowTime > 0f) yield return new WaitForSeconds(caughtItemShowTime); else yield return null; if (effects != null) effects.FadeCanvasGroup(caughtItemPanel, caughtItemCanvasGroup, false, panelFadeTime); else if (caughtItemPanel != null) caughtItemPanel.SetActive(false); if (caughtItemText != null) caughtItemText.gameObject.SetActive(false); if (itemHintText != null) itemHintText.gameObject.SetActive(false); caughtItemRoutine = null; } public void HideCaughtItem() { HideCaughtItem(false); } private void HideCaughtItem(bool instant) { if (caughtItemRoutine != null) { StopCoroutine(caughtItemRoutine); caughtItemRoutine = null; } if (instant || effects == null) { if (caughtItemPanel != null) caughtItemPanel.SetActive(false); if (caughtItemCanvasGroup != null) { caughtItemCanvasGroup.alpha = 0f; caughtItemCanvasGroup.interactable = false; caughtItemCanvasGroup.blocksRaycasts = false; } } else { effects.FadeCanvasGroup(caughtItemPanel, caughtItemCanvasGroup, false, panelFadeTime); } if (caughtItemText != null) caughtItemText.gameObject.SetActive(false); if (itemHintText != null) itemHintText.gameObject.SetActive(false); } private void ApplyItemIcon(FishingItemType itemType) { if (itemIcon == null) return; FishingItemIconData iconData = GetIconData(itemType); itemIcon.sprite = iconData != null ? iconData.icon : null; itemIcon.enabled = itemIcon.sprite != null; if (itemIcon.enabled && effects != null) effects.Pop(itemIcon.transform, 1.12f, 0.18f); } private FishingItemIconData GetIconData(FishingItemType itemType) { if (itemIcons == null) return null; for (int i = 0; i < itemIcons.Length; i++) { if (itemIcons[i] != null && itemIcons[i].itemType == itemType) return itemIcons[i]; } return null; } private string GetCaughtMessage(FishingItemType itemType, string displayName) { if (!string.IsNullOrWhiteSpace(displayName) && itemType != FishingItemType.None) return $"{displayName}을(를) 낚았다!"; switch (itemType) { case FishingItemType.Fish: return "생선을 낚았다!"; case FishingItemType.RottenFish: return "상한 생선을 낚았다..."; case FishingItemType.Trash: return "쓰레기를 건져냈다!"; case FishingItemType.Bottle: return "병을 건져냈다!"; case FishingItemType.PlasticBag: return "비닐봉지를 건져냈다!"; case FishingItemType.JellyfishPowder: return "해파리 가루를 얻었다!"; case FishingItemType.OldCompass: return "낡은 나침반을 얻었다!"; case FishingItemType.MemoryPiece: return "기억의 조각을 찾았다!"; default: return string.IsNullOrWhiteSpace(displayName) ? "무언가를 낚았다!" : displayName; } } private string GetDefaultHint(FishingItemType itemType, bool countsAsCleanupItem) { FishingItemIconData iconData = GetIconData(itemType); if (iconData != null && !string.IsNullOrWhiteSpace(iconData.hintText)) return iconData.hintText; if (countsAsCleanupItem) return "연못이 조금 맑아졌다."; switch (itemType) { case FishingItemType.Fish: return "생선은 고양이들이 좋아할 것 같다."; case FishingItemType.RottenFish: return "냄새가 심하다. 쓸 수 있을까?"; case FishingItemType.JellyfishPowder: return "이상한 빛을 내는 가루다."; case FishingItemType.OldCompass: return "길을 찾는 데 도움이 될 것 같다."; case FishingItemType.MemoryPiece: return "잃어버린 기억의 일부다."; default: return string.Empty; } } public void ShowNotice(string message) { if (noticeRoutine != null) StopCoroutine(noticeRoutine); noticeRoutine = StartCoroutine(NoticeRoutine(message)); } private IEnumerator NoticeRoutine(string message) { if (memoryPieceNoticePanel != null) memoryPieceNoticePanel.SetActive(true); if (memoryPieceNoticeCanvasGroup != null) { memoryPieceNoticeCanvasGroup.alpha = 0f; memoryPieceNoticeCanvasGroup.interactable = false; memoryPieceNoticeCanvasGroup.blocksRaycasts = false; } if (noticeText != null) { noticeText.gameObject.SetActive(true); noticeText.text = message; } if (effects != null) { effects.FadeCanvasGroup(memoryPieceNoticePanel, memoryPieceNoticeCanvasGroup, true, panelFadeTime); if (memoryPieceNoticePanel != null) effects.Pop(memoryPieceNoticePanel.transform, panelPopScale, panelPopTime); } else if (memoryPieceNoticeCanvasGroup != null) { memoryPieceNoticeCanvasGroup.alpha = 1f; } if (noticeShowTime > 0f) yield return new WaitForSeconds(noticeShowTime); else yield return null; if (effects != null) effects.FadeCanvasGroup(memoryPieceNoticePanel, memoryPieceNoticeCanvasGroup, false, panelFadeTime); else if (memoryPieceNoticePanel != null) memoryPieceNoticePanel.SetActive(false); if (noticeText != null) noticeText.gameObject.SetActive(false); noticeRoutine = null; } public void HideNotice() { HideNotice(false); } private void HideNotice(bool instant) { if (noticeRoutine != null) { StopCoroutine(noticeRoutine); noticeRoutine = null; } if (instant || effects == null) { if (memoryPieceNoticePanel != null) memoryPieceNoticePanel.SetActive(false); if (memoryPieceNoticeCanvasGroup != null) { memoryPieceNoticeCanvasGroup.alpha = 0f; memoryPieceNoticeCanvasGroup.interactable = false; memoryPieceNoticeCanvasGroup.blocksRaycasts = false; } } else { effects.FadeCanvasGroup(memoryPieceNoticePanel, memoryPieceNoticeCanvasGroup, false, panelFadeTime); } if (noticeText != null) noticeText.gameObject.SetActive(false); } public void HideControllerGuide() { SetControllerGuideVisible(false, false); } public void SetControllerGuideVisible(bool visible) { SetControllerGuideVisible(visible, false); } private void SetControllerGuideVisible(bool visible, bool instant) { if (effects != null && !instant) { effects.FadeCanvasGroup(controllerGuidePanel, controllerGuideCanvasGroup, visible, panelFadeTime); } else { if (controllerGuidePanel != null) controllerGuidePanel.SetActive(visible); if (controllerGuideCanvasGroup != null) { controllerGuideCanvasGroup.alpha = visible ? 1f : 0f; controllerGuideCanvasGroup.interactable = visible; controllerGuideCanvasGroup.blocksRaycasts = visible; } } } public void SetCatchButtonVisible(bool visible) { if (catchButton != null) catchButton.SetActive(visible); } public void FlashFeedback(Color color) { if (feedbackFlash == null) return; if (effects != null) effects.Flash(feedbackFlash, color, flashMaxAlpha, flashTime); else { Color clear = color; clear.a = 0f; feedbackFlash.color = clear; feedbackFlash.gameObject.SetActive(false); } } public void FlashFeedbackForType(FishingGameManager.ResultType resultType) { switch (resultType) { case FishingGameManager.ResultType.Perfect: FlashFeedback(perfectColor); break; case FishingGameManager.ResultType.Good: FlashFeedback(goodColor); break; case FishingGameManager.ResultType.Miss: FlashFeedback(missColor); break; } } private void SetFeedbackFlashVisible(bool visible, Color color) { if (feedbackFlash == null) return; feedbackFlash.gameObject.SetActive(visible); feedbackFlash.color = color; } public void HideRoundResult() { if (resultRoutine != null) { StopCoroutine(resultRoutine); resultRoutine = null; } if (resultText != null) resultText.gameObject.SetActive(false); } private void SetGameplayUIVisible(bool visible) { SetGameObjectVisible(gameplayBackgroundRoot, visible); if (titleText != null) titleText.gameObject.SetActive(visible); if (pondStateText != null) pondStateText.gameObject.SetActive(visible); if (objectiveText != null) objectiveText.gameObject.SetActive(visible); SetGameObjectVisible(counterPanel, visible); SetGameObjectVisible(itemSlotPanel, visible); SetGameObjectVisible(cleanupGaugeRoot, visible); if (!visible) SetSimpleReelUIVisible(false); if (gaugeGroupRoot != null) gaugeGroupRoot.SetActive(visible); else if (gaugeGroupRect != null) gaugeGroupRect.gameObject.SetActive(visible); SetControllerGuideVisible(visible && showControllerGuideOnInitialize, true); SetCatchButtonVisible(visible && showCatchButtonOnInitialize); if (!visible) { HideRoundResult(); HideCaughtItem(true); HideNotice(true); SetFeedbackFlashVisible(false, Color.clear); } } private void SetGameObjectVisible(GameObject target, bool visible) { if (target != null) target.SetActive(visible); } public void ShowFinalResult(string text) { HideRoundResult(); if (hideCaughtItemOnFinalResult) HideCaughtItem(true); HideNotice(true); if (showOnlyFinalResult) { SetGameplayUIVisible(false); } else { if (hideCounterOnFinalResult) HideCounter(); if (hideGuideOnFinalResult) SetControllerGuideVisible(false, true); } if (finalResultPanel != null) finalResultPanel.SetActive(true); if (finalResultCanvasGroup != null) { finalResultCanvasGroup.alpha = 0f; finalResultCanvasGroup.interactable = true; finalResultCanvasGroup.blocksRaycasts = true; } if (finalResultText != null) { finalResultText.gameObject.SetActive(true); finalResultText.text = text; } if (effects != null) { effects.FadeCanvasGroup(finalResultPanel, finalResultCanvasGroup, true, panelFadeTime); if (finalResultPanel != null) effects.Pop(finalResultPanel.transform, finalPanelPopScale, finalPanelPopTime); } else if (finalResultCanvasGroup != null) { finalResultCanvasGroup.alpha = 1f; } } public void HideFinalResult() { HideFinalResult(true, false); } public void HideFinalResult(bool showCounterAfterHide) { HideFinalResult(showCounterAfterHide, false); } private void HideFinalResult(bool showCounterAfterHide, bool instant) { if (instant || effects == null) { if (finalResultPanel != null) finalResultPanel.SetActive(false); if (finalResultCanvasGroup != null) { finalResultCanvasGroup.alpha = 0f; finalResultCanvasGroup.interactable = false; finalResultCanvasGroup.blocksRaycasts = false; } } else { effects.FadeCanvasGroup(finalResultPanel, finalResultCanvasGroup, false, panelFadeTime); } if (finalResultText != null) finalResultText.gameObject.SetActive(false); if (showCounterAfterHide) { if (showOnlyFinalResult) { if (restoreGameplayUIAfterFinalResult) SetGameplayUIVisible(true); } else { ShowCounter(); } } } private GameObject FindGameObject(params string[] names) { Transform found = FindTransformByName(transform, names); return found != null ? found.gameObject : null; } private T FindComponentByName(params string[] names) where T : Component { Transform found = FindTransformByName(transform, names); return found != null ? found.GetComponent() : null; } private Transform FindTransformByName(Transform root, params string[] names) { if (root == null || names == null) return null; for (int i = 0; i < names.Length; i++) { Transform exact = FindTransformRecursive(root, names[i], false); if (exact != null) return exact; } for (int i = 0; i < names.Length; i++) { Transform normalized = FindTransformRecursive(root, names[i], true); if (normalized != null) return normalized; } return null; } private Transform FindTransformRecursive(Transform current, string targetName, bool normalize) { if (current == null || string.IsNullOrEmpty(targetName)) return null; string currentName = normalize ? NormalizeName(current.name) : current.name; string target = normalize ? NormalizeName(targetName) : targetName; if (currentName == target) return current; for (int i = 0; i < current.childCount; i++) { Transform found = FindTransformRecursive(current.GetChild(i), targetName, normalize); if (found != null) return found; } return null; } private string NormalizeName(string value) { return value.Replace(" ", string.Empty) .Replace("_", string.Empty) .Replace("-", string.Empty) .ToLowerInvariant(); } }