using System.Collections; using System.Collections.Generic; using UnityEngine; public class BallShooter : MonoBehaviour { public BubblesGenerator bubblesGenerator; public Transform BallSpawnPoint; public SpriteRenderer NextBallSpriteRenderer; public GameObject BallPrefab; public Transform BallDirectionVisualizer; public Transform ghostBallVisualizer; public float angleOffset = 90f; public float bubbleSize = 1f; public int maxReflections = 5; // Limit to prevent infinite recursion public float rayLength = 100f; // Length of the ray for visualization public float raycastOffset = 0.01f; // Small offset to prevent self-collision public float ballSpeed = 10f; private List currentPath = new List(); Sprite nextSprite; void SetNextSprite(Sprite sprite) { nextSprite = sprite; NextBallSpriteRenderer.sprite = sprite; } void Start() { SetNextSprite(bubblesGenerator.GetRandomBubbleSprite()); } void Update() { if (isShooting) { return; } Vector2 mouseWorldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector2 direction = (mouseWorldPosition - (Vector2)BallSpawnPoint.position).normalized; Vector2 spawnPos = (Vector2)BallSpawnPoint.position; currentPath.Clear(); currentPath.Add(spawnPos); // Add starting point ReflectRay(spawnPos, direction, maxReflections); // Update ball direction visualizer rotation based on mouse direction float angle = angleOffset + Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; BallDirectionVisualizer.rotation = Quaternion.Euler(0, 0, angle); Vector2 endBubbleGridPosition = Vector2.zero; if (currentPath.Count > 1) { for (int i = 1; i < currentPath.Count - 1; i++) { Debug.DrawLine(currentPath[i], currentPath[i - 1], Color.red, 0.1f); Vector2 dir = (currentPath[i] - currentPath[i - 1]).normalized; float distance = Vector2.Distance(currentPath[i], currentPath[i - 1]); int counter = (int)(distance / bubbleSize); //Debug.Log("counter: " + counter); if (counter < 1) { counter = 1; } Vector2 endBubblePosition = Vector2.zero; for (int j = 0; j <= counter; j++) { float mult = (float)j / (float)counter; Vector2 bubblePos = Vector2.Lerp(currentPath[i], currentPath[i - 1], 1-mult); DrawDebugCircle(bubblePos, bubbleSize/2f, Color.red); Collider2D[] colliders = Physics2D.OverlapCircleAll(bubblePos, bubbleSize / 2f); if (colliders.Length > 0 && colliders[0].tag == "Ball") { Bubble bubble = colliders[0].GetComponent(); DrawDebugCircle(bubblePos, bubbleSize / 2f, Color.blue); Vector2 bubbleGridPosition = GetBubblePositionByHit(colliders[0], bubblePos); Vector2 bubblePosition = bubblesGenerator.GetBubblePosition(bubbleGridPosition); DrawDebugCircle(bubblePosition, bubbleSize / 2f, Color.green); endBubblePosition = bubblePosition; endBubbleGridPosition = bubbleGridPosition; break; //andleBallHit(colliders[0], bubblePos); } } if (endBubblePosition != Vector2.zero) { currentPath.RemoveAt(currentPath.Count - 1); currentPath[currentPath.Count - 1] = (endBubblePosition); break; } } } if(!isShooting){ if(bubblesGenerator.bubbles.ContainsKey(endBubbleGridPosition)){ ghostBallVisualizer.position = new Vector3(100,100); return; } ghostBallVisualizer.position = bubblesGenerator.GetBubblePosition(endBubbleGridPosition); } if (Input.GetMouseButtonDown(0)) { StartCoroutine(ShootBall(currentPath, endBubbleGridPosition)); } } bool isShooting = false; IEnumerator ShootBall(List ballPath, Vector2 endBubbleGridPosition) { isShooting = true; GameObject ball = Instantiate(BallPrefab); ball.transform.position = ballPath[0]; Sprite sprite = nextSprite; ball.GetComponent().sprite = sprite; float speed = ballSpeed; // Units per second for (int i = 1; i < ballPath.Count; i++) { Vector2 startPos = ballPath[i - 1]; Vector2 endPos = ballPath[i]; float distance = Vector2.Distance(startPos, endPos); float duration = distance / speed; float elapsedTime = 0f; while (elapsedTime < duration) { float t = elapsedTime / duration; ball.transform.position = Vector2.Lerp(startPos, endPos, t); elapsedTime += Time.deltaTime; yield return null; } ball.transform.position = endPos; } Destroy(ball); GameObject newBubble = bubblesGenerator.AddBubble(endBubbleGridPosition, sprite); HashSet checkedBubbles = new HashSet(); List bubblesToCheck = new List(); List allMatchingBubbles = new List(); bubblesToCheck.Add(newBubble); checkedBubbles.Add(newBubble); while (bubblesToCheck.Count > 0) { GameObject currentBubble = bubblesToCheck[0]; bubblesToCheck.RemoveAt(0); allMatchingBubbles.Add(currentBubble); List neighbors = bubblesGenerator.GetNeighbors(currentBubble, true); foreach (GameObject neighbor in neighbors) { if (!checkedBubbles.Contains(neighbor)) { bubblesToCheck.Add(neighbor); checkedBubbles.Add(neighbor); } } } yield return new WaitForSeconds(0.2f); if (allMatchingBubbles.Count > 1) { foreach (GameObject bubble in allMatchingBubbles) { bubblesGenerator.RemoveBubble(bubble); } } SetNextSprite(bubblesGenerator.GetRandomBubbleSprite()); bubblesGenerator.UpdateAllNeighbors(); isShooting = false; yield return null; } public void DrawDebugCircle(Vector2 position, float size, Color color) { float a = 0; float step = 0.25f; while (a < Mathf.PI * 2) { a += step; Vector2 curPoint = new Vector2(Mathf.Cos(a), Mathf.Sin(a)) * size; Vector2 prevPoint = new Vector2(Mathf.Cos(a - step), Mathf.Sin(a - step)) * size; Debug.DrawLine(position + prevPoint, position + curPoint, color, 0.1f); } } Vector2 GetBubblePositionByHit(Collider2D hitCollider, Vector2 hitPoint) { Bubble bubble = hitCollider.GetComponent(); int i = (int)bubble.gridPosition.x; int j = (int)bubble.gridPosition.y; bool sameRow = false; if (hitPoint.y < hitCollider.transform.position.y) { j++; } else { sameRow = true; } if (hitPoint.x < hitCollider.transform.position.x) { i--; } else { if (sameRow) { i++; } } if (j % 2 == 0) { i++; } return new Vector2(i, j); } Vector2 FindClosestAvailablePosition(Vector2 targetGridPos, BubblesGenerator bubblesGenerator) { // Define possible neighbor offsets for hexagonal grid Vector2[] neighborOffsets = new Vector2[] { new Vector2(1, 0), // right new Vector2(-1, 0), // left new Vector2(0, 1), // up new Vector2(0, -1), // down new Vector2(1, 1), // up-right new Vector2(-1, 1), // up-left new Vector2(1, -1), // down-right new Vector2(-1, -1) // down-left }; // Adjust offsets based on even/odd row if ((int)targetGridPos.y % 2 == 0) { for (int i = 0; i < neighborOffsets.Length; i++) { neighborOffsets[i] = new Vector2(neighborOffsets[i].x + 0.5f, neighborOffsets[i].y); } } Vector2 closestPos = targetGridPos; float minDistance = float.MaxValue; // Check each neighbor position foreach (Vector2 offset in neighborOffsets) { Vector2 checkPos = targetGridPos + offset; Vector2 worldPos = bubblesGenerator.GetBubblePosition(checkPos); // Check if position is already occupied Collider2D[] colliders = Physics2D.OverlapCircleAll(worldPos, bubbleSize / 2f); bool isOccupied = false; foreach (Collider2D collider in colliders) { if (collider.tag == "Ball") { isOccupied = true; break; } } if (!isOccupied) { float distance = Vector2.Distance(targetGridPos, checkPos); if (distance < minDistance) { minDistance = distance; closestPos = checkPos; } } } return closestPos; } KeyValuePair HandleBallHit(Collider2D hitCollider, Vector2 hitPoint) { Bubble bubble = hitCollider.GetComponent(); int i = (int)bubble.gridPosition.x; int j = (int)bubble.gridPosition.y; bool sameRow = false; if (hitPoint.y < hitCollider.transform.position.y) { j++; } else { sameRow = true; } if (hitPoint.x < hitCollider.transform.position.x) { i--; } else { if (sameRow) { i++; } } if (j % 2 == 0) { i++; } Vector2 targetGridPos = new Vector2(i, j); Vector2 bubblePosition = bubble.bubblesGenerator.GetBubblePosition(targetGridPos); // Check if position is already occupied Collider2D[] colliders = Physics2D.OverlapCircleAll(bubblePosition, bubbleSize / 2f); bool isOccupied = false; foreach (Collider2D collider in colliders) { if (collider.tag == "Ball") { isOccupied = true; break; } } // If position is occupied, find closest available position if (isOccupied) { targetGridPos = FindClosestAvailablePosition(targetGridPos, bubble.bubblesGenerator); bubblePosition = bubble.bubblesGenerator.GetBubblePosition(targetGridPos); } currentPath.Add(hitPoint); // Add hit point currentPath.Add(bubblePosition); // Add final position return new KeyValuePair(maxReflections, bubblePosition); } KeyValuePair ReflectRay(Vector2 startPosition, Vector2 direction, int reflectionsRemaining) { if (reflectionsRemaining <= 0) return new KeyValuePair(reflectionsRemaining, startPosition); // Draw the ray path // Debug.DrawRay(startPosition, direction * rayLength, Color.green, 0.1f); Vector2 rayStart = startPosition + direction * raycastOffset; RaycastHit2D hit = Physics2D.Raycast(rayStart, direction, rayLength); bool foundBall = false; if (hit.collider != null) { if (hit.collider.tag == "Ball") { return HandleBallHit(hit.collider, hit.point); } Vector2 hitPoint = hit.point; currentPath.Add(hitPoint); // Add reflection point Vector2 reflectDir = Vector2.Reflect(direction, hit.normal).normalized; // Draw the reflected ray // Debug.DrawRay(hitPoint, reflectDir * rayLength, Color.red, 0.1f); Vector2 nextStartPoint = hitPoint + reflectDir * raycastOffset; return ReflectRay(nextStartPoint, reflectDir, reflectionsRemaining - 1); } return new KeyValuePair(reflectionsRemaining, hit.point); } // Call this to get the current path public List GetCurrentPath() { return currentPath; } }