815 lines
28 KiB
C#
815 lines
28 KiB
C#
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class FishingGameManager : MonoBehaviour
|
|
{
|
|
[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 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;
|
|
|
|
[Tooltip("릴 낚시 시작 시 줄 길이가 성공 길이보다 너무 짧으면 이 거리만큼 최소 시작 길이를 보정합니다.")]
|
|
[SerializeField] private float simpleReelMinimumPullDistance = 0.35f;
|
|
|
|
[Tooltip("릴 낚기 성공 후 찌를 낚싯대 쪽 대기 위치로 회수합니다.")]
|
|
[SerializeField] private bool returnBobberAfterCatch = true;
|
|
|
|
[Tooltip("SimpleReel 모드에서는 StartFishing이 입질마다 호출됩니다. 켜면 첫 시작 때만 연못 상태를 초기화합니다.")]
|
|
[SerializeField] private bool resetPondOnlyOnFirstStart = true;
|
|
|
|
[Header("Reset Input")]
|
|
[Tooltip("낚시 상태를 다시 시작할 컨트롤러 입력입니다. 필요 없으면 비워둬도 됩니다.")]
|
|
[SerializeField] private InputActionReference resetAction;
|
|
|
|
[Tooltip("씬에 Input Action Manager가 없거나 액션이 자동 활성화되지 않으면 켜두세요.")]
|
|
[SerializeField] private bool enableInputActionsManually = true;
|
|
|
|
[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("Start / Final Result")]
|
|
[Tooltip("VR 릴 낚시에서는 보통 꺼두세요. 입질이 왔을 때 FishingRodVRController가 StartFishing을 호출하는 구조를 추천합니다.")]
|
|
[SerializeField] private bool startOnAwake = false;
|
|
|
|
[Tooltip("최종 결과를 잠깐 보여준 뒤 uiRoot를 자동으로 끕니다. uiRoot가 비어 있으면 동작하지 않습니다.")]
|
|
[SerializeField] private bool hideUIRootAfterFinalResult = false;
|
|
|
|
[SerializeField] private float finalResultShowTime = 1.5f;
|
|
|
|
[Header("Debug")]
|
|
[SerializeField] private bool showDebugLog = true;
|
|
|
|
private int cleanupItemCount;
|
|
private int totalCaughtItems;
|
|
|
|
// 임시 낚시 세션 카운트입니다. 영구 저장은 FishingInventoryBinder를 통해 FishingInventory에 연결하세요.
|
|
private int sessionFishCount;
|
|
private int sessionTrashCount;
|
|
private int sessionMemoryPieceCount;
|
|
|
|
private bool activeGame;
|
|
private bool fishingSessionRunning;
|
|
private bool pondCleaned;
|
|
private bool memoryPieceCollected;
|
|
|
|
private bool simpleReelCatchActive;
|
|
private bool pondInitialized;
|
|
private float simpleReelStartLineLength;
|
|
private float simpleReelProgress;
|
|
|
|
private bool resetActionWasEnabled;
|
|
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 CleanupItemCount => cleanupItemCount;
|
|
public int RequiredCleanupItems => requiredCleanupItems;
|
|
public int TotalCaughtItems => totalCaughtItems;
|
|
public int SessionFishCount => sessionFishCount;
|
|
public int SessionTrashCount => sessionTrashCount;
|
|
public int SessionMemoryPieceCount => sessionMemoryPieceCount;
|
|
public bool IsSimpleReelCatchActive => simpleReelCatchActive;
|
|
public float SimpleReelProgress => simpleReelProgress;
|
|
|
|
public event Action<FishingItemType, int> ItemCaught;
|
|
|
|
private void OnEnable()
|
|
{
|
|
RegisterInputAction(resetAction, OnResetAction, ref resetActionWasEnabled);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
UnregisterInputAction(resetAction, OnResetAction, resetActionWasEnabled);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (autoBindMissingReferences)
|
|
AutoBindMissingReferences();
|
|
|
|
ValidateRuntimeSettings();
|
|
|
|
if (startOnAwake)
|
|
StartFishing();
|
|
else
|
|
InitializeIdleUI();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!activeGame)
|
|
return;
|
|
|
|
UpdateSimpleReelCatch();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
UpdateMemoryPieceResultFollow();
|
|
}
|
|
|
|
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 (gameClear == null)
|
|
gameClear = searchRoot != null ? searchRoot.GetComponentInChildren<GameClear>(true) : GetComponentInChildren<GameClear>(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;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
requiredCleanupItems = Mathf.Max(1, requiredCleanupItems);
|
|
catchCompleteLineLength = Mathf.Max(0.05f, catchCompleteLineLength);
|
|
simpleReelMinimumPullDistance = Mathf.Max(0.01f, simpleReelMinimumPullDistance);
|
|
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)
|
|
return;
|
|
|
|
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()
|
|
{
|
|
StartFishing(false);
|
|
}
|
|
|
|
public void StartFishing(bool forceRestart)
|
|
{
|
|
ValidateRuntimeSettings();
|
|
|
|
if (preventRestartWhileSessionRunning && fishingSessionRunning && !forceRestart)
|
|
{
|
|
if (showDebugLog)
|
|
Debug.Log("낚시 세션이 이미 진행 중이라 StartFishing 호출을 무시했습니다.", this);
|
|
return;
|
|
}
|
|
|
|
StopRunningRoutines();
|
|
|
|
if (uiRoot != null)
|
|
uiRoot.SetActive(true);
|
|
|
|
bool shouldResetPondState = forceRestart || resetPondStateOnStart;
|
|
|
|
if (resetPondOnlyOnFirstStart)
|
|
shouldResetPondState = forceRestart || (resetPondStateOnStart && !pondInitialized);
|
|
|
|
if (shouldResetPondState)
|
|
{
|
|
ResetPondState();
|
|
pondInitialized = true;
|
|
}
|
|
|
|
fishingSessionRunning = true;
|
|
|
|
if (ui != null)
|
|
ui.InitializeFishingUI();
|
|
|
|
StartSimpleReelCatch();
|
|
|
|
if (showDebugLog)
|
|
Debug.Log("릴 감기 낚시 시작", this);
|
|
}
|
|
|
|
public void StopFishing(bool hideUI = false)
|
|
{
|
|
StopRunningRoutines();
|
|
|
|
activeGame = false;
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
simpleReelProgress = 0f;
|
|
|
|
if (ui != null)
|
|
ui.SetSimpleReelUIVisible(false);
|
|
|
|
if (hideUI && uiRoot != null)
|
|
uiRoot.SetActive(false);
|
|
}
|
|
|
|
public void ResetFishing()
|
|
{
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
pondInitialized = false;
|
|
StartFishing(true);
|
|
}
|
|
|
|
public void ResetPondState()
|
|
{
|
|
cleanupItemCount = 0;
|
|
totalCaughtItems = 0;
|
|
sessionFishCount = 0;
|
|
sessionTrashCount = 0;
|
|
sessionMemoryPieceCount = 0;
|
|
pondCleaned = false;
|
|
memoryPieceCollected = false;
|
|
simpleReelCatchActive = false;
|
|
simpleReelProgress = 0f;
|
|
simpleReelStartLineLength = 0f;
|
|
|
|
HideMemoryPieceResultObject();
|
|
}
|
|
|
|
private void StartSimpleReelCatch()
|
|
{
|
|
if (rodController == null && autoBindMissingReferences)
|
|
AutoBindMissingReferences();
|
|
|
|
if (rodController == null)
|
|
{
|
|
Debug.LogWarning("[FishingGameManager] 릴 감기 낚시에는 FishingRodVRController 참조가 필요합니다.", this);
|
|
activeGame = false;
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
return;
|
|
}
|
|
|
|
activeGame = true;
|
|
simpleReelCatchActive = true;
|
|
|
|
// 중요: 기존 코드에서는 simpleReelStartLineLength만 보정하고 실제 줄 길이는 그대로라서
|
|
// 찌가 대기 위치에 있으면 시작 즉시 성공할 수 있었습니다.
|
|
// 릴 전용 버전에서는 실제 줄 길이도 최소 시작 길이로 맞춥니다.
|
|
float minimumStartLength = catchCompleteLineLength + simpleReelMinimumPullDistance;
|
|
|
|
if (rodController.CurrentLineLength < minimumStartLength)
|
|
rodController.ForceSetReelLineLength(minimumStartLength);
|
|
|
|
rodController.ForceMiniGameActiveState();
|
|
|
|
simpleReelStartLineLength = Mathf.Max(rodController.CurrentLineLength, minimumStartLength);
|
|
simpleReelProgress = 0f;
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.SetSimpleReelUIVisible(true);
|
|
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;
|
|
activeGame = false;
|
|
|
|
if (haptic != null)
|
|
haptic.Perfect();
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.UpdateSimpleReelUI(1f, currentLineLength, false);
|
|
ui.ShowPersistentResult("낚았다!");
|
|
}
|
|
|
|
CatchItemSuccess();
|
|
}
|
|
}
|
|
|
|
private void StopRunningRoutines()
|
|
{
|
|
if (finalResultRoutine != null)
|
|
{
|
|
StopCoroutine(finalResultRoutine);
|
|
finalResultRoutine = null;
|
|
}
|
|
|
|
if (memoryPieceResultRoutine != null)
|
|
{
|
|
StopCoroutine(memoryPieceResultRoutine);
|
|
memoryPieceResultRoutine = null;
|
|
}
|
|
|
|
HideMemoryPieceResultObject();
|
|
}
|
|
|
|
private void CatchItemSuccess()
|
|
{
|
|
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기억의 조각이 모습을 드러냈다!");
|
|
}
|
|
|
|
if (showDebugLog)
|
|
Debug.Log($"{catchResult.DisplayName}을(를) 낚았다!", this);
|
|
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
activeGame = false;
|
|
|
|
if (returnBobberAfterCatch && rodController != null)
|
|
rodController.ReturnBobberToRest();
|
|
|
|
if (catchResult.IsMemoryPiece)
|
|
FishingSuccess();
|
|
}
|
|
|
|
private void AddToSessionCounts(FishingItemType itemType)
|
|
{
|
|
switch (itemType)
|
|
{
|
|
case FishingItemType.Fish:
|
|
sessionFishCount++;
|
|
break;
|
|
|
|
case FishingItemType.Trash:
|
|
sessionTrashCount++;
|
|
break;
|
|
|
|
case FishingItemType.MemoryPiece:
|
|
sessionMemoryPieceCount++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UpdateUI()
|
|
{
|
|
if (ui == null)
|
|
return;
|
|
|
|
ui.SetSimpleReelUIVisible(activeGame || simpleReelCatchActive);
|
|
ui.UpdateFishingProgress(cleanupItemCount, requiredCleanupItems, pondCleaned, memoryPieceCollected, totalCaughtItems);
|
|
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;
|
|
fishingSessionRunning = false;
|
|
simpleReelCatchActive = false;
|
|
|
|
UpdateUI();
|
|
|
|
if (ui != null)
|
|
ui.ShowFinalResult("기억의 조각 획득!\n기묘한 낚시터 클리어!");
|
|
|
|
ShowMemoryPieceResultObject();
|
|
|
|
if (gameClear != null)
|
|
{
|
|
gameClear.OnGameClear();
|
|
}
|
|
else if (showDebugLog)
|
|
{
|
|
Debug.LogWarning("[FishingGameManager] GameClear가 연결되지 않아 NPC 이동/대화존 변경/문 선택지 세팅을 실행하지 못했습니다.", this);
|
|
}
|
|
|
|
if (showDebugLog)
|
|
Debug.Log("기묘한 낚시터 클리어", this);
|
|
|
|
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);
|
|
|
|
HideMemoryPieceResultObject();
|
|
|
|
finalResultRoutine = null;
|
|
}
|
|
|
|
private void ShowMemoryPieceResultObject()
|
|
{
|
|
EnsureMemoryPiecePlayerHead();
|
|
|
|
if (memoryPiecePlayerHead == null)
|
|
{
|
|
Debug.LogWarning("[FishingGameManager] Memory Piece Player Head가 없습니다. XR Origin의 Main Camera를 연결하세요.", this);
|
|
return;
|
|
}
|
|
|
|
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 HideMemoryPieceResultObject()
|
|
{
|
|
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<Collider>(true);
|
|
|
|
for (int i = 0; i < colliders.Length; i++)
|
|
colliders[i].enabled = false;
|
|
}
|
|
|
|
if (disableMemoryPieceResultFloatMotion)
|
|
{
|
|
StarPickup[] starPickups = target.GetComponentsInChildren<StarPickup>(true);
|
|
|
|
for (int i = 0; i < starPickups.Length; i++)
|
|
starPickups[i].floatMotion = false;
|
|
}
|
|
}
|
|
|
|
private void OnResetAction(InputAction.CallbackContext context)
|
|
{
|
|
ResetFishing();
|
|
}
|
|
}
|