TO SQUASH: Nuke ball explosion.

This commit is contained in:
2026-08-28 18:23:35 -04:00
parent 716e35c615
commit c91a8a4011
7 changed files with 456 additions and 17 deletions
+5
View File
@@ -199,4 +199,9 @@ public class BallBase : MonoBehaviour
bounds = default;
return false;
}
public void StopBall()
{
speed = new Vector2(0.0f, 0.0f);
}
}
+62 -2
View File
@@ -1,4 +1,6 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class NukeBall : BallBase
{
@@ -6,6 +8,8 @@ public class NukeBall : BallBase
public AudioClip explosionSound;
public GameObject explosionEffectPrefab;
public WorldFiller worldFiller;
// Tracks escalating danger/critical phases for audio cues.
@@ -30,6 +34,8 @@ public class NukeBall : BallBase
private float bounceDuration = 1.00f;
private bool exploding = false;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
@@ -55,6 +61,11 @@ public class NukeBall : BallBase
// Update is called once per frame
void FixedUpdate()
{
if (exploding)
{
return;
}
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (stateInfo.IsName("NuclearBallBounce")) {
@@ -100,14 +111,20 @@ public class NukeBall : BallBase
{
// Set critical state.
var area = worldFiller != null ? worldFiller.GetAreaPercentOfFreeRegion(transform.position) : -1.0f;
criticalState = (area < 0.2f) ? 2 : (area < 0.4f) ? 1 : 0;
if (area == -1.0f || exploding)
{
return;
}
criticalState = (area < 0.12f) ? 3 : (area < 0.2f) ? 2 : (area < 0.4f) ? 1 : 0;
// State 1 beeps every 0.5s, state 2 beeps every 0.25s.
criticalTime = (criticalState == 2) ? 0.5f : 1.0f;
// If in a critical state, increment the critical timer and play the
// critical sound if the timer exceeds the critical time.
if (criticalState >= 1)
if (criticalState == 1 || criticalState == 2)
{
criticalTimer += Time.deltaTime;
@@ -119,6 +136,28 @@ public class NukeBall : BallBase
criticalTimer -= criticalTime;
}
}
else if (criticalState == 3)
{
audioSource.PlayOneShot(explosionSound);
var balls = worldFiller != null ?
worldFiller.GetBallsInSameFreeRegion(transform.position) :
new Queue<GameObject>();
foreach (var ball in balls)
{
ball.GetComponent<BallBase>()?.StopBall();
if (explosionEffectPrefab != null)
{
Instantiate(explosionEffectPrefab, ball.transform.position, Quaternion.identity);
}
}
exploding = true;
StartCoroutine(DestroyAndFillRegion(balls));
}
else
{
criticalTimer = 0.0f;
@@ -198,4 +237,25 @@ public class NukeBall : BallBase
}
}
}
private IEnumerator DestroyAndFillRegion(Queue<GameObject> balls)
{
yield return new WaitForSeconds(1.4f);
foreach (var ball in balls)
{
Destroy(ball);
}
var freeRegion = worldFiller != null ?
worldFiller.GetFreeRegionOfPosition(transform.position) :
null;
if (freeRegion != null)
{
worldFiller.FillFreeRegion(freeRegion.Value);
}
Destroy(gameObject);
}
}
+251 -1
View File
@@ -9,6 +9,12 @@ public class WorldFiller : MonoBehaviour
public GameObject wallPrefab;
public BallSpawner ballSpawner;
public AudioClip areaDestroyed;
private AudioSource audioSource;
[SerializeField]
private float fallbackBarThicknessWorldUnits = 0.08f;
@@ -33,6 +39,7 @@ public class WorldFiller : MonoBehaviour
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
audioSource = GetComponent<AudioSource>();
gameBoardBounds = gameBoardSpriteRenderer.bounds;
InitializeFreeAreas();
ClearWalls();
@@ -299,7 +306,7 @@ public class WorldFiller : MonoBehaviour
return false;
}
public float GetAreaPercentOfFreeRegion(Vector2 position)
public float GetAreaPercentOfFreeRegion(Vector2 position)
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
{
@@ -317,6 +324,75 @@ public float GetAreaPercentOfFreeRegion(Vector2 position)
return -1.0f;
}
public Rect? GetFreeRegionOfPosition(Vector2 position)
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectContainsWithTolerance(freeAreas[i], position))
{
return freeAreas[i];
}
}
return null;
}
public bool FillFreeRegion(Rect freeRegion)
{
if ((freeAreas.Count == 0 || boardArea <= geometryEpsilon) && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
if (!TryGetMatchingFreeArea(freeRegion, out Rect regionToFill))
{
Debug.LogWarning("Requested region is not currently a free area.");
return false;
}
if (!TrySpawnWallInRect(regionToFill))
{
return false;
}
audioSource.PlayOneShot(areaDestroyed);
return true;
}
public Queue<GameObject> GetBallsInSameFreeRegion(Vector2 position)
{
Queue<GameObject> ballsInSameRegion = new Queue<GameObject>();
if (ballSpawner == null)
{
return ballsInSameRegion;
}
var targetRegion = GetFreeRegionOfPosition(position);
if (targetRegion == null)
{
return ballsInSameRegion;
}
foreach (var ball in ballSpawner.GetBalls())
{
var ballRegion = GetFreeRegionOfPosition(ball.transform.position);
if (ballRegion != null && ballRegion.Value == targetRegion.Value)
{
ballsInSameRegion.Enqueue(ball);
}
}
return ballsInSameRegion;
}
public Vector2 GetRandomPositionInFreeAreas(Bounds? clearingArea = null)
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
@@ -531,6 +607,180 @@ public float GetAreaPercentOfFreeRegion(Vector2 position)
}
}
private bool TryGetMatchingFreeArea(Rect requestedRegion, out Rect matchedRegion)
{
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectsApproximatelyEqual(freeAreas[i], requestedRegion))
{
matchedRegion = freeAreas[i];
return true;
}
}
matchedRegion = default;
return false;
}
private bool RectsApproximatelyEqual(Rect a, Rect b)
{
return Mathf.Abs(a.xMin - b.xMin) <= geometryEpsilon
&& Mathf.Abs(a.yMin - b.yMin) <= geometryEpsilon
&& Mathf.Abs(a.xMax - b.xMax) <= geometryEpsilon
&& Mathf.Abs(a.yMax - b.yMax) <= geometryEpsilon;
}
private bool TrySpawnWallInRect(Rect targetRect)
{
if (wallPrefab == null)
{
Debug.LogError("Wall prefab is not assigned.");
return false;
}
if (gameBoardSpriteRenderer == null)
{
Debug.LogError("Game board SpriteRenderer is not assigned.");
return false;
}
gameBoardBounds = gameBoardSpriteRenderer.bounds;
Rect boardRect = Rect.MinMaxRect(
gameBoardBounds.min.x,
gameBoardBounds.min.y,
gameBoardBounds.max.x,
gameBoardBounds.max.y
);
if (!TryGetRectIntersection(targetRect, boardRect, out Rect clampedRect))
{
Debug.LogWarning("Target wall rectangle is outside the game board bounds.");
return false;
}
GameObject wall = Instantiate(wallPrefab, Vector3.zero, Quaternion.identity);
var wallRenderer = wall.GetComponentInChildren<SpriteRenderer>();
if (wallRenderer == null)
{
Debug.LogError("Wall prefab is missing a SpriteRenderer in children.");
Destroy(wall);
return false;
}
float wallPaddingWorldUnits = PixelsToWorldUnits(wallPaddingPixelsPerSide, wallRenderer);
Rect paddedWallRect = ExpandRectInsideBounds(clampedRect, wallPaddingWorldUnits, boardRect);
ApplyWallTransform(wall.transform, wallRenderer, paddedWallRect);
spawnedWalls.Enqueue(wall);
UpdateFreeAreas(paddedWallRect);
ResolveWallIntersections(wallRenderer.bounds);
DestroyBarSegments();
if (boardArea <= geometryEpsilon)
{
boardArea = boardRect.width * boardRect.height;
}
int areaPercentage = boardArea > geometryEpsilon
? Mathf.RoundToInt((paddedWallRect.width * paddedWallRect.height / boardArea) * 100.0f)
: 0;
if (player != null)
{
player.OnWallSpawned(areaPercentage);
}
return true;
}
private void ResolveWallIntersections(Bounds wallBounds)
{
// Yummies and multipliers are awarded if intersected by the spawned wall.
var yummies = GameObject.FindGameObjectsWithTag("Yummy");
var multipliers = GameObject.FindGameObjectsWithTag("Multiplier");
var shark = GameObject.FindGameObjectWithTag("Shark");
foreach (var yummy in yummies)
{
if (yummy == null)
{
continue;
}
var yummyRenderer = yummy.GetComponent<SpriteRenderer>();
if (yummyRenderer == null || !wallBounds.Intersects(yummyRenderer.bounds))
{
continue;
}
YummyBase yummyBase = yummy.GetComponent<YummyBase>();
if (yummyBase != null)
{
yummyBase.AwardToPlayer(player);
}
else
{
Debug.LogError("YummyBase component not found on yummy object.");
}
}
foreach (var multiplier in multipliers)
{
if (multiplier == null)
{
continue;
}
var multiplierRenderer = multiplier.GetComponent<SpriteRenderer>();
if (multiplierRenderer == null || !wallBounds.Intersects(multiplierRenderer.bounds))
{
continue;
}
Multiplier multiplierComponent = multiplier.GetComponent<Multiplier>();
if (multiplierComponent != null)
{
multiplierComponent.AwardToPlayer(player);
}
else
{
Debug.LogError("Multiplier component not found on multiplier object.");
}
}
if (shark == null)
{
return;
}
var sharkRenderer = shark.GetComponent<SpriteRenderer>();
if (sharkRenderer == null || !wallBounds.Intersects(sharkRenderer.bounds))
{
return;
}
Shark sharkComponent = shark.GetComponent<Shark>();
if (sharkComponent != null)
{
sharkComponent.KillShark();
}
}
private void DestroyBarSegments()
{
GameObject[] barSegments = GameObject.FindGameObjectsWithTag("Bar");
foreach (GameObject barSegment in barSegments)
{
Destroy(barSegment);
}
}
private void ApplyWallTransform(Transform wallTransform, SpriteRenderer wallRenderer, Rect targetRect)
{
Vector2 baseSize = wallRenderer.bounds.size;