using System; using System.Collections; using System.Collections.Generic; using Management; using UnityEngine; using UnityEngine.InputSystem; namespace Player { [RequireComponent(typeof(Rigidbody2D))] public class Movement : MonoBehaviour { public event Action OnDeath; public event Action EnterSpring; public event Action GetPickup; private static readonly int Spring = Animator.StringToHash("spring"); private static readonly int Grounded = Animator.StringToHash("grounded"); private static readonly int Speed = Animator.StringToHash("speed"); private static readonly int Vspeed = Animator.StringToHash("vspeed"); [SerializeField] private float runSpeed; [SerializeField] private float springPower; [SerializeField] private float jumpForce; [SerializeField] private float airJumpForce; [SerializeField] private SpriteRenderer spriteRenderer; [SerializeField] private float turnDelay; [SerializeField] private float impactPauseTime; [SerializeField] private float bouncePower; [SerializeField] private Animator animator; [SerializeField] private GameObject deathScreenPrefab; [Header("Audio")] [SerializeField] private AudioClip jumpSound; [SerializeField] private AudioClip airJumpSound; [SerializeField] private AudioClip pickupSound; [SerializeField] private AudioClip springSound; [SerializeField] private AudioClip bumpSound; [Header("Wind Sound")] [SerializeField] private AudioSource windSound; [SerializeField] private float maxSpeedWindSound; private Animator _springAnimator; private Rigidbody2D _rb; private bool _grounded = true; private bool _facingLeft; private float _turnDelayTimer; private float _groundedCheckTimer; private bool _interact; private int _airJumpCharges; private bool _jumpPressedThisFrame; private bool _fireSpring; private bool _jump; private bool _airJump; private Vector2 _airJumpDir; private bool _autoDrive = true; private bool _deathArmed; private bool _firstSpring; private void Awake() { _rb = GetComponent(); } private void Start() { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; Services.Instance.InputRouter.OnInteract += HandleInteraction; } private void OnDestroy() { Services.Instance.InputRouter.OnInteract -= HandleInteraction; } private void HandleInteraction(bool pressed) { _interact = pressed; _jumpPressedThisFrame = _interact; } private void Update() { Vector2 mouseScreenPos = Mouse.current.position.ReadValue(); Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint( new Vector3(mouseScreenPos.x, mouseScreenPos.y, -Camera.main.transform.position.z) ); _airJumpDir = ((Vector2)mouseWorldPos - (Vector2)transform.position).normalized; _groundedCheckTimer += Time.deltaTime; _turnDelayTimer += Time.deltaTime; if (!_autoDrive && Mathf.Abs(_rb.linearVelocityX) > 0.1f) _facingLeft = _rb.linearVelocity.x < 0; spriteRenderer.flipX = !_facingLeft; if (_springAnimator && _jumpPressedThisFrame) { _groundedCheckTimer = 0f; _springAnimator.SetTrigger(Spring); _springAnimator = null; _grounded = false; _fireSpring = true; _autoDrive = false; _deathArmed = true; } if (_grounded && _jumpPressedThisFrame) { _grounded = false; _groundedCheckTimer = 0f; _jump = true; _jumpPressedThisFrame = false; } if (!_grounded && _airJumpCharges > 0 && _jumpPressedThisFrame) { _airJump = true; } windSound.volume = Mathf.Lerp(0, 1, Mathf.Abs(_rb.linearVelocityY / maxSpeedWindSound)); _jumpPressedThisFrame = false; } private void FixedUpdate() { if (_jump) { Services.Instance.SFX.PlayOneShot(jumpSound); _jump = false; _grounded = false; _rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse); } if (_airJump) { Services.Instance.SFX.PlayOneShot(airJumpSound); StartCoroutine(ImpactPause(impactPauseTime)); _airJump = false; _airJumpCharges--; _rb.linearVelocity = Vector3.zero; _rb.AddForce(_airJumpDir * airJumpForce, ForceMode2D.Impulse); } if (_fireSpring) { _firstSpring = true; Services.Instance.SFX.PlayOneShot(springSound); Services.Instance.BGM.FadeIn(BGM.TrackType.Core); _fireSpring = false; _rb.linearVelocity = Vector2.zero; _rb.AddForce(Vector2.up * springPower, ForceMode2D.Impulse); } if (_autoDrive) { _rb.linearVelocityX = _facingLeft ? -runSpeed : runSpeed; } else { if (_rb.linearVelocityX < 0) { _facingLeft = true; } if (_rb.linearVelocityX > 0) { _facingLeft = false; } } animator.SetBool(Grounded, _grounded); animator.SetFloat(Speed, Mathf.Abs(_rb.linearVelocity.x)); animator.SetFloat(Vspeed, _rb.linearVelocity.y); if (_firstSpring) { if (_autoDrive) { Services.Instance.BGM.FadeIn(BGM.TrackType.March); } else { Services.Instance.BGM.FadeIn(BGM.TrackType.Core); } } } private IEnumerator ImpactPause(float pauseTime) { Time.timeScale = 0f; yield return new WaitForSecondsRealtime(pauseTime); Time.timeScale = 1f; } private void OnTriggerEnter2D(Collider2D other) { if (_deathArmed && other.gameObject.layer == LayerMask.NameToLayer("DeathBox")) { HandleDeath(); return; } if (other.gameObject.layer == LayerMask.NameToLayer("Spring")) { EnterSpring?.Invoke(); _springAnimator = other.gameObject.GetComponent(); } if (other.gameObject.layer == LayerMask.NameToLayer("Pickup")) { GetPickup?.Invoke(); Services.Instance.SFX.PlayOneShot(pickupSound); _airJumpCharges++; Destroy(other.gameObject); } } private void HandleDeath() { OnDeath?.Invoke(); } private void OnTriggerExit2D(Collider2D other) { if (other.gameObject.layer == LayerMask.NameToLayer("Spring")) { _springAnimator = null; } } private void OnCollisionEnter2D(Collision2D other) { CheckCollisions(other); } private void OnCollisionStay2D(Collision2D other) { CheckCollisions(other); } private void CheckCollisions(Collision2D other) { var contacts = new List(); other.GetContacts(contacts); if (_turnDelayTimer >= turnDelay && other.gameObject.layer != LayerMask.NameToLayer("Spring") && other.gameObject.layer != LayerMask.NameToLayer("Pickup")) { foreach (var c in contacts) { var x = c.normal.x; if (Mathf.Abs(x) > 0.8f) { _turnDelayTimer = 0f; if (!_autoDrive) { _rb.AddForce(new Vector2(x, 0f) * bouncePower, ForceMode2D.Impulse); } else { _rb.linearVelocityX = -_rb.linearVelocityX; _facingLeft = !_facingLeft; } Services.Instance.SFX.PlayOneShot(bumpSound); break; } } } _grounded = false; if (_groundedCheckTimer > turnDelay) { foreach (var c in contacts) { var y = c.normal.y; _grounded = Mathf.Approximately(1f, y); if (_grounded) { _autoDrive = true; return; } } } } } }