using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; [RequireComponent(typeof(Rigidbody), typeof(SphereCollider))] public sealed class CoinRunLab : MonoBehaviour { [SerializeField] private float acceleration = 4f; private Rigidbody body; private InputAction move; private Vector2 input; private RunState state; private bool reported; private void Awake() { body = GetComponent(); body.useGravity = false; body.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotation; state = new RunState(3, 15.0); move = new InputAction("Move", InputActionType.Value); move.AddCompositeBinding("2DVector").With("Up", "/w").With("Down", "/s").With("Left", "/a").With("Right", "/d"); } private void OnEnable() { if (move != null) move.Enable(); } private void OnDisable() { if (move != null) move.Disable(); } private void OnDestroy() { if (move != null) move.Dispose(); } private void Update() { if (state.Phase != RunPhase.Running) return; input = move.ReadValue(); state.Tick(Time.deltaTime); ReportOutcome(); } private void FixedUpdate() { if (state.Phase != RunPhase.Running) return; body.AddForce(new Vector3(input.x, 0f, input.y) * acceleration, ForceMode.Acceleration); } private void OnTriggerEnter(Collider other) { if (!isActiveAndEnabled || state == null) return; if (!other.CompareTag("Collectible") || !state.Collect(other.gameObject.GetInstanceID())) return; other.gameObject.SetActive(false); Debug.Log("Score: " + state.Score + "/3", this); ReportOutcome(); } private void ReportOutcome() { if (reported || state.Phase == RunPhase.Running) return; reported = true; body.linearVelocity = Vector3.zero; body.angularVelocity = Vector3.zero; Debug.Log("Run: " + state.Phase, this); } public enum RunPhase { Running, Won, Lost } public sealed class RunState { private readonly HashSet collected = new HashSet(); private readonly int target; public int Score { get; private set; } public double Remaining { get; private set; } public RunPhase Phase { get; private set; } public RunState(int target, double seconds) { if (target < 1 || seconds <= 0 || double.IsNaN(seconds) || double.IsInfinity(seconds)) throw new ArgumentOutOfRangeException(); this.target = target; Remaining = seconds; } public bool Collect(int id) { if (Phase != RunPhase.Running || !collected.Add(id)) return false; Score++; if (Score >= target) Phase = RunPhase.Won; return true; } public void Tick(double seconds) { if (seconds < 0 || double.IsNaN(seconds) || double.IsInfinity(seconds)) throw new ArgumentOutOfRangeException(); if (Phase != RunPhase.Running) return; Remaining = Math.Max(0, Remaining - seconds); if (Remaining == 0) Phase = RunPhase.Lost; } } }