2026.06.25 Fishing scene

This commit is contained in:
2026-06-25 17:39:42 +09:00
parent 3a3b3dcb41
commit e8f67c0609
44 changed files with 4061 additions and 133 deletions

View File

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

Binary file not shown.

View File

@@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: b205275e9229b8b40bcfb446b9fa4cb4
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 715df9372183c47e389bb6e19fbc3b52, type: 3}
editorImportSettings:
generateSecondaryUVSet: 0
importSettings:
nodeNameMethod: 1
animationMethod: 2
generateMipMaps: 1
texturesReadable: 0
defaultMinFilterMode: 9729
defaultMagFilterMode: 9729
anisotropicFilterLevel: 1
instantiationSettings:
mask: -1
layer: 0
skinUpdateWhenOffscreen: 1
lightIntensityFactor: 1
sceneObjectCreation: 2
assetDependencies: []
reportItems: []

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e9d8394fffa0b5b4e81c71ad0000f45a
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -11,12 +12,29 @@ public enum ResultType
Miss
}
[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("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;
@@ -30,29 +48,97 @@ public enum ResultType
[SerializeField] private float maxZoneSize = 120f;
[SerializeField] private float zoneCenter;
[Header("Rules")]
[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;
[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 pondCleaned;
private bool memoryPieceCollected;
private bool submitActionWasEnabled;
private bool resetActionWasEnabled;
private Coroutine nextRoundRoutine;
private Coroutine nextCatchRoutine;
private Coroutine finalResultRoutine;
public bool IsActiveGame => activeGame;
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 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();
@@ -74,13 +160,137 @@ private void Update()
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 (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);
pointerAngle = Normalize(pointerAngle);
zoneCenter = Normalize(zoneCenter);
cleanupItemCount = Mathf.Max(0, cleanupItemCount);
totalCaughtItems = Mathf.Max(0, totalCaughtItems);
}
private void InitializeIdleUI()
{
if (ui != null)
{
ui.HideRoundResult();
ui.HideFinalResult();
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);
}
@@ -91,30 +301,73 @@ private void InitializeIdleUI()
public void StartFishing()
{
if (nextRoundRoutine != null)
{
StopCoroutine(nextRoundRoutine);
nextRoundRoutine = null;
}
ValidateRuntimeSettings();
StopRunningRoutines();
successCount = 0;
failCount = 0;
if (uiRoot != null)
uiRoot.SetActive(true);
pointerAngle = 0f;
pointerSpeed = minSpeed;
zoneSize = startZoneSize;
activeGame = true;
inputLocked = false;
clockwise = Random.value > 0.5f;
if (resetPondStateOnStart)
ResetPondState();
if (ui != null)
ui.InitializeFishingUI();
StartNewCatchAttempt();
if (showDebugLog)
Debug.Log("기묘한 낚시터 시작");
}
public void StopFishing(bool hideUI = false)
{
StopRunningRoutines();
activeGame = false;
inputLocked = true;
if (centerIconRotate != null)
centerIconRotate.StopRotate();
if (hideUI && uiRoot != null)
uiRoot.SetActive(false);
}
public void ResetFishing()
{
StartFishing();
}
public void ResetPondState()
{
successCount = 0;
failCount = 0;
cleanupItemCount = 0;
totalCaughtItems = 0;
sessionFishCount = 0;
sessionTrashCount = 0;
sessionMemoryPieceCount = 0;
sessionCompassCount = 0;
pondCleaned = false;
memoryPieceCollected = false;
}
private void StartNewCatchAttempt()
{
successCount = 0;
failCount = 0;
inputLocked = false;
activeGame = true;
if (resetDifficultyEachCatch)
{
ui.HideRoundResult();
ui.HideFinalResult();
pointerAngle = 0f;
pointerSpeed = minSpeed;
zoneSize = startZoneSize;
}
clockwise = UnityEngine.Random.value > 0.5f;
if (centerIconRotate != null)
{
centerIconRotate.ResetRotation();
@@ -123,9 +376,27 @@ public void StartFishing()
RandomizeZone();
UpdateUI();
}
if (showDebugLog)
Debug.Log("낚시 시작");
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()
@@ -149,8 +420,17 @@ public void SubmitAttempt()
inputLocked = true;
if (ui != null)
ui.HideControllerGuide();
ResultType result = EvaluateResult();
if (ui != null)
{
ui.ShowResultForType(result);
ui.FlashFeedbackForType(result);
}
switch (result)
{
case ResultType.Perfect:
@@ -168,7 +448,7 @@ public void SubmitAttempt()
if (successCount >= requiredSuccesses)
{
FishingSuccess();
CatchItemSuccess();
return;
}
@@ -207,11 +487,8 @@ private void OnPerfect()
if (haptic != null)
haptic.Perfect();
if (ui != null)
ui.ShowResult("완벽!");
if (showDebugLog)
Debug.Log("낚시 판정: Perfect");
Debug.Log("낚시 판정: Perfect");
ClampDifficulty();
UpdateUI();
@@ -227,11 +504,8 @@ private void OnGood()
if (haptic != null)
haptic.Good();
if (ui != null)
ui.ShowResult("성공!");
if (showDebugLog)
Debug.Log("낚시 판정: Good");
Debug.Log("낚시 판정: Good");
ClampDifficulty();
UpdateUI();
@@ -247,11 +521,8 @@ private void OnMiss()
if (haptic != null)
haptic.Miss();
if (ui != null)
ui.ShowResult("실패!");
if (showDebugLog)
Debug.Log("낚시 판정: Miss");
Debug.Log("낚시 판정: Miss");
ClampDifficulty();
UpdateUI();
@@ -262,10 +533,13 @@ private IEnumerator NextRoundRoutine()
yield return new WaitForSeconds(nextRoundDelay);
if (!activeGame)
{
nextRoundRoutine = null;
yield break;
}
if (randomDirectionEachRound)
clockwise = Random.value > 0.5f;
clockwise = UnityEngine.Random.value > 0.5f;
RandomizeZone();
UpdateUI();
@@ -274,6 +548,122 @@ private IEnumerator NextRoundRoutine()
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;
}
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);
@@ -282,7 +672,7 @@ private void ClampDifficulty()
private void RandomizeZone()
{
zoneCenter = Random.Range(0f, 360f);
zoneCenter = UnityEngine.Random.Range(0f, 360f);
}
private void UpdateUI()
@@ -292,6 +682,8 @@ private void UpdateUI()
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()
@@ -302,14 +694,15 @@ private void FishingSuccess()
if (centerIconRotate != null)
centerIconRotate.StopRotate();
if (ui != null)
ui.ShowFinalResult("낚시 성공!");
UpdateUI();
if (rewardSystem != null)
rewardSystem.GiveReward();
if (ui != null)
ui.ShowFinalResult("기억의 조각 획득!\n기묘한 낚시터 클리어!");
if (showDebugLog)
Debug.Log("낚시 최종 성공");
Debug.Log("기묘한 낚시터 클리어");
BeginFinalResultRoutineIfNeeded();
}
private void FishingFail()
@@ -321,15 +714,33 @@ private void FishingFail()
centerIconRotate.StopRotate();
if (ui != null)
ui.ShowFinalResult("낚시 실패!");
ui.ShowFinalResult("낚싯줄이 끊어졌다!\n다시 천천히 낚아보자.");
if (showDebugLog)
Debug.Log("낚시 최종 실패");
Debug.Log("낚시 실패: 낚싯줄 끊어짐");
BeginFinalResultRoutineIfNeeded();
}
public void ResetFishing()
private void BeginFinalResultRoutineIfNeeded()
{
StartFishing();
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)
@@ -342,17 +753,13 @@ private float Normalize(float angle)
return angle;
}
// XR Input Action의 Select / Trigger 버튼에 연결
public void OnSubmit(InputValue value)
private void OnSubmitAction(InputAction.CallbackContext context)
{
if (value.isPressed)
SubmitAttempt();
SubmitAttempt();
}
// Reset 버튼에 연결 가능
public void OnReset(InputValue value)
private void OnResetAction(InputAction.CallbackContext context)
{
if (value.isPressed)
ResetFishing();
ResetFishing();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ public class FishingHapticManager : MonoBehaviour
[Header("Haptic Settings")]
[SerializeField] private bool useHaptic = true;
[SerializeField] private bool showHapticDebugLog = false;
[SerializeField] private float perfectAmplitude = 1f;
[SerializeField] private float perfectDuration = 0.2f;
@@ -18,6 +19,19 @@ public class FishingHapticManager : MonoBehaviour
[SerializeField] private float missAmplitude = 0.2f;
[SerializeField] private float missDuration = 0.05f;
public XRNode TargetHand => targetHand;
private void OnValidate()
{
perfectAmplitude = Mathf.Clamp01(perfectAmplitude);
goodAmplitude = Mathf.Clamp01(goodAmplitude);
missAmplitude = Mathf.Clamp01(missAmplitude);
perfectDuration = Mathf.Max(0f, perfectDuration);
goodDuration = Mathf.Max(0f, goodDuration);
missDuration = Mathf.Max(0f, missDuration);
}
private void SendHaptic(float amplitude, float duration)
{
if (!useHaptic)
@@ -26,15 +40,46 @@ private void SendHaptic(float amplitude, float duration)
InputDevice device = InputDevices.GetDeviceAtXRNode(targetHand);
if (!device.isValid)
return;
if (device.TryGetHapticCapabilities(out HapticCapabilities capabilities))
{
if (!capabilities.supportsImpulse)
return;
LogHapticWarning("Haptic device not found.");
return;
}
device.SendHapticImpulse(0u, Mathf.Clamp01(amplitude), duration);
if (!device.TryGetHapticCapabilities(out HapticCapabilities capabilities))
{
LogHapticWarning("Haptic capabilities not available.");
return;
}
if (!capabilities.supportsImpulse)
{
LogHapticWarning("Haptic impulse is not supported on this device.");
return;
}
if (capabilities.numChannels < 1)
{
LogHapticWarning("Haptic channel count is zero.");
return;
}
device.SendHapticImpulse(0u, Mathf.Clamp01(amplitude), Mathf.Max(0f, duration));
}
private void LogHapticWarning(string message)
{
if (showHapticDebugLog)
Debug.LogWarning($"[FishingHapticManager] {message}", this);
}
public void SetTargetHand(XRNode hand)
{
targetHand = hand;
}
public void SetUseHaptic(bool value)
{
useHaptic = value;
}
public void Perfect()
@@ -51,4 +96,4 @@ public void Miss()
{
SendHaptic(missAmplitude, missDuration);
}
}
}

View File

@@ -0,0 +1,178 @@
using UnityEngine;
public class FishingInventory : MonoBehaviour
{
[Header("Item Counts")]
[SerializeField] private int fishCount;
[SerializeField] private int rottenFishCount;
[SerializeField] private int trashCount;
[SerializeField] private int bottleCount;
[SerializeField] private int plasticBagCount;
[SerializeField] private int jellyfishPowderCount;
[SerializeField] private int oldCompassCount;
[SerializeField] private int memoryPieceCount;
public int FishCount => fishCount;
public int RottenFishCount => rottenFishCount;
public int TrashCount => trashCount;
public int BottleCount => bottleCount;
public int PlasticBagCount => plasticBagCount;
public int JellyfishPowderCount => jellyfishPowderCount;
public int OldCompassCount => oldCompassCount;
public int MemoryPieceCount => memoryPieceCount;
private void OnValidate()
{
ClampCounts();
}
private void ClampCounts()
{
fishCount = Mathf.Max(0, fishCount);
rottenFishCount = Mathf.Max(0, rottenFishCount);
trashCount = Mathf.Max(0, trashCount);
bottleCount = Mathf.Max(0, bottleCount);
plasticBagCount = Mathf.Max(0, plasticBagCount);
jellyfishPowderCount = Mathf.Max(0, jellyfishPowderCount);
oldCompassCount = Mathf.Max(0, oldCompassCount);
memoryPieceCount = Mathf.Max(0, memoryPieceCount);
}
public void AddItem(FishingItemType itemType, int amount = 1)
{
if (itemType == FishingItemType.None || amount <= 0)
return;
switch (itemType)
{
case FishingItemType.Fish:
fishCount += amount;
break;
case FishingItemType.RottenFish:
rottenFishCount += amount;
break;
case FishingItemType.Trash:
trashCount += amount;
break;
case FishingItemType.Bottle:
bottleCount += amount;
break;
case FishingItemType.PlasticBag:
plasticBagCount += amount;
break;
case FishingItemType.JellyfishPowder:
jellyfishPowderCount += amount;
break;
case FishingItemType.OldCompass:
oldCompassCount += amount;
break;
case FishingItemType.MemoryPiece:
memoryPieceCount += amount;
break;
}
}
public bool HasItem(FishingItemType itemType, int amount = 1)
{
if (itemType == FishingItemType.None || amount <= 0)
return false;
return GetItemCount(itemType) >= amount;
}
public bool ConsumeItem(FishingItemType itemType, int amount = 1)
{
if (!HasItem(itemType, amount))
return false;
switch (itemType)
{
case FishingItemType.Fish:
fishCount -= amount;
break;
case FishingItemType.RottenFish:
rottenFishCount -= amount;
break;
case FishingItemType.Trash:
trashCount -= amount;
break;
case FishingItemType.Bottle:
bottleCount -= amount;
break;
case FishingItemType.PlasticBag:
plasticBagCount -= amount;
break;
case FishingItemType.JellyfishPowder:
jellyfishPowderCount -= amount;
break;
case FishingItemType.OldCompass:
oldCompassCount -= amount;
break;
case FishingItemType.MemoryPiece:
memoryPieceCount -= amount;
break;
}
return true;
}
public int GetItemCount(FishingItemType itemType)
{
switch (itemType)
{
case FishingItemType.Fish:
return fishCount;
case FishingItemType.RottenFish:
return rottenFishCount;
case FishingItemType.Trash:
return trashCount;
case FishingItemType.Bottle:
return bottleCount;
case FishingItemType.PlasticBag:
return plasticBagCount;
case FishingItemType.JellyfishPowder:
return jellyfishPowderCount;
case FishingItemType.OldCompass:
return oldCompassCount;
case FishingItemType.MemoryPiece:
return memoryPieceCount;
case FishingItemType.None:
default:
return 0;
}
}
public void ResetInventory()
{
fishCount = 0;
rottenFishCount = 0;
trashCount = 0;
bottleCount = 0;
plasticBagCount = 0;
jellyfishPowderCount = 0;
oldCompassCount = 0;
memoryPieceCount = 0;
}
}

View File

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

View File

@@ -0,0 +1,12 @@
public enum FishingItemType
{
None,
Fish,
RottenFish,
Trash,
Bottle,
PlasticBag,
JellyfishPowder,
OldCompass,
MemoryPiece
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6365b670fd7854c4bb6024c911f30c5c

View File

@@ -0,0 +1,350 @@
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);
}
}

View File

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

View File

@@ -1,53 +1,255 @@
using System;
using System.Collections.Generic;
using UnityEngine;
public class FishingRewardSystem : MonoBehaviour
{
public enum RewardType
[Serializable]
public class LootEntry
{
Normal,
Rare
public FishingItemType itemType = FishingItemType.Fish;
public string displayName = "생선";
[Min(0f)] public float weight = 1f;
public LootEntry(FishingItemType itemType, string displayName, float weight)
{
this.itemType = itemType;
this.displayName = displayName;
this.weight = Mathf.Max(0f, weight);
}
}
[Header("Reward Chance")]
[Range(0f, 100f)]
[SerializeField] private float rareChance = 10f;
[Header("Reward Count")]
[SerializeField] private int normalMemory;
[SerializeField] private int rareMemory;
public int NormalMemory => normalMemory;
public int RareMemory => rareMemory;
public RewardType GiveReward()
public struct CatchResult
{
if (Random.value < rareChance / 100f)
return GiveRareMemory();
public FishingItemType ItemType;
public string DisplayName;
public bool CountsAsCleanupItem;
public bool IsMemoryPiece;
return GiveNormalMemory();
public CatchResult(FishingItemType itemType, string displayName, bool countsAsCleanupItem, bool isMemoryPiece)
{
ItemType = itemType;
DisplayName = displayName;
CountsAsCleanupItem = countsAsCleanupItem;
IsMemoryPiece = isMemoryPiece;
}
}
private RewardType GiveNormalMemory()
[Header("Loot Table - Dirty Pond")]
[Tooltip("연못이 정화되기 전 낚이는 아이템 목록입니다. 기억의 조각은 이 테이블에 있어도 자동 제외됩니다.")]
[SerializeField] private List<LootEntry> dirtyPondLootTable = new List<LootEntry>();
[Header("Loot Table - Clean Pond")]
[Tooltip("연못이 정화된 뒤 낚이는 아이템 목록입니다. 기억의 조각을 포함할 수 있습니다.")]
[SerializeField] private List<LootEntry> cleanPondLootTable = new List<LootEntry>();
[Header("Debug")]
[SerializeField] private bool showDebugLog = true;
public IReadOnlyList<LootEntry> DirtyPondLootTable => dirtyPondLootTable;
public IReadOnlyList<LootEntry> CleanPondLootTable => cleanPondLootTable;
private void Reset()
{
normalMemory++;
Debug.Log("ÀÏ¹Ý ±â¾ï +1");
return RewardType.Normal;
SetDefaultLootTables();
}
private RewardType GiveRareMemory()
private void Awake()
{
rareMemory++;
Debug.Log("Èñ±Í ±â¾ï +1");
return RewardType.Rare;
EnsureLootTables();
}
public void ResetRewardCount()
private void OnValidate()
{
normalMemory = 0;
rareMemory = 0;
EnsureLootTables();
ClampLootTable(dirtyPondLootTable);
ClampLootTable(cleanPondLootTable);
}
}
private void EnsureLootTables()
{
if (dirtyPondLootTable == null)
dirtyPondLootTable = new List<LootEntry>();
if (cleanPondLootTable == null)
cleanPondLootTable = new List<LootEntry>();
if (dirtyPondLootTable.Count == 0)
SetDefaultDirtyLootTable();
if (cleanPondLootTable.Count == 0)
SetDefaultCleanLootTable();
}
private void SetDefaultLootTables()
{
SetDefaultDirtyLootTable();
SetDefaultCleanLootTable();
}
private void SetDefaultDirtyLootTable()
{
dirtyPondLootTable = new List<LootEntry>
{
new LootEntry(FishingItemType.Fish, "생선", 25f),
new LootEntry(FishingItemType.RottenFish, "상한 생선", 8f),
new LootEntry(FishingItemType.Trash, "쓰레기", 28f),
new LootEntry(FishingItemType.Bottle, "병", 18f),
new LootEntry(FishingItemType.PlasticBag, "봉지", 18f),
new LootEntry(FishingItemType.JellyfishPowder, "해파리 가루", 6f),
new LootEntry(FishingItemType.OldCompass, "낡은 나침반", 4f)
};
}
private void SetDefaultCleanLootTable()
{
cleanPondLootTable = new List<LootEntry>
{
new LootEntry(FishingItemType.MemoryPiece, "기억의 조각", 30f),
new LootEntry(FishingItemType.Fish, "생선", 30f),
new LootEntry(FishingItemType.OldCompass, "낡은 나침반", 15f),
new LootEntry(FishingItemType.JellyfishPowder, "해파리 가루", 15f),
new LootEntry(FishingItemType.RottenFish, "상한 생선", 4f)
};
}
private void ClampLootTable(List<LootEntry> table)
{
if (table == null)
return;
for (int i = 0; i < table.Count; i++)
{
if (table[i] == null)
{
table[i] = new LootEntry(FishingItemType.Fish, "생선", 1f);
continue;
}
table[i].weight = Mathf.Max(0f, table[i].weight);
if (string.IsNullOrWhiteSpace(table[i].displayName))
table[i].displayName = GetDefaultDisplayName(table[i].itemType);
}
}
public CatchResult RollCatch(bool pondCleaned, bool forceMemoryPiece)
{
if (forceMemoryPiece)
return CreateCatchResult(FishingItemType.MemoryPiece, GetDefaultDisplayName(FishingItemType.MemoryPiece));
List<LootEntry> table = pondCleaned ? cleanPondLootTable : dirtyPondLootTable;
LootEntry selectedEntry = RollFromTable(table, pondCleaned);
if (selectedEntry == null)
return CreateCatchResult(FishingItemType.Fish, GetDefaultDisplayName(FishingItemType.Fish));
return CreateCatchResult(selectedEntry.itemType, selectedEntry.displayName);
}
private LootEntry RollFromTable(List<LootEntry> table, bool pondCleaned)
{
if (table == null || table.Count == 0)
return null;
float totalWeight = 0f;
for (int i = 0; i < table.Count; i++)
{
LootEntry entry = table[i];
if (!IsRollableEntry(entry, pondCleaned))
continue;
totalWeight += entry.weight;
}
if (totalWeight <= 0f)
return null;
float roll = UnityEngine.Random.Range(0f, totalWeight);
float current = 0f;
for (int i = 0; i < table.Count; i++)
{
LootEntry entry = table[i];
if (!IsRollableEntry(entry, pondCleaned))
continue;
current += entry.weight;
if (roll <= current)
return entry;
}
return null;
}
private bool IsRollableEntry(LootEntry entry, bool pondCleaned)
{
if (entry == null)
return false;
if (entry.weight <= 0f)
return false;
if (!pondCleaned && entry.itemType == FishingItemType.MemoryPiece)
return false;
return entry.itemType != FishingItemType.None;
}
private CatchResult CreateCatchResult(FishingItemType itemType, string displayName)
{
string finalDisplayName = string.IsNullOrWhiteSpace(displayName) ? GetDefaultDisplayName(itemType) : displayName;
bool countsAsCleanupItem = IsCleanupItem(itemType);
bool isMemoryPiece = itemType == FishingItemType.MemoryPiece;
if (showDebugLog)
Debug.Log($"낚시 획득: {finalDisplayName}");
return new CatchResult(itemType, finalDisplayName, countsAsCleanupItem, isMemoryPiece);
}
public bool IsCleanupItem(FishingItemType itemType)
{
return itemType == FishingItemType.Trash ||
itemType == FishingItemType.Bottle ||
itemType == FishingItemType.PlasticBag;
}
public string GetDefaultDisplayName(FishingItemType itemType)
{
switch (itemType)
{
case FishingItemType.Fish:
return "생선";
case FishingItemType.RottenFish:
return "상한 생선";
case FishingItemType.Trash:
return "쓰레기";
case FishingItemType.Bottle:
return "병";
case FishingItemType.PlasticBag:
return "봉지";
case FishingItemType.JellyfishPowder:
return "해파리 가루";
case FishingItemType.OldCompass:
return "낡은 나침반";
case FishingItemType.MemoryPiece:
return "기억의 조각";
case FishingItemType.None:
default:
return "없음";
}
}
}

View File

@@ -0,0 +1,37 @@
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 ? "낚싯대 잡음" : "낚싯대 놓음");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 19b938e652163b1459e2a5d8d862510b

View File

@@ -0,0 +1,244 @@
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);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 861cf89cbda384e4b9bf8207fcb6e487

View File

@@ -0,0 +1,361 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class FishingUIEffects : MonoBehaviour
{
private readonly Dictionary<Object, Coroutine> runningEffects = new Dictionary<Object, Coroutine>();
private readonly Dictionary<Transform, Vector3> originalScales = new Dictionary<Transform, Vector3>();
private readonly Dictionary<RectTransform, Vector2> originalAnchoredPositions = new Dictionary<RectTransform, Vector2>();
public CanvasGroup EnsureCanvasGroup(GameObject target)
{
if (target == null)
return null;
CanvasGroup group = target.GetComponent<CanvasGroup>();
if (group == null)
group = target.AddComponent<CanvasGroup>();
return group;
}
public void SetCanvasGroupInstant(GameObject target, CanvasGroup group, bool visible)
{
if (target == null && group == null)
return;
if (target != null)
target.SetActive(visible);
if (group != null)
{
group.alpha = visible ? 1f : 0f;
group.interactable = visible;
group.blocksRaycasts = visible;
}
}
public void FadeCanvasGroup(GameObject target, CanvasGroup group, bool visible, float duration)
{
if (target == null && group == null)
return;
if (group == null && target != null)
group = EnsureCanvasGroup(target);
if (group == null)
{
if (target != null)
target.SetActive(visible);
return;
}
StopManaged(group);
runningEffects[group] = StartCoroutine(FadeCanvasGroupRoutine(target, group, visible, Mathf.Max(0f, duration)));
}
private IEnumerator FadeCanvasGroupRoutine(GameObject target, CanvasGroup group, bool visible, float duration)
{
if (target != null && visible)
target.SetActive(true);
group.interactable = visible;
group.blocksRaycasts = visible;
float startAlpha = group.alpha;
float endAlpha = visible ? 1f : 0f;
if (duration <= 0f)
{
group.alpha = endAlpha;
}
else
{
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / duration);
t = EaseOutCubic(t);
group.alpha = Mathf.Lerp(startAlpha, endAlpha, t);
yield return null;
}
group.alpha = endAlpha;
}
if (target != null && !visible)
target.SetActive(false);
runningEffects.Remove(group);
}
public void Pop(Transform target, float peakScale = 1.12f, float duration = 0.18f)
{
if (target == null)
return;
StopManaged(target);
runningEffects[target] = StartCoroutine(PopRoutine(target, Mathf.Max(1f, peakScale), Mathf.Max(0.01f, duration)));
}
private IEnumerator PopRoutine(Transform target, float peakScale, float duration)
{
if (!originalScales.TryGetValue(target, out Vector3 originalScale))
{
originalScale = target.localScale;
originalScales[target] = originalScale;
}
float half = duration * 0.5f;
float timer = 0f;
while (timer < half)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / half);
target.localScale = Vector3.Lerp(originalScale, originalScale * peakScale, EaseOutCubic(t));
yield return null;
}
timer = 0f;
Vector3 peak = originalScale * peakScale;
while (timer < half)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / half);
target.localScale = Vector3.Lerp(peak, originalScale, EaseOutCubic(t));
yield return null;
}
target.localScale = originalScale;
runningEffects.Remove(target);
}
public void Shake(RectTransform target, float strength = 12f, float duration = 0.18f)
{
if (target == null)
return;
StopManaged(target);
runningEffects[target] = StartCoroutine(ShakeRoutine(target, Mathf.Max(0f, strength), Mathf.Max(0.01f, duration)));
}
private IEnumerator ShakeRoutine(RectTransform target, float strength, float duration)
{
if (!originalAnchoredPositions.TryGetValue(target, out Vector2 originalPosition))
{
originalPosition = target.anchoredPosition;
originalAnchoredPositions[target] = originalPosition;
}
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / duration);
float power = Mathf.Lerp(strength, 0f, t);
Vector2 offset = UnityEngine.Random.insideUnitCircle * power;
target.anchoredPosition = originalPosition + offset;
yield return null;
}
target.anchoredPosition = originalPosition;
runningEffects.Remove(target);
}
public void Flash(Image image, Color color, float maxAlpha = 0.28f, float duration = 0.16f)
{
if (image == null)
return;
StopManaged(image);
runningEffects[image] = StartCoroutine(FlashRoutine(image, color, Mathf.Clamp01(maxAlpha), Mathf.Max(0.01f, duration)));
}
private IEnumerator FlashRoutine(Image image, Color color, float maxAlpha, float duration)
{
image.gameObject.SetActive(true);
image.raycastTarget = false;
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / duration);
Color current = color;
current.a = Mathf.Lerp(maxAlpha, 0f, EaseOutCubic(t));
image.color = current;
yield return null;
}
Color clear = color;
clear.a = 0f;
image.color = clear;
image.gameObject.SetActive(false);
runningEffects.Remove(image);
}
public void FillImage(Image image, float targetFill, float duration)
{
if (image == null)
return;
targetFill = Mathf.Clamp01(targetFill);
StopManaged(image);
runningEffects[image] = StartCoroutine(FillImageRoutine(image, targetFill, Mathf.Max(0f, duration)));
}
private IEnumerator FillImageRoutine(Image image, float targetFill, float duration)
{
float startFill = image.fillAmount;
if (duration <= 0f)
{
image.fillAmount = targetFill;
}
else
{
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / duration);
image.fillAmount = Mathf.Lerp(startFill, targetFill, EaseOutCubic(t));
yield return null;
}
image.fillAmount = targetFill;
}
runningEffects.Remove(image);
}
public void HighlightSlot(Image background, Image icon, Transform slotRoot, Color highlightColor, float duration, float popScale)
{
Object key = background != null ? background : icon != null ? icon : slotRoot;
if (key == null)
return;
StopManaged(key);
runningEffects[key] = StartCoroutine(HighlightSlotRoutine(background, icon, slotRoot, highlightColor, Mathf.Max(0.01f, duration), Mathf.Max(1f, popScale), key));
}
private IEnumerator HighlightSlotRoutine(Image background, Image icon, Transform slotRoot, Color highlightColor, float duration, float popScale, Object key)
{
Color? originalBackground = null;
Color? originalIcon = null;
Vector3 originalScale = Vector3.one;
if (background != null)
{
originalBackground = background.color;
Color c = Color.Lerp(background.color, highlightColor, 0.65f);
c.a = 1f;
background.color = c;
}
if (icon != null)
{
originalIcon = icon.color;
Color c = icon.color;
c.a = 1f;
icon.color = c;
}
if (slotRoot != null)
{
if (!originalScales.TryGetValue(slotRoot, out originalScale))
{
originalScale = slotRoot.localScale;
originalScales[slotRoot] = originalScale;
}
}
float half = duration * 0.5f;
float timer = 0f;
while (timer < half)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / half);
if (slotRoot != null)
slotRoot.localScale = Vector3.Lerp(originalScale, originalScale * popScale, EaseOutCubic(t));
yield return null;
}
timer = 0f;
Vector3 peak = originalScale * popScale;
while (timer < half)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / half);
if (slotRoot != null)
slotRoot.localScale = Vector3.Lerp(peak, originalScale, EaseOutCubic(t));
if (background != null && originalBackground.HasValue)
background.color = Color.Lerp(background.color, originalBackground.Value, t);
if (icon != null && originalIcon.HasValue)
icon.color = Color.Lerp(icon.color, originalIcon.Value, t);
yield return null;
}
if (slotRoot != null)
slotRoot.localScale = originalScale;
if (background != null && originalBackground.HasValue)
background.color = originalBackground.Value;
if (icon != null && originalIcon.HasValue)
icon.color = originalIcon.Value;
runningEffects.Remove(key);
}
public void StopAllEffects()
{
foreach (Coroutine coroutine in runningEffects.Values)
{
if (coroutine != null)
StopCoroutine(coroutine);
}
runningEffects.Clear();
foreach (KeyValuePair<Transform, Vector3> pair in originalScales)
{
if (pair.Key != null)
pair.Key.localScale = pair.Value;
}
foreach (KeyValuePair<RectTransform, Vector2> pair in originalAnchoredPositions)
{
if (pair.Key != null)
pair.Key.anchoredPosition = pair.Value;
}
}
private void StopManaged(Object key)
{
if (key == null)
return;
if (runningEffects.TryGetValue(key, out Coroutine coroutine) && coroutine != null)
StopCoroutine(coroutine);
runningEffects.Remove(key);
}
private float EaseOutCubic(float t)
{
t = Mathf.Clamp01(t);
t = 1f - Mathf.Pow(1f - t, 3f);
return t;
}
}

View File

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

View File

@@ -5,15 +5,22 @@ public class RotateUI : MonoBehaviour
[Header("Rotation Settings")]
[SerializeField] private float rotateSpeed = 30f;
[SerializeField] private bool clockwise = true;
[SerializeField] private bool playOnStart = true;
[SerializeField] private bool playOnStart = false;
private bool isRotating;
public bool IsRotating => isRotating;
private void Awake()
{
isRotating = playOnStart;
}
private void OnValidate()
{
rotateSpeed = Mathf.Max(0f, rotateSpeed);
}
private void Update()
{
if (!isRotating)
@@ -39,8 +46,13 @@ public void SetClockwise(bool value)
clockwise = value;
}
public void SetRotateSpeed(float value)
{
rotateSpeed = Mathf.Max(0f, value);
}
public void ResetRotation()
{
transform.localEulerAngles = Vector3.zero;
}
}
}

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: e36e14e60fc40cf41bfd445f80e62535
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 9b1669e795bdb7b4c88e4376a98347d9
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: d57aa12e9412ea74d844438588adc0f5
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: a9e1c44292c7bdb4da8926e8fc9addd2
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: e76bfcda04825f043b3f6c1e6cef400b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 6365c45c31605c745bf1a33e3a1e0791
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 5d0cced1099954d478056e93dd67e0b4
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: b20f3401dfb723541b090e4f3bcf5901
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Binary file not shown.

View File

@@ -40,7 +40,7 @@ Material:
- _ZCLIP_ON
- _ZWRITE_ON
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_EnableInstancingVariants: 1
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}

View File

@@ -38,7 +38,7 @@ Material:
- _WORLDSPACEUV_ON
- _ZCLIP_ON
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_EnableInstancingVariants: 1
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
@@ -185,6 +185,7 @@ Material:
- _FoamBubblesSpread: 1
- _FoamBubblesStrength: 0.217
- _FoamClipping: 0.5
- _FoamClippingDynamic: 0
- _FoamDistanceOn: 1
- _FoamDistortion: 0.34
- _FoamOn: 1
@@ -238,6 +239,7 @@ Material:
- _PlanarReflectionsParams: 0
- _PointSpotLightReflectionDistortion: 0.5
- _PointSpotLightReflectionExp: 64
- _PointSpotLightReflectionSharp: 0
- _PointSpotLightReflectionSize: 0
- _PointSpotLightReflectionStrength: 10
- _QueueOffset: 0
@@ -282,6 +284,7 @@ Material:
- _SrcBlend: 1
- _SunReflectionDistortion: 0.49
- _SunReflectionPerturbance: 1
- _SunReflectionSharp: 0
- _SunReflectionSize: 0.122
- _SunReflectionStrength: 0
- _Surface: 0
@@ -295,6 +298,7 @@ Material:
- _TranslucencyReflectionMask: 0
- _TranslucencyStrength: 1
- _TranslucencyStrengthDirect: 0.05
- _UnderwaterReflectionStrength: 0.5
- _UnderwaterRefractionOffset: 0.2
- _UnderwaterSurfaceSmoothness: 0.8
- _VertexColorFoam: 0