bubble_shooter/Assets/Scripts/BallShooter.cs
2025-05-20 10:03:01 +05:30

376 lines
13 KiB
C#

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<Vector2> currentPath = new List<Vector2>();
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<Bubble>();
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<Vector2> ballPath, Vector2 endBubbleGridPosition)
{
isShooting = true;
GameObject ball = Instantiate(BallPrefab);
ball.transform.position = ballPath[0];
Sprite sprite = nextSprite;
ball.GetComponent<SpriteRenderer>().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<GameObject> checkedBubbles = new HashSet<GameObject>();
List<GameObject> bubblesToCheck = new List<GameObject>();
List<GameObject> allMatchingBubbles = new List<GameObject>();
bubblesToCheck.Add(newBubble);
checkedBubbles.Add(newBubble);
while (bubblesToCheck.Count > 0)
{
GameObject currentBubble = bubblesToCheck[0];
bubblesToCheck.RemoveAt(0);
allMatchingBubbles.Add(currentBubble);
List<GameObject> 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<Bubble>();
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<int, Vector2> HandleBallHit(Collider2D hitCollider, Vector2 hitPoint)
{
Bubble bubble = hitCollider.GetComponent<Bubble>();
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<int, Vector2>(maxReflections, bubblePosition);
}
KeyValuePair<int, Vector2> ReflectRay(Vector2 startPosition, Vector2 direction, int reflectionsRemaining)
{
if (reflectionsRemaining <= 0)
return new KeyValuePair<int, Vector2>(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<int, Vector2>(reflectionsRemaining, hit.point);
}
// Call this to get the current path
public List<Vector2> GetCurrentPath()
{
return currentPath;
}
}