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(); if (rb == null) { Debug.LogError("Rigidbody2D component is missing!"); return; } // Устанавливаем цвет мяча SpriteRenderer spriteRenderer = GetComponent(); 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(); 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(); // 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(); Paddle paddle = collision.gameObject.GetComponent(); 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); } } }