964 lines
30 KiB
C#
964 lines
30 KiB
C#
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
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;
|
|
|
|
[Header("References")]
|
|
[Tooltip("낚시 UI 전체 루트입니다. UI를 자동으로 켜고 끄고 싶을 때만 연결하세요.")]
|
|
[SerializeField] private GameObject uiRoot;
|
|
|
|
[SerializeField] private FishingGaugeUI ui;
|
|
[SerializeField] private FishingRewardSystem rewardSystem;
|
|
[SerializeField] private FishingHapticManager haptic;
|
|
[SerializeField] private RotateUI centerIconRotate;
|
|
|
|
[Header("Simple Reel Fishing")]
|
|
[Tooltip("TimingGauge는 기존 원형 타이밍 게이지 방식, SimpleReel은 릴을 감아 줄 길이를 줄이면 성공하는 방식입니다.")]
|
|
[SerializeField] private FishingControlMode fishingControlMode = FishingControlMode.SimpleReel;
|
|
|
|
[Tooltip("낚싯대 / 찌 / 릴 제어 스크립트입니다. SimpleReel 모드에서 줄 길이를 읽습니다.")]
|
|
[SerializeField] private FishingRodVRController rodController;
|
|
|
|
[Tooltip("현재 줄 길이가 이 값 이하가 되면 낚기 성공으로 처리합니다.")]
|
|
[SerializeField] private float catchCompleteLineLength = 0.7f;
|
|
|
|
[Tooltip("릴 낚시 시작 시 줄 길이가 성공 길이보다 너무 짧으면 이 거리만큼 최소 시작 길이를 보정합니다.")]
|
|
[SerializeField] private float simpleReelMinimumPullDistance = 0.35f;
|
|
|
|
[Tooltip("릴 낚기 성공 후 찌를 낚싯대 쪽 대기 위치로 회수합니다.")]
|
|
[SerializeField] private bool returnBobberAfterSimpleReelCatch = true;
|
|
|
|
[Tooltip("SimpleReel 모드에서 기존 타이밍 게이지 UI를 숨기고 진행도/줄 길이 중심으로 표시합니다.")]
|
|
[SerializeField] private bool hideTimingGaugeInSimpleReelMode = true;
|
|
|
|
[Tooltip("SimpleReel 모드에서는 StartFishing이 여러 번 호출됩니다. 켜면 첫 시작 때만 연못 상태를 초기화합니다.")]
|
|
[SerializeField] private bool resetPondOnlyOnFirstSimpleReelStart = true;
|
|
|
|
[Header("XR Controller Input")]
|
|
[Tooltip("낚시 판정을 실행할 컨트롤러 입력입니다. 예: XRI Right Interaction / Select")]
|
|
[SerializeField] private InputActionReference submitAction;
|
|
|
|
[Tooltip("낚시를 다시 시작할 컨트롤러 입력입니다. 필요 없으면 비워둬도 됩니다.")]
|
|
[SerializeField] private InputActionReference resetAction;
|
|
|
|
[Tooltip("씬에 Input Action Manager가 없거나 액션이 자동 활성화되지 않으면 켜두세요.")]
|
|
[SerializeField] private bool enableInputActionsManually = true;
|
|
|
|
[Header("Pointer")]
|
|
[SerializeField] private float pointerAngle;
|
|
[SerializeField] private float pointerSpeed = 180f;
|
|
[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("연못을 맑게 만들기 위해 필요한 쓰레기성 아이템 수입니다. 쓰레기, 병, 봉지가 여기에 포함됩니다.")]
|
|
[SerializeField] private int requiredCleanupItems = 3;
|
|
|
|
[Tooltip("연못이 맑아진 뒤 다음 성공 낚시에서 기억의 조각을 확정으로 낚습니다.")]
|
|
[SerializeField] private bool guaranteeMemoryPieceAfterCleaned = true;
|
|
|
|
[Tooltip("StartFishing을 호출할 때 쓰레기 수거/연못 상태를 초기화합니다.")]
|
|
[SerializeField] private bool resetPondStateOnStart = true;
|
|
|
|
[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("Final Result Settings")]
|
|
[Tooltip("최종 결과를 잠깐 보여준 뒤 uiRoot를 자동으로 끕니다. uiRoot가 비어 있으면 동작하지 않습니다.")]
|
|
[SerializeField] private bool hideUIRootAfterFinalResult = false;
|
|
|
|
[SerializeField] private float finalResultShowTime = 1.5f;
|
|
|
|
[Header("Debug")]
|
|
[SerializeField] private bool showDebugLog = true;
|
|
|
|
private int successCount;
|
|
private int failCount;
|
|
private int cleanupItemCount;
|
|
private int totalCaughtItems;
|
|
|
|
// 임시 낚시 세션 카운트입니다. 나중에 공용 인벤토리와 연결할 때는 저장용으로 쓰지 말고 UI 표시용으로만 쓰세요.
|
|
private int sessionFishCount;
|
|
private int sessionTrashCount;
|
|
private int sessionMemoryPieceCount;
|
|
private int sessionCompassCount;
|
|
|
|
private bool clockwise = true;
|
|
private bool activeGame;
|
|
private bool inputLocked;
|
|
private bool fishingSessionRunning;
|
|
private bool pondCleaned;
|
|
private bool memoryPieceCollected;
|
|
|
|
private bool simpleReelCatchActive;
|
|
private bool simpleReelPondInitialized;
|
|
private float simpleReelStartLineLength;
|
|
private float simpleReelProgress;
|
|
|
|
private bool submitActionWasEnabled;
|
|
private bool resetActionWasEnabled;
|
|
|
|
private Coroutine nextRoundRoutine;
|
|
private Coroutine nextCatchRoutine;
|
|
private Coroutine finalResultRoutine;
|
|
|
|
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;
|
|
|
|
public event Action<FishingItemType, int> ItemCaught;
|
|
|
|
private void OnEnable()
|
|
{
|
|
RegisterInputAction(submitAction, OnSubmitAction, ref submitActionWasEnabled);
|
|
RegisterInputAction(resetAction, OnResetAction, ref resetActionWasEnabled);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
UnregisterInputAction(submitAction, OnSubmitAction, submitActionWasEnabled);
|
|
UnregisterInputAction(resetAction, OnResetAction, resetActionWasEnabled);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (autoBindMissingReferences)
|
|
AutoBindMissingReferences();
|
|
|
|
ValidateRuntimeSettings();
|
|
|
|
if (startOnAwake)
|
|
{
|
|
StartFishing();
|
|
}
|
|
else
|
|
{
|
|
InitializeIdleUI();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!activeGame)
|
|
return;
|
|
|
|
if (fishingControlMode == FishingControlMode.SimpleReel)
|
|
{
|
|
UpdateSimpleReelCatch();
|
|
return;
|
|
}
|
|
|
|
if (inputLocked)
|
|
return;
|
|
|
|
RotatePointer();
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
ValidateRuntimeSettings();
|
|
}
|
|
|
|
|
|
|
|
[ContextMenu("Auto Bind Fishing References")]
|
|
public void AutoBindMissingReferences()
|
|
{
|
|
Transform searchRoot = GetSearchRoot();
|
|
|
|
if (uiRoot == null)
|
|
{
|
|
Transform canvasTransform = FindTransformRecursive(searchRoot, "FishingCanvas");
|
|
if (canvasTransform != null)
|
|
uiRoot = canvasTransform.gameObject;
|
|
}
|
|
|
|
if (ui == null)
|
|
ui = searchRoot != null ? searchRoot.GetComponentInChildren<FishingGaugeUI>(true) : GetComponentInChildren<FishingGaugeUI>(true);
|
|
|
|
if (rewardSystem == null)
|
|
rewardSystem = searchRoot != null ? searchRoot.GetComponentInChildren<FishingRewardSystem>(true) : GetComponentInChildren<FishingRewardSystem>(true);
|
|
|
|
if (haptic == null)
|
|
haptic = searchRoot != null ? searchRoot.GetComponentInChildren<FishingHapticManager>(true) : GetComponentInChildren<FishingHapticManager>(true);
|
|
|
|
if (rodController == null)
|
|
rodController = searchRoot != null ? searchRoot.GetComponentInChildren<FishingRodVRController>(true) : GetComponentInChildren<FishingRodVRController>(true);
|
|
|
|
if (centerIconRotate == null)
|
|
{
|
|
Transform centerIconTransform = FindTransformRecursive(searchRoot, "CenterIcon");
|
|
if (centerIconTransform != null)
|
|
centerIconRotate = centerIconTransform.GetComponent<RotateUI>();
|
|
|
|
if (centerIconRotate == null && searchRoot != null)
|
|
centerIconRotate = searchRoot.GetComponentInChildren<RotateUI>(true);
|
|
}
|
|
}
|
|
|
|
private Transform GetSearchRoot()
|
|
{
|
|
Transform current = transform;
|
|
|
|
while (current.parent != null)
|
|
current = current.parent;
|
|
|
|
return current;
|
|
}
|
|
|
|
private Transform FindTransformRecursive(Transform current, string targetName)
|
|
{
|
|
if (current == null || string.IsNullOrEmpty(targetName))
|
|
return null;
|
|
|
|
if (NormalizeName(current.name) == NormalizeName(targetName))
|
|
return current;
|
|
|
|
for (int i = 0; i < current.childCount; i++)
|
|
{
|
|
Transform found = FindTransformRecursive(current.GetChild(i), targetName);
|
|
if (found != null)
|
|
return found;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private string NormalizeName(string value)
|
|
{
|
|
return value.Replace(" ", string.Empty)
|
|
.Replace("_", string.Empty)
|
|
.Replace("-", string.Empty)
|
|
.ToLowerInvariant();
|
|
}
|
|
|
|
private void RegisterInputAction(InputActionReference actionReference, System.Action<InputAction.CallbackContext> callback, ref bool wasEnabled)
|
|
{
|
|
if (actionReference == null || actionReference.action == null)
|
|
return;
|
|
|
|
wasEnabled = actionReference.action.enabled;
|
|
actionReference.action.performed += callback;
|
|
|
|
if (enableInputActionsManually && !wasEnabled)
|
|
actionReference.action.Enable();
|
|
}
|
|
|
|
private void UnregisterInputAction(InputActionReference actionReference, System.Action<InputAction.CallbackContext> callback, bool wasEnabled)
|
|
{
|
|
if (actionReference == null || actionReference.action == null)
|
|
return;
|
|
|
|
actionReference.action.performed -= callback;
|
|
|
|
if (enableInputActionsManually && !wasEnabled)
|
|
actionReference.action.Disable();
|
|
}
|
|
|
|
private void ValidateRuntimeSettings()
|
|
{
|
|
requiredSuccesses = Mathf.Max(1, requiredSuccesses);
|
|
allowedFails = Mathf.Max(1, allowedFails);
|
|
requiredCleanupItems = Mathf.Max(1, requiredCleanupItems);
|
|
|
|
minSpeed = Mathf.Max(1f, minSpeed);
|
|
maxSpeed = Mathf.Max(minSpeed, maxSpeed);
|
|
pointerSpeed = Mathf.Clamp(pointerSpeed, minSpeed, maxSpeed);
|
|
|
|
minZoneSize = Mathf.Clamp(minZoneSize, 1f, 360f);
|
|
maxZoneSize = Mathf.Clamp(maxZoneSize, minZoneSize, 360f);
|
|
startZoneSize = Mathf.Clamp(startZoneSize, minZoneSize, maxZoneSize);
|
|
zoneSize = Mathf.Clamp(zoneSize, minZoneSize, maxZoneSize);
|
|
|
|
nextRoundDelay = Mathf.Max(0f, nextRoundDelay);
|
|
nextCatchDelay = Mathf.Max(0f, nextCatchDelay);
|
|
finalResultShowTime = Mathf.Max(0f, finalResultShowTime);
|
|
catchCompleteLineLength = Mathf.Max(0.05f, catchCompleteLineLength);
|
|
simpleReelMinimumPullDistance = Mathf.Max(0.01f, simpleReelMinimumPullDistance);
|
|
|
|
pointerAngle = Normalize(pointerAngle);
|
|
zoneCenter = Normalize(zoneCenter);
|
|
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 (centerIconRotate != null)
|
|
centerIconRotate.StopRotate();
|
|
}
|
|
|
|
public void StartFishing()
|
|
{
|
|
StartFishing(false);
|
|
}
|
|
|
|
public void StartFishing(bool forceRestart)
|
|
{
|
|
ValidateRuntimeSettings();
|
|
|
|
if (preventRestartWhileSessionRunning && fishingSessionRunning && !forceRestart)
|
|
{
|
|
if (showDebugLog)
|
|
Debug.Log("낚시 세션이 이미 진행 중이라 StartFishing 호출을 무시했습니다.");
|
|
return;
|
|
}
|
|
|
|
StopRunningRoutines();
|
|
|
|
if (uiRoot != null)
|
|
uiRoot.SetActive(true);
|
|
|
|
bool shouldResetPondState = forceRestart || resetPondStateOnStart;
|
|
|
|
if (fishingControlMode == FishingControlMode.SimpleReel && resetPondOnlyOnFirstSimpleReelStart)
|
|
shouldResetPondState = forceRestart || (resetPondStateOnStart && !simpleReelPondInitialized);
|
|
|
|
if (shouldResetPondState)
|
|
{
|
|
ResetPondState();
|
|
if (fishingControlMode == FishingControlMode.SimpleReel)
|
|
simpleReelPondInitialized = true;
|
|
}
|
|
|
|
fishingSessionRunning = true;
|
|
|
|
if (ui != null)
|
|
ui.InitializeFishingUI();
|
|
|
|
if (fishingControlMode == FishingControlMode.SimpleReel)
|
|
StartSimpleReelCatch();
|
|
else
|
|
StartNewCatchAttempt();
|
|
|
|
if (showDebugLog)
|
|
Debug.Log(fishingControlMode == FishingControlMode.SimpleReel ? "릴 감기 낚시 시작" : "기묘한 낚시터 시작");
|
|
}
|
|
|
|
public void StopFishing(bool hideUI = false)
|
|
{
|
|
StopRunningRoutines();
|
|
|
|
activeGame = false;
|
|
inputLocked = true;
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
|
|
if (centerIconRotate != null)
|
|
centerIconRotate.StopRotate();
|
|
|
|
if (hideUI && uiRoot != null)
|
|
uiRoot.SetActive(false);
|
|
}
|
|
|
|
public void ResetFishing()
|
|
{
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
simpleReelPondInitialized = 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;
|
|
}
|
|
|
|
private void StartSimpleReelCatch()
|
|
{
|
|
if (rodController == null && autoBindMissingReferences)
|
|
AutoBindMissingReferences();
|
|
|
|
if (rodController == null)
|
|
{
|
|
Debug.LogWarning("[FishingGameManager] SimpleReel 모드에는 FishingRodVRController 참조가 필요합니다.", this);
|
|
activeGame = false;
|
|
inputLocked = true;
|
|
fishingSessionRunning = false;
|
|
return;
|
|
}
|
|
|
|
successCount = 0;
|
|
failCount = 0;
|
|
inputLocked = false;
|
|
activeGame = true;
|
|
simpleReelCatchActive = true;
|
|
|
|
float currentLineLength = Mathf.Max(rodController.CurrentLineLength, catchCompleteLineLength + simpleReelMinimumPullDistance);
|
|
simpleReelStartLineLength = currentLineLength;
|
|
simpleReelProgress = 0f;
|
|
|
|
if (centerIconRotate != null)
|
|
{
|
|
centerIconRotate.ResetRotation();
|
|
centerIconRotate.StopRotate();
|
|
}
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.SetSimpleReelUIVisible(true);
|
|
if (hideTimingGaugeInSimpleReelMode)
|
|
ui.SetTimingGaugeVisible(false);
|
|
ui.UpdateSimpleReelUI(simpleReelProgress, rodController.CurrentLineLength, rodController.IsReeling);
|
|
ui.ShowPersistentResult("릴을 감아 끌어올려라!");
|
|
}
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
private void UpdateSimpleReelCatch()
|
|
{
|
|
if (!simpleReelCatchActive || rodController == null)
|
|
return;
|
|
|
|
float currentLineLength = rodController.CurrentLineLength;
|
|
float totalPullDistance = Mathf.Max(0.001f, simpleReelStartLineLength - catchCompleteLineLength);
|
|
float pulledDistance = simpleReelStartLineLength - currentLineLength;
|
|
simpleReelProgress = Mathf.Clamp01(pulledDistance / totalPullDistance);
|
|
|
|
if (ui != null)
|
|
ui.UpdateSimpleReelUI(simpleReelProgress, currentLineLength, rodController.IsReeling);
|
|
|
|
if (currentLineLength <= catchCompleteLineLength || simpleReelProgress >= 1f)
|
|
{
|
|
simpleReelCatchActive = false;
|
|
inputLocked = true;
|
|
|
|
if (haptic != null)
|
|
haptic.Perfect();
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.UpdateSimpleReelUI(1f, currentLineLength, false);
|
|
ui.ShowPersistentResult("낚았다!");
|
|
}
|
|
|
|
CatchItemSuccess();
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ui.ShowResultForType(result);
|
|
ui.FlashFeedbackForType(result);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private void CatchItemSuccess()
|
|
{
|
|
activeGame = false;
|
|
inputLocked = true;
|
|
|
|
if (centerIconRotate != null)
|
|
centerIconRotate.StopRotate();
|
|
|
|
bool forceMemoryPiece = pondCleaned && guaranteeMemoryPieceAfterCleaned && !memoryPieceCollected;
|
|
|
|
FishingRewardSystem.CatchResult catchResult;
|
|
|
|
if (rewardSystem != null)
|
|
catchResult = rewardSystem.RollCatch(pondCleaned, forceMemoryPiece);
|
|
else
|
|
catchResult = new FishingRewardSystem.CatchResult(FishingItemType.Fish, "생선", false, false);
|
|
|
|
ApplyCatchResult(catchResult);
|
|
}
|
|
|
|
private void ApplyCatchResult(FishingRewardSystem.CatchResult catchResult)
|
|
{
|
|
totalCaughtItems++;
|
|
|
|
AddToSessionCounts(catchResult.ItemType);
|
|
ItemCaught?.Invoke(catchResult.ItemType, 1);
|
|
|
|
bool pondJustCleaned = false;
|
|
string extraHint = string.Empty;
|
|
|
|
if (catchResult.CountsAsCleanupItem)
|
|
{
|
|
cleanupItemCount = Mathf.Min(cleanupItemCount + 1, requiredCleanupItems);
|
|
extraHint = $"연못 정화 {cleanupItemCount}/{requiredCleanupItems}";
|
|
|
|
if (!pondCleaned && cleanupItemCount >= requiredCleanupItems)
|
|
{
|
|
pondCleaned = true;
|
|
pondJustCleaned = true;
|
|
extraHint = "연못이 맑아졌다. 기억의 조각이 모습을 드러낸다.";
|
|
}
|
|
}
|
|
|
|
if (catchResult.IsMemoryPiece)
|
|
{
|
|
memoryPieceCollected = true;
|
|
extraHint = "잃어버린 기억의 일부다.";
|
|
}
|
|
|
|
UpdateUI();
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.HighlightSlotForItem(catchResult.ItemType);
|
|
|
|
if (!catchResult.IsMemoryPiece)
|
|
ui.ShowCaughtItem(catchResult.ItemType, catchResult.DisplayName, catchResult.CountsAsCleanupItem, extraHint);
|
|
|
|
if (pondJustCleaned)
|
|
ui.ShowNotice("연못이 맑아졌다!\n기억의 조각이 모습을 드러냈다!");
|
|
|
|
// 기억의 조각 획득은 FinalResultPanel에서 크게 보여줍니다.
|
|
// NoticePanel까지 동시에 띄우면 ShowFinalResult에서 바로 숨겨져 연출이 겹칠 수 있습니다.
|
|
}
|
|
|
|
if (showDebugLog)
|
|
Debug.Log($"{catchResult.DisplayName}을(를) 낚았다!");
|
|
|
|
if (catchResult.IsMemoryPiece)
|
|
{
|
|
FishingSuccess();
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
case FishingItemType.Fish:
|
|
sessionFishCount++;
|
|
break;
|
|
|
|
case FishingItemType.Trash:
|
|
case FishingItemType.Bottle:
|
|
case FishingItemType.PlasticBag:
|
|
sessionTrashCount++;
|
|
break;
|
|
|
|
case FishingItemType.MemoryPiece:
|
|
sessionMemoryPieceCount++;
|
|
break;
|
|
|
|
case FishingItemType.OldCompass:
|
|
sessionCompassCount++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void ClampDifficulty()
|
|
{
|
|
zoneSize = Mathf.Clamp(zoneSize, minZoneSize, maxZoneSize);
|
|
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.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems);
|
|
ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount, sessionCompassCount);
|
|
}
|
|
|
|
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("기묘한 낚시터 클리어");
|
|
|
|
BeginFinalResultRoutineIfNeeded();
|
|
}
|
|
|
|
private void FishingFail()
|
|
{
|
|
activeGame = false;
|
|
inputLocked = true;
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
|
|
if (centerIconRotate != null)
|
|
centerIconRotate.StopRotate();
|
|
|
|
if (ui != null)
|
|
ui.ShowFinalResult("낚싯줄이 끊어졌다!\n다시 천천히 낚아보자.");
|
|
|
|
if (showDebugLog)
|
|
Debug.Log("낚시 실패: 낚싯줄 끊어짐");
|
|
|
|
BeginFinalResultRoutineIfNeeded();
|
|
}
|
|
|
|
private void BeginFinalResultRoutineIfNeeded()
|
|
{
|
|
if (!hideUIRootAfterFinalResult || uiRoot == null)
|
|
return;
|
|
|
|
if (finalResultRoutine != null)
|
|
StopCoroutine(finalResultRoutine);
|
|
|
|
finalResultRoutine = StartCoroutine(HideUIRootAfterFinalResultRoutine());
|
|
}
|
|
|
|
private IEnumerator HideUIRootAfterFinalResultRoutine()
|
|
{
|
|
yield return new WaitForSeconds(finalResultShowTime);
|
|
|
|
if (uiRoot != null)
|
|
uiRoot.SetActive(false);
|
|
|
|
finalResultRoutine = null;
|
|
}
|
|
|
|
private float Normalize(float angle)
|
|
{
|
|
angle %= 360f;
|
|
|
|
if (angle < 0f)
|
|
angle += 360f;
|
|
|
|
return angle;
|
|
}
|
|
|
|
private void OnSubmitAction(InputAction.CallbackContext context)
|
|
{
|
|
SubmitAttempt();
|
|
}
|
|
|
|
private void OnResetAction(InputAction.CallbackContext context)
|
|
{
|
|
ResetFishing();
|
|
}
|
|
}
|