diff --git a/Assets/My project/Fishing Scripts/Prefabs.meta b/Assets/My project/Fishing Scripts/Prefabs.meta new file mode 100644 index 00000000..78c63d7a --- /dev/null +++ b/Assets/My project/Fishing Scripts/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1e1f71c106e40544a96417f581abeec7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/Prefabs/14.glb b/Assets/My project/Fishing Scripts/Prefabs/14.glb new file mode 100644 index 00000000..914c1102 Binary files /dev/null and b/Assets/My project/Fishing Scripts/Prefabs/14.glb differ diff --git a/Assets/My project/Fishing Scripts/Prefabs/14.glb.meta b/Assets/My project/Fishing Scripts/Prefabs/14.glb.meta new file mode 100644 index 00000000..88485447 --- /dev/null +++ b/Assets/My project/Fishing Scripts/Prefabs/14.glb.meta @@ -0,0 +1,28 @@ +fileFormatVersion: 2 +guid: b205275e9229b8b40bcfb446b9fa4cb4 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 715df9372183c47e389bb6e19fbc3b52, type: 3} + editorImportSettings: + generateSecondaryUVSet: 0 + importSettings: + nodeNameMethod: 1 + animationMethod: 2 + generateMipMaps: 1 + texturesReadable: 0 + defaultMinFilterMode: 9729 + defaultMagFilterMode: 9729 + anisotropicFilterLevel: 1 + instantiationSettings: + mask: -1 + layer: 0 + skinUpdateWhenOffscreen: 1 + lightIntensityFactor: 1 + sceneObjectCreation: 2 + assetDependencies: [] + reportItems: [] diff --git a/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab b/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab new file mode 100644 index 00000000..caa8e5c5 --- /dev/null +++ b/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56c8b7a1d030d16e34a454b3fa08469d16f67e2946a8c9314878f11488b9b27c +size 154142 diff --git a/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab.meta b/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab.meta new file mode 100644 index 00000000..67c53b66 --- /dev/null +++ b/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e9d8394fffa0b5b4e81c71ad0000f45a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs b/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs index b111ce28..3bb6daba 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using UnityEngine; using UnityEngine.InputSystem; @@ -11,12 +12,29 @@ public enum ResultType Miss } + [Header("Auto Bind")] + [Tooltip("현재 만든 Prefab 구조 기준으로 비어 있는 참조를 자동 연결합니다.")] + [SerializeField] private bool autoBindMissingReferences = true; + [Header("References")] + [Tooltip("낚시 UI 전체 루트입니다. UI를 자동으로 켜고 끄고 싶을 때만 연결하세요.")] + [SerializeField] private GameObject uiRoot; + [SerializeField] private FishingGaugeUI ui; [SerializeField] private FishingRewardSystem rewardSystem; [SerializeField] private FishingHapticManager haptic; [SerializeField] private RotateUI centerIconRotate; + [Header("XR Controller Input")] + [Tooltip("낚시 판정을 실행할 컨트롤러 입력입니다. 예: XRI Right Interaction / Select")] + [SerializeField] private InputActionReference submitAction; + + [Tooltip("낚시를 다시 시작할 컨트롤러 입력입니다. 필요 없으면 비워둬도 됩니다.")] + [SerializeField] private InputActionReference resetAction; + + [Tooltip("씬에 Input Action Manager가 없거나 액션이 자동 활성화되지 않으면 켜두세요.")] + [SerializeField] private bool enableInputActionsManually = true; + [Header("Pointer")] [SerializeField] private float pointerAngle; [SerializeField] private float pointerSpeed = 180f; @@ -30,29 +48,97 @@ public enum ResultType [SerializeField] private float maxZoneSize = 120f; [SerializeField] private float zoneCenter; - [Header("Rules")] + [Header("Catch Rules")] + [Tooltip("아이템 1개를 낚기 위해 필요한 성공 횟수입니다.")] [SerializeField] private int requiredSuccesses = 3; + + [Tooltip("아이템 1개를 낚는 동안 허용되는 실패 횟수입니다. 초과하면 낚싯줄이 끊어진 것으로 처리합니다.")] [SerializeField] private int allowedFails = 3; + [Header("Pond Cleanup Rules")] + [Tooltip("연못을 맑게 만들기 위해 필요한 쓰레기성 아이템 수입니다. 쓰레기, 병, 봉지가 여기에 포함됩니다.")] + [SerializeField] private int requiredCleanupItems = 3; + + [Tooltip("연못이 맑아진 뒤 다음 성공 낚시에서 기억의 조각을 확정으로 낚습니다.")] + [SerializeField] private bool guaranteeMemoryPieceAfterCleaned = true; + + [Tooltip("StartFishing을 호출할 때 쓰레기 수거/연못 상태를 초기화합니다.")] + [SerializeField] private bool resetPondStateOnStart = true; + [Header("Round Settings")] [SerializeField] private float nextRoundDelay = 0.35f; + [SerializeField] private float nextCatchDelay = 2.0f; [SerializeField] private bool randomDirectionEachRound = true; + [SerializeField] private bool resetDifficultyEachCatch = true; [SerializeField] private bool startOnAwake = true; + [Header("Final Result Settings")] + [Tooltip("최종 결과를 잠깐 보여준 뒤 uiRoot를 자동으로 끕니다. uiRoot가 비어 있으면 동작하지 않습니다.")] + [SerializeField] private bool hideUIRootAfterFinalResult = false; + + [SerializeField] private float finalResultShowTime = 1.5f; + [Header("Debug")] [SerializeField] private bool showDebugLog = true; private int successCount; private int failCount; + private int cleanupItemCount; + private int totalCaughtItems; + + // 임시 낚시 세션 카운트입니다. 나중에 공용 인벤토리와 연결할 때는 저장용으로 쓰지 말고 UI 표시용으로만 쓰세요. + private int sessionFishCount; + private int sessionTrashCount; + private int sessionMemoryPieceCount; + private int sessionCompassCount; private bool clockwise = true; private bool activeGame; private bool inputLocked; + private bool pondCleaned; + private bool memoryPieceCollected; + + private bool submitActionWasEnabled; + private bool resetActionWasEnabled; private Coroutine nextRoundRoutine; + private Coroutine nextCatchRoutine; + private Coroutine finalResultRoutine; + + public bool IsActiveGame => activeGame; + public bool IsPondCleaned => pondCleaned; + public bool IsMemoryPieceCollected => memoryPieceCollected; + public int SuccessCount => successCount; + public int FailCount => failCount; + public int CleanupItemCount => cleanupItemCount; + public int RequiredCleanupItems => requiredCleanupItems; + public int TotalCaughtItems => totalCaughtItems; + public int SessionFishCount => sessionFishCount; + public int SessionTrashCount => sessionTrashCount; + public int SessionMemoryPieceCount => sessionMemoryPieceCount; + public int SessionCompassCount => sessionCompassCount; + + public event Action ItemCaught; + + private void OnEnable() + { + RegisterInputAction(submitAction, OnSubmitAction, ref submitActionWasEnabled); + RegisterInputAction(resetAction, OnResetAction, ref resetActionWasEnabled); + } + + private void OnDisable() + { + UnregisterInputAction(submitAction, OnSubmitAction, submitActionWasEnabled); + UnregisterInputAction(resetAction, OnResetAction, resetActionWasEnabled); + } private void Start() { + if (autoBindMissingReferences) + AutoBindMissingReferences(); + + ValidateRuntimeSettings(); + if (startOnAwake) { StartFishing(); @@ -74,13 +160,137 @@ private void Update() RotatePointer(); } + private void OnValidate() + { + ValidateRuntimeSettings(); + } + + + + [ContextMenu("Auto Bind Fishing References")] + public void AutoBindMissingReferences() + { + Transform searchRoot = GetSearchRoot(); + + if (uiRoot == null) + { + Transform canvasTransform = FindTransformRecursive(searchRoot, "FishingCanvas"); + if (canvasTransform != null) + uiRoot = canvasTransform.gameObject; + } + + if (ui == null) + ui = searchRoot != null ? searchRoot.GetComponentInChildren(true) : GetComponentInChildren(true); + + if (rewardSystem == null) + rewardSystem = searchRoot != null ? searchRoot.GetComponentInChildren(true) : GetComponentInChildren(true); + + if (haptic == null) + haptic = searchRoot != null ? searchRoot.GetComponentInChildren(true) : GetComponentInChildren(true); + + if (centerIconRotate == null) + { + Transform centerIconTransform = FindTransformRecursive(searchRoot, "CenterIcon"); + if (centerIconTransform != null) + centerIconRotate = centerIconTransform.GetComponent(); + + if (centerIconRotate == null && searchRoot != null) + centerIconRotate = searchRoot.GetComponentInChildren(true); + } + } + + private Transform GetSearchRoot() + { + Transform current = transform; + + while (current.parent != null) + current = current.parent; + + return current; + } + + private Transform FindTransformRecursive(Transform current, string targetName) + { + if (current == null || string.IsNullOrEmpty(targetName)) + return null; + + if (NormalizeName(current.name) == NormalizeName(targetName)) + return current; + + for (int i = 0; i < current.childCount; i++) + { + Transform found = FindTransformRecursive(current.GetChild(i), targetName); + if (found != null) + return found; + } + + return null; + } + + private string NormalizeName(string value) + { + return value.Replace(" ", string.Empty) + .Replace("_", string.Empty) + .Replace("-", string.Empty) + .ToLowerInvariant(); + } + + private void RegisterInputAction(InputActionReference actionReference, System.Action callback, ref bool wasEnabled) + { + if (actionReference == null || actionReference.action == null) + return; + + wasEnabled = actionReference.action.enabled; + actionReference.action.performed += callback; + + if (enableInputActionsManually && !wasEnabled) + actionReference.action.Enable(); + } + + private void UnregisterInputAction(InputActionReference actionReference, System.Action callback, bool wasEnabled) + { + if (actionReference == null || actionReference.action == null) + return; + + actionReference.action.performed -= callback; + + if (enableInputActionsManually && !wasEnabled) + actionReference.action.Disable(); + } + + private void ValidateRuntimeSettings() + { + requiredSuccesses = Mathf.Max(1, requiredSuccesses); + allowedFails = Mathf.Max(1, allowedFails); + requiredCleanupItems = Mathf.Max(1, requiredCleanupItems); + + minSpeed = Mathf.Max(1f, minSpeed); + maxSpeed = Mathf.Max(minSpeed, maxSpeed); + pointerSpeed = Mathf.Clamp(pointerSpeed, minSpeed, maxSpeed); + + minZoneSize = Mathf.Clamp(minZoneSize, 1f, 360f); + maxZoneSize = Mathf.Clamp(maxZoneSize, minZoneSize, 360f); + startZoneSize = Mathf.Clamp(startZoneSize, minZoneSize, maxZoneSize); + zoneSize = Mathf.Clamp(zoneSize, minZoneSize, maxZoneSize); + + nextRoundDelay = Mathf.Max(0f, nextRoundDelay); + nextCatchDelay = Mathf.Max(0f, nextCatchDelay); + finalResultShowTime = Mathf.Max(0f, finalResultShowTime); + + pointerAngle = Normalize(pointerAngle); + zoneCenter = Normalize(zoneCenter); + cleanupItemCount = Mathf.Max(0, cleanupItemCount); + totalCaughtItems = Mathf.Max(0, totalCaughtItems); + } + private void InitializeIdleUI() { if (ui != null) { - ui.HideRoundResult(); - ui.HideFinalResult(); + ui.InitializeFishingUI(); ui.UpdateCounter(0, requiredSuccesses, 0, allowedFails); + ui.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems); + ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount, sessionCompassCount); ui.SetPointerRotation(0f); ui.SetZone(0f, startZoneSize); } @@ -91,30 +301,73 @@ private void InitializeIdleUI() public void StartFishing() { - if (nextRoundRoutine != null) - { - StopCoroutine(nextRoundRoutine); - nextRoundRoutine = null; - } + ValidateRuntimeSettings(); + StopRunningRoutines(); - successCount = 0; - failCount = 0; + if (uiRoot != null) + uiRoot.SetActive(true); - pointerAngle = 0f; - pointerSpeed = minSpeed; - zoneSize = startZoneSize; - - activeGame = true; - inputLocked = false; - - clockwise = Random.value > 0.5f; + if (resetPondStateOnStart) + ResetPondState(); if (ui != null) + ui.InitializeFishingUI(); + + StartNewCatchAttempt(); + + if (showDebugLog) + Debug.Log("기묘한 낚시터 시작"); + } + + public void StopFishing(bool hideUI = false) + { + StopRunningRoutines(); + + activeGame = false; + inputLocked = true; + + if (centerIconRotate != null) + centerIconRotate.StopRotate(); + + if (hideUI && uiRoot != null) + uiRoot.SetActive(false); + } + + public void ResetFishing() + { + StartFishing(); + } + + public void ResetPondState() + { + successCount = 0; + failCount = 0; + cleanupItemCount = 0; + totalCaughtItems = 0; + sessionFishCount = 0; + sessionTrashCount = 0; + sessionMemoryPieceCount = 0; + sessionCompassCount = 0; + pondCleaned = false; + memoryPieceCollected = false; + } + + private void StartNewCatchAttempt() + { + successCount = 0; + failCount = 0; + inputLocked = false; + activeGame = true; + + if (resetDifficultyEachCatch) { - ui.HideRoundResult(); - ui.HideFinalResult(); + pointerAngle = 0f; + pointerSpeed = minSpeed; + zoneSize = startZoneSize; } + clockwise = UnityEngine.Random.value > 0.5f; + if (centerIconRotate != null) { centerIconRotate.ResetRotation(); @@ -123,9 +376,27 @@ public void StartFishing() RandomizeZone(); UpdateUI(); + } - if (showDebugLog) - Debug.Log(" "); + private void StopRunningRoutines() + { + if (nextRoundRoutine != null) + { + StopCoroutine(nextRoundRoutine); + nextRoundRoutine = null; + } + + if (nextCatchRoutine != null) + { + StopCoroutine(nextCatchRoutine); + nextCatchRoutine = null; + } + + if (finalResultRoutine != null) + { + StopCoroutine(finalResultRoutine); + finalResultRoutine = null; + } } private void RotatePointer() @@ -149,8 +420,17 @@ public void SubmitAttempt() inputLocked = true; + if (ui != null) + ui.HideControllerGuide(); + ResultType result = EvaluateResult(); + if (ui != null) + { + ui.ShowResultForType(result); + ui.FlashFeedbackForType(result); + } + switch (result) { case ResultType.Perfect: @@ -168,7 +448,7 @@ public void SubmitAttempt() if (successCount >= requiredSuccesses) { - FishingSuccess(); + CatchItemSuccess(); return; } @@ -207,11 +487,8 @@ private void OnPerfect() if (haptic != null) haptic.Perfect(); - if (ui != null) - ui.ShowResult("Ϻ!"); - if (showDebugLog) - Debug.Log(" : Perfect"); + Debug.Log("낚시 판정: Perfect"); ClampDifficulty(); UpdateUI(); @@ -227,11 +504,8 @@ private void OnGood() if (haptic != null) haptic.Good(); - if (ui != null) - ui.ShowResult("!"); - if (showDebugLog) - Debug.Log(" : Good"); + Debug.Log("낚시 판정: Good"); ClampDifficulty(); UpdateUI(); @@ -247,11 +521,8 @@ private void OnMiss() if (haptic != null) haptic.Miss(); - if (ui != null) - ui.ShowResult("!"); - if (showDebugLog) - Debug.Log(" : Miss"); + Debug.Log("낚시 판정: Miss"); ClampDifficulty(); UpdateUI(); @@ -262,10 +533,13 @@ private IEnumerator NextRoundRoutine() yield return new WaitForSeconds(nextRoundDelay); if (!activeGame) + { + nextRoundRoutine = null; yield break; + } if (randomDirectionEachRound) - clockwise = Random.value > 0.5f; + clockwise = UnityEngine.Random.value > 0.5f; RandomizeZone(); UpdateUI(); @@ -274,6 +548,122 @@ private IEnumerator NextRoundRoutine() nextRoundRoutine = null; } + private void CatchItemSuccess() + { + activeGame = false; + inputLocked = true; + + if (centerIconRotate != null) + centerIconRotate.StopRotate(); + + bool forceMemoryPiece = pondCleaned && guaranteeMemoryPieceAfterCleaned && !memoryPieceCollected; + + FishingRewardSystem.CatchResult catchResult; + + if (rewardSystem != null) + catchResult = rewardSystem.RollCatch(pondCleaned, forceMemoryPiece); + else + catchResult = new FishingRewardSystem.CatchResult(FishingItemType.Fish, "생선", false, false); + + ApplyCatchResult(catchResult); + } + + private void ApplyCatchResult(FishingRewardSystem.CatchResult catchResult) + { + totalCaughtItems++; + + AddToSessionCounts(catchResult.ItemType); + ItemCaught?.Invoke(catchResult.ItemType, 1); + + bool pondJustCleaned = false; + string extraHint = string.Empty; + + if (catchResult.CountsAsCleanupItem) + { + cleanupItemCount = Mathf.Min(cleanupItemCount + 1, requiredCleanupItems); + extraHint = $"연못 정화 {cleanupItemCount}/{requiredCleanupItems}"; + + if (!pondCleaned && cleanupItemCount >= requiredCleanupItems) + { + pondCleaned = true; + pondJustCleaned = true; + extraHint = "연못이 맑아졌다. 기억의 조각이 모습을 드러낸다."; + } + } + + if (catchResult.IsMemoryPiece) + { + memoryPieceCollected = true; + extraHint = "잃어버린 기억의 일부다."; + } + + UpdateUI(); + + if (ui != null) + { + ui.HighlightSlotForItem(catchResult.ItemType); + + if (!catchResult.IsMemoryPiece) + ui.ShowCaughtItem(catchResult.ItemType, catchResult.DisplayName, catchResult.CountsAsCleanupItem, extraHint); + + if (pondJustCleaned) + ui.ShowNotice("연못이 맑아졌다!\n기억의 조각이 모습을 드러냈다!"); + + // 기억의 조각 획득은 FinalResultPanel에서 크게 보여줍니다. + // NoticePanel까지 동시에 띄우면 ShowFinalResult에서 바로 숨겨져 연출이 겹칠 수 있습니다. + } + + if (showDebugLog) + Debug.Log($"{catchResult.DisplayName}을(를) 낚았다!"); + + if (catchResult.IsMemoryPiece) + { + FishingSuccess(); + return; + } + + nextCatchRoutine = StartCoroutine(NextCatchRoutine()); + } + + private IEnumerator NextCatchRoutine() + { + yield return new WaitForSeconds(nextCatchDelay); + + if (memoryPieceCollected) + { + nextCatchRoutine = null; + yield break; + } + + StartNewCatchAttempt(); + nextCatchRoutine = null; + } + + + private void AddToSessionCounts(FishingItemType itemType) + { + switch (itemType) + { + case FishingItemType.Fish: + sessionFishCount++; + break; + + case FishingItemType.Trash: + case FishingItemType.Bottle: + case FishingItemType.PlasticBag: + sessionTrashCount++; + break; + + case FishingItemType.MemoryPiece: + sessionMemoryPieceCount++; + break; + + case FishingItemType.OldCompass: + sessionCompassCount++; + break; + } + } + private void ClampDifficulty() { zoneSize = Mathf.Clamp(zoneSize, minZoneSize, maxZoneSize); @@ -282,7 +672,7 @@ private void ClampDifficulty() private void RandomizeZone() { - zoneCenter = Random.Range(0f, 360f); + zoneCenter = UnityEngine.Random.Range(0f, 360f); } private void UpdateUI() @@ -292,6 +682,8 @@ private void UpdateUI() ui.SetZone(zoneCenter, zoneSize); ui.UpdateCounter(successCount, requiredSuccesses, failCount, allowedFails); + ui.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems); + ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount, sessionCompassCount); } private void FishingSuccess() @@ -302,14 +694,15 @@ private void FishingSuccess() if (centerIconRotate != null) centerIconRotate.StopRotate(); - if (ui != null) - ui.ShowFinalResult(" !"); + UpdateUI(); - if (rewardSystem != null) - rewardSystem.GiveReward(); + if (ui != null) + ui.ShowFinalResult("기억의 조각 획득!\n기묘한 낚시터 클리어!"); if (showDebugLog) - Debug.Log(" "); + Debug.Log("기묘한 낚시터 클리어"); + + BeginFinalResultRoutineIfNeeded(); } private void FishingFail() @@ -321,15 +714,33 @@ private void FishingFail() centerIconRotate.StopRotate(); if (ui != null) - ui.ShowFinalResult(" !"); + ui.ShowFinalResult("낚싯줄이 끊어졌다!\n다시 천천히 낚아보자."); if (showDebugLog) - Debug.Log(" "); + Debug.Log("낚시 실패: 낚싯줄 끊어짐"); + + BeginFinalResultRoutineIfNeeded(); } - public void ResetFishing() + private void BeginFinalResultRoutineIfNeeded() { - StartFishing(); + if (!hideUIRootAfterFinalResult || uiRoot == null) + return; + + if (finalResultRoutine != null) + StopCoroutine(finalResultRoutine); + + finalResultRoutine = StartCoroutine(HideUIRootAfterFinalResultRoutine()); + } + + private IEnumerator HideUIRootAfterFinalResultRoutine() + { + yield return new WaitForSeconds(finalResultShowTime); + + if (uiRoot != null) + uiRoot.SetActive(false); + + finalResultRoutine = null; } private float Normalize(float angle) @@ -342,17 +753,13 @@ private float Normalize(float angle) return angle; } - // XR Input Action Select / Trigger ư - public void OnSubmit(InputValue value) + private void OnSubmitAction(InputAction.CallbackContext context) { - if (value.isPressed) - SubmitAttempt(); + SubmitAttempt(); } - // Reset ư - public void OnReset(InputValue value) + private void OnResetAction(InputAction.CallbackContext context) { - if (value.isPressed) - ResetFishing(); + ResetFishing(); } -} \ No newline at end of file +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs b/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs index c0a7969d..1358d03b 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs @@ -1,39 +1,348 @@ +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("Panel UI")] + [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("Gauge UI")] - [SerializeField] private RectTransform pointerPivot; - [SerializeField] private Image successZone; + [Header("Top Texts")] + [SerializeField] private TMP_Text titleText; + [SerializeField] private TMP_Text pondStateText; + [SerializeField] private TMP_Text objectiveText; - [Header("Text UI")] + [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("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("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")] - [Tooltip(" ̹ ġ ߳ ϼ.")] [SerializeField] private float zoneVisualOffset = 0f; - [Header("Result Settings")] - [SerializeField] private float resultShowTime = 1f; - [SerializeField] private bool hideCounterOnFinalResult = true; + [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() { - HideRoundResult(); - HideFinalResult(); + 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 (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); + + 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) @@ -64,19 +373,213 @@ public void SetZoneRotation(float centerAngle, float size) return; float startAngle = centerAngle - size * 0.5f + zoneVisualOffset; - successZone.rectTransform.localEulerAngles = - new Vector3(0f, 0f, -startAngle); + successZone.rectTransform.localEulerAngles = new Vector3(0f, 0f, -startAngle); } public void UpdateCounter(int success, int successTarget, int fail, int failTarget) { ShowCounter(); - if (successText != null) - successText.text = $" {success}/{successTarget}"; + if (catchCountText != null) + catchCountText.text = $"낚기 {success}/{successTarget}"; + + if (successText != null && successText != catchCountText) + successText.text = $"낚기 {success}/{successTarget}"; if (failText != null) - failText.text = $" {fail}/{failTarget}"; + failText.text = $"실패 {fail}/{failTarget}"; + } + + 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() @@ -91,7 +594,24 @@ public void HideCounter() 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; @@ -99,20 +619,391 @@ public void ShowResult(string text) if (resultRoutine != null) StopCoroutine(resultRoutine); - resultRoutine = StartCoroutine(ResultRoutine(text)); + resultRoutine = StartCoroutine(ResultRoutine(text, color)); } - private IEnumerator ResultRoutine(string text) + 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; - yield return new WaitForSeconds(resultShowTime); + 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; + } + 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; + } + } + + 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) @@ -125,31 +1016,198 @@ public void HideRoundResult() 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 (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 (hideCounterOnFinalResult) - HideCounter(); + 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() { - if (finalResultPanel != null) - finalResultPanel.SetActive(false); + 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); - ShowCounter(); + if (showCounterAfterHide) + { + if (showOnlyFinalResult) + SetGameplayUIVisible(true); + else + ShowCounter(); + } } -} \ No newline at end of file + + 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(); + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingHapticManager.cs b/Assets/My project/Fishing Scripts/UI/FishingHapticManager.cs index df257072..55e5f40f 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingHapticManager.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingHapticManager.cs @@ -8,6 +8,7 @@ public class FishingHapticManager : MonoBehaviour [Header("Haptic Settings")] [SerializeField] private bool useHaptic = true; + [SerializeField] private bool showHapticDebugLog = false; [SerializeField] private float perfectAmplitude = 1f; [SerializeField] private float perfectDuration = 0.2f; @@ -18,6 +19,19 @@ public class FishingHapticManager : MonoBehaviour [SerializeField] private float missAmplitude = 0.2f; [SerializeField] private float missDuration = 0.05f; + public XRNode TargetHand => targetHand; + + private void OnValidate() + { + perfectAmplitude = Mathf.Clamp01(perfectAmplitude); + goodAmplitude = Mathf.Clamp01(goodAmplitude); + missAmplitude = Mathf.Clamp01(missAmplitude); + + perfectDuration = Mathf.Max(0f, perfectDuration); + goodDuration = Mathf.Max(0f, goodDuration); + missDuration = Mathf.Max(0f, missDuration); + } + private void SendHaptic(float amplitude, float duration) { if (!useHaptic) @@ -26,15 +40,46 @@ private void SendHaptic(float amplitude, float duration) InputDevice device = InputDevices.GetDeviceAtXRNode(targetHand); if (!device.isValid) - return; - - if (device.TryGetHapticCapabilities(out HapticCapabilities capabilities)) { - if (!capabilities.supportsImpulse) - return; + LogHapticWarning("Haptic device not found."); + return; } - device.SendHapticImpulse(0u, Mathf.Clamp01(amplitude), duration); + if (!device.TryGetHapticCapabilities(out HapticCapabilities capabilities)) + { + LogHapticWarning("Haptic capabilities not available."); + return; + } + + if (!capabilities.supportsImpulse) + { + LogHapticWarning("Haptic impulse is not supported on this device."); + return; + } + + if (capabilities.numChannels < 1) + { + LogHapticWarning("Haptic channel count is zero."); + return; + } + + device.SendHapticImpulse(0u, Mathf.Clamp01(amplitude), Mathf.Max(0f, duration)); + } + + private void LogHapticWarning(string message) + { + if (showHapticDebugLog) + Debug.LogWarning($"[FishingHapticManager] {message}", this); + } + + public void SetTargetHand(XRNode hand) + { + targetHand = hand; + } + + public void SetUseHaptic(bool value) + { + useHaptic = value; } public void Perfect() @@ -51,4 +96,4 @@ public void Miss() { SendHaptic(missAmplitude, missDuration); } -} \ No newline at end of file +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingInventory.cs b/Assets/My project/Fishing Scripts/UI/FishingInventory.cs new file mode 100644 index 00000000..11f421b5 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingInventory.cs @@ -0,0 +1,178 @@ +using UnityEngine; + +public class FishingInventory : MonoBehaviour +{ + [Header("Item Counts")] + [SerializeField] private int fishCount; + [SerializeField] private int rottenFishCount; + [SerializeField] private int trashCount; + [SerializeField] private int bottleCount; + [SerializeField] private int plasticBagCount; + [SerializeField] private int jellyfishPowderCount; + [SerializeField] private int oldCompassCount; + [SerializeField] private int memoryPieceCount; + + public int FishCount => fishCount; + public int RottenFishCount => rottenFishCount; + public int TrashCount => trashCount; + public int BottleCount => bottleCount; + public int PlasticBagCount => plasticBagCount; + public int JellyfishPowderCount => jellyfishPowderCount; + public int OldCompassCount => oldCompassCount; + public int MemoryPieceCount => memoryPieceCount; + + private void OnValidate() + { + ClampCounts(); + } + + private void ClampCounts() + { + fishCount = Mathf.Max(0, fishCount); + rottenFishCount = Mathf.Max(0, rottenFishCount); + trashCount = Mathf.Max(0, trashCount); + bottleCount = Mathf.Max(0, bottleCount); + plasticBagCount = Mathf.Max(0, plasticBagCount); + jellyfishPowderCount = Mathf.Max(0, jellyfishPowderCount); + oldCompassCount = Mathf.Max(0, oldCompassCount); + memoryPieceCount = Mathf.Max(0, memoryPieceCount); + } + + public void AddItem(FishingItemType itemType, int amount = 1) + { + if (itemType == FishingItemType.None || amount <= 0) + return; + + switch (itemType) + { + case FishingItemType.Fish: + fishCount += amount; + break; + + case FishingItemType.RottenFish: + rottenFishCount += amount; + break; + + case FishingItemType.Trash: + trashCount += amount; + break; + + case FishingItemType.Bottle: + bottleCount += amount; + break; + + case FishingItemType.PlasticBag: + plasticBagCount += amount; + break; + + case FishingItemType.JellyfishPowder: + jellyfishPowderCount += amount; + break; + + case FishingItemType.OldCompass: + oldCompassCount += amount; + break; + + case FishingItemType.MemoryPiece: + memoryPieceCount += amount; + break; + } + } + + public bool HasItem(FishingItemType itemType, int amount = 1) + { + if (itemType == FishingItemType.None || amount <= 0) + return false; + + return GetItemCount(itemType) >= amount; + } + + public bool ConsumeItem(FishingItemType itemType, int amount = 1) + { + if (!HasItem(itemType, amount)) + return false; + + switch (itemType) + { + case FishingItemType.Fish: + fishCount -= amount; + break; + + case FishingItemType.RottenFish: + rottenFishCount -= amount; + break; + + case FishingItemType.Trash: + trashCount -= amount; + break; + + case FishingItemType.Bottle: + bottleCount -= amount; + break; + + case FishingItemType.PlasticBag: + plasticBagCount -= amount; + break; + + case FishingItemType.JellyfishPowder: + jellyfishPowderCount -= amount; + break; + + case FishingItemType.OldCompass: + oldCompassCount -= amount; + break; + + case FishingItemType.MemoryPiece: + memoryPieceCount -= amount; + break; + } + + return true; + } + + public int GetItemCount(FishingItemType itemType) + { + switch (itemType) + { + case FishingItemType.Fish: + return fishCount; + + case FishingItemType.RottenFish: + return rottenFishCount; + + case FishingItemType.Trash: + return trashCount; + + case FishingItemType.Bottle: + return bottleCount; + + case FishingItemType.PlasticBag: + return plasticBagCount; + + case FishingItemType.JellyfishPowder: + return jellyfishPowderCount; + + case FishingItemType.OldCompass: + return oldCompassCount; + + case FishingItemType.MemoryPiece: + return memoryPieceCount; + + case FishingItemType.None: + default: + return 0; + } + } + + public void ResetInventory() + { + fishCount = 0; + rottenFishCount = 0; + trashCount = 0; + bottleCount = 0; + plasticBagCount = 0; + jellyfishPowderCount = 0; + oldCompassCount = 0; + memoryPieceCount = 0; + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingInventory.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingInventory.cs.meta new file mode 100644 index 00000000..9d7b279c --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingInventory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: df5f5119d6ecc8746a4ee16c9f1b50cd \ No newline at end of file diff --git a/Assets/My project/Fishing Scripts/UI/FishingItemType.cs b/Assets/My project/Fishing Scripts/UI/FishingItemType.cs new file mode 100644 index 00000000..a95ffe54 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingItemType.cs @@ -0,0 +1,12 @@ +public enum FishingItemType +{ + None, + Fish, + RottenFish, + Trash, + Bottle, + PlasticBag, + JellyfishPowder, + OldCompass, + MemoryPiece +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingItemType.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingItemType.cs.meta new file mode 100644 index 00000000..c12f1b74 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingItemType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6365b670fd7854c4bb6024c911f30c5c \ No newline at end of file diff --git a/Assets/My project/Fishing Scripts/UI/FishingModelLineFollower.cs b/Assets/My project/Fishing Scripts/UI/FishingModelLineFollower.cs new file mode 100644 index 00000000..87868294 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingModelLineFollower.cs @@ -0,0 +1,350 @@ +using UnityEngine; + +/// +/// 모델 낚시줄을 두 포인트 사이에 맞춰 배치/회전/길이 스케일하는 스크립트입니다. +/// 권장 사용 방식: +/// - StretchLineRoot 빈 오브젝트에 이 스크립트를 붙입니다. +/// - StretchLineRoot 자식으로 '늘어나는 중간 줄 모델'만 넣습니다. +/// - RodFixedLinePart, BobberFixedLinePart, Bobber/Hook은 StretchLineRoot 안에 넣지 않습니다. +/// - 낚시찌는 BobberLineStartPoint의 자식으로 넣어 줄 끝점에 고정합니다. +/// +public class FishingModelLineFollower : MonoBehaviour +{ + public enum LineUseMode + { + WholeModelLine, + StretchSegmentOnly + } + + public enum LineAxis + { + LocalX, + LocalY, + LocalZ + } + + public enum PivotMode + { + Center, + Start, + End + } + + [Header("Mode")] + [Tooltip("WholeModelLine: 전체 줄 모델을 두 포인트 사이에 맞춤 / StretchSegmentOnly: 분리된 중간 줄 조각만 늘리는 권장 모드")] + [SerializeField] private LineUseMode lineUseMode = LineUseMode.StretchSegmentOnly; + + [Header("Line Targets")] + [Tooltip("늘어나는 줄의 시작점입니다. StretchSegmentOnly에서는 RodLineEndPoint를 연결하세요.")] + [SerializeField] private Transform rodTipPoint; + + [Tooltip("늘어나는 줄의 끝점입니다. StretchSegmentOnly에서는 BobberLineStartPoint를 연결하세요.")] + [SerializeField] private Transform bobberPoint; + + [Header("Stretch Settings")] + [Tooltip("모델 줄의 길이 방향 축입니다. 보통 LocalY부터 테스트하세요.")] + [SerializeField] private LineAxis lineAxis = LineAxis.LocalY; + + [Tooltip("모델 줄의 원본 길이입니다. 줄이 너무 길게 늘어나면 값을 키우고, 너무 짧으면 값을 줄이세요.")] + [Min(0.0001f)] + [SerializeField] private float originalLength = 1f; + + [Tooltip("두 포인트가 너무 가까울 때 사용할 최소 길이입니다.")] + [Min(0.0001f)] + [SerializeField] private float minLength = 0.03f; + + [Tooltip("줄 두께 배율입니다. 1이면 원본 두께를 유지합니다.")] + [Min(0.0001f)] + [SerializeField] private float thicknessScale = 1f; + + [Tooltip("모델 줄의 앞뒤 방향이 거꾸로 보일 때 켜세요. 포인트 연결은 바꾸지 않는 것을 추천합니다.")] + [SerializeField] private bool reverseVisualDirection; + + [Tooltip("대부분 Center를 권장합니다. 모델 Pivot이 시작점/끝점에 있으면 Start 또는 End를 테스트하세요.")] + [SerializeField] private PivotMode pivotMode = PivotMode.Center; + + [Header("Visual Mesh Correction")] + [Tooltip("실제 Mesh 자식입니다. 비워두면 첫 번째 Renderer가 있는 자식을 자동으로 찾습니다. 위치 보정은 Root가 아니라 이 Visual에 적용하세요.")] + [SerializeField] private Transform visualRoot; + + [Tooltip("Visual Mesh의 로컬 위치 보정값입니다. 줄이 살짝 위/옆으로 떠 있을 때 작은 값으로만 보정하세요.")] + [SerializeField] private Vector3 visualLocalPositionOffset; + + [Tooltip("Visual Mesh의 로컬 회전 보정값입니다. 모델 자체가 90도 틀어져 있을 때 사용하세요.")] + [SerializeField] private Vector3 visualLocalRotationOffsetEuler; + + [Header("Root Rotation Correction")] + [Tooltip("루트 회전 보정값입니다. 가능하면 0,0,0으로 두고 Line Axis / Reverse Visual Direction / Visual Rotation Offset으로 먼저 맞추세요.")] + [SerializeField] private Vector3 rootRotationOffsetEuler; + + [Header("Visibility / Update")] + [SerializeField] private bool hideWhenMissingTarget = true; + [SerializeField] private bool hideWhenTooShort = false; + [SerializeField] private bool updateInLateUpdate = true; + + [Header("Debug")] + [SerializeField] private bool drawDebugLine = true; + [SerializeField] private Color debugLineColor = Color.cyan; + + private Vector3 initialLocalScale = Vector3.one; + private Vector3 visualInitialLocalPosition; + private Quaternion visualInitialLocalRotation = Quaternion.identity; + private bool initialized; + + public LineUseMode CurrentLineUseMode => lineUseMode; + public Transform RodTipPoint => rodTipPoint; + public Transform BobberPoint => bobberPoint; + + private void Awake() + { + InitializeIfNeeded(); + } + + private void Reset() + { + lineUseMode = LineUseMode.StretchSegmentOnly; + lineAxis = LineAxis.LocalY; + originalLength = 1f; + minLength = 0.03f; + thicknessScale = 1f; + pivotMode = PivotMode.Center; + updateInLateUpdate = true; + drawDebugLine = true; + TryAutoFindVisualRoot(); + } + + private void Update() + { + if (!updateInLateUpdate) + UpdateLine(); + } + + private void LateUpdate() + { + if (updateInLateUpdate) + UpdateLine(); + } + + private void OnValidate() + { + originalLength = Mathf.Max(0.0001f, originalLength); + minLength = Mathf.Max(0.0001f, minLength); + thicknessScale = Mathf.Max(0.0001f, thicknessScale); + + if (!Application.isPlaying) + TryAutoFindVisualRoot(); + } + + private void InitializeIfNeeded() + { + if (initialized) + return; + + initialLocalScale = transform.localScale; + + if (visualRoot == null) + TryAutoFindVisualRoot(); + + if (visualRoot != null) + { + visualInitialLocalPosition = visualRoot.localPosition; + visualInitialLocalRotation = visualRoot.localRotation; + } + + initialized = true; + } + + private void UpdateLine() + { + InitializeIfNeeded(); + + if (rodTipPoint == null || bobberPoint == null) + { + SetVisible(!hideWhenMissingTarget); + return; + } + + Vector3 start = rodTipPoint.position; + Vector3 end = bobberPoint.position; + Vector3 segment = end - start; + float distance = segment.magnitude; + + if (distance < minLength) + { + if (hideWhenTooShort) + { + SetVisible(false); + return; + } + + distance = minLength; + } + + Vector3 direction = segment.sqrMagnitude > 0.000001f ? segment.normalized : transform.forward; + Vector3 visualDirection = reverseVisualDirection ? -direction : direction; + + SetVisible(true); + + transform.position = GetRootPosition(start, end); + transform.rotation = Quaternion.FromToRotation(GetAxisVector(lineAxis), visualDirection) * Quaternion.Euler(rootRotationOffsetEuler); + transform.localScale = GetScaledVector(distance); + + ApplyVisualCorrection(); + + if (drawDebugLine) + Debug.DrawLine(start, end, debugLineColor); + } + + private Vector3 GetRootPosition(Vector3 start, Vector3 end) + { + switch (pivotMode) + { + case PivotMode.Start: + return start; + case PivotMode.End: + return end; + case PivotMode.Center: + default: + return (start + end) * 0.5f; + } + } + + private Vector3 GetScaledVector(float targetLength) + { + float lengthScale = targetLength / Mathf.Max(0.0001f, originalLength); + + Vector3 scale = initialLocalScale; + + switch (lineAxis) + { + case LineAxis.LocalX: + scale.x = initialLocalScale.x * lengthScale; + scale.y = initialLocalScale.y * thicknessScale; + scale.z = initialLocalScale.z * thicknessScale; + break; + + case LineAxis.LocalY: + scale.x = initialLocalScale.x * thicknessScale; + scale.y = initialLocalScale.y * lengthScale; + scale.z = initialLocalScale.z * thicknessScale; + break; + + case LineAxis.LocalZ: + scale.x = initialLocalScale.x * thicknessScale; + scale.y = initialLocalScale.y * thicknessScale; + scale.z = initialLocalScale.z * lengthScale; + break; + } + + return scale; + } + + private static Vector3 GetAxisVector(LineAxis axis) + { + switch (axis) + { + case LineAxis.LocalX: + return Vector3.right; + case LineAxis.LocalY: + return Vector3.up; + case LineAxis.LocalZ: + return Vector3.forward; + default: + return Vector3.up; + } + } + + private void ApplyVisualCorrection() + { + if (visualRoot == null) + return; + + visualRoot.localPosition = visualInitialLocalPosition + visualLocalPositionOffset; + visualRoot.localRotation = visualInitialLocalRotation * Quaternion.Euler(visualLocalRotationOffsetEuler); + } + + private void SetVisible(bool visible) + { + if (visualRoot != null) + { + SetRenderersVisible(visualRoot, visible); + return; + } + + SetRenderersVisible(transform, visible); + } + + private void SetRenderersVisible(Transform target, bool visible) + { + if (target == null) + return; + + Renderer[] renderers = target.GetComponentsInChildren(true); + for (int i = 0; i < renderers.Length; i++) + renderers[i].enabled = visible; + } + + private void TryAutoFindVisualRoot() + { + if (visualRoot != null) + return; + + Renderer directRenderer = GetComponent(); + if (directRenderer != null) + { + visualRoot = transform; + return; + } + + Renderer childRenderer = GetComponentInChildren(true); + if (childRenderer != null) + visualRoot = childRenderer.transform; + } + + [ContextMenu("Set Mode: Stretch Segment Only")] + private void SetStretchSegmentOnlyMode() + { + lineUseMode = LineUseMode.StretchSegmentOnly; + } + + [ContextMenu("Reset Corrections")] + private void ResetCorrections() + { + rootRotationOffsetEuler = Vector3.zero; + visualLocalPositionOffset = Vector3.zero; + visualLocalRotationOffsetEuler = Vector3.zero; + } + + [ContextMenu("Calibrate Original Length From Renderer Bounds")] + private void CalibrateOriginalLengthFromRendererBounds() + { + Renderer renderer = visualRoot != null ? visualRoot.GetComponentInChildren(true) : GetComponentInChildren(true); + if (renderer == null) + { + Debug.LogWarning("FishingModelLineFollower: Renderer를 찾지 못해서 Original Length를 자동 계산할 수 없습니다.", this); + return; + } + + Vector3 size = renderer.bounds.size; + float measured = Mathf.Max(size.x, Mathf.Max(size.y, size.z)); + originalLength = Mathf.Max(0.0001f, measured); + + Debug.Log($"FishingModelLineFollower: Original Length를 {originalLength:F4}로 설정했습니다.", this); + } + + public void SetTargets(Transform startPoint, Transform endPoint) + { + rodTipPoint = startPoint; + bobberPoint = endPoint; + } + + public void SetReverseVisualDirection(bool value) + { + reverseVisualDirection = value; + } + + public void SetOriginalLength(float value) + { + originalLength = Mathf.Max(0.0001f, value); + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingModelLineFollower.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingModelLineFollower.cs.meta new file mode 100644 index 00000000..e9b2ec2f --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingModelLineFollower.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a8d0073af66f0fc49a7fb32df0bf5e41 \ No newline at end of file diff --git a/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs b/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs index f199995e..f395ca15 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs @@ -1,53 +1,255 @@ +using System; +using System.Collections.Generic; using UnityEngine; public class FishingRewardSystem : MonoBehaviour { - public enum RewardType + [Serializable] + public class LootEntry { - Normal, - Rare + public FishingItemType itemType = FishingItemType.Fish; + public string displayName = "생선"; + [Min(0f)] public float weight = 1f; + + public LootEntry(FishingItemType itemType, string displayName, float weight) + { + this.itemType = itemType; + this.displayName = displayName; + this.weight = Mathf.Max(0f, weight); + } } - [Header("Reward Chance")] - [Range(0f, 100f)] - [SerializeField] private float rareChance = 10f; - - [Header("Reward Count")] - [SerializeField] private int normalMemory; - [SerializeField] private int rareMemory; - - public int NormalMemory => normalMemory; - public int RareMemory => rareMemory; - - public RewardType GiveReward() + public struct CatchResult { - if (Random.value < rareChance / 100f) - return GiveRareMemory(); + public FishingItemType ItemType; + public string DisplayName; + public bool CountsAsCleanupItem; + public bool IsMemoryPiece; - return GiveNormalMemory(); + public CatchResult(FishingItemType itemType, string displayName, bool countsAsCleanupItem, bool isMemoryPiece) + { + ItemType = itemType; + DisplayName = displayName; + CountsAsCleanupItem = countsAsCleanupItem; + IsMemoryPiece = isMemoryPiece; + } } - private RewardType GiveNormalMemory() + [Header("Loot Table - Dirty Pond")] + [Tooltip("연못이 정화되기 전 낚이는 아이템 목록입니다. 기억의 조각은 이 테이블에 있어도 자동 제외됩니다.")] + [SerializeField] private List dirtyPondLootTable = new List(); + + [Header("Loot Table - Clean Pond")] + [Tooltip("연못이 정화된 뒤 낚이는 아이템 목록입니다. 기억의 조각을 포함할 수 있습니다.")] + [SerializeField] private List cleanPondLootTable = new List(); + + [Header("Debug")] + [SerializeField] private bool showDebugLog = true; + + public IReadOnlyList DirtyPondLootTable => dirtyPondLootTable; + public IReadOnlyList CleanPondLootTable => cleanPondLootTable; + + private void Reset() { - normalMemory++; - - Debug.Log("Ϲ +1"); - - return RewardType.Normal; + SetDefaultLootTables(); } - private RewardType GiveRareMemory() + private void Awake() { - rareMemory++; - - Debug.Log(" +1"); - - return RewardType.Rare; + EnsureLootTables(); } - public void ResetRewardCount() + private void OnValidate() { - normalMemory = 0; - rareMemory = 0; + EnsureLootTables(); + ClampLootTable(dirtyPondLootTable); + ClampLootTable(cleanPondLootTable); } -} \ No newline at end of file + + private void EnsureLootTables() + { + if (dirtyPondLootTable == null) + dirtyPondLootTable = new List(); + + if (cleanPondLootTable == null) + cleanPondLootTable = new List(); + + if (dirtyPondLootTable.Count == 0) + SetDefaultDirtyLootTable(); + + if (cleanPondLootTable.Count == 0) + SetDefaultCleanLootTable(); + } + + private void SetDefaultLootTables() + { + SetDefaultDirtyLootTable(); + SetDefaultCleanLootTable(); + } + + private void SetDefaultDirtyLootTable() + { + dirtyPondLootTable = new List + { + new LootEntry(FishingItemType.Fish, "생선", 25f), + new LootEntry(FishingItemType.RottenFish, "상한 생선", 8f), + new LootEntry(FishingItemType.Trash, "쓰레기", 28f), + new LootEntry(FishingItemType.Bottle, "병", 18f), + new LootEntry(FishingItemType.PlasticBag, "봉지", 18f), + new LootEntry(FishingItemType.JellyfishPowder, "해파리 가루", 6f), + new LootEntry(FishingItemType.OldCompass, "낡은 나침반", 4f) + }; + } + + private void SetDefaultCleanLootTable() + { + cleanPondLootTable = new List + { + new LootEntry(FishingItemType.MemoryPiece, "기억의 조각", 30f), + new LootEntry(FishingItemType.Fish, "생선", 30f), + new LootEntry(FishingItemType.OldCompass, "낡은 나침반", 15f), + new LootEntry(FishingItemType.JellyfishPowder, "해파리 가루", 15f), + new LootEntry(FishingItemType.RottenFish, "상한 생선", 4f) + }; + } + + private void ClampLootTable(List table) + { + if (table == null) + return; + + for (int i = 0; i < table.Count; i++) + { + if (table[i] == null) + { + table[i] = new LootEntry(FishingItemType.Fish, "생선", 1f); + continue; + } + + table[i].weight = Mathf.Max(0f, table[i].weight); + + if (string.IsNullOrWhiteSpace(table[i].displayName)) + table[i].displayName = GetDefaultDisplayName(table[i].itemType); + } + } + + public CatchResult RollCatch(bool pondCleaned, bool forceMemoryPiece) + { + if (forceMemoryPiece) + return CreateCatchResult(FishingItemType.MemoryPiece, GetDefaultDisplayName(FishingItemType.MemoryPiece)); + + List table = pondCleaned ? cleanPondLootTable : dirtyPondLootTable; + LootEntry selectedEntry = RollFromTable(table, pondCleaned); + + if (selectedEntry == null) + return CreateCatchResult(FishingItemType.Fish, GetDefaultDisplayName(FishingItemType.Fish)); + + return CreateCatchResult(selectedEntry.itemType, selectedEntry.displayName); + } + + private LootEntry RollFromTable(List table, bool pondCleaned) + { + if (table == null || table.Count == 0) + return null; + + float totalWeight = 0f; + + for (int i = 0; i < table.Count; i++) + { + LootEntry entry = table[i]; + + if (!IsRollableEntry(entry, pondCleaned)) + continue; + + totalWeight += entry.weight; + } + + if (totalWeight <= 0f) + return null; + + float roll = UnityEngine.Random.Range(0f, totalWeight); + float current = 0f; + + for (int i = 0; i < table.Count; i++) + { + LootEntry entry = table[i]; + + if (!IsRollableEntry(entry, pondCleaned)) + continue; + + current += entry.weight; + + if (roll <= current) + return entry; + } + + return null; + } + + private bool IsRollableEntry(LootEntry entry, bool pondCleaned) + { + if (entry == null) + return false; + + if (entry.weight <= 0f) + return false; + + if (!pondCleaned && entry.itemType == FishingItemType.MemoryPiece) + return false; + + return entry.itemType != FishingItemType.None; + } + + private CatchResult CreateCatchResult(FishingItemType itemType, string displayName) + { + string finalDisplayName = string.IsNullOrWhiteSpace(displayName) ? GetDefaultDisplayName(itemType) : displayName; + bool countsAsCleanupItem = IsCleanupItem(itemType); + bool isMemoryPiece = itemType == FishingItemType.MemoryPiece; + + if (showDebugLog) + Debug.Log($"낚시 획득: {finalDisplayName}"); + + return new CatchResult(itemType, finalDisplayName, countsAsCleanupItem, isMemoryPiece); + } + + public bool IsCleanupItem(FishingItemType itemType) + { + return itemType == FishingItemType.Trash || + itemType == FishingItemType.Bottle || + itemType == FishingItemType.PlasticBag; + } + + public string GetDefaultDisplayName(FishingItemType itemType) + { + 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 "기억의 조각"; + + case FishingItemType.None: + default: + return "없음"; + } + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingRodState.cs b/Assets/My project/Fishing Scripts/UI/FishingRodState.cs new file mode 100644 index 00000000..ca8a9cad --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingRodState.cs @@ -0,0 +1,37 @@ +using UnityEngine; + +/// +/// 낚싯대를 잡고 있는지 기록하는 간단한 상태 스크립트입니다. +/// FishingRod 오브젝트에 붙이고, XR Grab Interactable의 Select Entered/Exited 이벤트에 연결하세요. +/// +public class FishingRodState : MonoBehaviour +{ + [Header("State")] + [SerializeField] private bool isHeld; + + [Header("Debug")] + [SerializeField] private bool showDebugLog = true; + + public bool IsHeld => isHeld; + + public void MarkHeld() + { + SetHeld(true); + } + + public void MarkReleased() + { + SetHeld(false); + } + + public void SetHeld(bool value) + { + if (isHeld == value) + return; + + isHeld = value; + + if (showDebugLog) + Debug.Log(isHeld ? "낚싯대 잡음" : "낚싯대 놓음"); + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingRodState.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingRodState.cs.meta new file mode 100644 index 00000000..49277dd9 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingRodState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 19b938e652163b1459e2a5d8d862510b \ No newline at end of file diff --git a/Assets/My project/Fishing Scripts/UI/FishingStartTrigger.cs b/Assets/My project/Fishing Scripts/UI/FishingStartTrigger.cs new file mode 100644 index 00000000..2b10e488 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingStartTrigger.cs @@ -0,0 +1,244 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +/// +/// 낚시터 시작 영역에 붙이는 스크립트입니다. +/// 플레이어가 Trigger 영역 안에 들어오면 안내 UI를 켜고, +/// 지정한 Start Action 입력이 들어오면 FishingGameManager.StartFishing()을 호출합니다. +/// 필요하면 낚싯대를 잡고 있을 때만 시작되도록 제한할 수 있습니다. +/// +[RequireComponent(typeof(Collider))] +public class FishingStartTrigger : MonoBehaviour +{ + [Header("References")] + [Tooltip("낚시 미니게임을 제어하는 FishingGameManager입니다.")] + [SerializeField] private FishingGameManager fishingGameManager; + + [Tooltip("낚시터 근처에 들어왔을 때 보여줄 시작 안내 UI입니다.")] + [SerializeField] private GameObject startGuideUI; + + [Header("Input")] + [Tooltip("낚시 시작 입력입니다. 예: XRI RightHand / Primary Button. SubmitAction과 같은 버튼은 피하는 것을 추천합니다.")] + [SerializeField] private InputActionReference startAction; + + [Tooltip("Input Action Manager가 없거나 액션이 자동 활성화되지 않으면 켜두세요.")] + [SerializeField] private bool enableInputActionManually = true; + + [Header("Player Check")] + [Tooltip("플레이어 루트 오브젝트의 태그입니다. 보통 XR Origin에 Player 태그를 붙입니다.")] + [SerializeField] private string playerTag = "Player"; + + [Tooltip("태그 체크를 끄고 모든 Collider 진입을 플레이어로 취급합니다. 테스트용입니다.")] + [SerializeField] private bool ignorePlayerTagForTest = false; + + [Header("Rod Requirement")] + [Tooltip("켜면 낚싯대를 잡고 있을 때만 낚시를 시작할 수 있습니다.")] + [SerializeField] private bool requireRodHeld = false; + + [Tooltip("낚싯대에 붙어 있는 FishingRodState입니다.")] + [SerializeField] private FishingRodState requiredRod; + + [Header("Rules")] + [Tooltip("플레이어가 영역에서 나가면 낚시를 중단하고 UI를 숨깁니다. VR에서는 처음에 Off 추천.")] + [SerializeField] private bool stopFishingOnExit = false; + + [Tooltip("낚시가 시작되면 시작 안내 UI를 숨깁니다.")] + [SerializeField] private bool hideGuideWhenFishingStarts = true; + + [Tooltip("기억의 조각을 이미 획득한 뒤에도 다시 시작할 수 있게 합니다.")] + [SerializeField] private bool allowRestartAfterClear = false; + + [Header("Debug")] + [SerializeField] private bool showDebugLog = true; + + private bool playerInside; + private bool actionWasEnabled; + + public bool PlayerInside => playerInside; + + private void Reset() + { + Collider col = GetComponent(); + if (col != null) + col.isTrigger = true; + } + + private void Awake() + { + Collider col = GetComponent(); + if (col != null && !col.isTrigger) + col.isTrigger = true; + + SetGuideVisible(false); + } + + private void OnEnable() + { + RegisterStartAction(); + } + + private void OnDisable() + { + UnregisterStartAction(); + } + + private void OnTriggerEnter(Collider other) + { + if (!IsPlayer(other)) + return; + + playerInside = true; + RefreshGuide(); + + if (showDebugLog) + Debug.Log("낚시 시작 영역 진입"); + } + + private void OnTriggerExit(Collider other) + { + if (!IsPlayer(other)) + return; + + playerInside = false; + SetGuideVisible(false); + + if (stopFishingOnExit && fishingGameManager != null) + fishingGameManager.StopFishing(true); + + if (showDebugLog) + Debug.Log("낚시 시작 영역 이탈"); + } + + private bool IsPlayer(Collider other) + { + if (ignorePlayerTagForTest) + return true; + + if (other == null) + return false; + + if (other.CompareTag(playerTag)) + return true; + + Transform current = other.transform; + while (current != null) + { + if (current.CompareTag(playerTag)) + return true; + + current = current.parent; + } + + return false; + } + + private void RegisterStartAction() + { + if (startAction == null || startAction.action == null) + return; + + actionWasEnabled = startAction.action.enabled; + startAction.action.performed += OnStartAction; + + if (enableInputActionManually && !actionWasEnabled) + startAction.action.Enable(); + } + + private void UnregisterStartAction() + { + if (startAction == null || startAction.action == null) + return; + + startAction.action.performed -= OnStartAction; + + if (enableInputActionManually && !actionWasEnabled) + startAction.action.Disable(); + } + + private void OnStartAction(InputAction.CallbackContext context) + { + TryStartFishing(); + } + + /// + /// UI 버튼이나 다른 스크립트에서도 호출할 수 있는 낚시 시작 함수입니다. + /// + public void TryStartFishing() + { + if (!CanStartFishing()) + return; + + fishingGameManager.StartFishing(); + + if (hideGuideWhenFishingStarts) + SetGuideVisible(false); + + if (showDebugLog) + Debug.Log("낚시 시작 요청 완료"); + } + + public bool CanStartFishing() + { + if (!playerInside) + return false; + + if (fishingGameManager == null) + { + if (showDebugLog) + Debug.LogWarning("FishingStartTrigger: FishingGameManager가 연결되지 않았습니다."); + + return false; + } + + if (fishingGameManager.IsActiveGame) + return false; + + if (fishingGameManager.IsMemoryPieceCollected && !allowRestartAfterClear) + return false; + + if (requireRodHeld) + { + if (requiredRod == null) + { + if (showDebugLog) + Debug.LogWarning("FishingStartTrigger: Require Rod Held가 켜져 있지만 Required Rod가 연결되지 않았습니다."); + + return false; + } + + if (!requiredRod.IsHeld) + return false; + } + + return true; + } + + public void RefreshGuide() + { + if (!playerInside) + { + SetGuideVisible(false); + return; + } + + if (fishingGameManager != null && fishingGameManager.IsMemoryPieceCollected && !allowRestartAfterClear) + { + SetGuideVisible(false); + return; + } + + if (fishingGameManager != null && fishingGameManager.IsActiveGame) + { + SetGuideVisible(false); + return; + } + + SetGuideVisible(true); + } + + private void SetGuideVisible(bool visible) + { + if (startGuideUI != null) + startGuideUI.SetActive(visible); + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingStartTrigger.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingStartTrigger.cs.meta new file mode 100644 index 00000000..5fba2800 --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingStartTrigger.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 861cf89cbda384e4b9bf8207fcb6e487 \ No newline at end of file diff --git a/Assets/My project/Fishing Scripts/UI/FishingUIEffects.cs b/Assets/My project/Fishing Scripts/UI/FishingUIEffects.cs new file mode 100644 index 00000000..14ef9bec --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingUIEffects.cs @@ -0,0 +1,361 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +public class FishingUIEffects : MonoBehaviour +{ + private readonly Dictionary runningEffects = new Dictionary(); + private readonly Dictionary originalScales = new Dictionary(); + private readonly Dictionary originalAnchoredPositions = new Dictionary(); + + public CanvasGroup EnsureCanvasGroup(GameObject target) + { + if (target == null) + return null; + + CanvasGroup group = target.GetComponent(); + if (group == null) + group = target.AddComponent(); + + return group; + } + + public void SetCanvasGroupInstant(GameObject target, CanvasGroup group, bool visible) + { + if (target == null && group == null) + return; + + if (target != null) + target.SetActive(visible); + + if (group != null) + { + group.alpha = visible ? 1f : 0f; + group.interactable = visible; + group.blocksRaycasts = visible; + } + } + + public void FadeCanvasGroup(GameObject target, CanvasGroup group, bool visible, float duration) + { + if (target == null && group == null) + return; + + if (group == null && target != null) + group = EnsureCanvasGroup(target); + + if (group == null) + { + if (target != null) + target.SetActive(visible); + return; + } + + StopManaged(group); + runningEffects[group] = StartCoroutine(FadeCanvasGroupRoutine(target, group, visible, Mathf.Max(0f, duration))); + } + + private IEnumerator FadeCanvasGroupRoutine(GameObject target, CanvasGroup group, bool visible, float duration) + { + if (target != null && visible) + target.SetActive(true); + + group.interactable = visible; + group.blocksRaycasts = visible; + + float startAlpha = group.alpha; + float endAlpha = visible ? 1f : 0f; + + if (duration <= 0f) + { + group.alpha = endAlpha; + } + else + { + float timer = 0f; + while (timer < duration) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / duration); + t = EaseOutCubic(t); + group.alpha = Mathf.Lerp(startAlpha, endAlpha, t); + yield return null; + } + + group.alpha = endAlpha; + } + + if (target != null && !visible) + target.SetActive(false); + + runningEffects.Remove(group); + } + + public void Pop(Transform target, float peakScale = 1.12f, float duration = 0.18f) + { + if (target == null) + return; + + StopManaged(target); + runningEffects[target] = StartCoroutine(PopRoutine(target, Mathf.Max(1f, peakScale), Mathf.Max(0.01f, duration))); + } + + private IEnumerator PopRoutine(Transform target, float peakScale, float duration) + { + if (!originalScales.TryGetValue(target, out Vector3 originalScale)) + { + originalScale = target.localScale; + originalScales[target] = originalScale; + } + + float half = duration * 0.5f; + float timer = 0f; + + while (timer < half) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / half); + target.localScale = Vector3.Lerp(originalScale, originalScale * peakScale, EaseOutCubic(t)); + yield return null; + } + + timer = 0f; + Vector3 peak = originalScale * peakScale; + + while (timer < half) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / half); + target.localScale = Vector3.Lerp(peak, originalScale, EaseOutCubic(t)); + yield return null; + } + + target.localScale = originalScale; + runningEffects.Remove(target); + } + + public void Shake(RectTransform target, float strength = 12f, float duration = 0.18f) + { + if (target == null) + return; + + StopManaged(target); + runningEffects[target] = StartCoroutine(ShakeRoutine(target, Mathf.Max(0f, strength), Mathf.Max(0.01f, duration))); + } + + private IEnumerator ShakeRoutine(RectTransform target, float strength, float duration) + { + if (!originalAnchoredPositions.TryGetValue(target, out Vector2 originalPosition)) + { + originalPosition = target.anchoredPosition; + originalAnchoredPositions[target] = originalPosition; + } + + float timer = 0f; + while (timer < duration) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / duration); + float power = Mathf.Lerp(strength, 0f, t); + Vector2 offset = UnityEngine.Random.insideUnitCircle * power; + target.anchoredPosition = originalPosition + offset; + yield return null; + } + + target.anchoredPosition = originalPosition; + runningEffects.Remove(target); + } + + public void Flash(Image image, Color color, float maxAlpha = 0.28f, float duration = 0.16f) + { + if (image == null) + return; + + StopManaged(image); + runningEffects[image] = StartCoroutine(FlashRoutine(image, color, Mathf.Clamp01(maxAlpha), Mathf.Max(0.01f, duration))); + } + + private IEnumerator FlashRoutine(Image image, Color color, float maxAlpha, float duration) + { + image.gameObject.SetActive(true); + image.raycastTarget = false; + + float timer = 0f; + while (timer < duration) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / duration); + Color current = color; + current.a = Mathf.Lerp(maxAlpha, 0f, EaseOutCubic(t)); + image.color = current; + yield return null; + } + + Color clear = color; + clear.a = 0f; + image.color = clear; + image.gameObject.SetActive(false); + runningEffects.Remove(image); + } + + public void FillImage(Image image, float targetFill, float duration) + { + if (image == null) + return; + + targetFill = Mathf.Clamp01(targetFill); + + StopManaged(image); + runningEffects[image] = StartCoroutine(FillImageRoutine(image, targetFill, Mathf.Max(0f, duration))); + } + + private IEnumerator FillImageRoutine(Image image, float targetFill, float duration) + { + float startFill = image.fillAmount; + + if (duration <= 0f) + { + image.fillAmount = targetFill; + } + else + { + float timer = 0f; + while (timer < duration) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / duration); + image.fillAmount = Mathf.Lerp(startFill, targetFill, EaseOutCubic(t)); + yield return null; + } + + image.fillAmount = targetFill; + } + + runningEffects.Remove(image); + } + + public void HighlightSlot(Image background, Image icon, Transform slotRoot, Color highlightColor, float duration, float popScale) + { + Object key = background != null ? background : icon != null ? icon : slotRoot; + if (key == null) + return; + + StopManaged(key); + runningEffects[key] = StartCoroutine(HighlightSlotRoutine(background, icon, slotRoot, highlightColor, Mathf.Max(0.01f, duration), Mathf.Max(1f, popScale), key)); + } + + private IEnumerator HighlightSlotRoutine(Image background, Image icon, Transform slotRoot, Color highlightColor, float duration, float popScale, Object key) + { + Color? originalBackground = null; + Color? originalIcon = null; + Vector3 originalScale = Vector3.one; + + if (background != null) + { + originalBackground = background.color; + Color c = Color.Lerp(background.color, highlightColor, 0.65f); + c.a = 1f; + background.color = c; + } + + if (icon != null) + { + originalIcon = icon.color; + Color c = icon.color; + c.a = 1f; + icon.color = c; + } + + if (slotRoot != null) + { + if (!originalScales.TryGetValue(slotRoot, out originalScale)) + { + originalScale = slotRoot.localScale; + originalScales[slotRoot] = originalScale; + } + } + + float half = duration * 0.5f; + float timer = 0f; + while (timer < half) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / half); + if (slotRoot != null) + slotRoot.localScale = Vector3.Lerp(originalScale, originalScale * popScale, EaseOutCubic(t)); + yield return null; + } + + timer = 0f; + Vector3 peak = originalScale * popScale; + while (timer < half) + { + timer += Time.deltaTime; + float t = Mathf.Clamp01(timer / half); + + if (slotRoot != null) + slotRoot.localScale = Vector3.Lerp(peak, originalScale, EaseOutCubic(t)); + + if (background != null && originalBackground.HasValue) + background.color = Color.Lerp(background.color, originalBackground.Value, t); + + if (icon != null && originalIcon.HasValue) + icon.color = Color.Lerp(icon.color, originalIcon.Value, t); + + yield return null; + } + + if (slotRoot != null) + slotRoot.localScale = originalScale; + + if (background != null && originalBackground.HasValue) + background.color = originalBackground.Value; + + if (icon != null && originalIcon.HasValue) + icon.color = originalIcon.Value; + + runningEffects.Remove(key); + } + + public void StopAllEffects() + { + foreach (Coroutine coroutine in runningEffects.Values) + { + if (coroutine != null) + StopCoroutine(coroutine); + } + + runningEffects.Clear(); + + foreach (KeyValuePair pair in originalScales) + { + if (pair.Key != null) + pair.Key.localScale = pair.Value; + } + + foreach (KeyValuePair pair in originalAnchoredPositions) + { + if (pair.Key != null) + pair.Key.anchoredPosition = pair.Value; + } + } + + private void StopManaged(Object key) + { + if (key == null) + return; + + if (runningEffects.TryGetValue(key, out Coroutine coroutine) && coroutine != null) + StopCoroutine(coroutine); + + runningEffects.Remove(key); + } + + private float EaseOutCubic(float t) + { + t = Mathf.Clamp01(t); + t = 1f - Mathf.Pow(1f - t, 3f); + return t; + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingUIEffects.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingUIEffects.cs.meta new file mode 100644 index 00000000..aa67d19f --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingUIEffects.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b9b3bfbbd08e39e42b891728dcad3a9c \ No newline at end of file diff --git a/Assets/My project/Fishing Scripts/UI/RotateUI.cs b/Assets/My project/Fishing Scripts/UI/RotateUI.cs index 15471656..bad45262 100644 --- a/Assets/My project/Fishing Scripts/UI/RotateUI.cs +++ b/Assets/My project/Fishing Scripts/UI/RotateUI.cs @@ -5,15 +5,22 @@ public class RotateUI : MonoBehaviour [Header("Rotation Settings")] [SerializeField] private float rotateSpeed = 30f; [SerializeField] private bool clockwise = true; - [SerializeField] private bool playOnStart = true; + [SerializeField] private bool playOnStart = false; private bool isRotating; + public bool IsRotating => isRotating; + private void Awake() { isRotating = playOnStart; } + private void OnValidate() + { + rotateSpeed = Mathf.Max(0f, rotateSpeed); + } + private void Update() { if (!isRotating) @@ -39,8 +46,13 @@ public void SetClockwise(bool value) clockwise = value; } + public void SetRotateSpeed(float value) + { + rotateSpeed = Mathf.Max(0f, value); + } + public void ResetRotation() { transform.localEulerAngles = Vector3.zero; } -} \ No newline at end of file +} diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (1)-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (1)-Photoroom.png new file mode 100644 index 00000000..4309447e --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (1)-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e15c07504a960d011578fbc86c82db02fb349e6d56701f0a48028246557589f1 +size 1691308 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (1)-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (1)-Photoroom.png.meta new file mode 100644 index 00000000..dfd1546a --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (1)-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: e36e14e60fc40cf41bfd445f80e62535 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (2)-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (2)-Photoroom.png new file mode 100644 index 00000000..4d5c0c6a --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (2)-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87c18ab0fce8dd68b0928ca89f8e796aef46fea1177b2cede65cdbac8f3c6134 +size 879161 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (2)-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (2)-Photoroom.png.meta new file mode 100644 index 00000000..80a6619b --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_24 (2)-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 9b1669e795bdb7b4c88e4376a98347d9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 0 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_25 (3)-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_25 (3)-Photoroom.png new file mode 100644 index 00000000..f41566bc --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_25 (3)-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e2f75c920c25713be3f93a34b9a6bceb961e5ea4c094ac6eb05f5a0cac2e967 +size 826794 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_25 (3)-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_25 (3)-Photoroom.png.meta new file mode 100644 index 00000000..7845ba5d --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_25 (3)-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: d57aa12e9412ea74d844438588adc0f5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 0 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (5)-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (5)-Photoroom.png new file mode 100644 index 00000000..553fe337 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (5)-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3083be1f1934941ffef3ef5a89a27338cb45dc3ad4bc70d0e53e6dbe179bf435 +size 1532183 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (5)-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (5)-Photoroom.png.meta new file mode 100644 index 00000000..12476e15 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (5)-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: a9e1c44292c7bdb4da8926e8fc9addd2 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (6)-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (6)-Photoroom.png new file mode 100644 index 00000000..eb2c5da9 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (6)-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd017434935ca7e74ece87de9f42e69b8d2cb9384cb217ce17ee60943e6adc92 +size 1438465 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (6)-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (6)-Photoroom.png.meta new file mode 100644 index 00000000..b38f5962 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (6)-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: e76bfcda04825f043b3f6c1e6cef400b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (7)-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (7)-Photoroom.png new file mode 100644 index 00000000..30d04aa3 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (7)-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35fbb8f4db4cfefe5af142e396a6942c1a1ec1625b3f0e539b336a736b056c9a +size 862300 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (7)-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (7)-Photoroom.png.meta new file mode 100644 index 00000000..55d3e2e7 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_05_26 (7)-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 6365c45c31605c745bf1a33e3a1e0791 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_14-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_14-Photoroom.png new file mode 100644 index 00000000..5787bc06 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_14-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04a6f9118dda44335f08fd4d1f5cea8271861bd76238c4e6c745cabd0965b836 +size 652262 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_14-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_14-Photoroom.png.meta new file mode 100644 index 00000000..4dc42f74 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_14-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 5d0cced1099954d478056e93dd67e0b4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_24-Photoroom.png b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_24-Photoroom.png new file mode 100644 index 00000000..c37c48c2 --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_24-Photoroom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8c282108a674b34316923e540f55f2967a6cced5f480322ad7097e691b3ff79 +size 712530 diff --git a/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_24-Photoroom.png.meta b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_24-Photoroom.png.meta new file mode 100644 index 00000000..23a15c4c --- /dev/null +++ b/Assets/My project/Fishing Scripts/lmage/ChatGPT Image 2026년 6월 25일 오전 11_53_24-Photoroom.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: b20f3401dfb723541b090e4f3bcf5901 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fonts/Pretendard-Black SDF.asset b/Assets/My project/Fonts/Pretendard-Black SDF.asset index 3300bdc0..afbcf0ab 100644 --- a/Assets/My project/Fonts/Pretendard-Black SDF.asset +++ b/Assets/My project/Fonts/Pretendard-Black SDF.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c616c8042770497f1e36d9d327e8901d13d0c8a1cb649f1aadb4475bd2a5159e -size 41370591 +oid sha256:a81a06b57f04031dc553ed2a18857edbf18e5f2ef32f65f713e538f6cebf5da5 +size 7807136 diff --git a/Assets/My project/fishhook scene.unity b/Assets/My project/fishhook scene.unity index 82cb414c..70e9756d 100644 --- a/Assets/My project/fishhook scene.unity +++ b/Assets/My project/fishhook scene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c767dca1603a90e7ac5307e9f437c8129f741963db933b4e7df51de7db84675 -size 13754 +oid sha256:cc597fe373ac41742270b04d5e02c7db900781242ef07bbe6d0134a6350db18c +size 65495 diff --git a/Assets/My project/test scene.unity b/Assets/My project/test scene.unity index 7af367dd..7cb5f30c 100644 --- a/Assets/My project/test scene.unity +++ b/Assets/My project/test scene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47771cf6284c71c5d634e530889cfa73f06f3848b5cec1a176cca669b3df5792 -size 1344364 +oid sha256:6bf40d5cf8ff76ca7b214aa21dce7377165ed7b9ca4e17015e55fd346256e287 +size 1292493 diff --git a/Assets/Stylized Water 3/Materials/StylizedWater3_Ocean.mat b/Assets/Stylized Water 3/Materials/StylizedWater3_Ocean.mat index 65dd3f09..a554f63d 100644 --- a/Assets/Stylized Water 3/Materials/StylizedWater3_Ocean.mat +++ b/Assets/Stylized Water 3/Materials/StylizedWater3_Ocean.mat @@ -40,7 +40,7 @@ Material: - _ZCLIP_ON - _ZWRITE_ON m_LightmapFlags: 4 - m_EnableInstancingVariants: 0 + m_EnableInstancingVariants: 1 m_DoubleSidedGI: 0 m_CustomRenderQueue: -1 stringTagMap: {} diff --git a/Assets/Stylized Water 3/Materials/StylizedWater3_Toon.mat b/Assets/Stylized Water 3/Materials/StylizedWater3_Toon.mat index 0517f2c3..1037aa8e 100644 --- a/Assets/Stylized Water 3/Materials/StylizedWater3_Toon.mat +++ b/Assets/Stylized Water 3/Materials/StylizedWater3_Toon.mat @@ -38,7 +38,7 @@ Material: - _WORLDSPACEUV_ON - _ZCLIP_ON m_LightmapFlags: 4 - m_EnableInstancingVariants: 0 + m_EnableInstancingVariants: 1 m_DoubleSidedGI: 0 m_CustomRenderQueue: -1 stringTagMap: {} @@ -185,6 +185,7 @@ Material: - _FoamBubblesSpread: 1 - _FoamBubblesStrength: 0.217 - _FoamClipping: 0.5 + - _FoamClippingDynamic: 0 - _FoamDistanceOn: 1 - _FoamDistortion: 0.34 - _FoamOn: 1 @@ -238,6 +239,7 @@ Material: - _PlanarReflectionsParams: 0 - _PointSpotLightReflectionDistortion: 0.5 - _PointSpotLightReflectionExp: 64 + - _PointSpotLightReflectionSharp: 0 - _PointSpotLightReflectionSize: 0 - _PointSpotLightReflectionStrength: 10 - _QueueOffset: 0 @@ -282,6 +284,7 @@ Material: - _SrcBlend: 1 - _SunReflectionDistortion: 0.49 - _SunReflectionPerturbance: 1 + - _SunReflectionSharp: 0 - _SunReflectionSize: 0.122 - _SunReflectionStrength: 0 - _Surface: 0 @@ -295,6 +298,7 @@ Material: - _TranslucencyReflectionMask: 0 - _TranslucencyStrength: 1 - _TranslucencyStrengthDirect: 0.05 + - _UnderwaterReflectionStrength: 0.5 - _UnderwaterRefractionOffset: 0.2 - _UnderwaterSurfaceSmoothness: 0.8 - _VertexColorFoam: 0 diff --git a/Assets/XR/Settings/OpenXRPackageSettings.asset b/Assets/XR/Settings/OpenXRPackageSettings.asset index e1978558..8d7af198 100644 --- a/Assets/XR/Settings/OpenXRPackageSettings.asset +++ b/Assets/XR/Settings/OpenXRPackageSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:541f41beb968bf1698702e33dd837acb9bba3d27757762a4f6b25ed555774c20 -size 98721 +oid sha256:b526fcd7984fc67a9c47bfd01df1fc3d2499699bdcdad8b9e58f90ea69a9db46 +size 98723