284 lines
9.9 KiB
C#
284 lines
9.9 KiB
C#
|
using UnityEngine;
|
||
|
|
||
|
[RequireComponent(typeof(Rigidbody2D))]
|
||
|
[RequireComponent(typeof(CircleCollider2D))]
|
||
|
public class Ball : MonoBehaviour
|
||
|
{
|
||
|
public float speed = 8f;
|
||
|
public float maxSpeed = 15f;
|
||
|
public float speedIncrease = 0.1f;
|
||
|
public float maxBounceAngle = 75f; // Maximum angle the ball can bounce
|
||
|
public float randomBounceIntensity = 0.2f; // Интенсивность случайного отклонения
|
||
|
public float paddleBounceIntensity = 0.3f; // Дополнительная интенсивность для отскока от ракетки
|
||
|
public float minVerticalVelocity = 2f; // Минимальная вертикальная скорость
|
||
|
public Color ballColor = Color.white; // Цвет мяча
|
||
|
|
||
|
[HideInInspector]
|
||
|
public bool canSpawnBalls = true; // Флаг, определяющий может ли мяч создавать новые мячи
|
||
|
|
||
|
protected Rigidbody2D rb;
|
||
|
protected bool isPlaying;
|
||
|
private Vector2 startPosition;
|
||
|
private Transform paddleTransform;
|
||
|
private Vector2 offset;
|
||
|
private Paddle paddle;
|
||
|
protected Vector2 lastFrameVelocity;
|
||
|
|
||
|
protected virtual void Start()
|
||
|
{
|
||
|
rb = GetComponent<Rigidbody2D>();
|
||
|
if (rb == null)
|
||
|
{
|
||
|
Debug.LogError("Rigidbody2D component is missing!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Устанавливаем цвет мяча
|
||
|
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
|
||
|
if (spriteRenderer != null)
|
||
|
{
|
||
|
spriteRenderer.color = ballColor;
|
||
|
}
|
||
|
|
||
|
// Set up physics properties for proper reflection
|
||
|
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||
|
rb.velocity = Vector2.zero;
|
||
|
rb.angularVelocity = 0f;
|
||
|
rb.gravityScale = 0f;
|
||
|
rb.sharedMaterial = new PhysicsMaterial2D
|
||
|
{
|
||
|
bounciness = 1f,
|
||
|
friction = 0f
|
||
|
};
|
||
|
|
||
|
var collider = GetComponent<CircleCollider2D>();
|
||
|
if (collider == null)
|
||
|
{
|
||
|
Debug.LogError("CircleCollider2D component is missing!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Debug.Log("Ball components initialized successfully");
|
||
|
startPosition = transform.position;
|
||
|
|
||
|
// Find the paddle
|
||
|
GameObject paddleObj = GameObject.FindGameObjectWithTag("Paddle");
|
||
|
if (paddleObj != null)
|
||
|
{
|
||
|
paddleTransform = paddleObj.transform;
|
||
|
paddle = paddleObj.GetComponent<Paddle>();
|
||
|
// Calculate initial offset from paddle
|
||
|
offset = transform.position - paddleTransform.position;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Debug.LogError("Paddle not found! Make sure it has the 'Paddle' tag.");
|
||
|
}
|
||
|
|
||
|
PrepareNewBall();
|
||
|
}
|
||
|
|
||
|
protected virtual void Update()
|
||
|
{
|
||
|
if (!isPlaying)
|
||
|
{
|
||
|
// Stick to the paddle
|
||
|
if (paddleTransform != null)
|
||
|
{
|
||
|
transform.position = paddleTransform.position + (Vector3)offset;
|
||
|
}
|
||
|
|
||
|
// Launch on space or left mouse click
|
||
|
if (Input.GetButtonDown("Fire1") || Input.GetKeyDown(KeyCode.Space))
|
||
|
{
|
||
|
LaunchBall();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Maintain constant speed without changing direction
|
||
|
if (rb.velocity.magnitude != speed)
|
||
|
{
|
||
|
rb.velocity = rb.velocity.normalized * speed;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Сохраняем скорость предыдущего кадра
|
||
|
lastFrameVelocity = rb.velocity;
|
||
|
}
|
||
|
|
||
|
void LaunchBall()
|
||
|
{
|
||
|
isPlaying = true;
|
||
|
|
||
|
// Get base launch velocity (upward direction)
|
||
|
Vector2 launchVelocity = Vector2.up * speed;
|
||
|
|
||
|
// Add paddle's velocity influence if available
|
||
|
if (paddle != null)
|
||
|
{
|
||
|
Vector2 paddleVelocity = paddle.GetLaunchVelocity();
|
||
|
launchVelocity += paddleVelocity;
|
||
|
|
||
|
// Ensure the ball always goes upward regardless of paddle movement
|
||
|
if (launchVelocity.y < speed * 0.5f)
|
||
|
{
|
||
|
launchVelocity.y = speed * 0.5f;
|
||
|
}
|
||
|
|
||
|
// Normalize and apply speed to maintain consistent velocity
|
||
|
launchVelocity = launchVelocity.normalized * speed;
|
||
|
}
|
||
|
|
||
|
rb.velocity = launchVelocity;
|
||
|
}
|
||
|
|
||
|
public void PrepareNewBall()
|
||
|
{
|
||
|
isPlaying = false;
|
||
|
rb.velocity = Vector2.zero;
|
||
|
|
||
|
if (paddleTransform != null)
|
||
|
{
|
||
|
// Reset position relative to paddle
|
||
|
transform.position = paddleTransform.position + (Vector3)offset;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
transform.position = startPosition;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void OnCollisionEnter2D(Collision2D collision)
|
||
|
{
|
||
|
if (!isPlaying) return;
|
||
|
|
||
|
// Get the incoming direction
|
||
|
Vector2 inDirection = lastFrameVelocity.normalized;
|
||
|
|
||
|
if (collision == null)
|
||
|
{
|
||
|
Debug.LogError("Collision is null!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Special handling for paddle collisions
|
||
|
if (collision.gameObject.CompareTag("Paddle"))
|
||
|
{
|
||
|
HandlePaddleCollision(collision);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// For walls and bricks, let physics handle the reflection naturally
|
||
|
// Just maintain the constant speed without changing the direction
|
||
|
Vector2 reflected = Vector2.Reflect(lastFrameVelocity.normalized, collision.contacts[0].normal);
|
||
|
|
||
|
// Добавляем случайное отклонение
|
||
|
float intensity = randomBounceIntensity;
|
||
|
|
||
|
// Применяем случайное отклонение
|
||
|
reflected = AddRandomness(reflected, intensity);
|
||
|
|
||
|
// Обеспечиваем минимальную вертикальную скорость
|
||
|
if (Mathf.Abs(reflected.y) < minVerticalVelocity)
|
||
|
{
|
||
|
reflected.y = reflected.y < 0 ? -minVerticalVelocity : minVerticalVelocity;
|
||
|
// Нормализуем вектор и сохраняем скорость
|
||
|
reflected = reflected.normalized;
|
||
|
}
|
||
|
|
||
|
// Применяем скорость, сохраняя текущую величину
|
||
|
float currentSpeed = lastFrameVelocity.magnitude;
|
||
|
rb.velocity = reflected * Mathf.Min(currentSpeed, maxSpeed);
|
||
|
}
|
||
|
|
||
|
void HandlePaddleCollision(Collision2D collision)
|
||
|
{
|
||
|
rb = GetComponent<Rigidbody2D>();
|
||
|
Paddle paddle = collision.gameObject.GetComponent<Paddle>();
|
||
|
if (paddle == null) return;
|
||
|
|
||
|
// Get the paddle's velocity
|
||
|
Vector2 paddleVel = paddle.GetBounceVelocity();
|
||
|
|
||
|
// Calculate hit position relative to paddle center (-1 to 1)
|
||
|
float hitPosition = (transform.position.x - collision.transform.position.x) /
|
||
|
(collision.collider.bounds.size.x / 2f);
|
||
|
|
||
|
// Calculate base bounce angle
|
||
|
float bounceAngle = hitPosition * maxBounceAngle;
|
||
|
float angleRad = bounceAngle * Mathf.Deg2Rad;
|
||
|
|
||
|
// Calculate base reflection direction
|
||
|
Vector2 baseDirection = new Vector2(Mathf.Sin(angleRad), Mathf.Cos(angleRad));
|
||
|
|
||
|
// Calculate incoming velocity relative to paddle
|
||
|
Vector2 relativeVelocity = rb.velocity - paddleVel;
|
||
|
|
||
|
// Calculate reflection velocity
|
||
|
Vector2 reflection = Vector2.Reflect(relativeVelocity.normalized, collision.contacts[0].normal);
|
||
|
|
||
|
// Combine base direction with reflection and paddle velocity
|
||
|
Vector2 finalDirection = (baseDirection + reflection.normalized).normalized;
|
||
|
|
||
|
// Add paddle velocity influence
|
||
|
Vector2 newVelocity = finalDirection * speed + paddleVel * 0.5f;
|
||
|
|
||
|
// Ensure the ball always goes upward
|
||
|
if (newVelocity.y < speed * 0.2f)
|
||
|
{
|
||
|
newVelocity.y = speed * 0.2f;
|
||
|
newVelocity = newVelocity.normalized * speed;
|
||
|
}
|
||
|
|
||
|
// Добавляем случайное отклонение
|
||
|
float intensity = randomBounceIntensity + paddleBounceIntensity;
|
||
|
|
||
|
// Применяем случайное отклонение
|
||
|
newVelocity = AddRandomness(newVelocity, intensity);
|
||
|
|
||
|
// Обеспечиваем минимальную вертикальную скорость
|
||
|
if (Mathf.Abs(newVelocity.y) < minVerticalVelocity)
|
||
|
{
|
||
|
newVelocity.y = newVelocity.y < 0 ? -minVerticalVelocity : minVerticalVelocity;
|
||
|
// Нормализуем вектор и сохраняем скорость
|
||
|
newVelocity = newVelocity.normalized;
|
||
|
}
|
||
|
|
||
|
// Применяем скорость, сохраняя текущую величину
|
||
|
float currentSpeed = lastFrameVelocity.magnitude;
|
||
|
rb.velocity = newVelocity * Mathf.Min(currentSpeed, maxSpeed);
|
||
|
|
||
|
// Легкая вибрация при отскоке от ракетки
|
||
|
if (HapticManager.Instance != null)
|
||
|
{
|
||
|
HapticManager.Instance.LightTap();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vector2 AddRandomness(Vector2 direction, float intensity)
|
||
|
{
|
||
|
// Генерируем случайный угол в радианах
|
||
|
float randomAngle = Random.Range(-intensity * Mathf.PI / 3f, intensity * Mathf.PI / 3f);
|
||
|
|
||
|
// Поворачиваем вектор на случайный угол
|
||
|
float cos = Mathf.Cos(randomAngle);
|
||
|
float sin = Mathf.Sin(randomAngle);
|
||
|
return new Vector2(
|
||
|
direction.x * cos - direction.y * sin,
|
||
|
direction.x * sin + direction.y * cos
|
||
|
).normalized;
|
||
|
}
|
||
|
|
||
|
void OnTriggerEnter2D(Collider2D other)
|
||
|
{
|
||
|
Debug.Log("Triggered");
|
||
|
// If ball hits the bottom of the screen
|
||
|
if (other.CompareTag("DeathZone"))
|
||
|
{
|
||
|
Debug.Log("Ball hit DeathZone");
|
||
|
GameManager.Instance.LoseBall(this);
|
||
|
}
|
||
|
}
|
||
|
}
|