Files
WhaleAdventure_VR/Assets/My project/shell Scripts/UI/UIManager.cs
skrwns304@gmail.com b1e85a5b89 2026-06-19 UI, UI로직
2026-06-19 14:27:40 +09:00

338 lines
9.2 KiB
C#

using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
[Header("Follow Camera UI")]
[SerializeField] private bool followCamera = true;
[Tooltip("XR Origin > Camera Offset > Main Camera를 넣으세요.")]
[SerializeField] private Transform cameraTarget;
[Tooltip("따라다닐 UI 전체 Root입니다. 비워두면 이 UIManager가 붙은 오브젝트를 움직입니다.")]
[SerializeField] private Transform followRoot;
[Tooltip("카메라 앞 몇 m 위치에 UI를 둘지 정합니다.")]
[SerializeField] private float followDistance = 1.3f;
[Tooltip("카메라 높이 기준 위/아래 위치 보정값입니다. 양수면 위로, 음수면 아래로 갑니다.")]
[SerializeField] private float heightOffset = 0.1f;
[Tooltip("카메라 기준 좌우 위치 보정값입니다.")]
[SerializeField] private float sideOffset = 0f;
[Tooltip("켜면 UI가 부드럽게 따라옵니다.")]
[SerializeField] private bool smoothFollow = true;
[SerializeField] private float followSpeed = 8f;
[Tooltip("켜면 회전도 부드럽게 따라옵니다.")]
[SerializeField] private bool smoothRotation = true;
[SerializeField] private float rotationSpeed = 8f;
[Tooltip("켜면 카메라의 위아래 기울기는 무시하고 좌우 방향만 따라갑니다.")]
[SerializeField] private bool useFlatForward = true;
[Tooltip("글자가 뒤집혀 보이면 켜세요.")]
[SerializeField] private bool flipRotation = false;
[Header("Timing UI")]
[SerializeField] private Image timingBarFill;
[Header("Control UI")]
[SerializeField] private TextMeshProUGUI controlText;
[Header("Prompt UI")]
[Tooltip("CompletePromptText 또는 CollectPromptText를 연결하세요.")]
[SerializeField] private TextMeshProUGUI promptText;
[Header("Shell UI")]
[SerializeField] private TextMeshProUGUI shellStateText;
[Header("Movement Arrows")]
[SerializeField] private Image upArrow;
[SerializeField] private Image downArrow;
[SerializeField] private Image leftArrow;
[SerializeField] private Image rightArrow;
[SerializeField] private Color activeColor = Color.white;
[SerializeField] private Color inactiveColor = new Color(1f, 1f, 1f, 0.2f);
[Header("Reward Panel")]
[SerializeField] private GameObject rewardPanel;
[SerializeField] private TextMeshProUGUI rewardText;
[SerializeField] private TextMeshProUGUI resultSubText;
[Header("Text Colors")]
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private Color reversedColor = new Color(1f, 0.25f, 0.25f);
[SerializeField] private Color openColor = new Color(0.3f, 1f, 0.65f);
[SerializeField] private Color closedColor = new Color(1f, 0.25f, 0.25f);
private void Awake()
{
if (followRoot == null)
followRoot = transform;
if (cameraTarget == null && Camera.main != null)
cameraTarget = Camera.main.transform;
HideReward();
ResetArrows();
SetControl(true);
SetShellState(false);
SetShellTimer(0f, false);
SetPromptDefault();
}
private void LateUpdate()
{
FollowCamera();
}
public void SetControl(bool reversed)
{
if (controlText == null)
return;
if (reversed)
{
controlText.text = "조작 반전 중";
controlText.color = reversedColor;
}
else
{
controlText.text = "일반 조작";
controlText.color = normalColor;
}
}
public void SetPromptDefault()
{
if (promptText == null)
return;
promptText.text = "조개가 열렸을 때 트리거를 누르세요";
promptText.color = normalColor;
}
public void SetPromptFarFromShell()
{
if (promptText == null)
return;
promptText.text = "조개 가까이 이동하세요";
promptText.color = normalColor;
}
public void SetPromptNearShell(bool isOpen)
{
if (promptText == null)
return;
if (isOpen)
{
promptText.text = "지금 트리거를 누르세요!";
promptText.color = openColor;
}
else
{
promptText.text = "조개가 열릴 때까지 기다리세요";
promptText.color = closedColor;
}
}
public void SetShellTimer(float value01, bool isOpen)
{
if (timingBarFill == null)
return;
timingBarFill.fillAmount = Mathf.Clamp01(value01);
timingBarFill.color = isOpen ? openColor : closedColor;
}
public void SetShellState(bool isOpen)
{
if (shellStateText == null)
return;
if (isOpen)
{
shellStateText.text = "조개 상태: 열림";
shellStateText.color = openColor;
}
else
{
shellStateText.text = "조개 상태: 닫힘";
shellStateText.color = closedColor;
}
}
public void SetMovementDebug(Vector2 input, Vector2 output)
{
UpdateArrows(output);
}
private void UpdateArrows(Vector2 output)
{
ResetArrows();
if (output.y > 0.1f && upArrow != null)
upArrow.color = activeColor;
if (output.y < -0.1f && downArrow != null)
downArrow.color = activeColor;
if (output.x < -0.1f && leftArrow != null)
leftArrow.color = activeColor;
if (output.x > 0.1f && rightArrow != null)
rightArrow.color = activeColor;
}
private void ResetArrows()
{
if (upArrow != null)
upArrow.color = inactiveColor;
if (downArrow != null)
downArrow.color = inactiveColor;
if (leftArrow != null)
leftArrow.color = inactiveColor;
if (rightArrow != null)
rightArrow.color = inactiveColor;
}
public void ShowReward(bool success)
{
if (rewardPanel != null)
{
rewardPanel.SetActive(true);
rewardPanel.transform.SetAsLastSibling();
}
else
{
Debug.LogWarning("[UIManager] RewardPanel이 연결되지 않았습니다.");
}
if (rewardText != null)
{
rewardText.gameObject.SetActive(true);
rewardText.text = success ? "성공!" : "실패!";
rewardText.color = success ? openColor : closedColor;
}
else
{
Debug.LogWarning("[UIManager] RewardText가 연결되지 않았습니다.");
}
if (resultSubText != null)
{
resultSubText.gameObject.SetActive(true);
resultSubText.text = success ? "기억 조각을 획득했습니다" : "조개가 닫혀 있었습니다";
resultSubText.color = normalColor;
}
else
{
Debug.LogWarning("[UIManager] ResultSubText가 연결되지 않았습니다.");
}
}
public void HideReward()
{
if (rewardPanel != null)
rewardPanel.SetActive(false);
}
private void FollowCamera()
{
if (!followCamera)
return;
if (followRoot == null)
return;
if (cameraTarget == null)
{
if (Camera.main != null)
cameraTarget = Camera.main.transform;
else
return;
}
Vector3 forward = cameraTarget.forward;
Vector3 right = cameraTarget.right;
if (useFlatForward)
{
forward.y = 0f;
right.y = 0f;
if (forward.sqrMagnitude < 0.001f)
{
forward = cameraTarget.parent != null
? cameraTarget.parent.forward
: Vector3.forward;
forward.y = 0f;
}
if (right.sqrMagnitude < 0.001f)
{
right = cameraTarget.parent != null
? cameraTarget.parent.right
: Vector3.right;
right.y = 0f;
}
}
forward.Normalize();
right.Normalize();
Vector3 targetPosition =
cameraTarget.position +
forward * followDistance +
right * sideOffset;
targetPosition.y = cameraTarget.position.y + heightOffset;
Quaternion targetRotation = Quaternion.LookRotation(forward, Vector3.up);
if (flipRotation)
{
targetRotation *= Quaternion.Euler(0f, 180f, 0f);
}
if (smoothFollow)
{
followRoot.position = Vector3.Lerp(
followRoot.position,
targetPosition,
Time.deltaTime * followSpeed
);
}
else
{
followRoot.position = targetPosition;
}
if (smoothRotation)
{
followRoot.rotation = Quaternion.Slerp(
followRoot.rotation,
targetRotation,
Time.deltaTime * rotationSpeed
);
}
else
{
followRoot.rotation = targetRotation;
}
}
}