using System.Collections; using System.Collections.Generic; using UnityEngine; public class FootprintHintManager : MonoBehaviour { [Header("VR Player Reference")] [Tooltip("XR OriginÀÇ Main Camera TransformÀ» ³Ö´Â °ÍÀ» ÃßõÇÕ´Ï´Ù.")] [SerializeField] private Transform playerTarget; [Header("Footprint Prefabs")] [SerializeField] private GameObject leftFootPrefab; [SerializeField] private GameObject rightFootPrefab; [Header("Footprint Settings")] [SerializeField] private int footprintCount = 6; [SerializeField] private float spacing = 0.45f; [SerializeField] private float sideOffset = 0.13f; [SerializeField] private float showTime = 2f; [SerializeField] private bool startWithLeftFoot = true; [Header("Ground Settings")] [SerializeField] private bool useGroundRaycast = true; [SerializeField] private LayerMask groundMask = ~0; [SerializeField] private float raycastHeight = 2f; [SerializeField] private float raycastDistance = 5f; [SerializeField] private float groundOffset = 0.03f; [SerializeField] private float fallbackGroundY = 0f; private readonly List spawnedFootprints = new List(); private Coroutine footprintRoutine; public void ShowFootprints(MazeDirection direction) { if (footprintRoutine != null) StopCoroutine(footprintRoutine); footprintRoutine = StartCoroutine(ShowFootprintRoutine(direction)); } private IEnumerator ShowFootprintRoutine(MazeDirection direction) { ClearFootprints(); if (playerTarget == null) { Debug.LogWarning("FootprintHintManager: Player Target is missing."); footprintRoutine = null; yield break; } if (leftFootPrefab == null || rightFootPrefab == null) { Debug.LogWarning("FootprintHintManager: Left Foot Prefab or Right Foot Prefab is missing."); footprintRoutine = null; yield break; } Vector3 moveDirection = GetWorldDirection(direction); if (moveDirection == Vector3.zero) { footprintRoutine = null; yield break; } Vector3 sideDirection = Vector3.Cross(Vector3.up, moveDirection).normalized; for (int i = 0; i < footprintCount; i++) { bool isLeftFoot = startWithLeftFoot ? i % 2 == 0 : i % 2 != 0; GameObject prefabToUse = isLeftFoot ? leftFootPrefab : rightFootPrefab; float forwardDistance = spacing * (i + 1); float leftRightOffset = isLeftFoot ? -sideOffset : sideOffset; Vector3 spawnPosition = playerTarget.position + moveDirection * forwardDistance + sideDirection * leftRightOffset; Quaternion spawnRotation = Quaternion.LookRotation(moveDirection, Vector3.up); PlaceOnGround(ref spawnPosition, ref spawnRotation, moveDirection); GameObject footprint = Instantiate( prefabToUse, spawnPosition, spawnRotation ); spawnedFootprints.Add(footprint); } yield return new WaitForSeconds(showTime); ClearFootprints(); footprintRoutine = null; } private Vector3 GetWorldDirection(MazeDirection direction) { Vector3 forward = playerTarget.forward; Vector3 right = playerTarget.right; forward.y = 0f; right.y = 0f; forward.Normalize(); right.Normalize(); switch (direction) { case MazeDirection.Forward: return forward; case MazeDirection.Backward: return -forward; case MazeDirection.Left: return -right; case MazeDirection.Right: return right; case MazeDirection.None: default: return Vector3.zero; } } private void PlaceOnGround( ref Vector3 position, ref Quaternion rotation, Vector3 moveDirection ) { if (!useGroundRaycast) { position.y = fallbackGroundY + groundOffset; return; } Vector3 rayStart = position + Vector3.up * raycastHeight; if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, raycastDistance, groundMask)) { position = hit.point + hit.normal * groundOffset; Vector3 projectedForward = Vector3.ProjectOnPlane(moveDirection, hit.normal).normalized; if (projectedForward.sqrMagnitude > 0.001f) rotation = Quaternion.LookRotation(projectedForward, hit.normal); } else { position.y = fallbackGroundY + groundOffset; } } public void ClearFootprints() { foreach (GameObject footprint in spawnedFootprints) { if (footprint != null) Destroy(footprint); } spawnedFootprints.Clear(); } }