using UnityEngine; /// /// Minimal Rigidbody-based third-person controller for the lab prototype. /// Uses the classic Unity input axes, so it works in a default 3D template. /// [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(CapsuleCollider))] public class SimplePlayerController : MonoBehaviour { public float moveSpeed = 6f; public float jumpForce = 7f; public float groundCheckRadius = 0.25f; public LayerMask groundMask = ~0; public float fallResetY = -10f; public Transform respawnPoint; public Vector3 lastSafePosition; private Rigidbody rb; private CapsuleCollider capsuleCollider; private Collider[] ownColliders; private bool isGrounded; private void Awake() { rb = GetComponent(); capsuleCollider = GetComponent(); ownColliders = GetComponentsInChildren(); rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; lastSafePosition = transform.position; } private void Update() { UpdateGroundedState(); if (Input.GetKeyDown(KeyCode.Space) && isGrounded && rb.velocity.y <= 0.1f) { Vector3 velocity = rb.velocity; velocity.y = 0f; rb.velocity = velocity; rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); } if (transform.position.y < fallResetY) { ResetToLastSafePosition(); } } private void FixedUpdate() { float horizontal = Input.GetAxisRaw("Horizontal"); float vertical = Input.GetAxisRaw("Vertical"); Vector3 input = new Vector3(horizontal, 0f, vertical); if (input.sqrMagnitude > 1f) { input.Normalize(); } Vector3 horizontalVelocity = input * moveSpeed; rb.velocity = new Vector3(horizontalVelocity.x, rb.velocity.y, horizontalVelocity.z); if (input.sqrMagnitude > 0.01f) { Quaternion targetRotation = Quaternion.LookRotation(input, Vector3.up); rb.MoveRotation(Quaternion.Slerp(rb.rotation, targetRotation, 12f * Time.fixedDeltaTime)); } if (isGrounded && rb.velocity.y <= 0.05f) { lastSafePosition = transform.position; } } public void ResetToPosition(Vector3 position) { if (rb == null) { rb = GetComponent(); } transform.position = position; lastSafePosition = position; if (rb != null) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } } public void ResetToLastSafePosition() { Vector3 target = respawnPoint != null ? respawnPoint.position : lastSafePosition; ResetToPosition(target + Vector3.up * 0.25f); } private void UpdateGroundedState() { Vector3 capsuleCenter = transform.TransformPoint(capsuleCollider.center); Vector3 sphereCenter = capsuleCenter + Vector3.down * ((capsuleCollider.height * 0.5f) - (groundCheckRadius * 0.5f)); Collider[] hits = Physics.OverlapSphere( sphereCenter, groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore); isGrounded = false; for (int i = 0; i < hits.Length; i++) { Collider hit = hits[i]; if (hit == null || IsOwnCollider(hit)) { continue; } // A valid jump surface must be below the player's feet, not inside the player body. if (hit.bounds.max.y <= sphereCenter.y + groundCheckRadius) { isGrounded = true; return; } } } private bool IsOwnCollider(Collider collider) { if (ownColliders == null || ownColliders.Length == 0) { ownColliders = GetComponentsInChildren(); } for (int i = 0; i < ownColliders.Length; i++) { if (ownColliders[i] == collider) { return true; } } return false; } }