피싱룸
This commit is contained in:
Binary file not shown.
BIN
Assets/My project/Fishing Scripts/Prefabs/fish.fbx
LFS
Normal file
BIN
Assets/My project/Fishing Scripts/Prefabs/fish.fbx
LFS
Normal file
Binary file not shown.
110
Assets/My project/Fishing Scripts/Prefabs/fish.fbx.meta
Normal file
110
Assets/My project/Fishing Scripts/Prefabs/fish.fbx.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f6af38d640059d49af824ba9ba06e73
|
||||
ModelImporter:
|
||||
serializedVersion: 24200
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
materials:
|
||||
materialImportMode: 2
|
||||
materialName: 0
|
||||
materialSearch: 1
|
||||
materialLocation: 1
|
||||
animations:
|
||||
legacyGenerateAnimations: 4
|
||||
bakeSimulation: 0
|
||||
resampleCurves: 1
|
||||
optimizeGameObjects: 0
|
||||
removeConstantScaleCurves: 0
|
||||
motionNodeName:
|
||||
animationImportErrors:
|
||||
animationImportWarnings:
|
||||
animationRetargetingWarnings:
|
||||
animationDoRetargetingWarnings: 0
|
||||
importAnimatedCustomProperties: 0
|
||||
importConstraints: 0
|
||||
animationCompression: 1
|
||||
animationRotationError: 0.5
|
||||
animationPositionError: 0.5
|
||||
animationScaleError: 0.5
|
||||
animationWrapMode: 0
|
||||
extraExposedTransformPaths: []
|
||||
extraUserProperties: []
|
||||
clipAnimations: []
|
||||
isReadable: 0
|
||||
meshes:
|
||||
lODScreenPercentages: []
|
||||
globalScale: 1
|
||||
meshCompression: 0
|
||||
addColliders: 0
|
||||
useSRGBMaterialColor: 1
|
||||
sortHierarchyByName: 1
|
||||
importPhysicalCameras: 1
|
||||
importVisibility: 1
|
||||
importBlendShapes: 1
|
||||
importCameras: 1
|
||||
importLights: 1
|
||||
nodeNameCollisionStrategy: 1
|
||||
fileIdsGeneration: 2
|
||||
swapUVChannels: 0
|
||||
generateSecondaryUV: 0
|
||||
useFileUnits: 1
|
||||
keepQuads: 0
|
||||
weldVertices: 1
|
||||
bakeAxisConversion: 0
|
||||
preserveHierarchy: 0
|
||||
skinWeightsMode: 0
|
||||
maxBonesPerVertex: 4
|
||||
minBoneWeight: 0.001
|
||||
optimizeBones: 1
|
||||
generateMeshLods: 0
|
||||
meshLodGenerationFlags: 0
|
||||
maximumMeshLod: -1
|
||||
meshOptimizationFlags: -1
|
||||
indexFormat: 0
|
||||
secondaryUVAngleDistortion: 8
|
||||
secondaryUVAreaDistortion: 15.000001
|
||||
secondaryUVHardAngle: 88
|
||||
secondaryUVMarginMethod: 1
|
||||
secondaryUVMinLightmapResolution: 40
|
||||
secondaryUVMinObjectScale: 1
|
||||
secondaryUVPackMargin: 4
|
||||
useFileScale: 1
|
||||
strictVertexDataChecks: 0
|
||||
tangentSpace:
|
||||
normalSmoothAngle: 60
|
||||
normalImportMode: 0
|
||||
tangentImportMode: 3
|
||||
normalCalculationMode: 4
|
||||
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
|
||||
blendShapeNormalImportMode: 1
|
||||
normalSmoothingSource: 0
|
||||
referencedClips: []
|
||||
importAnimation: 1
|
||||
humanDescription:
|
||||
serializedVersion: 3
|
||||
human: []
|
||||
skeleton: []
|
||||
armTwist: 0.5
|
||||
foreArmTwist: 0.5
|
||||
upperLegTwist: 0.5
|
||||
legTwist: 0.5
|
||||
armStretch: 0.05
|
||||
legStretch: 0.05
|
||||
feetSpacing: 0
|
||||
globalScale: 1
|
||||
rootMotionBoneName:
|
||||
hasTranslationDoF: 0
|
||||
hasExtraRoot: 0
|
||||
skeletonHasParents: 1
|
||||
lastHumanDescriptionAvatarSource: {instanceID: 0}
|
||||
autoGenerateAvatarMappingIfUnspecified: 1
|
||||
animationType: 2
|
||||
humanoidOversampling: 1
|
||||
avatarSetup: 0
|
||||
addHumanoidExtraRootOnlyWhenUsingAvatar: 1
|
||||
importBlendShapeDeformPercent: 1
|
||||
remapMaterialsIfMaterialImportModeIsNone: 0
|
||||
additionalBone: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -12,6 +12,12 @@ public enum ResultType
|
||||
Miss
|
||||
}
|
||||
|
||||
public enum FishingControlMode
|
||||
{
|
||||
TimingGauge,
|
||||
SimpleReel
|
||||
}
|
||||
|
||||
[Header("Auto Bind")]
|
||||
[Tooltip("현재 만든 Prefab 구조 기준으로 비어 있는 참조를 자동 연결합니다.")]
|
||||
[SerializeField] private bool autoBindMissingReferences = true;
|
||||
@@ -25,6 +31,28 @@ public enum ResultType
|
||||
[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;
|
||||
@@ -65,6 +93,9 @@ public enum ResultType
|
||||
[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;
|
||||
@@ -95,9 +126,15 @@ public enum ResultType
|
||||
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;
|
||||
|
||||
@@ -106,6 +143,7 @@ public enum ResultType
|
||||
private Coroutine finalResultRoutine;
|
||||
|
||||
public bool IsActiveGame => activeGame;
|
||||
public bool IsFishingSessionRunning => fishingSessionRunning;
|
||||
public bool IsPondCleaned => pondCleaned;
|
||||
public bool IsMemoryPieceCollected => memoryPieceCollected;
|
||||
public int SuccessCount => successCount;
|
||||
@@ -117,6 +155,9 @@ public enum ResultType
|
||||
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;
|
||||
|
||||
@@ -154,6 +195,12 @@ private void Update()
|
||||
if (!activeGame)
|
||||
return;
|
||||
|
||||
if (fishingControlMode == FishingControlMode.SimpleReel)
|
||||
{
|
||||
UpdateSimpleReelCatch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputLocked)
|
||||
return;
|
||||
|
||||
@@ -188,6 +235,9 @@ public void AutoBindMissingReferences()
|
||||
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");
|
||||
@@ -276,6 +326,8 @@ private void ValidateRuntimeSettings()
|
||||
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);
|
||||
@@ -293,6 +345,8 @@ private void InitializeIdleUI()
|
||||
ui.UpdateInventoryUI(sessionFishCount, sessionTrashCount, sessionMemoryPieceCount, sessionCompassCount);
|
||||
ui.SetPointerRotation(0f);
|
||||
ui.SetZone(0f, startZoneSize);
|
||||
ui.SetSimpleReelUIVisible(false);
|
||||
ui.SetTimingGaugeVisible(fishingControlMode == FishingControlMode.TimingGauge);
|
||||
}
|
||||
|
||||
if (centerIconRotate != null)
|
||||
@@ -300,23 +354,50 @@ private void InitializeIdleUI()
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (resetPondStateOnStart)
|
||||
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();
|
||||
|
||||
StartNewCatchAttempt();
|
||||
if (fishingControlMode == FishingControlMode.SimpleReel)
|
||||
StartSimpleReelCatch();
|
||||
else
|
||||
StartNewCatchAttempt();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("기묘한 낚시터 시작");
|
||||
Debug.Log(fishingControlMode == FishingControlMode.SimpleReel ? "릴 감기 낚시 시작" : "기묘한 낚시터 시작");
|
||||
}
|
||||
|
||||
public void StopFishing(bool hideUI = false)
|
||||
@@ -325,6 +406,8 @@ public void StopFishing(bool hideUI = false)
|
||||
|
||||
activeGame = false;
|
||||
inputLocked = true;
|
||||
fishingSessionRunning = false;
|
||||
simpleReelCatchActive = false;
|
||||
|
||||
if (centerIconRotate != null)
|
||||
centerIconRotate.StopRotate();
|
||||
@@ -335,7 +418,10 @@ public void StopFishing(bool hideUI = false)
|
||||
|
||||
public void ResetFishing()
|
||||
{
|
||||
StartFishing();
|
||||
fishingSessionRunning = false;
|
||||
simpleReelCatchActive = false;
|
||||
simpleReelPondInitialized = false;
|
||||
StartFishing(true);
|
||||
}
|
||||
|
||||
public void ResetPondState()
|
||||
@@ -350,6 +436,82 @@ public void ResetPondState()
|
||||
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()
|
||||
@@ -366,7 +528,8 @@ private void StartNewCatchAttempt()
|
||||
zoneSize = startZoneSize;
|
||||
}
|
||||
|
||||
clockwise = UnityEngine.Random.value > 0.5f;
|
||||
if (randomDirectionEachRound)
|
||||
clockwise = UnityEngine.Random.value > 0.5f;
|
||||
|
||||
if (centerIconRotate != null)
|
||||
{
|
||||
@@ -412,6 +575,9 @@ private void RotatePointer()
|
||||
|
||||
public void SubmitAttempt()
|
||||
{
|
||||
if (fishingControlMode == FishingControlMode.SimpleReel)
|
||||
return;
|
||||
|
||||
if (!activeGame)
|
||||
return;
|
||||
|
||||
@@ -622,6 +788,17 @@ private void ApplyCatchResult(FishingRewardSystem.CatchResult catchResult)
|
||||
return;
|
||||
}
|
||||
|
||||
if (fishingControlMode == FishingControlMode.SimpleReel)
|
||||
{
|
||||
fishingSessionRunning = false;
|
||||
simpleReelCatchActive = false;
|
||||
|
||||
if (returnBobberAfterSimpleReelCatch && rodController != null)
|
||||
rodController.ReturnBobberToRest();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
nextCatchRoutine = StartCoroutine(NextCatchRoutine());
|
||||
}
|
||||
|
||||
@@ -680,6 +857,23 @@ 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);
|
||||
@@ -690,6 +884,8 @@ private void FishingSuccess()
|
||||
{
|
||||
activeGame = false;
|
||||
inputLocked = true;
|
||||
fishingSessionRunning = false;
|
||||
simpleReelCatchActive = false;
|
||||
|
||||
if (centerIconRotate != null)
|
||||
centerIconRotate.StopRotate();
|
||||
@@ -709,6 +905,8 @@ private void FishingFail()
|
||||
{
|
||||
activeGame = false;
|
||||
inputLocked = true;
|
||||
fishingSessionRunning = false;
|
||||
simpleReelCatchActive = false;
|
||||
|
||||
if (centerIconRotate != null)
|
||||
centerIconRotate.StopRotate();
|
||||
|
||||
@@ -49,6 +49,20 @@ public class FishingGaugeUI : MonoBehaviour
|
||||
[Tooltip("SuccessZone. Image Type = Filled, Fill Method = Radial 360")]
|
||||
[SerializeField] private Image successZone;
|
||||
|
||||
[Header("Simple Reel UI")]
|
||||
[Tooltip("릴 감기 진행도 게이지 루트입니다. 없으면 텍스트만 업데이트됩니다.")]
|
||||
[SerializeField] private GameObject reelProgressGaugeRoot;
|
||||
|
||||
[Tooltip("릴 감기 진행도 Fill Image입니다. Image Type = Filled / Horizontal / Left 추천.")]
|
||||
[SerializeField] private Image reelProgressGaugeFill;
|
||||
|
||||
[Tooltip("현재 줄 길이를 보여줄 텍스트입니다. CounterPanel 안에 LineLengthText를 추가해서 연결하세요.")]
|
||||
[SerializeField] private TMP_Text lineLengthText;
|
||||
|
||||
[SerializeField] private string simpleReelObjectiveText = "릴을 감아 끌어올려라";
|
||||
[SerializeField] private string simpleReelIdleText = "릴 손잡이를 잡고 돌려라";
|
||||
[SerializeField] private string simpleReelReelingText = "좋다, 계속 감아라!";
|
||||
|
||||
[Header("Round Result UI")]
|
||||
[SerializeField] private TMP_Text resultText;
|
||||
|
||||
@@ -134,6 +148,9 @@ public class FishingGaugeUI : MonoBehaviour
|
||||
[Tooltip("게임 클리어/실패 최종 결과가 뜰 때 진행 UI를 전부 숨기고 FinalResultPanel만 보이게 합니다.")]
|
||||
[SerializeField] private bool showOnlyFinalResult = true;
|
||||
|
||||
[Tooltip("FinalResultPanel을 숨길 때 진행 UI를 다시 켤지 결정합니다. 클리어 후 UI를 완전히 닫는 구조면 Off로 두세요.")]
|
||||
[SerializeField] private bool restoreGameplayUIAfterFinalResult = true;
|
||||
|
||||
[Tooltip("BackgroundPanel. 최종 결과창만 보이고 싶으면 자동 연결하거나 직접 연결하세요.")]
|
||||
[SerializeField] private GameObject gameplayBackgroundRoot;
|
||||
|
||||
@@ -267,6 +284,9 @@ public void AutoBindMissingReferences()
|
||||
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");
|
||||
if (resultText == null) resultText = FindComponentByName<TMP_Text>("ResultText");
|
||||
|
||||
if (itemIcon == null) itemIcon = FindComponentByName<Image>("ItemIcon");
|
||||
@@ -327,6 +347,7 @@ public void InitializeFishingUI()
|
||||
UpdateInventoryUI(0, 0, 0, 0);
|
||||
lastCleanupFill = -1f;
|
||||
SetCleanupFill(0f, true);
|
||||
SetSimpleReelUIVisible(false);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
@@ -390,6 +411,55 @@ public void UpdateCounter(int success, int successTarget, int fail, int failTarg
|
||||
failText.text = $"실패 {fail}/{failTarget}";
|
||||
}
|
||||
|
||||
public void SetTimingGaugeVisible(bool visible)
|
||||
{
|
||||
if (gaugeGroupRoot != null)
|
||||
gaugeGroupRoot.SetActive(visible);
|
||||
else if (gaugeGroupRect != null)
|
||||
gaugeGroupRect.gameObject.SetActive(visible);
|
||||
}
|
||||
|
||||
public void SetSimpleReelUIVisible(bool visible)
|
||||
{
|
||||
if (reelProgressGaugeRoot != null)
|
||||
reelProgressGaugeRoot.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 (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);
|
||||
@@ -622,6 +692,30 @@ public void ShowResult(string text, Color color)
|
||||
resultRoutine = StartCoroutine(ResultRoutine(text, color));
|
||||
}
|
||||
|
||||
public void ShowPersistentResult(string text)
|
||||
{
|
||||
ShowPersistentResult(text, defaultResultColor);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -922,7 +1016,11 @@ private void HideNotice(bool instant)
|
||||
memoryPieceNoticePanel.SetActive(false);
|
||||
|
||||
if (memoryPieceNoticeCanvasGroup != null)
|
||||
{
|
||||
memoryPieceNoticeCanvasGroup.alpha = 0f;
|
||||
memoryPieceNoticeCanvasGroup.interactable = false;
|
||||
memoryPieceNoticeCanvasGroup.blocksRaycasts = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -946,14 +1044,20 @@ public void SetControllerGuideVisible(bool visible)
|
||||
private void SetControllerGuideVisible(bool visible, bool instant)
|
||||
{
|
||||
if (effects != null && !instant)
|
||||
{
|
||||
effects.FadeCanvasGroup(controllerGuidePanel, controllerGuideCanvasGroup, visible, panelFadeTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (controllerGuidePanel != null)
|
||||
controllerGuidePanel.SetActive(visible);
|
||||
|
||||
if (controllerGuideCanvasGroup != null)
|
||||
{
|
||||
controllerGuideCanvasGroup.alpha = visible ? 1f : 0f;
|
||||
controllerGuideCanvasGroup.interactable = visible;
|
||||
controllerGuideCanvasGroup.blocksRaycasts = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1034,6 +1138,9 @@ private void SetGameplayUIVisible(bool visible)
|
||||
SetGameObjectVisible(itemSlotPanel, visible);
|
||||
SetGameObjectVisible(cleanupGaugeRoot, visible);
|
||||
|
||||
if (!visible)
|
||||
SetSimpleReelUIVisible(false);
|
||||
|
||||
if (gaugeGroupRoot != null)
|
||||
gaugeGroupRoot.SetActive(visible);
|
||||
else if (gaugeGroupRect != null)
|
||||
@@ -1142,9 +1249,14 @@ private void HideFinalResult(bool showCounterAfterHide, bool instant)
|
||||
if (showCounterAfterHide)
|
||||
{
|
||||
if (showOnlyFinalResult)
|
||||
SetGameplayUIVisible(true);
|
||||
{
|
||||
if (restoreGameplayUIAfterFinalResult)
|
||||
SetGameplayUIVisible(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowCounter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,350 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 모델 낚시줄을 두 포인트 사이에 맞춰 배치/회전/길이 스케일하는 스크립트입니다.
|
||||
/// 권장 사용 방식:
|
||||
/// - StretchLineRoot 빈 오브젝트에 이 스크립트를 붙입니다.
|
||||
/// - StretchLineRoot 자식으로 '늘어나는 중간 줄 모델'만 넣습니다.
|
||||
/// - RodFixedLinePart, BobberFixedLinePart, Bobber/Hook은 StretchLineRoot 안에 넣지 않습니다.
|
||||
/// - 낚시찌는 BobberLineStartPoint의 자식으로 넣어 줄 끝점에 고정합니다.
|
||||
/// </summary>
|
||||
public class FishingModelLineFollower : MonoBehaviour
|
||||
{
|
||||
public enum LineUseMode
|
||||
{
|
||||
WholeModelLine,
|
||||
StretchSegmentOnly
|
||||
}
|
||||
|
||||
public enum LineAxis
|
||||
{
|
||||
LocalX,
|
||||
LocalY,
|
||||
LocalZ
|
||||
}
|
||||
|
||||
public enum PivotMode
|
||||
{
|
||||
Center,
|
||||
Start,
|
||||
End
|
||||
}
|
||||
|
||||
[Header("Mode")]
|
||||
[Tooltip("WholeModelLine: 전체 줄 모델을 두 포인트 사이에 맞춤 / StretchSegmentOnly: 분리된 중간 줄 조각만 늘리는 권장 모드")]
|
||||
[SerializeField] private LineUseMode lineUseMode = LineUseMode.StretchSegmentOnly;
|
||||
|
||||
[Header("Line Targets")]
|
||||
[Tooltip("늘어나는 줄의 시작점입니다. StretchSegmentOnly에서는 RodLineEndPoint를 연결하세요.")]
|
||||
[SerializeField] private Transform rodTipPoint;
|
||||
|
||||
[Tooltip("늘어나는 줄의 끝점입니다. StretchSegmentOnly에서는 BobberLineStartPoint를 연결하세요.")]
|
||||
[SerializeField] private Transform bobberPoint;
|
||||
|
||||
[Header("Stretch Settings")]
|
||||
[Tooltip("모델 줄의 길이 방향 축입니다. 보통 LocalY부터 테스트하세요.")]
|
||||
[SerializeField] private LineAxis lineAxis = LineAxis.LocalY;
|
||||
|
||||
[Tooltip("모델 줄의 원본 길이입니다. 줄이 너무 길게 늘어나면 값을 키우고, 너무 짧으면 값을 줄이세요.")]
|
||||
[Min(0.0001f)]
|
||||
[SerializeField] private float originalLength = 1f;
|
||||
|
||||
[Tooltip("두 포인트가 너무 가까울 때 사용할 최소 길이입니다.")]
|
||||
[Min(0.0001f)]
|
||||
[SerializeField] private float minLength = 0.03f;
|
||||
|
||||
[Tooltip("줄 두께 배율입니다. 1이면 원본 두께를 유지합니다.")]
|
||||
[Min(0.0001f)]
|
||||
[SerializeField] private float thicknessScale = 1f;
|
||||
|
||||
[Tooltip("모델 줄의 앞뒤 방향이 거꾸로 보일 때 켜세요. 포인트 연결은 바꾸지 않는 것을 추천합니다.")]
|
||||
[SerializeField] private bool reverseVisualDirection;
|
||||
|
||||
[Tooltip("대부분 Center를 권장합니다. 모델 Pivot이 시작점/끝점에 있으면 Start 또는 End를 테스트하세요.")]
|
||||
[SerializeField] private PivotMode pivotMode = PivotMode.Center;
|
||||
|
||||
[Header("Visual Mesh Correction")]
|
||||
[Tooltip("실제 Mesh 자식입니다. 비워두면 첫 번째 Renderer가 있는 자식을 자동으로 찾습니다. 위치 보정은 Root가 아니라 이 Visual에 적용하세요.")]
|
||||
[SerializeField] private Transform visualRoot;
|
||||
|
||||
[Tooltip("Visual Mesh의 로컬 위치 보정값입니다. 줄이 살짝 위/옆으로 떠 있을 때 작은 값으로만 보정하세요.")]
|
||||
[SerializeField] private Vector3 visualLocalPositionOffset;
|
||||
|
||||
[Tooltip("Visual Mesh의 로컬 회전 보정값입니다. 모델 자체가 90도 틀어져 있을 때 사용하세요.")]
|
||||
[SerializeField] private Vector3 visualLocalRotationOffsetEuler;
|
||||
|
||||
[Header("Root Rotation Correction")]
|
||||
[Tooltip("루트 회전 보정값입니다. 가능하면 0,0,0으로 두고 Line Axis / Reverse Visual Direction / Visual Rotation Offset으로 먼저 맞추세요.")]
|
||||
[SerializeField] private Vector3 rootRotationOffsetEuler;
|
||||
|
||||
[Header("Visibility / Update")]
|
||||
[SerializeField] private bool hideWhenMissingTarget = true;
|
||||
[SerializeField] private bool hideWhenTooShort = false;
|
||||
[SerializeField] private bool updateInLateUpdate = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool drawDebugLine = true;
|
||||
[SerializeField] private Color debugLineColor = Color.cyan;
|
||||
|
||||
private Vector3 initialLocalScale = Vector3.one;
|
||||
private Vector3 visualInitialLocalPosition;
|
||||
private Quaternion visualInitialLocalRotation = Quaternion.identity;
|
||||
private bool initialized;
|
||||
|
||||
public LineUseMode CurrentLineUseMode => lineUseMode;
|
||||
public Transform RodTipPoint => rodTipPoint;
|
||||
public Transform BobberPoint => bobberPoint;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
lineUseMode = LineUseMode.StretchSegmentOnly;
|
||||
lineAxis = LineAxis.LocalY;
|
||||
originalLength = 1f;
|
||||
minLength = 0.03f;
|
||||
thicknessScale = 1f;
|
||||
pivotMode = PivotMode.Center;
|
||||
updateInLateUpdate = true;
|
||||
drawDebugLine = true;
|
||||
TryAutoFindVisualRoot();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!updateInLateUpdate)
|
||||
UpdateLine();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (updateInLateUpdate)
|
||||
UpdateLine();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
originalLength = Mathf.Max(0.0001f, originalLength);
|
||||
minLength = Mathf.Max(0.0001f, minLength);
|
||||
thicknessScale = Mathf.Max(0.0001f, thicknessScale);
|
||||
|
||||
if (!Application.isPlaying)
|
||||
TryAutoFindVisualRoot();
|
||||
}
|
||||
|
||||
private void InitializeIfNeeded()
|
||||
{
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
initialLocalScale = transform.localScale;
|
||||
|
||||
if (visualRoot == null)
|
||||
TryAutoFindVisualRoot();
|
||||
|
||||
if (visualRoot != null)
|
||||
{
|
||||
visualInitialLocalPosition = visualRoot.localPosition;
|
||||
visualInitialLocalRotation = visualRoot.localRotation;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void UpdateLine()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
|
||||
if (rodTipPoint == null || bobberPoint == null)
|
||||
{
|
||||
SetVisible(!hideWhenMissingTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 start = rodTipPoint.position;
|
||||
Vector3 end = bobberPoint.position;
|
||||
Vector3 segment = end - start;
|
||||
float distance = segment.magnitude;
|
||||
|
||||
if (distance < minLength)
|
||||
{
|
||||
if (hideWhenTooShort)
|
||||
{
|
||||
SetVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
distance = minLength;
|
||||
}
|
||||
|
||||
Vector3 direction = segment.sqrMagnitude > 0.000001f ? segment.normalized : transform.forward;
|
||||
Vector3 visualDirection = reverseVisualDirection ? -direction : direction;
|
||||
|
||||
SetVisible(true);
|
||||
|
||||
transform.position = GetRootPosition(start, end);
|
||||
transform.rotation = Quaternion.FromToRotation(GetAxisVector(lineAxis), visualDirection) * Quaternion.Euler(rootRotationOffsetEuler);
|
||||
transform.localScale = GetScaledVector(distance);
|
||||
|
||||
ApplyVisualCorrection();
|
||||
|
||||
if (drawDebugLine)
|
||||
Debug.DrawLine(start, end, debugLineColor);
|
||||
}
|
||||
|
||||
private Vector3 GetRootPosition(Vector3 start, Vector3 end)
|
||||
{
|
||||
switch (pivotMode)
|
||||
{
|
||||
case PivotMode.Start:
|
||||
return start;
|
||||
case PivotMode.End:
|
||||
return end;
|
||||
case PivotMode.Center:
|
||||
default:
|
||||
return (start + end) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetScaledVector(float targetLength)
|
||||
{
|
||||
float lengthScale = targetLength / Mathf.Max(0.0001f, originalLength);
|
||||
|
||||
Vector3 scale = initialLocalScale;
|
||||
|
||||
switch (lineAxis)
|
||||
{
|
||||
case LineAxis.LocalX:
|
||||
scale.x = initialLocalScale.x * lengthScale;
|
||||
scale.y = initialLocalScale.y * thicknessScale;
|
||||
scale.z = initialLocalScale.z * thicknessScale;
|
||||
break;
|
||||
|
||||
case LineAxis.LocalY:
|
||||
scale.x = initialLocalScale.x * thicknessScale;
|
||||
scale.y = initialLocalScale.y * lengthScale;
|
||||
scale.z = initialLocalScale.z * thicknessScale;
|
||||
break;
|
||||
|
||||
case LineAxis.LocalZ:
|
||||
scale.x = initialLocalScale.x * thicknessScale;
|
||||
scale.y = initialLocalScale.y * thicknessScale;
|
||||
scale.z = initialLocalScale.z * lengthScale;
|
||||
break;
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
private static Vector3 GetAxisVector(LineAxis axis)
|
||||
{
|
||||
switch (axis)
|
||||
{
|
||||
case LineAxis.LocalX:
|
||||
return Vector3.right;
|
||||
case LineAxis.LocalY:
|
||||
return Vector3.up;
|
||||
case LineAxis.LocalZ:
|
||||
return Vector3.forward;
|
||||
default:
|
||||
return Vector3.up;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyVisualCorrection()
|
||||
{
|
||||
if (visualRoot == null)
|
||||
return;
|
||||
|
||||
visualRoot.localPosition = visualInitialLocalPosition + visualLocalPositionOffset;
|
||||
visualRoot.localRotation = visualInitialLocalRotation * Quaternion.Euler(visualLocalRotationOffsetEuler);
|
||||
}
|
||||
|
||||
private void SetVisible(bool visible)
|
||||
{
|
||||
if (visualRoot != null)
|
||||
{
|
||||
SetRenderersVisible(visualRoot, visible);
|
||||
return;
|
||||
}
|
||||
|
||||
SetRenderersVisible(transform, visible);
|
||||
}
|
||||
|
||||
private void SetRenderersVisible(Transform target, bool visible)
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
Renderer[] renderers = target.GetComponentsInChildren<Renderer>(true);
|
||||
for (int i = 0; i < renderers.Length; i++)
|
||||
renderers[i].enabled = visible;
|
||||
}
|
||||
|
||||
private void TryAutoFindVisualRoot()
|
||||
{
|
||||
if (visualRoot != null)
|
||||
return;
|
||||
|
||||
Renderer directRenderer = GetComponent<Renderer>();
|
||||
if (directRenderer != null)
|
||||
{
|
||||
visualRoot = transform;
|
||||
return;
|
||||
}
|
||||
|
||||
Renderer childRenderer = GetComponentInChildren<Renderer>(true);
|
||||
if (childRenderer != null)
|
||||
visualRoot = childRenderer.transform;
|
||||
}
|
||||
|
||||
[ContextMenu("Set Mode: Stretch Segment Only")]
|
||||
private void SetStretchSegmentOnlyMode()
|
||||
{
|
||||
lineUseMode = LineUseMode.StretchSegmentOnly;
|
||||
}
|
||||
|
||||
[ContextMenu("Reset Corrections")]
|
||||
private void ResetCorrections()
|
||||
{
|
||||
rootRotationOffsetEuler = Vector3.zero;
|
||||
visualLocalPositionOffset = Vector3.zero;
|
||||
visualLocalRotationOffsetEuler = Vector3.zero;
|
||||
}
|
||||
|
||||
[ContextMenu("Calibrate Original Length From Renderer Bounds")]
|
||||
private void CalibrateOriginalLengthFromRendererBounds()
|
||||
{
|
||||
Renderer renderer = visualRoot != null ? visualRoot.GetComponentInChildren<Renderer>(true) : GetComponentInChildren<Renderer>(true);
|
||||
if (renderer == null)
|
||||
{
|
||||
Debug.LogWarning("FishingModelLineFollower: Renderer를 찾지 못해서 Original Length를 자동 계산할 수 없습니다.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 size = renderer.bounds.size;
|
||||
float measured = Mathf.Max(size.x, Mathf.Max(size.y, size.z));
|
||||
originalLength = Mathf.Max(0.0001f, measured);
|
||||
|
||||
Debug.Log($"FishingModelLineFollower: Original Length를 {originalLength:F4}로 설정했습니다.", this);
|
||||
}
|
||||
|
||||
public void SetTargets(Transform startPoint, Transform endPoint)
|
||||
{
|
||||
rodTipPoint = startPoint;
|
||||
bobberPoint = endPoint;
|
||||
}
|
||||
|
||||
public void SetReverseVisualDirection(bool value)
|
||||
{
|
||||
reverseVisualDirection = value;
|
||||
}
|
||||
|
||||
public void SetOriginalLength(float value)
|
||||
{
|
||||
originalLength = Mathf.Max(0.0001f, value);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8d0073af66f0fc49a7fb32df0bf5e41
|
||||
@@ -1,37 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 낚싯대를 잡고 있는지 기록하는 간단한 상태 스크립트입니다.
|
||||
/// FishingRod 오브젝트에 붙이고, XR Grab Interactable의 Select Entered/Exited 이벤트에 연결하세요.
|
||||
/// </summary>
|
||||
public class FishingRodState : MonoBehaviour
|
||||
{
|
||||
[Header("State")]
|
||||
[SerializeField] private bool isHeld;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
public bool IsHeld => isHeld;
|
||||
|
||||
public void MarkHeld()
|
||||
{
|
||||
SetHeld(true);
|
||||
}
|
||||
|
||||
public void MarkReleased()
|
||||
{
|
||||
SetHeld(false);
|
||||
}
|
||||
|
||||
public void SetHeld(bool value)
|
||||
{
|
||||
if (isHeld == value)
|
||||
return;
|
||||
|
||||
isHeld = value;
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log(isHeld ? "낚싯대 잡음" : "낚싯대 놓음");
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19b938e652163b1459e2a5d8d862510b
|
||||
1537
Assets/My project/Fishing Scripts/UI/FishingRodVRController.cs
Normal file
1537
Assets/My project/Fishing Scripts/UI/FishingRodVRController.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8b17139274961d4d9b190b0bb7d6e8e
|
||||
@@ -1,244 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
/// <summary>
|
||||
/// 낚시터 시작 영역에 붙이는 스크립트입니다.
|
||||
/// 플레이어가 Trigger 영역 안에 들어오면 안내 UI를 켜고,
|
||||
/// 지정한 Start Action 입력이 들어오면 FishingGameManager.StartFishing()을 호출합니다.
|
||||
/// 필요하면 낚싯대를 잡고 있을 때만 시작되도록 제한할 수 있습니다.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class FishingStartTrigger : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("낚시 미니게임을 제어하는 FishingGameManager입니다.")]
|
||||
[SerializeField] private FishingGameManager fishingGameManager;
|
||||
|
||||
[Tooltip("낚시터 근처에 들어왔을 때 보여줄 시작 안내 UI입니다.")]
|
||||
[SerializeField] private GameObject startGuideUI;
|
||||
|
||||
[Header("Input")]
|
||||
[Tooltip("낚시 시작 입력입니다. 예: XRI RightHand / Primary Button. SubmitAction과 같은 버튼은 피하는 것을 추천합니다.")]
|
||||
[SerializeField] private InputActionReference startAction;
|
||||
|
||||
[Tooltip("Input Action Manager가 없거나 액션이 자동 활성화되지 않으면 켜두세요.")]
|
||||
[SerializeField] private bool enableInputActionManually = true;
|
||||
|
||||
[Header("Player Check")]
|
||||
[Tooltip("플레이어 루트 오브젝트의 태그입니다. 보통 XR Origin에 Player 태그를 붙입니다.")]
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
|
||||
[Tooltip("태그 체크를 끄고 모든 Collider 진입을 플레이어로 취급합니다. 테스트용입니다.")]
|
||||
[SerializeField] private bool ignorePlayerTagForTest = false;
|
||||
|
||||
[Header("Rod Requirement")]
|
||||
[Tooltip("켜면 낚싯대를 잡고 있을 때만 낚시를 시작할 수 있습니다.")]
|
||||
[SerializeField] private bool requireRodHeld = false;
|
||||
|
||||
[Tooltip("낚싯대에 붙어 있는 FishingRodState입니다.")]
|
||||
[SerializeField] private FishingRodState requiredRod;
|
||||
|
||||
[Header("Rules")]
|
||||
[Tooltip("플레이어가 영역에서 나가면 낚시를 중단하고 UI를 숨깁니다. VR에서는 처음에 Off 추천.")]
|
||||
[SerializeField] private bool stopFishingOnExit = false;
|
||||
|
||||
[Tooltip("낚시가 시작되면 시작 안내 UI를 숨깁니다.")]
|
||||
[SerializeField] private bool hideGuideWhenFishingStarts = true;
|
||||
|
||||
[Tooltip("기억의 조각을 이미 획득한 뒤에도 다시 시작할 수 있게 합니다.")]
|
||||
[SerializeField] private bool allowRestartAfterClear = false;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private bool playerInside;
|
||||
private bool actionWasEnabled;
|
||||
|
||||
public bool PlayerInside => playerInside;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
Collider col = GetComponent<Collider>();
|
||||
if (col != null)
|
||||
col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Collider col = GetComponent<Collider>();
|
||||
if (col != null && !col.isTrigger)
|
||||
col.isTrigger = true;
|
||||
|
||||
SetGuideVisible(false);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RegisterStartAction();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnregisterStartAction();
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!IsPlayer(other))
|
||||
return;
|
||||
|
||||
playerInside = true;
|
||||
RefreshGuide();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 시작 영역 진입");
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (!IsPlayer(other))
|
||||
return;
|
||||
|
||||
playerInside = false;
|
||||
SetGuideVisible(false);
|
||||
|
||||
if (stopFishingOnExit && fishingGameManager != null)
|
||||
fishingGameManager.StopFishing(true);
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 시작 영역 이탈");
|
||||
}
|
||||
|
||||
private bool IsPlayer(Collider other)
|
||||
{
|
||||
if (ignorePlayerTagForTest)
|
||||
return true;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (other.CompareTag(playerTag))
|
||||
return true;
|
||||
|
||||
Transform current = other.transform;
|
||||
while (current != null)
|
||||
{
|
||||
if (current.CompareTag(playerTag))
|
||||
return true;
|
||||
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RegisterStartAction()
|
||||
{
|
||||
if (startAction == null || startAction.action == null)
|
||||
return;
|
||||
|
||||
actionWasEnabled = startAction.action.enabled;
|
||||
startAction.action.performed += OnStartAction;
|
||||
|
||||
if (enableInputActionManually && !actionWasEnabled)
|
||||
startAction.action.Enable();
|
||||
}
|
||||
|
||||
private void UnregisterStartAction()
|
||||
{
|
||||
if (startAction == null || startAction.action == null)
|
||||
return;
|
||||
|
||||
startAction.action.performed -= OnStartAction;
|
||||
|
||||
if (enableInputActionManually && !actionWasEnabled)
|
||||
startAction.action.Disable();
|
||||
}
|
||||
|
||||
private void OnStartAction(InputAction.CallbackContext context)
|
||||
{
|
||||
TryStartFishing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI 버튼이나 다른 스크립트에서도 호출할 수 있는 낚시 시작 함수입니다.
|
||||
/// </summary>
|
||||
public void TryStartFishing()
|
||||
{
|
||||
if (!CanStartFishing())
|
||||
return;
|
||||
|
||||
fishingGameManager.StartFishing();
|
||||
|
||||
if (hideGuideWhenFishingStarts)
|
||||
SetGuideVisible(false);
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 시작 요청 완료");
|
||||
}
|
||||
|
||||
public bool CanStartFishing()
|
||||
{
|
||||
if (!playerInside)
|
||||
return false;
|
||||
|
||||
if (fishingGameManager == null)
|
||||
{
|
||||
if (showDebugLog)
|
||||
Debug.LogWarning("FishingStartTrigger: FishingGameManager가 연결되지 않았습니다.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fishingGameManager.IsActiveGame)
|
||||
return false;
|
||||
|
||||
if (fishingGameManager.IsMemoryPieceCollected && !allowRestartAfterClear)
|
||||
return false;
|
||||
|
||||
if (requireRodHeld)
|
||||
{
|
||||
if (requiredRod == null)
|
||||
{
|
||||
if (showDebugLog)
|
||||
Debug.LogWarning("FishingStartTrigger: Require Rod Held가 켜져 있지만 Required Rod가 연결되지 않았습니다.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!requiredRod.IsHeld)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RefreshGuide()
|
||||
{
|
||||
if (!playerInside)
|
||||
{
|
||||
SetGuideVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fishingGameManager != null && fishingGameManager.IsMemoryPieceCollected && !allowRestartAfterClear)
|
||||
{
|
||||
SetGuideVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fishingGameManager != null && fishingGameManager.IsActiveGame)
|
||||
{
|
||||
SetGuideVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
SetGuideVisible(true);
|
||||
}
|
||||
|
||||
private void SetGuideVisible(bool visible)
|
||||
{
|
||||
if (startGuideUI != null)
|
||||
startGuideUI.SetActive(visible);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 861cf89cbda384e4b9bf8207fcb6e487
|
||||
Reference in New Issue
Block a user