2026-06-19 UI, UI로직
This commit is contained in:
358
Assets/My project/Fishing Scripts/UI/FishingGameManager.cs
Normal file
358
Assets/My project/Fishing Scripts/UI/FishingGameManager.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public class FishingGameManager : MonoBehaviour
|
||||
{
|
||||
public enum ResultType
|
||||
{
|
||||
Perfect,
|
||||
Good,
|
||||
Miss
|
||||
}
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private FishingGaugeUI ui;
|
||||
[SerializeField] private FishingRewardSystem rewardSystem;
|
||||
[SerializeField] private FishingHapticManager haptic;
|
||||
[SerializeField] private RotateUI centerIconRotate;
|
||||
|
||||
[Header("Pointer")]
|
||||
[SerializeField] private float pointerAngle;
|
||||
[SerializeField] private float pointerSpeed = 180f;
|
||||
[SerializeField] private float minSpeed = 180f;
|
||||
[SerializeField] private float maxSpeed = 450f;
|
||||
|
||||
[Header("Zone")]
|
||||
[SerializeField] private float startZoneSize = 90f;
|
||||
[SerializeField] private float zoneSize = 90f;
|
||||
[SerializeField] private float minZoneSize = 30f;
|
||||
[SerializeField] private float maxZoneSize = 120f;
|
||||
[SerializeField] private float zoneCenter;
|
||||
|
||||
[Header("Rules")]
|
||||
[SerializeField] private int requiredSuccesses = 3;
|
||||
[SerializeField] private int allowedFails = 3;
|
||||
|
||||
[Header("Round Settings")]
|
||||
[SerializeField] private float nextRoundDelay = 0.35f;
|
||||
[SerializeField] private bool randomDirectionEachRound = true;
|
||||
[SerializeField] private bool startOnAwake = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private int successCount;
|
||||
private int failCount;
|
||||
|
||||
private bool clockwise = true;
|
||||
private bool activeGame;
|
||||
private bool inputLocked;
|
||||
|
||||
private Coroutine nextRoundRoutine;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (startOnAwake)
|
||||
{
|
||||
StartFishing();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeIdleUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!activeGame)
|
||||
return;
|
||||
|
||||
if (inputLocked)
|
||||
return;
|
||||
|
||||
RotatePointer();
|
||||
}
|
||||
|
||||
private void InitializeIdleUI()
|
||||
{
|
||||
if (ui != null)
|
||||
{
|
||||
ui.HideRoundResult();
|
||||
ui.HideFinalResult();
|
||||
ui.UpdateCounter(0, requiredSuccesses, 0, allowedFails);
|
||||
ui.SetPointerRotation(0f);
|
||||
ui.SetZone(0f, startZoneSize);
|
||||
}
|
||||
|
||||
if (centerIconRotate != null)
|
||||
centerIconRotate.StopRotate();
|
||||
}
|
||||
|
||||
public void StartFishing()
|
||||
{
|
||||
if (nextRoundRoutine != null)
|
||||
{
|
||||
StopCoroutine(nextRoundRoutine);
|
||||
nextRoundRoutine = null;
|
||||
}
|
||||
|
||||
successCount = 0;
|
||||
failCount = 0;
|
||||
|
||||
pointerAngle = 0f;
|
||||
pointerSpeed = minSpeed;
|
||||
zoneSize = startZoneSize;
|
||||
|
||||
activeGame = true;
|
||||
inputLocked = false;
|
||||
|
||||
clockwise = Random.value > 0.5f;
|
||||
|
||||
if (ui != null)
|
||||
{
|
||||
ui.HideRoundResult();
|
||||
ui.HideFinalResult();
|
||||
}
|
||||
|
||||
if (centerIconRotate != null)
|
||||
{
|
||||
centerIconRotate.ResetRotation();
|
||||
centerIconRotate.StartRotate();
|
||||
}
|
||||
|
||||
RandomizeZone();
|
||||
UpdateUI();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 시작");
|
||||
}
|
||||
|
||||
private void RotatePointer()
|
||||
{
|
||||
float dir = clockwise ? 1f : -1f;
|
||||
|
||||
pointerAngle += dir * pointerSpeed * Time.deltaTime;
|
||||
pointerAngle = Normalize(pointerAngle);
|
||||
|
||||
if (ui != null)
|
||||
ui.SetPointerRotation(pointerAngle);
|
||||
}
|
||||
|
||||
public void SubmitAttempt()
|
||||
{
|
||||
if (!activeGame)
|
||||
return;
|
||||
|
||||
if (inputLocked)
|
||||
return;
|
||||
|
||||
inputLocked = true;
|
||||
|
||||
ResultType result = EvaluateResult();
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case ResultType.Perfect:
|
||||
OnPerfect();
|
||||
break;
|
||||
|
||||
case ResultType.Good:
|
||||
OnGood();
|
||||
break;
|
||||
|
||||
case ResultType.Miss:
|
||||
OnMiss();
|
||||
break;
|
||||
}
|
||||
|
||||
if (successCount >= requiredSuccesses)
|
||||
{
|
||||
FishingSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
if (failCount >= allowedFails)
|
||||
{
|
||||
FishingFail();
|
||||
return;
|
||||
}
|
||||
|
||||
nextRoundRoutine = StartCoroutine(NextRoundRoutine());
|
||||
}
|
||||
|
||||
private ResultType EvaluateResult()
|
||||
{
|
||||
float delta = Mathf.Abs(Mathf.DeltaAngle(pointerAngle, zoneCenter));
|
||||
|
||||
float perfectRange = zoneSize * 0.2f;
|
||||
float goodRange = zoneSize * 0.5f;
|
||||
|
||||
if (delta <= perfectRange)
|
||||
return ResultType.Perfect;
|
||||
|
||||
if (delta <= goodRange)
|
||||
return ResultType.Good;
|
||||
|
||||
return ResultType.Miss;
|
||||
}
|
||||
|
||||
private void OnPerfect()
|
||||
{
|
||||
successCount++;
|
||||
|
||||
zoneSize *= 0.9f;
|
||||
pointerSpeed *= 1.05f;
|
||||
|
||||
if (haptic != null)
|
||||
haptic.Perfect();
|
||||
|
||||
if (ui != null)
|
||||
ui.ShowResult("완벽!");
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 판정: Perfect");
|
||||
|
||||
ClampDifficulty();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void OnGood()
|
||||
{
|
||||
successCount++;
|
||||
|
||||
zoneSize *= 0.95f;
|
||||
pointerSpeed *= 1.02f;
|
||||
|
||||
if (haptic != null)
|
||||
haptic.Good();
|
||||
|
||||
if (ui != null)
|
||||
ui.ShowResult("성공!");
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 판정: Good");
|
||||
|
||||
ClampDifficulty();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void OnMiss()
|
||||
{
|
||||
failCount++;
|
||||
|
||||
zoneSize *= 1.05f;
|
||||
pointerSpeed *= 0.95f;
|
||||
|
||||
if (haptic != null)
|
||||
haptic.Miss();
|
||||
|
||||
if (ui != null)
|
||||
ui.ShowResult("실패!");
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 판정: Miss");
|
||||
|
||||
ClampDifficulty();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private IEnumerator NextRoundRoutine()
|
||||
{
|
||||
yield return new WaitForSeconds(nextRoundDelay);
|
||||
|
||||
if (!activeGame)
|
||||
yield break;
|
||||
|
||||
if (randomDirectionEachRound)
|
||||
clockwise = Random.value > 0.5f;
|
||||
|
||||
RandomizeZone();
|
||||
UpdateUI();
|
||||
|
||||
inputLocked = false;
|
||||
nextRoundRoutine = null;
|
||||
}
|
||||
|
||||
private void ClampDifficulty()
|
||||
{
|
||||
zoneSize = Mathf.Clamp(zoneSize, minZoneSize, maxZoneSize);
|
||||
pointerSpeed = Mathf.Clamp(pointerSpeed, minSpeed, maxSpeed);
|
||||
}
|
||||
|
||||
private void RandomizeZone()
|
||||
{
|
||||
zoneCenter = Random.Range(0f, 360f);
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
if (ui == null)
|
||||
return;
|
||||
|
||||
ui.SetZone(zoneCenter, zoneSize);
|
||||
ui.UpdateCounter(successCount, requiredSuccesses, failCount, allowedFails);
|
||||
}
|
||||
|
||||
private void FishingSuccess()
|
||||
{
|
||||
activeGame = false;
|
||||
inputLocked = true;
|
||||
|
||||
if (centerIconRotate != null)
|
||||
centerIconRotate.StopRotate();
|
||||
|
||||
if (ui != null)
|
||||
ui.ShowFinalResult("낚시 성공!");
|
||||
|
||||
if (rewardSystem != null)
|
||||
rewardSystem.GiveReward();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 최종 성공");
|
||||
}
|
||||
|
||||
private void FishingFail()
|
||||
{
|
||||
activeGame = false;
|
||||
inputLocked = true;
|
||||
|
||||
if (centerIconRotate != null)
|
||||
centerIconRotate.StopRotate();
|
||||
|
||||
if (ui != null)
|
||||
ui.ShowFinalResult("낚시 실패!");
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("낚시 최종 실패");
|
||||
}
|
||||
|
||||
public void ResetFishing()
|
||||
{
|
||||
StartFishing();
|
||||
}
|
||||
|
||||
private float Normalize(float angle)
|
||||
{
|
||||
angle %= 360f;
|
||||
|
||||
if (angle < 0f)
|
||||
angle += 360f;
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
// XR Input Action의 Select / Trigger 버튼에 연결
|
||||
public void OnSubmit(InputValue value)
|
||||
{
|
||||
if (value.isPressed)
|
||||
SubmitAttempt();
|
||||
}
|
||||
|
||||
// Reset 버튼에 연결 가능
|
||||
public void OnReset(InputValue value)
|
||||
{
|
||||
if (value.isPressed)
|
||||
ResetFishing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64a24b6b9056f714b9641eb630b4ed0f
|
||||
155
Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs
Normal file
155
Assets/My project/Fishing Scripts/UI/FishingGaugeUI.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class FishingGaugeUI : MonoBehaviour
|
||||
{
|
||||
[Header("Panel UI")]
|
||||
[SerializeField] private GameObject counterPanel;
|
||||
[SerializeField] private GameObject finalResultPanel;
|
||||
|
||||
[Header("Gauge UI")]
|
||||
[SerializeField] private RectTransform pointerPivot;
|
||||
[SerializeField] private Image successZone;
|
||||
|
||||
[Header("Text UI")]
|
||||
[SerializeField] private TMP_Text successText;
|
||||
[SerializeField] private TMP_Text failText;
|
||||
[SerializeField] private TMP_Text resultText;
|
||||
[SerializeField] private TMP_Text finalResultText;
|
||||
|
||||
[Header("Zone Visual Correction")]
|
||||
[Tooltip("성공 구간 이미지가 판정 위치와 어긋나면 이 값을 조정하세요.")]
|
||||
[SerializeField] private float zoneVisualOffset = 0f;
|
||||
|
||||
[Header("Result Settings")]
|
||||
[SerializeField] private float resultShowTime = 1f;
|
||||
[SerializeField] private bool hideCounterOnFinalResult = true;
|
||||
|
||||
private Coroutine resultRoutine;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
HideRoundResult();
|
||||
HideFinalResult();
|
||||
ShowCounter();
|
||||
}
|
||||
|
||||
public void SetPointerRotation(float angle)
|
||||
{
|
||||
if (pointerPivot == null)
|
||||
return;
|
||||
|
||||
pointerPivot.localEulerAngles = new Vector3(0f, 0f, -angle);
|
||||
}
|
||||
|
||||
public void SetZone(float centerAngle, float size)
|
||||
{
|
||||
SetZoneSize(size);
|
||||
SetZoneRotation(centerAngle, size);
|
||||
}
|
||||
|
||||
public void SetZoneSize(float size)
|
||||
{
|
||||
if (successZone == null)
|
||||
return;
|
||||
|
||||
successZone.fillAmount = Mathf.Clamp01(size / 360f);
|
||||
}
|
||||
|
||||
public void SetZoneRotation(float centerAngle, float size)
|
||||
{
|
||||
if (successZone == null)
|
||||
return;
|
||||
|
||||
float startAngle = centerAngle - size * 0.5f + zoneVisualOffset;
|
||||
successZone.rectTransform.localEulerAngles =
|
||||
new Vector3(0f, 0f, -startAngle);
|
||||
}
|
||||
|
||||
public void UpdateCounter(int success, int successTarget, int fail, int failTarget)
|
||||
{
|
||||
ShowCounter();
|
||||
|
||||
if (successText != null)
|
||||
successText.text = $"성공 {success}/{successTarget}";
|
||||
|
||||
if (failText != null)
|
||||
failText.text = $"실패 {fail}/{failTarget}";
|
||||
}
|
||||
|
||||
public void ShowCounter()
|
||||
{
|
||||
if (counterPanel != null)
|
||||
counterPanel.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideCounter()
|
||||
{
|
||||
if (counterPanel != null)
|
||||
counterPanel.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowResult(string text)
|
||||
{
|
||||
if (resultText == null)
|
||||
return;
|
||||
|
||||
if (resultRoutine != null)
|
||||
StopCoroutine(resultRoutine);
|
||||
|
||||
resultRoutine = StartCoroutine(ResultRoutine(text));
|
||||
}
|
||||
|
||||
private IEnumerator ResultRoutine(string text)
|
||||
{
|
||||
resultText.gameObject.SetActive(true);
|
||||
resultText.text = text;
|
||||
|
||||
yield return new WaitForSeconds(resultShowTime);
|
||||
|
||||
resultText.gameObject.SetActive(false);
|
||||
resultRoutine = null;
|
||||
}
|
||||
|
||||
public void HideRoundResult()
|
||||
{
|
||||
if (resultRoutine != null)
|
||||
{
|
||||
StopCoroutine(resultRoutine);
|
||||
resultRoutine = null;
|
||||
}
|
||||
|
||||
if (resultText != null)
|
||||
resultText.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowFinalResult(string text)
|
||||
{
|
||||
HideRoundResult();
|
||||
|
||||
if (hideCounterOnFinalResult)
|
||||
HideCounter();
|
||||
|
||||
if (finalResultPanel != null)
|
||||
finalResultPanel.SetActive(true);
|
||||
|
||||
if (finalResultText != null)
|
||||
{
|
||||
finalResultText.gameObject.SetActive(true);
|
||||
finalResultText.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
public void HideFinalResult()
|
||||
{
|
||||
if (finalResultPanel != null)
|
||||
finalResultPanel.SetActive(false);
|
||||
|
||||
if (finalResultText != null)
|
||||
finalResultText.gameObject.SetActive(false);
|
||||
|
||||
ShowCounter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17b5efd201ace6b4c9a0af3b6c9b6c2b
|
||||
54
Assets/My project/Fishing Scripts/UI/FishingHapticManager.cs
Normal file
54
Assets/My project/Fishing Scripts/UI/FishingHapticManager.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR;
|
||||
|
||||
public class FishingHapticManager : MonoBehaviour
|
||||
{
|
||||
[Header("Haptic Target")]
|
||||
[SerializeField] private XRNode targetHand = XRNode.RightHand;
|
||||
|
||||
[Header("Haptic Settings")]
|
||||
[SerializeField] private bool useHaptic = true;
|
||||
|
||||
[SerializeField] private float perfectAmplitude = 1f;
|
||||
[SerializeField] private float perfectDuration = 0.2f;
|
||||
|
||||
[SerializeField] private float goodAmplitude = 0.5f;
|
||||
[SerializeField] private float goodDuration = 0.1f;
|
||||
|
||||
[SerializeField] private float missAmplitude = 0.2f;
|
||||
[SerializeField] private float missDuration = 0.05f;
|
||||
|
||||
private void SendHaptic(float amplitude, float duration)
|
||||
{
|
||||
if (!useHaptic)
|
||||
return;
|
||||
|
||||
InputDevice device = InputDevices.GetDeviceAtXRNode(targetHand);
|
||||
|
||||
if (!device.isValid)
|
||||
return;
|
||||
|
||||
if (device.TryGetHapticCapabilities(out HapticCapabilities capabilities))
|
||||
{
|
||||
if (!capabilities.supportsImpulse)
|
||||
return;
|
||||
}
|
||||
|
||||
device.SendHapticImpulse(0u, Mathf.Clamp01(amplitude), duration);
|
||||
}
|
||||
|
||||
public void Perfect()
|
||||
{
|
||||
SendHaptic(perfectAmplitude, perfectDuration);
|
||||
}
|
||||
|
||||
public void Good()
|
||||
{
|
||||
SendHaptic(goodAmplitude, goodDuration);
|
||||
}
|
||||
|
||||
public void Miss()
|
||||
{
|
||||
SendHaptic(missAmplitude, missDuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4126d14d0a225c4b83981d97a52f63d
|
||||
53
Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs
Normal file
53
Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class FishingRewardSystem : MonoBehaviour
|
||||
{
|
||||
public enum RewardType
|
||||
{
|
||||
Normal,
|
||||
Rare
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
if (Random.value < rareChance / 100f)
|
||||
return GiveRareMemory();
|
||||
|
||||
return GiveNormalMemory();
|
||||
}
|
||||
|
||||
private RewardType GiveNormalMemory()
|
||||
{
|
||||
normalMemory++;
|
||||
|
||||
Debug.Log("ÀÏ¹Ý ±â¾ï +1");
|
||||
|
||||
return RewardType.Normal;
|
||||
}
|
||||
|
||||
private RewardType GiveRareMemory()
|
||||
{
|
||||
rareMemory++;
|
||||
|
||||
Debug.Log("Èñ±Í ±â¾ï +1");
|
||||
|
||||
return RewardType.Rare;
|
||||
}
|
||||
|
||||
public void ResetRewardCount()
|
||||
{
|
||||
normalMemory = 0;
|
||||
rareMemory = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 924b1cf99b7e65c4cbeef57d4570a1d4
|
||||
46
Assets/My project/Fishing Scripts/UI/RotateUI.cs
Normal file
46
Assets/My project/Fishing Scripts/UI/RotateUI.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RotateUI : MonoBehaviour
|
||||
{
|
||||
[Header("Rotation Settings")]
|
||||
[SerializeField] private float rotateSpeed = 30f;
|
||||
[SerializeField] private bool clockwise = true;
|
||||
[SerializeField] private bool playOnStart = true;
|
||||
|
||||
private bool isRotating;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
isRotating = playOnStart;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isRotating)
|
||||
return;
|
||||
|
||||
float dir = clockwise ? -1f : 1f;
|
||||
|
||||
transform.Rotate(0f, 0f, dir * rotateSpeed * Time.deltaTime, Space.Self);
|
||||
}
|
||||
|
||||
public void StartRotate()
|
||||
{
|
||||
isRotating = true;
|
||||
}
|
||||
|
||||
public void StopRotate()
|
||||
{
|
||||
isRotating = false;
|
||||
}
|
||||
|
||||
public void SetClockwise(bool value)
|
||||
{
|
||||
clockwise = value;
|
||||
}
|
||||
|
||||
public void ResetRotation()
|
||||
{
|
||||
transform.localEulerAngles = Vector3.zero;
|
||||
}
|
||||
}
|
||||
2
Assets/My project/Fishing Scripts/UI/RotateUI.cs.meta
Normal file
2
Assets/My project/Fishing Scripts/UI/RotateUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d504793270ff7941b29b9861f3e3944
|
||||
Reference in New Issue
Block a user