diff --git a/Assets/01_Scenes/WhaleAdventure_VR/Normal/WhaleAttackScene.unity b/Assets/01_Scenes/WhaleAdventure_VR/Normal/WhaleAttackScene.unity index 3e9af72..face6ed 100644 --- a/Assets/01_Scenes/WhaleAdventure_VR/Normal/WhaleAttackScene.unity +++ b/Assets/01_Scenes/WhaleAdventure_VR/Normal/WhaleAttackScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:634d579546738aa2ce3edc55fb434a156e493132548d7290ad84eece61b8da3d -size 80352 +oid sha256:737587783636571775434eec1b9a9363acee72b5f206293e1ddcb66072b7c3e9 +size 80728 diff --git a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity index 19bd0f2..a0f0dd8 100644 --- a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity +++ b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/CatsRoom.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0aaa886ef605da0ebfc3e479e6f59fb3a5486925e2fbfea6ef31c83d626b732 -size 2940676 +oid sha256:9294f8f9e90dd2454bc20eef6a9e8a065656cb700247ab467623c8ddd7e08d6f +size 2941441 diff --git a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/FishhookRoom.unity b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/FishhookRoom.unity index 05b78d0..8e22390 100644 --- a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/FishhookRoom.unity +++ b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/FishhookRoom.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0317ae16c7f72b3caa0f59ec58ff150f8bc3da503890b2cd906d39c9fd8140f9 -size 900430 +oid sha256:0e280cc0bf67491580f222d374c71792184ede7ef2baac0ff5aa4e60aaa10fbf +size 247651 diff --git a/Assets/01_Scenes/WhaleAdventure_VR/Rooms/Gepto second.meta b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/Gepto second.meta new file mode 100644 index 0000000..a6d65ea --- /dev/null +++ b/Assets/01_Scenes/WhaleAdventure_VR/Rooms/Gepto second.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c60ef9a2426e27f41addd32bf4fda47c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/07_Data/Terrain/RoomOriginTerrain.asset b/Assets/07_Data/Terrain/RoomOriginTerrain.asset index e71d995..28c926c 100644 --- a/Assets/07_Data/Terrain/RoomOriginTerrain.asset +++ b/Assets/07_Data/Terrain/RoomOriginTerrain.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3e11c49539aa72f350f9932e7d1825d43a6173aad4dc64e01b9c440af40d1ed -size 5582252 +oid sha256:45d7dd8877872cfab85ea94cc8e89c9adafd4e933d8dc74be3e500bcd507fb73 +size 6037228 diff --git a/Assets/09_UI/talk/menu_panel_REAL_transparent_cropped.png b/Assets/09_UI/talk/menu_panel_REAL_transparent_cropped.png new file mode 100644 index 0000000..8f1bd3a --- /dev/null +++ b/Assets/09_UI/talk/menu_panel_REAL_transparent_cropped.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57a6ab4b1f7bb370c7efda9b0a8aefa96003938856bbb19d40b1d15cb8090e57 +size 2011118 diff --git a/Assets/09_UI/talk/menu_panel_REAL_transparent_cropped.png.meta b/Assets/09_UI/talk/menu_panel_REAL_transparent_cropped.png.meta new file mode 100644 index 0000000..e5d5907 --- /dev/null +++ b/Assets/09_UI/talk/menu_panel_REAL_transparent_cropped.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: 0e533587a03b84940b08e956a8615adf +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 + - serializedVersion: 4 + buildTarget: WebGL + 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/09_UI/talk/pinocchio_title_banner_REAL_transparent_cropped.png b/Assets/09_UI/talk/pinocchio_title_banner_REAL_transparent_cropped.png new file mode 100644 index 0000000..91c98af --- /dev/null +++ b/Assets/09_UI/talk/pinocchio_title_banner_REAL_transparent_cropped.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a20230fbfa2f34f1d087377ffd494588323ceb0b86a1845f2f4a8bb06d88c6cd +size 2128944 diff --git a/Assets/09_UI/talk/pinocchio_title_banner_REAL_transparent_cropped.png.meta b/Assets/09_UI/talk/pinocchio_title_banner_REAL_transparent_cropped.png.meta new file mode 100644 index 0000000..de2d016 --- /dev/null +++ b/Assets/09_UI/talk/pinocchio_title_banner_REAL_transparent_cropped.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: e947b9df5e461fd48a18b65eae417346 +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 + - serializedVersion: 4 + buildTarget: WebGL + 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/09_UI/talk/wood_button_plain_REAL_transparent.png b/Assets/09_UI/talk/wood_button_plain_REAL_transparent.png new file mode 100644 index 0000000..89358ba --- /dev/null +++ b/Assets/09_UI/talk/wood_button_plain_REAL_transparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc398b072a2a8eeab384ea53b9650dc488799ba64845406ffcbec5685e1323b8 +size 1447030 diff --git a/Assets/09_UI/talk/wood_button_plain_REAL_transparent.png.meta b/Assets/09_UI/talk/wood_button_plain_REAL_transparent.png.meta new file mode 100644 index 0000000..bb0f9ac --- /dev/null +++ b/Assets/09_UI/talk/wood_button_plain_REAL_transparent.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: 4e0c1ffb4b7796341b9614ba8650b534 +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 + - serializedVersion: 4 + buildTarget: WebGL + 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/Prefabs/FishingSystemPrefab.prefab b/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab index 7be143d..2132da1 100644 --- a/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab +++ b/Assets/My project/Fishing Scripts/Prefabs/FishingSystemPrefab.prefab @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64239652fbbd1b648441a25bbc7adae5c75781bab7b25c4133c73dd6fd9a6b28 -size 220519 +oid sha256:0685637ff18e602a95936fca72fd343675a69d9f4183e591128957c421de7e23 +size 191978 diff --git a/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs b/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs index 45084c9..115e42a 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingGameManager.cs @@ -3,21 +3,22 @@ using UnityEngine; using UnityEngine.InputSystem; +/// +/// Reel-only fishing game manager. +/// +/// Removed from the old mixed version: +/// - TimingGauge mode enum +/// - Perfect / Good / Miss timing judgement +/// - pointer rotation / success zone / round retry logic +/// - submit action for the timing gauge +/// +/// Flow: +/// FishingRodVRController detects bite -> StartFishing() +/// -> player reels in -> CurrentLineLength reaches catchCompleteLineLength +/// -> reward is rolled -> UI/inventory event updates -> bobber returns. +/// public class FishingGameManager : MonoBehaviour { - public enum ResultType - { - Perfect, - Good, - Miss - } - - public enum FishingControlMode - { - TimingGauge, - SimpleReel - } - [Header("Auto Bind")] [Tooltip("현재 만든 Prefab 구조 기준으로 비어 있는 참조를 자동 연결합니다.")] [SerializeField] private bool autoBindMissingReferences = true; @@ -29,15 +30,43 @@ public enum FishingControlMode [SerializeField] private FishingGaugeUI ui; [SerializeField] private FishingRewardSystem rewardSystem; [SerializeField] private FishingHapticManager haptic; - [SerializeField] private RotateUI centerIconRotate; - - [Header("Simple Reel Fishing")] - [Tooltip("TimingGauge는 기존 원형 타이밍 게이지 방식, SimpleReel은 릴을 감아 줄 길이를 줄이면 성공하는 방식입니다.")] - [SerializeField] private FishingControlMode fishingControlMode = FishingControlMode.SimpleReel; - - [Tooltip("낚싯대 / 찌 / 릴 제어 스크립트입니다. SimpleReel 모드에서 줄 길이를 읽습니다.")] [SerializeField] private FishingRodVRController rodController; + [SerializeField] private GameClear gameClear; + [Header("Memory Piece Result Object")] + [Tooltip("기억의 조각 획득 결과창이 뜰 때 플레이어 화면 앞에 생성할 오브젝트 프리팹입니다. StarPickup이 붙어 있어도 됩니다.")] + [SerializeField] private GameObject memoryPieceResultPrefab; + + [Tooltip("씬에 미리 배치해 둔 기억의 조각 오브젝트입니다. 비워두면 Prefab을 생성합니다.")] + [SerializeField] private GameObject memoryPieceResultSceneObject; + + [Tooltip("XR Origin의 Main Camera를 넣으세요. 비워두면 Camera.main을 자동으로 사용합니다.")] + [SerializeField] private Transform memoryPiecePlayerHead; + + [SerializeField] private float memoryPieceResultDistance = 1.2f; + [SerializeField] private float memoryPieceResultHeightOffset = -0.05f; + [SerializeField] private float memoryPieceResultSideOffset = 0f; + [SerializeField] private bool memoryPieceResultFollowPlayerHead = true; + [SerializeField] private bool memoryPieceResultYawOnly = true; + + [Tooltip("켜면 오브젝트가 플레이어를 바라보게 합니다. StarPickup의 visualRoot가 자기 자신이면 회전 연출이 덮일 수 있으니 기본값은 Off입니다.")] + [SerializeField] private bool memoryPieceResultFacePlayer = false; + + [Tooltip("0보다 크면 이 시간 뒤 기억의 조각 오브젝트를 숨깁니다. 결과창 자동 닫기 시간과 비슷하게 맞추세요.")] + [SerializeField] private float memoryPieceResultShowTime = 3f; + + [SerializeField] private bool memoryPieceResultUseCustomScale = false; + [SerializeField] private Vector3 memoryPieceResultCustomScale = Vector3.one; + + [Tooltip("켜면 StarPickup Trigger로 플레이어가 닿자마자 수집/Destroy되는 것을 막기 위해 Collider를 꺼둡니다.")] + [SerializeField] private bool disableMemoryPieceResultColliders = true; + + [Tooltip("켜면 StarPickup의 floatMotion을 꺼서 카메라 앞 고정 위치와 충돌하지 않게 합니다. 회전은 유지됩니다.")] + [SerializeField] private bool disableMemoryPieceResultFloatMotion = true; + + [SerializeField] private bool destroySpawnedMemoryPieceResultOnHide = true; + + [Header("Reel Fishing")] [Tooltip("현재 줄 길이가 이 값 이하가 되면 낚기 성공으로 처리합니다.")] [SerializeField] private float catchCompleteLineLength = 0.7f; @@ -45,46 +74,20 @@ public enum FishingControlMode [SerializeField] private float simpleReelMinimumPullDistance = 0.35f; [Tooltip("릴 낚기 성공 후 찌를 낚싯대 쪽 대기 위치로 회수합니다.")] - [SerializeField] private bool returnBobberAfterSimpleReelCatch = true; + [SerializeField] private bool returnBobberAfterCatch = true; - [Tooltip("SimpleReel 모드에서 기존 타이밍 게이지 UI를 숨기고 진행도/줄 길이 중심으로 표시합니다.")] - [SerializeField] private bool hideTimingGaugeInSimpleReelMode = true; + [Tooltip("SimpleReel 모드에서는 StartFishing이 입질마다 호출됩니다. 켜면 첫 시작 때만 연못 상태를 초기화합니다.")] + [SerializeField] private bool resetPondOnlyOnFirstStart = true; - [Tooltip("SimpleReel 모드에서는 StartFishing이 여러 번 호출됩니다. 켜면 첫 시작 때만 연못 상태를 초기화합니다.")] - [SerializeField] private bool resetPondOnlyOnFirstSimpleReelStart = true; - - [Header("XR Controller Input")] - [Tooltip("낚시 판정을 실행할 컨트롤러 입력입니다. 예: XRI Right Interaction / Select")] - [SerializeField] private InputActionReference submitAction; - - [Tooltip("낚시를 다시 시작할 컨트롤러 입력입니다. 필요 없으면 비워둬도 됩니다.")] + [Header("Reset Input")] + [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; - [SerializeField] private float minSpeed = 180f; - [SerializeField] private float maxSpeed = 450f; - - [Header("Zone")] - [SerializeField] private float startZoneSize = 90f; - [SerializeField] private float zoneSize = 90f; - [SerializeField] private float minZoneSize = 30f; - [SerializeField] private float maxZoneSize = 120f; - [SerializeField] private float zoneCenter; - - [Header("Catch Rules")] - [Tooltip("아이템 1개를 낚기 위해 필요한 성공 횟수입니다.")] - [SerializeField] private int requiredSuccesses = 3; - - [Tooltip("아이템 1개를 낚는 동안 허용되는 실패 횟수입니다. 초과하면 낚싯줄이 끊어진 것으로 처리합니다.")] - [SerializeField] private int allowedFails = 3; - [Header("Pond Cleanup Rules")] - [Tooltip("연못을 맑게 만들기 위해 필요한 쓰레기성 아이템 수입니다. 쓰레기, 병, 봉지가 여기에 포함됩니다.")] + [Tooltip("연못을 맑게 만들기 위해 필요한 쓰레기 수입니다. 이제 쓰레기만 정화 아이템으로 카운트됩니다.")] [SerializeField] private int requiredCleanupItems = 3; [Tooltip("연못이 맑아진 뒤 다음 성공 낚시에서 기억의 조각을 확정으로 낚습니다.")] @@ -96,14 +99,10 @@ public enum FishingControlMode [Tooltip("낚시 세션이 진행 중일 때 StartFishing이 중복 호출되는 것을 막습니다.")] [SerializeField] private bool preventRestartWhileSessionRunning = 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("Start / Final Result")] + [Tooltip("VR 릴 낚시에서는 보통 꺼두세요. 입질이 왔을 때 FishingRodVRController가 StartFishing을 호출하는 구조를 추천합니다.")] + [SerializeField] private bool startOnAwake = false; - [Header("Final Result Settings")] [Tooltip("최종 결과를 잠깐 보여준 뒤 uiRoot를 자동으로 끕니다. uiRoot가 비어 있으면 동작하지 않습니다.")] [SerializeField] private bool hideUIRootAfterFinalResult = false; @@ -112,50 +111,40 @@ public enum FishingControlMode [Header("Debug")] [SerializeField] private bool showDebugLog = true; - private int successCount; - private int failCount; private int cleanupItemCount; private int totalCaughtItems; - // 임시 낚시 세션 카운트입니다. 나중에 공용 인벤토리와 연결할 때는 저장용으로 쓰지 말고 UI 표시용으로만 쓰세요. + // 임시 낚시 세션 카운트입니다. 영구 저장은 FishingInventoryBinder를 통해 FishingInventory에 연결하세요. private int sessionFishCount; private int sessionTrashCount; private int sessionMemoryPieceCount; - private int sessionCompassCount; - private bool clockwise = true; private bool activeGame; - private bool inputLocked; private bool fishingSessionRunning; private bool pondCleaned; private bool memoryPieceCollected; private bool simpleReelCatchActive; - private bool simpleReelPondInitialized; + private bool pondInitialized; private float simpleReelStartLineLength; private float simpleReelProgress; - private bool submitActionWasEnabled; private bool resetActionWasEnabled; - - private Coroutine nextRoundRoutine; - private Coroutine nextCatchRoutine; private Coroutine finalResultRoutine; + private Coroutine memoryPieceResultRoutine; + private GameObject currentMemoryPieceResultObject; + private bool memoryPieceResultShowing; public bool IsActiveGame => activeGame; public bool IsFishingSessionRunning => fishingSessionRunning; 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 FishingControlMode ControlMode => fishingControlMode; public bool IsSimpleReelCatchActive => simpleReelCatchActive; public float SimpleReelProgress => simpleReelProgress; @@ -163,13 +152,11 @@ public enum FishingControlMode private void OnEnable() { - RegisterInputAction(submitAction, OnSubmitAction, ref submitActionWasEnabled); RegisterInputAction(resetAction, OnResetAction, ref resetActionWasEnabled); } private void OnDisable() { - UnregisterInputAction(submitAction, OnSubmitAction, submitActionWasEnabled); UnregisterInputAction(resetAction, OnResetAction, resetActionWasEnabled); } @@ -181,13 +168,9 @@ private void Start() ValidateRuntimeSettings(); if (startOnAwake) - { StartFishing(); - } else - { InitializeIdleUI(); - } } private void Update() @@ -195,16 +178,12 @@ private void Update() if (!activeGame) return; - if (fishingControlMode == FishingControlMode.SimpleReel) - { - UpdateSimpleReelCatch(); - return; - } + UpdateSimpleReelCatch(); + } - if (inputLocked) - return; - - RotatePointer(); + private void LateUpdate() + { + UpdateMemoryPieceResultFollow(); } private void OnValidate() @@ -212,8 +191,6 @@ private void OnValidate() ValidateRuntimeSettings(); } - - [ContextMenu("Auto Bind Fishing References")] public void AutoBindMissingReferences() { @@ -238,14 +215,21 @@ public void AutoBindMissingReferences() if (rodController == null) rodController = searchRoot != null ? searchRoot.GetComponentInChildren(true) : GetComponentInChildren(true); - if (centerIconRotate == null) - { - Transform centerIconTransform = FindTransformRecursive(searchRoot, "CenterIcon"); - if (centerIconTransform != null) - centerIconRotate = centerIconTransform.GetComponent(); + if (gameClear == null) + gameClear = searchRoot != null ? searchRoot.GetComponentInChildren(true) : GetComponentInChildren(true); - if (centerIconRotate == null && searchRoot != null) - centerIconRotate = searchRoot.GetComponentInChildren(true); + if (memoryPieceResultSceneObject == null) + { + Transform memoryPieceResultTransform = FindTransformRecursive(searchRoot, "MemoryPieceResultObject"); + + if (memoryPieceResultTransform == null) + memoryPieceResultTransform = FindTransformRecursive(searchRoot, "MemoryPieceScreenObject"); + + if (memoryPieceResultTransform == null) + memoryPieceResultTransform = FindTransformRecursive(searchRoot, "MemoryPieceResultVisual"); + + if (memoryPieceResultTransform != null) + memoryPieceResultSceneObject = memoryPieceResultTransform.gameObject; } } @@ -310,47 +294,26 @@ private void UnregisterInputAction(InputActionReference actionReference, System. 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); catchCompleteLineLength = Mathf.Max(0.05f, catchCompleteLineLength); simpleReelMinimumPullDistance = Mathf.Max(0.01f, simpleReelMinimumPullDistance); - - pointerAngle = Normalize(pointerAngle); - zoneCenter = Normalize(zoneCenter); + finalResultShowTime = Mathf.Max(0f, finalResultShowTime); + memoryPieceResultDistance = Mathf.Max(0.1f, memoryPieceResultDistance); + memoryPieceResultShowTime = Mathf.Max(0f, memoryPieceResultShowTime); cleanupItemCount = Mathf.Max(0, cleanupItemCount); totalCaughtItems = Mathf.Max(0, totalCaughtItems); } private void InitializeIdleUI() { - if (ui != null) - { - 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); - ui.SetSimpleReelUIVisible(false); - ui.SetTimingGaugeVisible(fishingControlMode == FishingControlMode.TimingGauge); - } + if (ui == null) + return; - if (centerIconRotate != null) - centerIconRotate.StopRotate(); + ui.InitializeFishingUI(); + ui.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems); + ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount); + ui.SetSimpleReelUIVisible(false); + ui.UpdateSimpleReelUI(0f, rodController != null ? rodController.CurrentLineLength : 0f, false); } public void StartFishing() @@ -365,7 +328,7 @@ public void StartFishing(bool forceRestart) if (preventRestartWhileSessionRunning && fishingSessionRunning && !forceRestart) { if (showDebugLog) - Debug.Log("낚시 세션이 이미 진행 중이라 StartFishing 호출을 무시했습니다."); + Debug.Log("낚시 세션이 이미 진행 중이라 StartFishing 호출을 무시했습니다.", this); return; } @@ -376,14 +339,13 @@ public void StartFishing(bool forceRestart) bool shouldResetPondState = forceRestart || resetPondStateOnStart; - if (fishingControlMode == FishingControlMode.SimpleReel && resetPondOnlyOnFirstSimpleReelStart) - shouldResetPondState = forceRestart || (resetPondStateOnStart && !simpleReelPondInitialized); + if (resetPondOnlyOnFirstStart) + shouldResetPondState = forceRestart || (resetPondStateOnStart && !pondInitialized); if (shouldResetPondState) { ResetPondState(); - if (fishingControlMode == FishingControlMode.SimpleReel) - simpleReelPondInitialized = true; + pondInitialized = true; } fishingSessionRunning = true; @@ -391,13 +353,10 @@ public void StartFishing(bool forceRestart) if (ui != null) ui.InitializeFishingUI(); - if (fishingControlMode == FishingControlMode.SimpleReel) - StartSimpleReelCatch(); - else - StartNewCatchAttempt(); + StartSimpleReelCatch(); if (showDebugLog) - Debug.Log(fishingControlMode == FishingControlMode.SimpleReel ? "릴 감기 낚시 시작" : "기묘한 낚시터 시작"); + Debug.Log("릴 감기 낚시 시작", this); } public void StopFishing(bool hideUI = false) @@ -405,12 +364,12 @@ public void StopFishing(bool hideUI = false) StopRunningRoutines(); activeGame = false; - inputLocked = true; fishingSessionRunning = false; simpleReelCatchActive = false; + simpleReelProgress = 0f; - if (centerIconRotate != null) - centerIconRotate.StopRotate(); + if (ui != null) + ui.SetSimpleReelUIVisible(false); if (hideUI && uiRoot != null) uiRoot.SetActive(false); @@ -420,25 +379,24 @@ public void ResetFishing() { fishingSessionRunning = false; simpleReelCatchActive = false; - simpleReelPondInitialized = false; + pondInitialized = false; StartFishing(true); } public void ResetPondState() { - successCount = 0; - failCount = 0; cleanupItemCount = 0; totalCaughtItems = 0; sessionFishCount = 0; sessionTrashCount = 0; sessionMemoryPieceCount = 0; - sessionCompassCount = 0; pondCleaned = false; memoryPieceCollected = false; simpleReelCatchActive = false; simpleReelProgress = 0f; simpleReelStartLineLength = 0f; + + HideMemoryPieceResultObject(); } private void StartSimpleReelCatch() @@ -448,34 +406,32 @@ private void StartSimpleReelCatch() if (rodController == null) { - Debug.LogWarning("[FishingGameManager] SimpleReel 모드에는 FishingRodVRController 참조가 필요합니다.", this); + Debug.LogWarning("[FishingGameManager] 릴 감기 낚시에는 FishingRodVRController 참조가 필요합니다.", this); activeGame = false; - inputLocked = true; fishingSessionRunning = false; + simpleReelCatchActive = false; return; } - successCount = 0; - failCount = 0; - inputLocked = false; activeGame = true; simpleReelCatchActive = true; - float currentLineLength = Mathf.Max(rodController.CurrentLineLength, catchCompleteLineLength + simpleReelMinimumPullDistance); - simpleReelStartLineLength = currentLineLength; - simpleReelProgress = 0f; + // 중요: 기존 코드에서는 simpleReelStartLineLength만 보정하고 실제 줄 길이는 그대로라서 + // 찌가 대기 위치에 있으면 시작 즉시 성공할 수 있었습니다. + // 릴 전용 버전에서는 실제 줄 길이도 최소 시작 길이로 맞춥니다. + float minimumStartLength = catchCompleteLineLength + simpleReelMinimumPullDistance; - if (centerIconRotate != null) - { - centerIconRotate.ResetRotation(); - centerIconRotate.StopRotate(); - } + if (rodController.CurrentLineLength < minimumStartLength) + rodController.ForceSetReelLineLength(minimumStartLength); + + rodController.ForceMiniGameActiveState(); + + simpleReelStartLineLength = Mathf.Max(rodController.CurrentLineLength, minimumStartLength); + simpleReelProgress = 0f; if (ui != null) { ui.SetSimpleReelUIVisible(true); - if (hideTimingGaugeInSimpleReelMode) - ui.SetTimingGaugeVisible(false); ui.UpdateSimpleReelUI(simpleReelProgress, rodController.CurrentLineLength, rodController.IsReeling); ui.ShowPersistentResult("릴을 감아 끌어올려라!"); } @@ -499,7 +455,7 @@ private void UpdateSimpleReelCatch() if (currentLineLength <= catchCompleteLineLength || simpleReelProgress >= 1f) { simpleReelCatchActive = false; - inputLocked = true; + activeGame = false; if (haptic != null) haptic.Perfect(); @@ -514,214 +470,25 @@ private void UpdateSimpleReelCatch() } } - private void StartNewCatchAttempt() - { - successCount = 0; - failCount = 0; - inputLocked = false; - activeGame = true; - - if (resetDifficultyEachCatch) - { - pointerAngle = 0f; - pointerSpeed = minSpeed; - zoneSize = startZoneSize; - } - - if (randomDirectionEachRound) - clockwise = UnityEngine.Random.value > 0.5f; - - if (centerIconRotate != null) - { - centerIconRotate.ResetRotation(); - centerIconRotate.StartRotate(); - } - - RandomizeZone(); - UpdateUI(); - } - 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() - { - float dir = clockwise ? 1f : -1f; - - pointerAngle += dir * pointerSpeed * Time.deltaTime; - pointerAngle = Normalize(pointerAngle); - - if (ui != null) - ui.SetPointerRotation(pointerAngle); - } - - public void SubmitAttempt() - { - if (fishingControlMode == FishingControlMode.SimpleReel) - return; - - if (!activeGame) - return; - - if (inputLocked) - return; - - inputLocked = true; - - if (ui != null) - ui.HideControllerGuide(); - - ResultType result = EvaluateResult(); - - if (ui != null) + if (memoryPieceResultRoutine != null) { - ui.ShowResultForType(result); - ui.FlashFeedbackForType(result); + StopCoroutine(memoryPieceResultRoutine); + memoryPieceResultRoutine = null; } - switch (result) - { - case ResultType.Perfect: - OnPerfect(); - break; - - case ResultType.Good: - OnGood(); - break; - - case ResultType.Miss: - OnMiss(); - break; - } - - if (successCount >= requiredSuccesses) - { - CatchItemSuccess(); - return; - } - - if (failCount >= allowedFails) - { - FishingFail(); - return; - } - - nextRoundRoutine = StartCoroutine(NextRoundRoutine()); - } - - private ResultType EvaluateResult() - { - float delta = Mathf.Abs(Mathf.DeltaAngle(pointerAngle, zoneCenter)); - - float perfectRange = zoneSize * 0.2f; - float goodRange = zoneSize * 0.5f; - - if (delta <= perfectRange) - return ResultType.Perfect; - - if (delta <= goodRange) - return ResultType.Good; - - return ResultType.Miss; - } - - private void OnPerfect() - { - successCount++; - - zoneSize *= 0.9f; - pointerSpeed *= 1.05f; - - if (haptic != null) - haptic.Perfect(); - - if (showDebugLog) - Debug.Log("낚시 판정: Perfect"); - - ClampDifficulty(); - UpdateUI(); - } - - private void OnGood() - { - successCount++; - - zoneSize *= 0.95f; - pointerSpeed *= 1.02f; - - if (haptic != null) - haptic.Good(); - - if (showDebugLog) - Debug.Log("낚시 판정: Good"); - - ClampDifficulty(); - UpdateUI(); - } - - private void OnMiss() - { - failCount++; - - zoneSize *= 1.05f; - pointerSpeed *= 0.95f; - - if (haptic != null) - haptic.Miss(); - - if (showDebugLog) - Debug.Log("낚시 판정: Miss"); - - ClampDifficulty(); - UpdateUI(); - } - - private IEnumerator NextRoundRoutine() - { - yield return new WaitForSeconds(nextRoundDelay); - - if (!activeGame) - { - nextRoundRoutine = null; - yield break; - } - - if (randomDirectionEachRound) - clockwise = UnityEngine.Random.value > 0.5f; - - RandomizeZone(); - UpdateUI(); - - inputLocked = false; - nextRoundRoutine = null; + HideMemoryPieceResultObject(); } private void CatchItemSuccess() { - activeGame = false; - inputLocked = true; - - if (centerIconRotate != null) - centerIconRotate.StopRotate(); - bool forceMemoryPiece = pondCleaned && guaranteeMemoryPieceAfterCleaned && !memoryPieceCollected; FishingRewardSystem.CatchResult catchResult; @@ -729,7 +496,7 @@ private void CatchItemSuccess() if (rewardSystem != null) catchResult = rewardSystem.RollCatch(pondCleaned, forceMemoryPiece); else - catchResult = new FishingRewardSystem.CatchResult(FishingItemType.Fish, "생선", false, false); + catchResult = new FishingRewardSystem.CatchResult(FishingItemType.Fish, "물고기", false, false); ApplyCatchResult(catchResult); } @@ -774,49 +541,22 @@ private void ApplyCatchResult(FishingRewardSystem.CatchResult catchResult) if (pondJustCleaned) ui.ShowNotice("연못이 맑아졌다!\n기억의 조각이 모습을 드러냈다!"); - - // 기억의 조각 획득은 FinalResultPanel에서 크게 보여줍니다. - // NoticePanel까지 동시에 띄우면 ShowFinalResult에서 바로 숨겨져 연출이 겹칠 수 있습니다. } if (showDebugLog) - Debug.Log($"{catchResult.DisplayName}을(를) 낚았다!"); + Debug.Log($"{catchResult.DisplayName}을(를) 낚았다!", this); + + fishingSessionRunning = false; + simpleReelCatchActive = false; + activeGame = false; + + if (returnBobberAfterCatch && rodController != null) + rodController.ReturnBobberToRest(); if (catchResult.IsMemoryPiece) - { FishingSuccess(); - return; - } - - if (fishingControlMode == FishingControlMode.SimpleReel) - { - fishingSessionRunning = false; - simpleReelCatchActive = false; - - if (returnBobberAfterSimpleReelCatch && rodController != null) - rodController.ReturnBobberToRest(); - - 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) @@ -826,96 +566,53 @@ private void AddToSessionCounts(FishingItemType itemType) 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); - pointerSpeed = Mathf.Clamp(pointerSpeed, minSpeed, maxSpeed); - } - - private void RandomizeZone() - { - zoneCenter = UnityEngine.Random.Range(0f, 360f); - } - private void UpdateUI() { if (ui == null) return; - if (fishingControlMode == FishingControlMode.SimpleReel) - { - ui.SetSimpleReelUIVisible(activeGame || simpleReelCatchActive); - if (hideTimingGaugeInSimpleReelMode) - ui.SetTimingGaugeVisible(false); - - ui.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems); - ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount, sessionCompassCount); - - float currentLineLength = rodController != null ? rodController.CurrentLineLength : 0f; - bool isReeling = rodController != null && rodController.IsReeling; - ui.UpdateSimpleReelUI(simpleReelProgress, currentLineLength, isReeling); - return; - } - - ui.SetSimpleReelUIVisible(false); - ui.SetTimingGaugeVisible(true); - ui.SetZone(zoneCenter, zoneSize); - ui.UpdateCounter(successCount, requiredSuccesses, failCount, allowedFails); + ui.SetSimpleReelUIVisible(activeGame || simpleReelCatchActive); ui.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems); - ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount, sessionCompassCount); + ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount); + + float currentLineLength = rodController != null ? rodController.CurrentLineLength : 0f; + bool isReeling = rodController != null && rodController.IsReeling; + ui.UpdateSimpleReelUI(simpleReelProgress, currentLineLength, isReeling); } private void FishingSuccess() { activeGame = false; - inputLocked = true; fishingSessionRunning = false; simpleReelCatchActive = false; - if (centerIconRotate != null) - centerIconRotate.StopRotate(); - UpdateUI(); if (ui != null) ui.ShowFinalResult("기억의 조각 획득!\n기묘한 낚시터 클리어!"); - if (showDebugLog) - Debug.Log("기묘한 낚시터 클리어"); + ShowMemoryPieceResultObject(); - BeginFinalResultRoutineIfNeeded(); - } - - private void FishingFail() - { - activeGame = false; - inputLocked = true; - fishingSessionRunning = false; - simpleReelCatchActive = false; - - if (centerIconRotate != null) - centerIconRotate.StopRotate(); - - if (ui != null) - ui.ShowFinalResult("낚싯줄이 끊어졌다!\n다시 천천히 낚아보자."); + if (gameClear != null) + { + gameClear.OnGameClear(); + } + else if (showDebugLog) + { + Debug.LogWarning("[FishingGameManager] GameClear가 연결되지 않아 NPC 이동/대화존 변경/문 선택지 세팅을 실행하지 못했습니다.", this); + } if (showDebugLog) - Debug.Log("낚시 실패: 낚싯줄 끊어짐"); + Debug.Log("기묘한 낚시터 클리어", this); BeginFinalResultRoutineIfNeeded(); } @@ -938,22 +635,176 @@ private IEnumerator HideUIRootAfterFinalResultRoutine() if (uiRoot != null) uiRoot.SetActive(false); + HideMemoryPieceResultObject(); + finalResultRoutine = null; } - private float Normalize(float angle) + private void ShowMemoryPieceResultObject() { - angle %= 360f; + EnsureMemoryPiecePlayerHead(); - if (angle < 0f) - angle += 360f; + if (memoryPiecePlayerHead == null) + { + Debug.LogWarning("[FishingGameManager] Memory Piece Player Head가 없습니다. XR Origin의 Main Camera를 연결하세요.", this); + return; + } - return angle; + if (memoryPieceResultRoutine != null) + { + StopCoroutine(memoryPieceResultRoutine); + memoryPieceResultRoutine = null; + } + + if (currentMemoryPieceResultObject != null && currentMemoryPieceResultObject != memoryPieceResultSceneObject) + { + Destroy(currentMemoryPieceResultObject); + currentMemoryPieceResultObject = null; + } + + if (memoryPieceResultSceneObject != null) + { + currentMemoryPieceResultObject = memoryPieceResultSceneObject; + } + else if (memoryPieceResultPrefab != null) + { + currentMemoryPieceResultObject = Instantiate(memoryPieceResultPrefab); + } + else + { + if (showDebugLog) + Debug.LogWarning("[FishingGameManager] Memory Piece Result Prefab 또는 Scene Object가 연결되지 않았습니다.", this); + return; + } + + currentMemoryPieceResultObject.SetActive(true); + + if (memoryPieceResultUseCustomScale) + currentMemoryPieceResultObject.transform.localScale = memoryPieceResultCustomScale; + + PrepareMemoryPieceResultObject(currentMemoryPieceResultObject); + PlaceMemoryPieceResultObjectInFrontOfPlayer(); + + memoryPieceResultShowing = true; + + if (memoryPieceResultShowTime > 0f) + memoryPieceResultRoutine = StartCoroutine(HideMemoryPieceResultObjectAfterDelay()); } - private void OnSubmitAction(InputAction.CallbackContext context) + private void HideMemoryPieceResultObject() { - SubmitAttempt(); + memoryPieceResultShowing = false; + + if (memoryPieceResultRoutine != null) + { + StopCoroutine(memoryPieceResultRoutine); + memoryPieceResultRoutine = null; + } + + if (currentMemoryPieceResultObject == null) + return; + + if (currentMemoryPieceResultObject == memoryPieceResultSceneObject) + { + currentMemoryPieceResultObject.SetActive(false); + } + else if (destroySpawnedMemoryPieceResultOnHide) + { + Destroy(currentMemoryPieceResultObject); + } + else + { + currentMemoryPieceResultObject.SetActive(false); + } + + currentMemoryPieceResultObject = null; + } + + private IEnumerator HideMemoryPieceResultObjectAfterDelay() + { + yield return new WaitForSeconds(memoryPieceResultShowTime); + memoryPieceResultRoutine = null; + HideMemoryPieceResultObject(); + } + + private void UpdateMemoryPieceResultFollow() + { + if (!memoryPieceResultShowing || !memoryPieceResultFollowPlayerHead || currentMemoryPieceResultObject == null) + return; + + PlaceMemoryPieceResultObjectInFrontOfPlayer(); + } + + private void EnsureMemoryPiecePlayerHead() + { + if (memoryPiecePlayerHead != null) + return; + + Camera mainCamera = Camera.main; + + if (mainCamera != null) + memoryPiecePlayerHead = mainCamera.transform; + } + + private void PlaceMemoryPieceResultObjectInFrontOfPlayer() + { + if (memoryPiecePlayerHead == null || currentMemoryPieceResultObject == null) + return; + + Vector3 forward = memoryPiecePlayerHead.forward; + Vector3 right = memoryPiecePlayerHead.right; + + if (memoryPieceResultYawOnly) + { + forward.y = 0f; + + if (forward.sqrMagnitude < 0.0001f) + forward = currentMemoryPieceResultObject.transform.forward; + + forward.Normalize(); + right = Vector3.Cross(Vector3.up, forward).normalized; + } + + Vector3 targetPosition = + memoryPiecePlayerHead.position + + forward * memoryPieceResultDistance + + Vector3.up * memoryPieceResultHeightOffset + + right * memoryPieceResultSideOffset; + + currentMemoryPieceResultObject.transform.position = targetPosition; + + if (!memoryPieceResultFacePlayer) + return; + + Vector3 lookDirection = memoryPiecePlayerHead.position - currentMemoryPieceResultObject.transform.position; + + if (memoryPieceResultYawOnly) + lookDirection.y = 0f; + + if (lookDirection.sqrMagnitude > 0.0001f) + currentMemoryPieceResultObject.transform.rotation = Quaternion.LookRotation(lookDirection.normalized, Vector3.up); + } + + private void PrepareMemoryPieceResultObject(GameObject target) + { + if (target == null) + return; + + if (disableMemoryPieceResultColliders) + { + Collider[] colliders = target.GetComponentsInChildren(true); + + for (int i = 0; i < colliders.Length; i++) + colliders[i].enabled = false; + } + + if (disableMemoryPieceResultFloatMotion) + { + StarPickup[] starPickups = target.GetComponentsInChildren(true); + + for (int i = 0; i < starPickups.Length; i++) + starPickups[i].floatMotion = false; + } } private void OnResetAction(InputAction.CallbackContext context) diff --git a/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs b/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs index ed67dfa..d650eea 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs @@ -12,6 +12,23 @@ public class FishingItemIconData [TextArea] public string hintText; } +/// +/// Reel-only fishing UI. +/// +/// Removed from the old mixed version: +/// - circular timing gauge references +/// - pointer / success zone rendering +/// - Perfect / Good / Miss result type helpers +/// - debug catch button handling +/// +/// Keeps: +/// - reel progress gauge +/// - line length text +/// - pond cleanup progress +/// - caught item panel +/// - final result panel +/// - slot highlight / simple UI effects +/// public class FishingGaugeUI : MonoBehaviour { [Header("Auto Bind")] @@ -25,7 +42,6 @@ public class FishingGaugeUI : MonoBehaviour [SerializeField] private GameObject controllerGuidePanel; [SerializeField] private GameObject memoryPieceNoticePanel; [SerializeField] private GameObject finalResultPanel; - [SerializeField] private GameObject catchButton; [Header("Top Texts")] [SerializeField] private TMP_Text titleText; @@ -34,8 +50,6 @@ public class FishingGaugeUI : MonoBehaviour [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")] @@ -43,12 +57,6 @@ public class FishingGaugeUI : MonoBehaviour [SerializeField] private Image cleanupGaugeFill; [SerializeField] private TMP_Text cleanupPercentText; - [Header("Gauge UI")] - [SerializeField] private RectTransform gaugeGroupRect; - [SerializeField] private RectTransform pointerPivot; - [Tooltip("SuccessZone. Image Type = Filled, Fill Method = Radial 360")] - [SerializeField] private Image successZone; - [Header("Simple Reel UI")] [Tooltip("릴 감기 진행도 게이지 루트입니다. 없으면 텍스트만 업데이트됩니다.")] [SerializeField] private GameObject reelProgressGaugeRoot; @@ -63,7 +71,7 @@ public class FishingGaugeUI : MonoBehaviour [SerializeField] private string simpleReelIdleText = "릴 손잡이를 잡고 돌려라"; [SerializeField] private string simpleReelReelingText = "좋다, 계속 감아라!"; - [Header("Round Result UI")] + [Header("Result UI")] [SerializeField] private TMP_Text resultText; [Header("Caught Item UI")] @@ -71,7 +79,7 @@ public class FishingGaugeUI : MonoBehaviour [SerializeField] private TMP_Text caughtItemText; [SerializeField] private TMP_Text itemHintText; - [Header("Item Slot UI - Fish")] + [Header("Item Slot UI - Fish / Other")] [SerializeField] private GameObject fishSlot; [SerializeField] private Image fishSlotBackground; [SerializeField] private Image fishIcon; @@ -116,10 +124,6 @@ public class FishingGaugeUI : MonoBehaviour [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; @@ -130,22 +134,14 @@ public class FishingGaugeUI : MonoBehaviour [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만 보이게 합니다.")] + [Tooltip("게임 클리어 최종 결과가 뜰 때 진행 UI를 전부 숨기고 FinalResultPanel만 보이게 합니다.")] [SerializeField] private bool showOnlyFinalResult = true; [Tooltip("FinalResultPanel을 숨길 때 진행 UI를 다시 켤지 결정합니다. 클리어 후 UI를 완전히 닫는 구조면 Off로 두세요.")] @@ -157,18 +153,11 @@ public class FishingGaugeUI : MonoBehaviour [Tooltip("CleanupGauge 루트 오브젝트입니다.")] [SerializeField] private GameObject cleanupGaugeRoot; - [Tooltip("GaugeGroup 루트 오브젝트입니다.")] - [SerializeField] private GameObject gaugeGroupRoot; - [Header("Item Icons")] [SerializeField] private FishingItemIconData[] itemIcons; - [Header("Zone Visual Correction")] - [SerializeField] private float zoneVisualOffset = 0f; - - [Header("Guide / Debug UI")] + [Header("Guide UI")] [SerializeField] private bool showControllerGuideOnInitialize = true; - [SerializeField] private bool showCatchButtonOnInitialize = true; [Header("Text Settings")] [SerializeField] private string titleDefaultText = "기묘한 낚시터"; @@ -176,16 +165,33 @@ public class FishingGaugeUI : MonoBehaviour [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; + + [Header("VR Follow")] + [Tooltip("켜면 FishingCanvas가 플레이어 머리 앞을 따라갑니다.")] + [SerializeField] private bool followPlayerHead = true; + + [Tooltip("비워두면 Camera.main을 자동으로 사용합니다. XR Origin의 Main Camera를 넣는 것을 추천합니다.")] + [SerializeField] private Transform playerHead; + + [Tooltip("실제로 이동시킬 UI 루트입니다. 비워두면 이 FishingGaugeUI가 붙은 오브젝트를 이동합니다.")] + [SerializeField] private Transform followRoot; + + [SerializeField] private float followDistance = 1.6f; + [SerializeField] private float followHeightOffset = -0.15f; + [SerializeField] private float followSideOffset = 0f; + + [Tooltip("고개를 위아래로 숙여도 UI는 수평으로 유지합니다. VR에서는 켜는 것을 추천합니다.")] + [SerializeField] private bool followYawOnly = true; + + [SerializeField] private float followPositionSpeed = 8f; + [SerializeField] private float followRotationSpeed = 10f; + + [Tooltip("UI가 카메라와 같은 방향을 바라보게 합니다. 글자가 뒤집히면 이 값을 바꾸거나 Canvas Y Rotation을 180도로 돌리세요.")] + [SerializeField] private bool faceSameDirectionAsHead = true; private Coroutine resultRoutine; private Coroutine caughtItemRoutine; private Coroutine noticeRoutine; - private bool initialized; private float lastCleanupFill = -1f; private void Awake() @@ -202,6 +208,11 @@ private void OnDisable() StopRunningRoutines(); } + private void LateUpdate() + { + UpdateFollowPlayerHead(); + } + private void OnValidate() { cleanupFillTime = Mathf.Max(0f, cleanupFillTime); @@ -215,18 +226,78 @@ private void OnValidate() 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); + followDistance = Mathf.Max(0.2f, followDistance); + followPositionSpeed = Mathf.Max(0.01f, followPositionSpeed); + followRotationSpeed = Mathf.Max(0.01f, followRotationSpeed); + } + + + private void UpdateFollowPlayerHead() + { + if (!followPlayerHead) + return; + + Transform root = followRoot != null ? followRoot : transform; + + if (playerHead == null) + { + Camera mainCamera = Camera.main; + if (mainCamera == null) + return; + + playerHead = mainCamera.transform; + } + + Vector3 forward = playerHead.forward; + Vector3 right = playerHead.right; + + if (followYawOnly) + { + forward.y = 0f; + + if (forward.sqrMagnitude < 0.0001f) + forward = root.forward; + + forward.Normalize(); + right = Vector3.Cross(Vector3.up, forward).normalized; + } + + Vector3 targetPosition = + playerHead.position + + forward * followDistance + + Vector3.up * followHeightOffset + + right * followSideOffset; + + float positionT = 1f - Mathf.Exp(-followPositionSpeed * Time.deltaTime); + root.position = Vector3.Lerp(root.position, targetPosition, positionT); + + Quaternion targetRotation; + + if (faceSameDirectionAsHead) + { + targetRotation = Quaternion.LookRotation(forward, Vector3.up); + } + else + { + Vector3 lookDirection = root.position - playerHead.position; + + if (followYawOnly) + lookDirection.y = 0f; + + if (lookDirection.sqrMagnitude < 0.0001f) + lookDirection = forward; + + targetRotation = Quaternion.LookRotation(lookDirection.normalized, Vector3.up); + } + + float rotationT = 1f - Mathf.Exp(-followRotationSpeed * Time.deltaTime); + root.rotation = Quaternion.Slerp(root.rotation, targetRotation, rotationT); } private void EnsureEffectReferences() @@ -253,13 +324,12 @@ private void EnsureEffectReferences() finalResultCanvasGroup = effects.EnsureCanvasGroup(finalResultPanel); } - [ContextMenu("Auto Bind UI References")] + [ContextMenu("Auto Bind Reel 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"); @@ -267,23 +337,17 @@ public void AutoBindMissingReferences() if (controllerGuidePanel == null) controllerGuidePanel = FindGameObject("ControllerGuidePanel"); if (memoryPieceNoticePanel == null) memoryPieceNoticePanel = FindGameObject("MemoryPieceNoticePanel"); if (finalResultPanel == null) finalResultPanel = FindGameObject("FinalResultPanel"); - if (catchButton == null) catchButton = FindGameObject("CatchButton", "DebugCatchButton"); if (titleText == null) titleText = FindComponentByName("TitleText"); if (pondStateText == null) pondStateText = FindComponentByName("PondStateText"); if (objectiveText == null) objectiveText = FindComponentByName("ObjectiveText"); if (catchCountText == null) catchCountText = FindComponentByName("CatchCountText", "SuccessText"); - if (successText == null) successText = FindComponentByName("SuccessText"); - if (failText == null) failText = FindComponentByName("FailText"); if (cleanupProgressText == null) cleanupProgressText = FindComponentByName("CleanupProgressText"); if (cleanupGaugeFill == null) cleanupGaugeFill = FindComponentByName("CleanupGaugeFill", "CleanupGaugeFillImage"); if (cleanupPercentText == null) cleanupPercentText = FindComponentByName("CleanupPercentText", "CleanupGaugePercentText"); - if (gaugeGroupRect == null) gaugeGroupRect = FindComponentByName("GaugeGroup"); - if (pointerPivot == null) pointerPivot = FindComponentByName("PointerPivot"); - if (successZone == null) successZone = FindComponentByName("SuccessZone"); if (reelProgressGaugeRoot == null) reelProgressGaugeRoot = FindGameObject("ReelProgressGauge", "ReelProgressGroup", "ReelFightProgressGauge"); if (reelProgressGaugeFill == null) reelProgressGaugeFill = FindComponentByName("ReelProgressGaugeFill", "ReelProgressFill", "ReelFightProgressFill"); if (lineLengthText == null) lineLengthText = FindComponentByName("LineLengthText", "Line Length Text", "LineText"); @@ -313,7 +377,6 @@ public void AutoBindMissingReferences() 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"); @@ -330,166 +393,88 @@ public void InitializeFishingUI() 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); + HideFinalResult(false, true); + UpdateInventoryUI(0, 0, 0); lastCleanupFill = -1f; SetCleanupFill(0f, true); SetSimpleReelUIVisible(false); - - initialized = true; } private void StopRunningRoutines() { - if (resultRoutine != null) StopCoroutine(resultRoutine); - if (caughtItemRoutine != null) StopCoroutine(caughtItemRoutine); - if (noticeRoutine != null) StopCoroutine(noticeRoutine); + if (resultRoutine != null) + { + StopCoroutine(resultRoutine); + resultRoutine = null; + } - resultRoutine = null; - caughtItemRoutine = null; - noticeRoutine = null; + if (caughtItemRoutine != null) + { + StopCoroutine(caughtItemRoutine); + caughtItemRoutine = null; + } - if (effects != null) - effects.StopAllEffects(); - } - - public void SetPointerRotation(float angle) - { - if (pointerPivot == null) - return; - - pointerPivot.localEulerAngles = new Vector3(0f, 0f, -angle); - } - - public void SetZone(float centerAngle, float size) - { - SetZoneSize(size); - SetZoneRotation(centerAngle, size); - } - - public void SetZoneSize(float size) - { - if (successZone == null) - return; - - successZone.fillAmount = Mathf.Clamp01(size / 360f); - } - - public void SetZoneRotation(float centerAngle, float size) - { - if (successZone == null) - return; - - float startAngle = centerAngle - size * 0.5f + zoneVisualOffset; - successZone.rectTransform.localEulerAngles = new Vector3(0f, 0f, -startAngle); - } - - public void UpdateCounter(int success, int successTarget, int fail, int failTarget) - { - ShowCounter(); - - if (catchCountText != null) - catchCountText.text = $"낚기 {success}/{successTarget}"; - - if (successText != null && successText != catchCountText) - successText.text = $"낚기 {success}/{successTarget}"; - - if (failText != null) - failText.text = $"실패 {fail}/{failTarget}"; - } - - public void SetTimingGaugeVisible(bool visible) - { - if (gaugeGroupRoot != null) - gaugeGroupRoot.SetActive(visible); - else if (gaugeGroupRect != null) - gaugeGroupRect.gameObject.SetActive(visible); + if (noticeRoutine != null) + { + StopCoroutine(noticeRoutine); + noticeRoutine = null; + } } public void SetSimpleReelUIVisible(bool visible) { if (reelProgressGaugeRoot != null) reelProgressGaugeRoot.SetActive(visible); + + if (lineLengthText != null) + lineLengthText.gameObject.SetActive(visible); } public void UpdateSimpleReelUI(float progress, float lineLength, bool isReeling) { - ShowCounter(); - progress = Mathf.Clamp01(progress); - lineLength = Mathf.Max(0f, lineLength); - int progressPercent = Mathf.RoundToInt(progress * 100f); - - if (catchCountText != null) - catchCountText.text = $"끌어올리기 {progressPercent}%"; - - if (successText != null && successText != catchCountText) - successText.text = $"끌어올리기 {progressPercent}%"; - - if (failText != null) - failText.text = $"줄 길이 {lineLength:0.0}m"; - - if (lineLengthText != null) - lineLengthText.text = $"줄 길이 {lineLength:0.0}m"; if (reelProgressGaugeFill != null) reelProgressGaugeFill.fillAmount = progress; + if (lineLengthText != null) + { + string stateText = isReeling ? simpleReelReelingText : simpleReelIdleText; + lineLengthText.text = $"줄 길이 {lineLength:0.00}m\n진행도 {Mathf.RoundToInt(progress * 100f)}%\n{stateText}"; + } + if (objectiveText != null && !string.IsNullOrWhiteSpace(simpleReelObjectiveText)) objectiveText.text = simpleReelObjectiveText; - - if (resultText != null && resultText.gameObject.activeSelf) - { - if (isReeling && progress < 1f) - resultText.text = simpleReelReelingText; - else if (progress < 1f && !string.IsNullOrWhiteSpace(simpleReelIdleText)) - resultText.text = simpleReelIdleText; - } } public void UpdateFishingProgress(int cleanupItemCount, int cleanupTarget, bool pondCleaned, bool memoryPieceCollected, int totalCaughtItems) { cleanupTarget = Mathf.Max(1, cleanupTarget); - int clampedCleanupCount = Mathf.Clamp(cleanupItemCount, 0, cleanupTarget); - float progress = Mathf.Clamp01((float)clampedCleanupCount / cleanupTarget); + float progress = Mathf.Clamp01((float)cleanupItemCount / cleanupTarget); + + if (catchCountText != null) + catchCountText.text = $"획득 {totalCaughtItems}"; if (cleanupProgressText != null) - { - cleanupProgressText.text = memoryPieceCollected - ? $"정화 완료 {cleanupTarget}/{cleanupTarget}" - : $"쓰레기 수거 {clampedCleanupCount}/{cleanupTarget}"; - } - - SetCleanupFill(progress, !initialized || !animateCleanupGauge); - - if (cleanupPercentText != null) - cleanupPercentText.text = $"{Mathf.RoundToInt(progress * 100f)}%"; + cleanupProgressText.text = pondCleaned ? "연못 정화 완료" : $"정화 {cleanupItemCount}/{cleanupTarget}"; if (pondStateText != null) { if (memoryPieceCollected) - pondStateText.text = "기억의 조각 획득"; + pondStateText.text = "기억의 조각을 찾았다"; else if (pondCleaned) - pondStateText.text = "연못이 맑아졌다!"; - else if (progress >= 0.66f) - pondStateText.text = "연못 상태: 거의 맑아짐"; - else if (progress >= 0.33f) - pondStateText.text = "연못 상태: 조금 맑아짐"; + pondStateText.text = "맑아진 연못"; else - pondStateText.text = "연못 상태: 탁함"; + pondStateText.text = "오염된 연못"; } if (objectiveText != null) @@ -501,94 +486,95 @@ public void UpdateFishingProgress(int cleanupItemCount, int cleanupTarget, bool else objectiveText.text = objectiveDirtyText; } + + SetCleanupFill(progress, false); } private void SetCleanupFill(float progress, bool instant) { - if (cleanupGaugeFill == null) - return; - progress = Mathf.Clamp01(progress); - if (!instant && Mathf.Abs(lastCleanupFill - progress) < 0.001f) - return; + if (cleanupGaugeFill != null) + { + if (instant || !animateCleanupGauge || effects == null || lastCleanupFill < 0f) + cleanupGaugeFill.fillAmount = progress; + else if (!Mathf.Approximately(lastCleanupFill, progress)) + effects.FillImage(cleanupGaugeFill, progress, cleanupFillTime); + } + + if (cleanupPercentText != null) + cleanupPercentText.text = $"{Mathf.RoundToInt(progress * 100f)}%"; 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) + public void UpdateInventoryUI(int fishCount, int trashCount, int memoryPieceCount) { - 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}"; + if (fishCountText != null) fishCountText.text = fishCount.ToString(); + if (trashCountText != null) trashCountText.text = trashCount.ToString(); + if (memoryPieceCountText != null) memoryPieceCountText.text = memoryPieceCount.ToString(); ApplySlotState(fishSlot, fishSlotBackground, fishIcon, fishCount > 0); ApplySlotState(trashSlot, trashSlotBackground, trashIcon, trashCount > 0); ApplySlotState(memorySlot, memorySlotBackground, memoryIcon, memoryPieceCount > 0); - ApplySlotState(compassSlot, compassSlotBackground, compassIcon, compassCount > 0); + + // 이제 낚시 아이템은 물고기 / 쓰레기 / 기억의 조각 3종만 사용합니다. + // 기존 Compass 슬롯이 하이어라키에 남아 있으면 자동으로 숨깁니다. + if (compassSlot != null) + compassSlot.SetActive(false); } public void HighlightSlotForItem(FishingItemType itemType) { - GameObject targetSlot = GetSlotForItem(itemType); - Image targetBackground = GetSlotBackgroundForItem(itemType); - Image targetIcon = GetSlotIconForItem(itemType); + switch (itemType) + { + case FishingItemType.Fish: + StartSlotHighlight(fishSlotBackground, fishIcon); + break; - if (targetSlot == null && targetBackground == null && targetIcon == null) - return; + case FishingItemType.Trash: + StartSlotHighlight(trashSlotBackground, trashIcon); + break; - if (effects != null) - effects.HighlightSlot(targetBackground, targetIcon, targetSlot != null ? targetSlot.transform : null, slotHighlightColor, slotHighlightTime, slotHighlightScale); - else - StartCoroutine(SimpleSlotHighlightRoutine(targetBackground, targetIcon)); + case FishingItemType.MemoryPiece: + StartSlotHighlight(memorySlotBackground, memoryIcon); + break; + } + } + + private void StartSlotHighlight(Image background, Image icon) + { + if (background != null) + StartCoroutine(SimpleSlotHighlightRoutine(background, icon)); + else if (icon != null && effects != null) + effects.Pop(icon.transform, slotHighlightScale, slotHighlightTime); } 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; - } + Color originalBackgroundColor = background.color; + Color originalIconColor = icon != null ? icon.color : Color.white; + background.color = slotHighlightColor; if (icon != null) { - originalIcon = icon.color; - Color c = icon.color; - c.a = 1f; - icon.color = c; + Color highlightedIcon = icon.color; + highlightedIcon.a = filledIconAlpha; + icon.color = highlightedIcon; + } + + if (effects != null) + { + effects.Pop(background.transform, slotHighlightScale, slotHighlightTime); + if (icon != null) + effects.Pop(icon.transform, slotHighlightScale, slotHighlightTime); } yield return new WaitForSeconds(slotHighlightTime); - if (background != null && originalBackground.HasValue) - background.color = originalBackground.Value; - - if (icon != null && originalIcon.HasValue) - icon.color = originalIcon.Value; + background.color = originalBackgroundColor; + if (icon != null) + icon.color = originalIconColor; } private void ApplySlotState(GameObject slotRoot, Image slotBackground, Image icon, bool hasItem) @@ -610,64 +596,20 @@ private void SetImageAlpha(Image image, float alpha) image.color = color; } - private GameObject GetSlotForItem(FishingItemType itemType) - { - switch (itemType) - { - case FishingItemType.Fish: return fishSlot; - case FishingItemType.Trash: - case FishingItemType.Bottle: - case FishingItemType.PlasticBag: return trashSlot; - case FishingItemType.MemoryPiece: return memorySlot; - case FishingItemType.OldCompass: return compassSlot; - default: return null; - } - } - - private Image GetSlotBackgroundForItem(FishingItemType itemType) - { - switch (itemType) - { - case FishingItemType.Fish: return fishSlotBackground; - case FishingItemType.Trash: - case FishingItemType.Bottle: - case FishingItemType.PlasticBag: return trashSlotBackground; - case FishingItemType.MemoryPiece: return memorySlotBackground; - case FishingItemType.OldCompass: return compassSlotBackground; - default: return null; - } - } - - private Image GetSlotIconForItem(FishingItemType itemType) - { - switch (itemType) - { - case FishingItemType.Fish: return fishIcon; - case FishingItemType.Trash: - case FishingItemType.Bottle: - case FishingItemType.PlasticBag: return trashIcon; - case FishingItemType.MemoryPiece: return memoryIcon; - case FishingItemType.OldCompass: return compassIcon; - default: return null; - } - } - public void ShowCounter() { - if (counterPanel != null) - counterPanel.SetActive(true); + SetCounterVisible(true); } public void HideCounter() { - if (counterPanel != null) - counterPanel.SetActive(false); + SetCounterVisible(false); } public void SetCounterVisible(bool visible) { - if (visible) ShowCounter(); - else HideCounter(); + if (counterPanel != null) + counterPanel.SetActive(visible); } public void SetItemSlotPanelVisible(bool visible) @@ -678,14 +620,11 @@ public void SetItemSlotPanelVisible(bool visible) public void ShowResult(string text) { - ShowResult(text, defaultResultColor); + ShowResult(text, Color.white); } public void ShowResult(string text, Color color) { - if (resultText == null) - return; - if (resultRoutine != null) StopCoroutine(resultRoutine); @@ -694,57 +633,36 @@ public void ShowResult(string text, Color color) public void ShowPersistentResult(string text) { - ShowPersistentResult(text, defaultResultColor); + ShowPersistentResult(text, Color.white); } public void ShowPersistentResult(string text, Color color) { - if (resultText == null) - return; - if (resultRoutine != null) { StopCoroutine(resultRoutine); resultRoutine = null; } - resultText.gameObject.SetActive(true); - resultText.color = color; - resultText.text = text; - - if (effects != null) - effects.Pop(resultText.transform, resultPopScale, resultPopTime); - } - - public void ShowResultForType(FishingGameManager.ResultType resultType) - { - switch (resultType) + if (resultText != null) { - case FishingGameManager.ResultType.Perfect: - ShowResult("완벽!", perfectColor); - if (effects != null && gaugeGroupRect != null) - effects.Pop(gaugeGroupRect, gaugePopScale, gaugePopTime); - break; + resultText.gameObject.SetActive(true); + resultText.text = text; + resultText.color = color; - 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; + if (effects != null) + effects.Pop(resultText.transform, resultPopScale, resultPopTime); } } private IEnumerator ResultRoutine(string text, Color color) { + if (resultText == null) + yield break; + resultText.gameObject.SetActive(true); - resultText.color = color; resultText.text = text; + resultText.color = color; if (effects != null) effects.Pop(resultText.transform, resultPopScale, resultPopTime); @@ -911,13 +829,8 @@ private string GetCaughtMessage(FishingItemType itemType, string displayName) switch (itemType) { - case FishingItemType.Fish: return "생선을 낚았다!"; - case FishingItemType.RottenFish: return "상한 생선을 낚았다..."; + case FishingItemType.Fish: 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; } @@ -935,10 +848,7 @@ private string GetDefaultHint(FishingItemType itemType, bool countsAsCleanupItem switch (itemType) { - case FishingItemType.Fish: return "생선은 고양이들이 좋아할 것 같다."; - case FishingItemType.RottenFish: return "냄새가 심하다. 쓸 수 있을까?"; - case FishingItemType.JellyfishPowder: return "이상한 빛을 내는 가루다."; - case FishingItemType.OldCompass: return "길을 찾는 데 도움이 될 것 같다."; + case FishingItemType.Fish: return "평범한 물고기다."; case FishingItemType.MemoryPiece: return "잃어버린 기억의 일부다."; default: return string.Empty; } @@ -1061,53 +971,6 @@ private void SetControllerGuideVisible(bool visible, bool instant) } } - 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) @@ -1120,7 +983,6 @@ public void HideRoundResult() resultText.gameObject.SetActive(false); } - private void SetGameplayUIVisible(bool visible) { SetGameObjectVisible(gameplayBackgroundRoot, visible); @@ -1137,24 +999,14 @@ private void SetGameplayUIVisible(bool visible) SetGameObjectVisible(counterPanel, visible); SetGameObjectVisible(itemSlotPanel, visible); SetGameObjectVisible(cleanupGaugeRoot, visible); - - if (!visible) - SetSimpleReelUIVisible(false); - - if (gaugeGroupRoot != null) - gaugeGroupRoot.SetActive(visible); - else if (gaugeGroupRect != null) - gaugeGroupRect.gameObject.SetActive(visible); - + SetSimpleReelUIVisible(visible); SetControllerGuideVisible(visible && showControllerGuideOnInitialize, true); - SetCatchButtonVisible(visible && showCatchButtonOnInitialize); if (!visible) { HideRoundResult(); HideCaughtItem(true); HideNotice(true); - SetFeedbackFlashVisible(false, Color.clear); } } @@ -1167,24 +1019,13 @@ private void SetGameObjectVisible(GameObject target, bool visible) public void ShowFinalResult(string text) { HideRoundResult(); - - if (hideCaughtItemOnFinalResult) - HideCaughtItem(true); - + HideCaughtItem(true); HideNotice(true); if (showOnlyFinalResult) - { SetGameplayUIVisible(false); - } else - { - if (hideCounterOnFinalResult) - HideCounter(); - - if (hideGuideOnFinalResult) - SetControllerGuideVisible(false, true); - } + SetControllerGuideVisible(false, true); if (finalResultPanel != null) finalResultPanel.SetActive(true); @@ -1219,12 +1060,12 @@ public void HideFinalResult() HideFinalResult(true, false); } - public void HideFinalResult(bool showCounterAfterHide) + public void HideFinalResult(bool showGameplayAfterHide) { - HideFinalResult(showCounterAfterHide, false); + HideFinalResult(showGameplayAfterHide, false); } - private void HideFinalResult(bool showCounterAfterHide, bool instant) + private void HideFinalResult(bool showGameplayAfterHide, bool instant) { if (instant || effects == null) { @@ -1246,18 +1087,8 @@ private void HideFinalResult(bool showCounterAfterHide, bool instant) if (finalResultText != null) finalResultText.gameObject.SetActive(false); - if (showCounterAfterHide) - { - if (showOnlyFinalResult) - { - if (restoreGameplayUIAfterFinalResult) - SetGameplayUIVisible(true); - } - else - { - ShowCounter(); - } - } + if (showGameplayAfterHide && showOnlyFinalResult && restoreGameplayUIAfterFinalResult) + SetGameplayUIVisible(true); } private GameObject FindGameObject(params string[] names) diff --git a/Assets/My project/Fishing Scripts/UI/FishingInventory.cs b/Assets/My project/Fishing Scripts/UI/FishingInventory.cs index 11f421b..290d314 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingInventory.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingInventory.cs @@ -4,21 +4,11 @@ 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() @@ -29,12 +19,7 @@ private void OnValidate() 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); } @@ -49,34 +34,16 @@ public void AddItem(FishingItemType itemType, int amount = 1) 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; } + + ClampCounts(); } public bool HasItem(FishingItemType itemType, int amount = 1) @@ -98,35 +65,16 @@ public bool ConsumeItem(FishingItemType itemType, int amount = 1) 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; } + ClampCounts(); return true; } @@ -137,24 +85,9 @@ public int GetItemCount(FishingItemType 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; @@ -167,12 +100,7 @@ public int GetItemCount(FishingItemType itemType) 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/FishingInventoryBinder.cs b/Assets/My project/Fishing Scripts/UI/FishingInventoryBinder.cs new file mode 100644 index 0000000..55d4bdc --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingInventoryBinder.cs @@ -0,0 +1,54 @@ +using UnityEngine; + +/// +/// Connects FishingGameManager's caught-item event to FishingInventory. +/// Add this to the same prefab/root object, then assign Game Manager and Inventory. +/// +public class FishingInventoryBinder : MonoBehaviour +{ + [SerializeField] private FishingGameManager gameManager; + [SerializeField] private FishingInventory inventory; + + private void Reset() + { + AutoBind(); + } + + private void Awake() + { + AutoBind(); + } + + private void OnEnable() + { + if (gameManager != null) + gameManager.ItemCaught += OnItemCaught; + } + + private void OnDisable() + { + if (gameManager != null) + gameManager.ItemCaught -= OnItemCaught; + } + + [ContextMenu("Auto Bind Inventory Binder")] + public void AutoBind() + { + if (gameManager == null) + gameManager = GetComponentInParent(); + + if (inventory == null) + inventory = GetComponentInParent(); + + if (inventory == null && gameManager != null) + inventory = gameManager.GetComponentInChildren(true); + } + + private void OnItemCaught(FishingItemType itemType, int amount) + { + if (inventory == null) + return; + + inventory.AddItem(itemType, amount); + } +} diff --git a/Assets/My project/Fishing Scripts/UI/FishingInventoryBinder.cs.meta b/Assets/My project/Fishing Scripts/UI/FishingInventoryBinder.cs.meta new file mode 100644 index 0000000..c480dbd --- /dev/null +++ b/Assets/My project/Fishing Scripts/UI/FishingInventoryBinder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a6aa548e80b5ed040b1d663b9d7bafa7 \ 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 index a95ffe5..9ef4e4c 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingItemType.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingItemType.cs @@ -2,11 +2,6 @@ public enum FishingItemType { None, Fish, - RottenFish, Trash, - Bottle, - PlasticBag, - JellyfishPowder, - OldCompass, MemoryPiece } diff --git a/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs b/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs index f395ca1..11de907 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs @@ -8,7 +8,7 @@ public class FishingRewardSystem : MonoBehaviour public class LootEntry { public FishingItemType itemType = FishingItemType.Fish; - public string displayName = "생선"; + public string displayName = "물고기"; [Min(0f)] public float weight = 1f; public LootEntry(FishingItemType itemType, string displayName, float weight) @@ -36,11 +36,11 @@ public CatchResult(FishingItemType itemType, string displayName, bool countsAsCl } [Header("Loot Table - Dirty Pond")] - [Tooltip("연못이 정화되기 전 낚이는 아이템 목록입니다. 기억의 조각은 이 테이블에 있어도 자동 제외됩니다.")] + [Tooltip("연못이 정화되기 전에는 물고기와 쓰레기만 낚입니다. 기억의 조각은 자동 제외됩니다.")] [SerializeField] private List dirtyPondLootTable = new List(); [Header("Loot Table - Clean Pond")] - [Tooltip("연못이 정화된 뒤 낚이는 아이템 목록입니다. 기억의 조각을 포함할 수 있습니다.")] + [Tooltip("연못이 정화된 뒤에는 기억의 조각이 낚입니다.")] [SerializeField] private List cleanPondLootTable = new List(); [Header("Debug")] @@ -74,6 +74,10 @@ private void EnsureLootTables() if (cleanPondLootTable == null) cleanPondLootTable = new List(); + // 이전 버전의 상한 생선/병/봉지/나침반 등이 인스펙터에 남아 있어도 + // 런타임에서는 물고기 / 쓰레기 / 기억의 조각 3종만 남도록 정리합니다. + SanitizeLootTables(); + if (dirtyPondLootTable.Count == 0) SetDefaultDirtyLootTable(); @@ -81,6 +85,45 @@ private void EnsureLootTables() SetDefaultCleanLootTable(); } + private void SanitizeLootTables() + { + SanitizeTable(dirtyPondLootTable, allowFish: true, allowTrash: true, allowMemoryPiece: false); + SanitizeTable(cleanPondLootTable, allowFish: false, allowTrash: false, allowMemoryPiece: true); + } + + private void SanitizeTable(List table, bool allowFish, bool allowTrash, bool allowMemoryPiece) + { + if (table == null) + return; + + for (int i = table.Count - 1; i >= 0; i--) + { + LootEntry entry = table[i]; + + if (entry == null || !IsAllowedInTable(entry.itemType, allowFish, allowTrash, allowMemoryPiece)) + table.RemoveAt(i); + } + } + + private bool IsAllowedInTable(FishingItemType itemType, bool allowFish, bool allowTrash, bool allowMemoryPiece) + { + switch (itemType) + { + case FishingItemType.Fish: + return allowFish; + + case FishingItemType.Trash: + return allowTrash; + + case FishingItemType.MemoryPiece: + return allowMemoryPiece; + + case FishingItemType.None: + default: + return false; + } + } + private void SetDefaultLootTables() { SetDefaultDirtyLootTable(); @@ -91,13 +134,8 @@ 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) + new LootEntry(FishingItemType.Fish, "물고기", 35f), + new LootEntry(FishingItemType.Trash, "쓰레기", 65f) }; } @@ -105,11 +143,7 @@ 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) + new LootEntry(FishingItemType.MemoryPiece, "기억의 조각", 100f) }; } @@ -122,14 +156,14 @@ private void ClampLootTable(List table) { if (table[i] == null) { - table[i] = new LootEntry(FishingItemType.Fish, "생선", 1f); + 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); + // 이전 버전에서 남은 표시 이름을 3종 아이템 이름으로 정리합니다. + table[i].displayName = GetDefaultDisplayName(table[i].itemType); } } @@ -142,7 +176,8 @@ public CatchResult RollCatch(bool pondCleaned, bool forceMemoryPiece) LootEntry selectedEntry = RollFromTable(table, pondCleaned); if (selectedEntry == null) - return CreateCatchResult(FishingItemType.Fish, GetDefaultDisplayName(FishingItemType.Fish)); + return CreateCatchResult(pondCleaned ? FishingItemType.MemoryPiece : FishingItemType.Fish, + GetDefaultDisplayName(pondCleaned ? FishingItemType.MemoryPiece : FishingItemType.Fish)); return CreateCatchResult(selectedEntry.itemType, selectedEntry.displayName); } @@ -194,10 +229,15 @@ private bool IsRollableEntry(LootEntry entry, bool pondCleaned) if (entry.weight <= 0f) return false; + if (entry.itemType == FishingItemType.None) + return false; + if (!pondCleaned && entry.itemType == FishingItemType.MemoryPiece) return false; - return entry.itemType != FishingItemType.None; + return entry.itemType == FishingItemType.Fish || + entry.itemType == FishingItemType.Trash || + entry.itemType == FishingItemType.MemoryPiece; } private CatchResult CreateCatchResult(FishingItemType itemType, string displayName) @@ -214,9 +254,7 @@ private CatchResult CreateCatchResult(FishingItemType itemType, string displayNa public bool IsCleanupItem(FishingItemType itemType) { - return itemType == FishingItemType.Trash || - itemType == FishingItemType.Bottle || - itemType == FishingItemType.PlasticBag; + return itemType == FishingItemType.Trash; } public string GetDefaultDisplayName(FishingItemType itemType) @@ -224,26 +262,11 @@ public string GetDefaultDisplayName(FishingItemType itemType) switch (itemType) { case FishingItemType.Fish: - return "생선"; - - case FishingItemType.RottenFish: - return "상한 생선"; + 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 "기억의 조각"; diff --git a/Assets/My project/Fishing Scripts/UI/FishingRodVRController.cs b/Assets/My project/Fishing Scripts/UI/FishingRodVRController.cs index 4c4dea3..5d84cde 100644 --- a/Assets/My project/Fishing Scripts/UI/FishingRodVRController.cs +++ b/Assets/My project/Fishing Scripts/UI/FishingRodVRController.cs @@ -46,6 +46,18 @@ public enum StartMode ManualOnly } + public enum BiteStartMode + { + [Tooltip("캐스팅이 끝나면 물 감지와 상관없이 입질 대기를 시작합니다. 개발/테스트 단계에서 추천합니다.")] + AfterCastAlways, + + [Tooltip("BobberLineStartPoint가 Water Collider로 감지될 때만 입질 대기를 시작합니다. 완성 단계에서 추천합니다.")] + WhenBobberInWater, + + [Tooltip("자동 입질 대기를 시작하지 않습니다. ForceBite() 또는 별도 이벤트로만 입질을 발생시킵니다.")] + ManualOnly + } + [Header("Core References")] [SerializeField] private FishingGameManager fishingGameManager; [SerializeField] private FishingHapticManager haptic; @@ -158,6 +170,10 @@ public enum StartMode [SerializeField] private bool useWaterTag = true; [SerializeField] private string waterTag = "Water"; + [Header("Bite Start Rule")] + [Tooltip("입질 대기를 언제 시작할지 결정합니다. 테스트 중에는 AfterCastAlways, 실제 물 판정이 필요하면 WhenBobberInWater를 사용하세요.")] + [SerializeField] private BiteStartMode biteStartMode = BiteStartMode.AfterCastAlways; + [Header("Bite")] [SerializeField] private bool useBiteTimer = true; [SerializeField] private Vector2 biteDelayRange = new Vector2(1.5f, 4.0f); @@ -181,7 +197,7 @@ public enum StartMode [Tooltip("If On, Reel Hold Action must be pressed to reel. If Off and Use Reel Grab Events is also Off, being near the reel is enough.")] [SerializeField] private bool useReelHoldAction = true; - [Tooltip("Button/Grip action used while reeling. Example binding: {LeftHand}/gripPressed or {LeftHand}/triggerPressed.")] + [Tooltip("Button/Grip action used while reeling. Example binding: {RightHand}/gripPressed or {RightHand}/triggerPressed when the right hand reels.")] [SerializeField] private InputActionReference reelHoldAction; [Tooltip("If On, call MarkReelHandleGrabbed/MarkReelHandleReleased from a reel handle XR Grab Interactable or trigger volume.")] @@ -597,9 +613,53 @@ private IEnumerator CastRoutine(Vector3 target) state = BobberState.WaitingInWater; Log("찌 캐스팅 완료"); + HandleBiteStartAfterCast(); + } - if (!useWaterOverlapCheck && useBiteTimer) - StartWaitingForBite(); + private void HandleBiteStartAfterCast() + { + if (!useBiteTimer) + { + Log("입질 타이머가 꺼져 있어 입질 대기를 시작하지 않습니다."); + return; + } + + switch (biteStartMode) + { + case BiteStartMode.AfterCastAlways: + Log("캐스팅 완료 후 입질 대기를 시작합니다. 물 감지는 진행을 막지 않습니다."); + StartWaitingForBite(); + break; + + case BiteStartMode.WhenBobberInWater: + if (!useWaterOverlapCheck) + { + Log("물 감지가 꺼져 있어 캐스팅 완료 후 입질 대기를 시작합니다."); + StartWaitingForBite(); + return; + } + + if (IsBobberOverlappingWater()) + { + if (!bobberInWater) + { + bobberInWater = true; + Log("찌가 물에 들어감"); + } + + StartWaitingForBite(); + } + else + { + bobberInWater = false; + Log("캐스팅은 완료됐지만 찌가 물로 감지되지 않았습니다. Water Collider/Tag/Layer와 BobberLineStartPoint 위치를 확인하세요."); + } + break; + + case BiteStartMode.ManualOnly: + Log("수동 입질 모드입니다. ForceBite() 또는 별도 이벤트로 입질을 발생시켜야 합니다."); + break; + } } private IEnumerator MoveBobberRoutine(Vector3 target, float duration, BobberState endState) @@ -625,7 +685,10 @@ private IEnumerator MoveBobberRoutine(Vector3 target, float duration, BobberStat private void StartWaitingForBite() { if (!useBiteTimer) + { + Log("입질 타이머가 꺼져 있어 입질 대기를 시작하지 않습니다."); return; + } if (biteWaitRoutine != null || state == BobberState.Bite || state == BobberState.MiniGameActive) return; @@ -644,8 +707,9 @@ private IEnumerator BiteWaitRoutine() { timer += Time.deltaTime; - if (useWaterOverlapCheck && !bobberInWater) + if (biteStartMode == BiteStartMode.WhenBobberInWater && useWaterOverlapCheck && !bobberInWater) { + Log("찌가 물에서 벗어나 입질 대기를 취소합니다."); biteWaitRoutine = null; yield break; } @@ -806,14 +870,19 @@ private void UpdateWaterState() if (bobberInWater) { Log("찌가 물에 들어감"); - if (state == BobberState.Casting || state == BobberState.WaitingInWater || state == BobberState.Idle) + + if (biteStartMode == BiteStartMode.WhenBobberInWater && + (state == BobberState.Casting || state == BobberState.WaitingInWater || state == BobberState.Idle)) + { StartWaitingForBite(); + } } else { Log("찌가 물에서 나감"); - if (state == BobberState.WaitingInWater || state == BobberState.Bite) + if (biteStartMode == BiteStartMode.WhenBobberInWater && + (state == BobberState.WaitingInWater || state == BobberState.Bite)) { StopBiteWaitOnly(); StopBiteShakeOnly(); diff --git a/Assets/My project/Fishing Scripts/model.meta b/Assets/My project/Fishing Scripts/model.meta new file mode 100644 index 0000000..278aa54 --- /dev/null +++ b/Assets/My project/Fishing Scripts/model.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b2e082a09cbefc649856544b86523b55 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/My project/Fishing Scripts/model/dock_house_-_stylized_wooden_pier.glb b/Assets/My project/Fishing Scripts/model/dock_house_-_stylized_wooden_pier.glb new file mode 100644 index 0000000..456f30d Binary files /dev/null and b/Assets/My project/Fishing Scripts/model/dock_house_-_stylized_wooden_pier.glb differ diff --git a/Assets/My project/Fishing Scripts/model/dock_house_-_stylized_wooden_pier.glb.meta b/Assets/My project/Fishing Scripts/model/dock_house_-_stylized_wooden_pier.glb.meta new file mode 100644 index 0000000..8582c1d --- /dev/null +++ b/Assets/My project/Fishing Scripts/model/dock_house_-_stylized_wooden_pier.glb.meta @@ -0,0 +1,28 @@ +fileFormatVersion: 2 +guid: fe3d1baeb660bcc439902d62dfc02c1b +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/fishhook scene.meta b/Assets/My project/fishhook scene.meta index 730cb1b..c71decf 100644 --- a/Assets/My project/fishhook scene.meta +++ b/Assets/My project/fishhook scene.meta @@ -1,5 +1,9 @@ fileFormatVersion: 2 +<<<<<<< HEAD +guid: c9e7644bbc6b3324eb679151bb0018fa +======= guid: a32c27501966c844da3c4e2e54236880 +>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/My project/fishhook scene.unity b/Assets/My project/fishhook scene.unity index 05b78d0..c9ab585 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:0317ae16c7f72b3caa0f59ec58ff150f8bc3da503890b2cd906d39c9fd8140f9 -size 900430 +oid sha256:c7c30bbad9a00349b29c192e0d50ed89babb9492dda32ce49f7955676e3d5e1f +size 900660 diff --git a/Assets/My project/fishhook scene.unity.meta b/Assets/My project/fishhook scene.unity.meta index 0e7bd9a..045a615 100644 --- a/Assets/My project/fishhook scene.unity.meta +++ b/Assets/My project/fishhook scene.unity.meta @@ -1,5 +1,9 @@ fileFormatVersion: 2 +<<<<<<< HEAD +guid: 96e0f6dd612977042b62b4b18a2f8c9a +======= guid: 5e660f31b31a3c1489ac3a3e5804f0aa +>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690 DefaultImporter: externalObjects: {} userData: diff --git a/Assets/My project/fishhook scene/NavMesh-Terrain.asset.meta b/Assets/My project/fishhook scene/NavMesh-Terrain.asset.meta index 721538f..6c281de 100644 --- a/Assets/My project/fishhook scene/NavMesh-Terrain.asset.meta +++ b/Assets/My project/fishhook scene/NavMesh-Terrain.asset.meta @@ -1,5 +1,9 @@ fileFormatVersion: 2 +<<<<<<< HEAD +guid: 73f8b205cedf821488958abbf597e518 +======= guid: 897f994e3e3765248902eea88350c6f7 +>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690 NativeFormatImporter: externalObjects: {} mainObjectFileID: 0 diff --git a/Assets/XR/AndroidXR/AndroidXRSettingsInitializer.meta b/Assets/XR/AndroidXR/AndroidXRSettingsInitializer.meta index f8f5516..5021ca1 100644 --- a/Assets/XR/AndroidXR/AndroidXRSettingsInitializer.meta +++ b/Assets/XR/AndroidXR/AndroidXRSettingsInitializer.meta @@ -1,5 +1,9 @@ fileFormatVersion: 2 +<<<<<<< HEAD +guid: 7f4320a32f7a846409fcf9cc7fa5fda1 +======= guid: 779ddf707232c4f49a357660d770bcf9 +>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690 DefaultImporter: externalObjects: {} userData: diff --git a/Assets/XR/Settings/OpenXR Package Settings.asset b/Assets/XR/Settings/OpenXR Package Settings.asset new file mode 100644 index 0000000..7db06b1 --- /dev/null +++ b/Assets/XR/Settings/OpenXR Package Settings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4859dc7f625a8b8ee4419129f119da15e224fe90d79332fc8a86e90c43dfd29c +size 42383 diff --git a/Assets/XR/Settings/OpenXR Package Settings.asset.meta b/Assets/XR/Settings/OpenXR Package Settings.asset.meta new file mode 100644 index 0000000..97fe19a --- /dev/null +++ b/Assets/XR/Settings/OpenXR Package Settings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9c0e704ea9510154c87905364d1e2058 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/XRI Default Input Actions.inputactions b/Assets/XRI Default Input Actions.inputactions index dea57a9..ad86575 100644 --- a/Assets/XRI Default Input Actions.inputactions +++ b/Assets/XRI Default Input Actions.inputactions @@ -3420,7 +3420,7 @@ { "name": "", "id": "deb6a7a2-8de5-46b1-812b-d24b20c907a2", - "path": "{RightHand}/secondaryButton", + "path": "{LeftHand}/secondaryButton", "interactions": "", "processors": "", "groups": "", @@ -3431,7 +3431,7 @@ { "name": "", "id": "22448257-ef2a-42bf-a6c0-66fad17ee5e8", - "path": "{RightHand}/primaryButton", + "path": "{LeftHand}/primaryButton", "interactions": "", "processors": "", "groups": "", @@ -3442,7 +3442,7 @@ { "name": "", "id": "a6cfa3d8-bd52-4c0e-8ff2-414ab0f22663", - "path": "{LeftHand}/gripPressed", + "path": "{RightHand}/gripPressed", "interactions": "", "processors": "", "groups": "", diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index 366c4c4..7ee9ebc 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7957bc4ebd8426a0b73ec962c33b2f8026fadedf014e5d424a18e27b8dde196c +oid sha256:b595fa1d898c1d4664042b99f968f8ea428237e30412c0e1b535b9845ad09554 size 2072