using UnityEngine; public class BallController : MonoBehaviour { [Header("Ссылки")] private Transform currentPlatform = null; [Header("Настройки Движения")] [SerializeField] private float moveSpeed = 15f; [SerializeField] private float airControlSpeed = 5f; [SerializeField] private float jumpForce = 7f; [SerializeField] private float trampolineBounceForce = 1.5f; // Множитель для отскока от батута private Rigidbody rb; private bool isGrounded; void Start() { rb = GetComponent(); rb.freezeRotation = true; } void Update() { HandleJumpInput(); } void FixedUpdate() { isGrounded = false; HandleMovement(); } public bool IsGrounded { get { return isGrounded; } } // --- Логика Управления Движением (Без изменений) --- private void HandleMovement() { float moveX = Input.GetAxis("Horizontal"); float moveZ = Input.GetAxis("Vertical"); Vector3 movement = new Vector3(moveX, 0.0f, moveZ).normalized; if (currentPlatform != null && isGrounded) { // Случай 1: Прилипли к ДВИЖУЩЕЙСЯ платформе (двигаем относительно родителя) if (movement.magnitude > 0.01f) { Vector3 localMovement = currentPlatform.TransformDirection(movement) * moveSpeed * Time.fixedDeltaTime; rb.MovePosition(rb.position + localMovement); } } else { // Случай 2: На СТАТИЧНОЙ земле, БАТУТЕ или в воздухе (используем AddForce) float currentSpeed = isGrounded ? moveSpeed : airControlSpeed; rb.AddForce(movement * currentSpeed, ForceMode.Acceleration); } } private void HandleJumpInput() { if (Input.GetButtonDown("Jump") && isGrounded) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGrounded = false; transform.SetParent(null); currentPlatform = null; } } // --- Логика Взаимодействия и Прилипания (Обновленная) --- private void OnCollisionEnter(Collision collision) { CheckIfGrounded(collision); // 1. Проверяем, является ли объект БАТУТОМ if (collision.gameObject.CompareTag("Trampoline")) { // Случай 3: БАТУТ -> Моментальный отскок // Если мы уже прилипли к чему-то, отлипаем if (currentPlatform != null) { transform.SetParent(null); currentPlatform = null; } // *** ГЛАВНОЕ ИЗМЕНЕНИЕ: Находим нормаль к поверхности *** // Получаем нормаль к поверхности в точке столкновения. // Это и есть направление, перпендикулярное поверхности. Vector3 surfaceNormal = Vector3.up; if (collision.contacts.Length > 0) { // Используем нормаль первого контакта для определения направления отскока surfaceNormal = collision.contacts[0].normal; } // Применяем сильный импульс в направлении нормали // Сбрасываем текущую вертикальную скорость шарика, чтобы отскок был одинаковым // Для более физически корректного отскока, мы можем очистить всю скорость rb.linearVelocity = Vector3.zero; // Применяем силу, направленную строго по нормали поверхности. rb.AddForce(surfaceNormal * jumpForce * trampolineBounceForce, ForceMode.Impulse); return; // Завершаем проверку, это батут, прилипать не нужно } // 2. Проверяем на ДВИЖУЩУЮСЯ ПЛАТФОРМУ MovingPlatform platformScript = collision.gameObject.GetComponent(); if (platformScript != null) { // Случай 1: ДВИЖУЩАЯСЯ ПЛАТФОРМА -> Прилипаем if (isGrounded) { transform.SetParent(collision.transform); currentPlatform = collision.transform; } } else { // Случай 2: СТАТИЧЕСКАЯ ЗЕМЛЯ (обычная неподвижная платформа) // Гарантируем, что мы не прилипли, если это не MovingPlatform if (currentPlatform != null) { transform.SetParent(null); currentPlatform = null; } } } private void OnCollisionStay(Collision collision) { CheckIfGrounded(collision); } private void OnCollisionExit(Collision collision) { // Логика отлипания: Сбрасываем родителя только если покидаем MovingPlatform MovingPlatform platformScript = collision.gameObject.GetComponent(); if (platformScript != null) { transform.SetParent(null); currentPlatform = null; } // Для батута и статики отлипание уже произошло в OnCollisionEnter } // Вспомогательный метод для проверки земли private void CheckIfGrounded(Collision collision) { if (isGrounded) return; if (collision.contacts.Length > 0) { Vector3 normal = collision.contacts[0].normal; if (normal.y > 0.7f) // ~45 градусов { isGrounded = true; } } } }