This commit is contained in:
2026-06-29 17:33:22 +09:00
32 changed files with 1223 additions and 1000 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c60ef9a2426e27f41addd32bf4fda47c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -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:

View File

@@ -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:

Binary file not shown.

View File

@@ -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:

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,23 @@ public class FishingItemIconData
[TextArea] public string hintText;
}
/// <summary>
/// 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
/// </summary>
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<TMP_Text>("TitleText");
if (pondStateText == null) pondStateText = FindComponentByName<TMP_Text>("PondStateText");
if (objectiveText == null) objectiveText = FindComponentByName<TMP_Text>("ObjectiveText");
if (catchCountText == null) catchCountText = FindComponentByName<TMP_Text>("CatchCountText", "SuccessText");
if (successText == null) successText = FindComponentByName<TMP_Text>("SuccessText");
if (failText == null) failText = FindComponentByName<TMP_Text>("FailText");
if (cleanupProgressText == null) cleanupProgressText = FindComponentByName<TMP_Text>("CleanupProgressText");
if (cleanupGaugeFill == null) cleanupGaugeFill = FindComponentByName<Image>("CleanupGaugeFill", "CleanupGaugeFillImage");
if (cleanupPercentText == null) cleanupPercentText = FindComponentByName<TMP_Text>("CleanupPercentText", "CleanupGaugePercentText");
if (gaugeGroupRect == null) gaugeGroupRect = FindComponentByName<RectTransform>("GaugeGroup");
if (pointerPivot == null) pointerPivot = FindComponentByName<RectTransform>("PointerPivot");
if (successZone == null) successZone = FindComponentByName<Image>("SuccessZone");
if (reelProgressGaugeRoot == null) reelProgressGaugeRoot = FindGameObject("ReelProgressGauge", "ReelProgressGroup", "ReelFightProgressGauge");
if (reelProgressGaugeFill == null) reelProgressGaugeFill = FindComponentByName<Image>("ReelProgressGaugeFill", "ReelProgressFill", "ReelFightProgressFill");
if (lineLengthText == null) lineLengthText = FindComponentByName<TMP_Text>("LineLengthText", "Line Length Text", "LineText");
@@ -313,7 +377,6 @@ public void AutoBindMissingReferences()
if (compassIcon == null) compassIcon = FindComponentByName<Image>("CompassIcon", "Compass Icon");
if (compassCountText == null) compassCountText = FindComponentByName<TMP_Text>("CompassCountText", "Compass CountText");
if (feedbackFlash == null) feedbackFlash = FindComponentByName<Image>("FeedbackFlash");
if (finalResultText == null) finalResultText = FindComponentByName<TMP_Text>("FinalResultText");
if (noticeText == null) noticeText = FindComponentByName<TMP_Text>("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)

View File

@@ -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;
}
}

View File

@@ -0,0 +1,54 @@
using UnityEngine;
/// <summary>
/// Connects FishingGameManager's caught-item event to FishingInventory.
/// Add this to the same prefab/root object, then assign Game Manager and Inventory.
/// </summary>
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<FishingGameManager>();
if (inventory == null)
inventory = GetComponentInParent<FishingInventory>();
if (inventory == null && gameManager != null)
inventory = gameManager.GetComponentInChildren<FishingInventory>(true);
}
private void OnItemCaught(FishingItemType itemType, int amount)
{
if (inventory == null)
return;
inventory.AddItem(itemType, amount);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a6aa548e80b5ed040b1d663b9d7bafa7

View File

@@ -2,11 +2,6 @@ public enum FishingItemType
{
None,
Fish,
RottenFish,
Trash,
Bottle,
PlasticBag,
JellyfishPowder,
OldCompass,
MemoryPiece
}

View File

@@ -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<LootEntry> dirtyPondLootTable = new List<LootEntry>();
[Header("Loot Table - Clean Pond")]
[Tooltip("연못이 정화된 뒤 낚이는 아이템 목록입니다. 기억의 조각을 포함할 수 있습니다.")]
[Tooltip("연못이 정화된 뒤에는 기억의 조각이 낚입니다.")]
[SerializeField] private List<LootEntry> cleanPondLootTable = new List<LootEntry>();
[Header("Debug")]
@@ -74,6 +74,10 @@ private void EnsureLootTables()
if (cleanPondLootTable == null)
cleanPondLootTable = new List<LootEntry>();
// 이전 버전의 상한 생선/병/봉지/나침반 등이 인스펙터에 남아 있어도
// 런타임에서는 물고기 / 쓰레기 / 기억의 조각 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<LootEntry> 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<LootEntry>
{
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<LootEntry>
{
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<LootEntry> 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 "기억의 조각";

View File

@@ -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: <XRController>{LeftHand}/gripPressed or <XRController>{LeftHand}/triggerPressed.")]
[Tooltip("Button/Grip action used while reeling. Example binding: <XRController>{RightHand}/gripPressed or <XRController>{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();

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b2e082a09cbefc649856544b86523b55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: []

View File

@@ -1,5 +1,9 @@
fileFormatVersion: 2
<<<<<<< HEAD
guid: c9e7644bbc6b3324eb679151bb0018fa
=======
guid: a32c27501966c844da3c4e2e54236880
>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690
folderAsset: yes
DefaultImporter:
externalObjects: {}

Binary file not shown.

View File

@@ -1,5 +1,9 @@
fileFormatVersion: 2
<<<<<<< HEAD
guid: 96e0f6dd612977042b62b4b18a2f8c9a
=======
guid: 5e660f31b31a3c1489ac3a3e5804f0aa
>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690
DefaultImporter:
externalObjects: {}
userData:

View File

@@ -1,5 +1,9 @@
fileFormatVersion: 2
<<<<<<< HEAD
guid: 73f8b205cedf821488958abbf597e518
=======
guid: 897f994e3e3765248902eea88350c6f7
>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0

View File

@@ -1,5 +1,9 @@
fileFormatVersion: 2
<<<<<<< HEAD
guid: 7f4320a32f7a846409fcf9cc7fa5fda1
=======
guid: 779ddf707232c4f49a357660d770bcf9
>>>>>>> 3887731db4d2b6ac4d3d2887acc3414ddf555690
DefaultImporter:
externalObjects: {}
userData:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c0e704ea9510154c87905364d1e2058
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3420,7 +3420,7 @@
{
"name": "",
"id": "deb6a7a2-8de5-46b1-812b-d24b20c907a2",
"path": "<XRController>{RightHand}/secondaryButton",
"path": "<XRController>{LeftHand}/secondaryButton",
"interactions": "",
"processors": "",
"groups": "",
@@ -3431,7 +3431,7 @@
{
"name": "",
"id": "22448257-ef2a-42bf-a6c0-66fad17ee5e8",
"path": "<XRController>{RightHand}/primaryButton",
"path": "<XRController>{LeftHand}/primaryButton",
"interactions": "",
"processors": "",
"groups": "",
@@ -3442,7 +3442,7 @@
{
"name": "",
"id": "a6cfa3d8-bd52-4c0e-8ff2-414ab0f22663",
"path": "<XRController>{LeftHand}/gripPressed",
"path": "<XRController>{RightHand}/gripPressed",
"interactions": "",
"processors": "",
"groups": "",