222 lines
8.3 KiB
C#
222 lines
8.3 KiB
C#
using UnityEngine;
|
||
|
||
public class BallController : MonoBehaviour
|
||
{
|
||
[Header("Ссылки")]
|
||
private Transform currentPlatform = null;
|
||
[SerializeField] private GameObject gameOverPanel;
|
||
|
||
[Header("Настройки Движения")]
|
||
[SerializeField] private float moveSpeed = 15f;
|
||
[SerializeField] private float airControlSpeed = 5f;
|
||
[SerializeField] private float jumpForce = 7f;
|
||
[SerializeField] private float trampolineBounceForce = 1.5f; // Множитель для отскока от батута
|
||
[SerializeField] private float fallTimeLimit = 10f;
|
||
|
||
private Rigidbody rb;
|
||
private bool isGrounded;
|
||
private float timeNotInAir = 0f;
|
||
private bool isGameOver = false;
|
||
void Start()
|
||
{
|
||
rb = GetComponent<Rigidbody>();
|
||
rb.freezeRotation = true;
|
||
|
||
// Убедимся, что при старте игры панель выключена
|
||
if (gameOverPanel != null)
|
||
{
|
||
gameOverPanel.SetActive(false);
|
||
}
|
||
|
||
// Сброс TimeScale на случай, если предыдущая игра его остановила
|
||
Time.timeScale = 1f;
|
||
isGameOver = false;
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (isGameOver) return;
|
||
|
||
HandleJumpInput();
|
||
}
|
||
|
||
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<MovingPlatform>();
|
||
|
||
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<MovingPlatform>();
|
||
if (platformScript != null)
|
||
{
|
||
transform.SetParent(null);
|
||
currentPlatform = null;
|
||
}
|
||
// Для батута и статики отлипание уже произошло в OnCollisionEnter
|
||
}
|
||
|
||
// Вспомогательный метод для проверки земли
|
||
private void CheckIfGrounded(Collision collision)
|
||
{
|
||
|
||
if (collision.contacts.Length > 0)
|
||
{
|
||
Vector3 normal = collision.contacts[0].normal;
|
||
if (normal.y > 0.7f)
|
||
{
|
||
isGrounded = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
void FixedUpdate()
|
||
{
|
||
// Не обрабатываем физику, если игра окончена
|
||
if (isGameOver) return;
|
||
|
||
// Мы проверяем 'isGrounded' из *предыдущего* физического кадра.
|
||
if (isGrounded)
|
||
{
|
||
// Если были на земле, сбрасываем таймер
|
||
timeNotInAir = 0f;
|
||
}
|
||
else
|
||
{
|
||
// Если в воздухе, увеличиваем таймер
|
||
timeNotInAir += Time.fixedDeltaTime;
|
||
|
||
if (timeNotInAir >= fallTimeLimit)
|
||
{
|
||
GameOver();
|
||
}
|
||
}
|
||
|
||
// Теперь сбрасываем isGrounded.
|
||
// OnCollisionStay в этом кадре установит его обратно в 'true',
|
||
// если шарик все еще касается земли.
|
||
isGrounded = false;
|
||
|
||
HandleMovement();
|
||
}
|
||
|
||
private void GameOver()
|
||
{
|
||
if (isGameOver) return;
|
||
|
||
isGameOver = true;
|
||
|
||
Time.timeScale = 0f;
|
||
|
||
if (gameOverPanel != null)
|
||
{
|
||
gameOverPanel.SetActive(true);
|
||
}
|
||
}
|
||
} |