338 lines
9.2 KiB
C#
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;
|
|
}
|
|
}
|
|
} |