using UnityEngine; using RR.Game2D.Core; namespace RR.Game2D.Behaviour { [DisallowMultipleComponent] [RequireComponent(typeof(Rigidbody2D), typeof(Collider2D), typeof(Animator))] public class PlayerController : MonoBehaviour { [Header("Горизонтальное движение")] [SerializeField] private float moveSpeed = 8f; [SerializeField] private float accel = 40f; [SerializeField] private float decel = 20f; [Header("Вертикальное движение")] [SerializeField] private float maxJumpHeight = 5f; [SerializeField] private float jumpCooldown = 0.1f; [SerializeField] private float jumpBuffer = 0.1f; [SerializeField, Range(0f, 1f)] private float jumpCut = 0.5f; private float spriteScale = 0.35f; private Rigidbody2D rb; private Collider2D col; float inputX; float lastGroundedTime; float lastJumpPressedTime; bool IsGrounded => Grounded(); [Header("Аниматор [Опционально]")] [SerializeField] private Animator animator; // оставить в инспекторе на всякий случай void Awake() { rb = GetComponent(); col = GetComponent(); rb.freezeRotation = true; lastJumpPressedTime = -999f; // если не задано в инспекторе — находим автоматически if (animator == null) animator = GetComponent(); if (animator == null) animator = GetComponentInChildren(true); if (animator == null) Debug.LogError($"[{name}] Animator не найден!"); } void Update() { // Передвижение inputX = Input.GetAxisRaw("Horizontal"); //if (Input.GetButtonDown("Jump")) // lastJumpPressedTime = Time.time; if (Input.GetButton("Jump")) lastJumpPressedTime = Time.time; if (Input.GetButtonUp("Jump")) // && rb.linearVelocity.y > 0f) rb.linearVelocity = new Vector2(rb.linearVelocity.x, rb.linearVelocity.y * jumpCut); if (inputX != 0f) transform.localScale = new Vector3(Mathf.Sign(inputX) * spriteScale, spriteScale, spriteScale); if (IsGrounded) lastGroundedTime = Time.time; // Данные для анимаций animator.SetFloat("Speed", Mathf.Abs(rb.linearVelocity.x)); animator.SetFloat("VSpeed", rb.linearVelocity.y); animator.SetBool("Grounded", IsGrounded); } void FixedUpdate() { float target = inputX * moveSpeed; float rate = (Mathf.Abs(target) > 0.01f) ? accel : decel; float vx = Mathf.MoveTowards(rb.linearVelocity.x, target, rate * Time.fixedDeltaTime); if (transform.position.y < -5) Die(); // Возможность прыжка bool canJump = (Time.time - lastGroundedTime) <= jumpCooldown; bool wantJump = (Time.time - lastJumpPressedTime) <= jumpBuffer; if (wantJump && canJump && IsGrounded) { float vy = CalcJumpSpeed(maxJumpHeight); rb.linearVelocity = new Vector2(vx, vy); animator.SetBool("Jump", true); lastJumpPressedTime = -999f; return; } rb.linearVelocity = new Vector2(vx, rb.linearVelocity.y); } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Coin")) { GameManager.Instance.AddCoin(); Destroy(collision.gameObject); } if (collision.CompareTag("Finish")) { GameManager.Instance.NextLevel(); } } //private void OnDestroy() => Debug.Log("Player destroyed"); /// /// Метод для просчитывания скорости прыжка с учетом высоты прыжка /// /// Высота прыжка (высшая точка по y) /// float jumpSpeed - высчитанная скорость прыжка float CalcJumpSpeed(float height) { float g = Physics2D.gravity.y * rb.gravityScale; return Mathf.Sqrt(-2f * g * height); } /// /// Метод для проверки соприкосновения с землей /// /// bool isGrounded - на земле ли игрок bool Grounded() { RaycastHit2D hit = Physics2D.CircleCast(new Vector2(col.bounds.center.x, col.bounds.min.y), 0.01f, Vector2.down, 0.1f); bool isGrounded = hit.collider != null; animator.SetBool("Jump", !isGrounded); return isGrounded; } /// /// Метод смерти персонажа. /// Базовое поведение - сброс уровня в GameManager /// public void Die() { GameManager.Instance.ResetLevel(); } } }