376 lines
13 KiB
C#
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;
|
|
}
|
|
}
|