using UnityEngine; using UnityEngine.InputSystem; using RR.Common.Interfaces; using RR.Game2D.Behaviour.ScriptableObjects; using RR.Game2D.Core.Managers; namespace RR.Game2D.Behaviour { [DisallowMultipleComponent] [RequireComponent(typeof(Rigidbody2D), typeof(Collider2D), typeof(Animator))] public class PlayerController : MonoBehaviour, IDamageable { [Header("Управление")] [SerializeField] private InputActionReference moveAction; // Value(Vector2), например "Move" (A/D, ←/→, stick) [SerializeField] private InputActionReference jumpAction; [Header("Горизонтальное движение")] [SerializeField] private float moveSpeed = 8f; [SerializeField] private float accel = 40f; [SerializeField] private float decel = 20f; [Header("Вертикальное движение")] [SerializeField] public float jumpVelocity = 50f; [SerializeField] private float jumpCut = 2f; public bool IsGrounded => Grounded(); [Header("Проверка призмеления")] [SerializeField] private Transform groundCheck; [SerializeField] private float groundCheckRadius = 0.05f; [SerializeField] private LayerMask groundMask; [Header("Аниматор [Опционально]")] [SerializeField] private Animator animator; // оставить в инспекторе на всякий случай [Header("Здоровье")] [SerializeField] private int maxHealth = 3; private Health _health; public float CurrentHealth => _health?.CurrentHealth ?? 0; public float MaxHealth => _health?.MaxHealth ?? 0; [Header("FX")] [SerializeField] public SFXAsset stepSounds; [SerializeField] public SFXAsset hurtSounds; [SerializeField] public SFXAsset jumpSounds; private Rigidbody2D rb; // private Collider2D col; private float _inputX; private bool _jumpPressed, _jumpReleased, _jumpHold; private float _fallSpeedYDampingChangeThreshold; // Аниматор private static readonly int Jump = Animator.StringToHash("Jump"); private static readonly int Speed = Animator.StringToHash("Speed"); private static readonly int VSpeed = Animator.StringToHash("VSpeed"); private static readonly int GroundedAnimator = Animator.StringToHash("Grounded"); private static readonly int Death = Animator.StringToHash("Death"); void OnEnable() { moveAction.action.Enable(); jumpAction.action.Enable(); } void OnDisable() { moveAction.action.Disable(); jumpAction.action.Disable(); } void Awake() { rb = GetComponent(); //col = GetComponent(); rb.freezeRotation = true; _fallSpeedYDampingChangeThreshold = CameraManager.instance._fallSpeedYDampingChangeThreshold; // если не задано в инспекторе — находим автоматически if (animator == null) animator = GetComponent(); if (animator == null) animator = GetComponentInChildren(true); if (animator == null) Debug.LogError($"[{name}] Animator не найден!"); _health = GetComponent(); if (_health == null) { _health = gameObject.AddComponent(); } _health.ResetHealth(); _health.SetOwner(this); _health.SetMaxHealth(maxHealth); } void Update() { // --- Ввод --- Vector2 move = moveAction ? moveAction.action.ReadValue() : Vector2.zero; _inputX = Mathf.Abs(move.x) > 0.01f ? Mathf.Sign(move.x) : 0f; _jumpPressed |= jumpAction.action.WasPressedThisFrame(); _jumpReleased |= jumpAction.action.WasReleasedThisFrame(); _jumpHold |= jumpAction.action.IsPressed(); if (_inputX != 0f) transform.localEulerAngles = Mathf.Sign(_inputX) > 0 ? Vector3.zero : new Vector3(0, 180f, 0); if (rb.linearVelocity.y < _fallSpeedYDampingChangeThreshold && !CameraManager.instance.IsLerpingYDamping && !CameraManager.instance.LerpedFromPlayerFalling) { CameraManager.instance.LerpYDamping(true); } if (rb.linearVelocity.y >= 0 && !CameraManager.instance.IsLerpingYDamping && CameraManager.instance.LerpedFromPlayerFalling) { CameraManager.instance.LerpedFromPlayerFalling = false; CameraManager.instance.LerpYDamping(false); } // --- Анимации --- animator.SetFloat(Speed, Mathf.Abs(rb.linearVelocity.x)); animator.SetFloat(VSpeed, rb.linearVelocity.y); animator.SetBool(GroundedAnimator, IsGrounded); } private void FixedUpdate() { // Горизонтальное движение float target = _inputX * moveSpeed; float rate = (Mathf.Abs(target) > 0.01) ? accel : decel; float velocityX = Mathf.MoveTowards(rb.linearVelocity.x, target, rate * Time.fixedDeltaTime); Vector2 newVelocity = rb.linearVelocity; newVelocity.x = velocityX; // Вертикальное движение if ((_jumpPressed || _jumpHold)&& IsGrounded) newVelocity.y = jumpVelocity; if (_jumpReleased && newVelocity.y > 0) newVelocity.y /= Mathf.Max(1f, jumpCut); rb.linearVelocity = newVelocity; _jumpPressed = _jumpReleased = _jumpHold = false; // Смерть // if (transform.position.y < deathY) Die(); } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Coin")) { GameManager.Instance.AddCoin(); Destroy(collision.gameObject); } if (collision.CompareTag("Finish")) { GameManager.Instance.NextLevel(); } if (collision.CompareTag("Enemy")) { TakeDamage(1f); } } public void TakeDamage(float amount) { _health.TakeDamage(amount); } public void Heal(float amount) { _health.Heal(amount); } public void Die() { gameObject.layer = LayerMask.NameToLayer("No Collision"); rb.constraints = RigidbodyConstraints2D.FreezeAll; animator.SetTrigger(Death); } /// /// Метод для проверки соприкосновения с землей /// /// bool isGrounded - на земле ли игрок bool Grounded() { Collider2D hit = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundMask); bool isGrounded = hit != null && hit.CompareTag("Ground"); //&& rb.linearVelocity.y < 0.01f; animator.SetBool(Jump, !isGrounded); return isGrounded; } } }