Unity_lab3/Unity3_lab/Assets/Scripts/BallController.cs
2025-11-13 20:26:18 +03:00

320 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
public class BallController : MonoBehaviour
{
[Header("Ссылки")]
private Transform currentPlatform = null;
[SerializeField] private GameObject gameOverPanel;
[Header("Настройки Движения")]
[SerializeField] private float maxSpeed = 15f; // Максимальная скорость на земле
[SerializeField] private float accelerationRate = 200f; // Как быстро шарик разгоняется
[SerializeField] private float airControlSpeed = 5f; // Скорость управления в прыжке
[SerializeField] private float jumpForce = 7f; // Сила прыжка
[SerializeField] private float trampolineBounceForce = 1.5f; // Множитель для отскока от батута
[Header("Настройки Контроля")]
[Tooltip("Сила торможения/трения. Применяется, когда нет ввода.")]
[SerializeField] private float decelerationRate = 5f; // Сила, замедляющая шарик (трение)
[SerializeField] private float groundCheckDistance = 0.1f; // Дистанция для проверки земли
[SerializeField] private float fallTimeLimit = 10f;
private Rigidbody rb;
private bool isGrounded;
private float sphereRadius;
// Переменные для считывания ввода
private float moveX;
private float moveZ;
private float timeNotInAir = 0f;
private bool isGameOver = false;
void Start()
{
rb = GetComponent<Rigidbody>();
sphereRadius = GetComponent<SphereCollider>().radius;// Убедимся, что при старте игры панель выключена
if (gameOverPanel != null)
{
gameOverPanel.SetActive(false);
}
// Сброс TimeScale на случай, если предыдущая игра его остановила
Time.timeScale = 1f;
isGameOver = false;
}
void Update()
{
// 1. Считываем ввод в Update
moveX = Input.GetAxisRaw("Horizontal");
moveZ = Input.GetAxisRaw("Vertical");
if (isGameOver) return;
HandleJumpInput();
}
// Свойство для проверки состояния "на земле" из других скриптов
public bool IsGrounded
{
get { return isGrounded; }
}
private void CheckGrounded()
{
isGrounded = false;
Vector3 origin = transform.position;
if (Physics.Raycast(origin, -Vector3.up, out RaycastHit hit, sphereRadius + groundCheckDistance))
{
if (hit.normal.y > 0.7f)
{
isGrounded = true;
}
}
}
// --- Логика Управления Движением ---
private void HandleMovement()
{
if (isGrounded)
{
// Определяем, есть ли ввод от игрока
bool hasInput = Mathf.Abs(moveX) > 0.01f || Mathf.Abs(moveZ) > 0.01f;
if (hasInput)
{
ApplyAcceleration();
}
else
{
ApplyDeceleration();
}
// Ограничение максимальной скорости (применяется всегда, если шарик на земле)
ClampHorizontalSpeed();
}
else
{
// Управление в воздухе
Vector3 movement = new Vector3(moveX, 0.0f, moveZ).normalized;
rb.AddForce(movement * airControlSpeed, ForceMode.Acceleration);
}
}
private void ApplyAcceleration()
{
Vector3 currentVel = rb.linearVelocity;
// 1. Ускорение по X (A/D)
if (Mathf.Abs(moveX) > 0.01f)
{
if (Mathf.Abs(currentVel.x) < maxSpeed)
{
rb.AddForce(new Vector3(moveX, 0, 0) * accelerationRate, ForceMode.Acceleration);
}
}
// 2. Ускорение по Z (W/S)
if (Mathf.Abs(moveZ) > 0.01f)
{
if (Mathf.Abs(currentVel.z) < maxSpeed)
{
rb.AddForce(new Vector3(0, 0, moveZ) * accelerationRate, ForceMode.Acceleration);
}
}
}
private void ApplyDeceleration()
{
Vector3 horizontalVel = new Vector3(rb.linearVelocity.x, 0, rb.linearVelocity.z);
// Применяем силу, противоположную скорости (имитация трения)
if (horizontalVel.sqrMagnitude > 0.01f)
{
// Направление силы, противоположное скорости
Vector3 frictionForce = -horizontalVel.normalized * decelerationRate;
rb.AddForce(frictionForce, ForceMode.Acceleration);
}
else
{
// Если скорость очень низка, останавливаем горизонтальное движение, чтобы избежать "дрейфа"
rb.linearVelocity = new Vector3(0, rb.linearVelocity.y, 0);
}
}
private void ClampHorizontalSpeed()
{
Vector3 horizontalVel = new Vector3(rb.linearVelocity.x, 0, rb.linearVelocity.z);
if (horizontalVel.sqrMagnitude > maxSpeed * maxSpeed)
{
Vector3 clampedVel = horizontalVel.normalized * maxSpeed;
// Устанавливаем горизонтальную скорость, сохраняя вертикальную
rb.linearVelocity = new Vector3(clampedVel.x, rb.linearVelocity.y, clampedVel.z);
}
}
private void HandleJumpInput()
{
if (Input.GetButtonDown("Jump") && isGrounded)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
// --- Логика Взаимодействия и Тегирования ---
private void OnCollisionEnter(Collision collision)
{
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;
}
}
HandleTrampolineBounce(collision);
}
private void HandleTrampolineBounce(Collision collision)
{
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);
}
void FixedUpdate()
{
CheckGrounded();
// Не обрабатываем физику, если игра окончена
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);
}
}
}