using UnityEngine; using System.Collections.Generic; public class WorldFiller : MonoBehaviour { public Player player; public SpriteRenderer gameBoardSpriteRenderer; public GameObject wallPrefab; [SerializeField] private float fallbackBarThicknessWorldUnits = 0.08f; [SerializeField] private float geometryEpsilon = 0.0001f; [SerializeField] private float wallPaddingPixelsPerSide = 1.0f; private Bounds gameBoardBounds; private Vector2?[] wallPositions; private int wallCount = 0; private Queue spawnedWalls = new Queue(); private List freeAreas = new List(); private float boardArea; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { gameBoardBounds = gameBoardSpriteRenderer.bounds; InitializeFreeAreas(); ClearWalls(); } public void ClearWalls() { wallPositions = new Vector2?[2]; wallPositions[0] = null; wallPositions[1] = null; wallCount = 0; } public void ClearSpawnedWalls() { while (spawnedWalls.Count > 0) { GameObject wall = spawnedWalls.Dequeue(); if (wall != null) { Destroy(wall); } } spawnedWalls.Clear(); } public void SetWallPosition(Vector2 position) { if (wallCount >= wallPositions.Length) { Debug.LogError("Index out of bounds for wall positions."); return; } wallPositions[wallCount] = position; wallCount++; SpawnWall(); } public void SpawnWall() { // If both wall positions are set, spawn a wall. if (wallPositions[0] != null && wallPositions[1] != null) { Vector2 wallPointA = wallPositions[0].Value; Vector2 wallPointB = wallPositions[1].Value; if (Vector2.Distance(wallPointA, wallPointB) <= geometryEpsilon) { Debug.LogWarning("Wall points are too close. Skipping wall spawn."); ClearWalls(); return; } // Get all the balls in the scene. var ballSpawner = FindAnyObjectByType(); var balls = ballSpawner != null ? ballSpawner.GetBalls() : null; bool isVerticalLine = Mathf.Abs(wallPointA.x - wallPointB.x) <= Mathf.Abs(wallPointA.y - wallPointB.y); float lineCenter = isVerticalLine ? (wallPointA.x + wallPointB.x) * 0.5f : (wallPointA.y + wallPointB.y) * 0.5f; float lineMin = isVerticalLine ? Mathf.Min(wallPointA.y, wallPointB.y) : Mathf.Min(wallPointA.x, wallPointB.x); float lineMax = isVerticalLine ? Mathf.Max(wallPointA.y, wallPointB.y) : Mathf.Max(wallPointA.x, wallPointB.x); float barThickness = GetCurrentBarThickness(isVerticalLine); float halfBarThickness = barThickness * 0.5f; Rect activeArea = FindActiveArea((wallPointA + wallPointB) * 0.5f, isVerticalLine, lineCenter, lineMin, lineMax); DetermineSideOccupancy( balls, activeArea, isVerticalLine, lineCenter, halfBarThickness, out bool sideAHasBall, out bool sideBHasBall ); Rect targetWallRect; // Spawn scenarios: // 1) Both sides empty => fill the whole available region. // 2) Only one side empty => fill that free side plus the bar strip. // 3) Both sides occupied => spawn only along the wall line, with bar thickness. if (!sideAHasBall && !sideBHasBall) { targetWallRect = activeArea; } else if (sideAHasBall && sideBHasBall) { if (isVerticalLine) { targetWallRect = Rect.MinMaxRect( lineCenter - halfBarThickness, lineMin, lineCenter + halfBarThickness, lineMax ); } else { targetWallRect = Rect.MinMaxRect( lineMin, lineCenter - halfBarThickness, lineMax, lineCenter + halfBarThickness ); } } else { if (isVerticalLine) { // Side A is left, side B is right for vertical lines. targetWallRect = !sideAHasBall ? Rect.MinMaxRect(activeArea.xMin, activeArea.yMin, lineCenter + halfBarThickness, activeArea.yMax) : Rect.MinMaxRect(lineCenter - halfBarThickness, activeArea.yMin, activeArea.xMax, activeArea.yMax); } else { // Side A is bottom, side B is top for horizontal lines. targetWallRect = !sideAHasBall ? Rect.MinMaxRect(activeArea.xMin, activeArea.yMin, activeArea.xMax, lineCenter + halfBarThickness) : Rect.MinMaxRect(activeArea.xMin, lineCenter - halfBarThickness, activeArea.xMax, activeArea.yMax); } } if (!TryGetRectIntersection(targetWallRect, activeArea, out Rect clampedWallRect)) { Debug.LogWarning("Could not create a valid wall rectangle inside the active free area."); ClearWalls(); return; } GameObject wall = Instantiate(wallPrefab, Vector3.zero, Quaternion.identity); var wallRenderer = wall.GetComponentInChildren(); if (wallRenderer == null) { Debug.LogError("Wall prefab is missing a SpriteRenderer in children."); Destroy(wall); ClearWalls(); return; } Rect boardRect = Rect.MinMaxRect( gameBoardBounds.min.x, gameBoardBounds.min.y, gameBoardBounds.max.x, gameBoardBounds.max.y ); float wallPaddingWorldUnits = PixelsToWorldUnits(wallPaddingPixelsPerSide, wallRenderer); Rect paddedWallRect = ExpandRectInsideBounds(clampedWallRect, wallPaddingWorldUnits, boardRect); ApplyWallTransform(wall.transform, wallRenderer, paddedWallRect); spawnedWalls.Enqueue(wall); var wallBounds = wallRenderer.bounds; UpdateFreeAreas(paddedWallRect); // Check if there are yummies, multipliers or the shark intersecting the spawned wall. // Yummies and multipliers are awarded to the player if they are intersected by the wall. // The shark is killed if it is intersected by the wall. var yummies = GameObject.FindGameObjectsWithTag("Yummy"); var multipliers = GameObject.FindGameObjectsWithTag("Multiplier"); var shark = GameObject.FindGameObjectWithTag("Shark"); foreach (var yummy in yummies) { if (yummy != null && wallBounds.Intersects(yummy.GetComponent().bounds)) { // Award the yummy to the player. YummyBase yummyBase = yummy.GetComponent(); if (yummyBase != null) { yummyBase.AwardToPlayer(player); } else { Debug.LogError("YummyBase component not found on yummy object."); } } } foreach (var multiplier in multipliers) { if (multiplier != null && wallBounds.Intersects(multiplier.GetComponent().bounds)) { // Award the multiplier to the player. Multiplier multiplierComponent = multiplier.GetComponent(); if (multiplierComponent != null) { multiplierComponent.AwardToPlayer(player); } else { Debug.LogError("Multiplier component not found on multiplier object."); } } } if (shark != null && wallBounds.Intersects(shark.GetComponent().bounds)) { // Kill the shark. shark.GetComponent().KillShark(); } // Destroy all bar segments after we are done. GameObject[] barSegments = GameObject.FindGameObjectsWithTag("Bar"); foreach (GameObject barSegment in barSegments) { Destroy(barSegment); } // Let the player know that the wall has been spawned. int areaPercentage = boardArea > geometryEpsilon ? Mathf.RoundToInt((paddedWallRect.width * paddedWallRect.height / boardArea) * 100.0f) : 0; player.OnWallSpawned(areaPercentage); // Clear the pending wall line. ClearWalls(); } } public void InitializeFreeAreas() { gameBoardBounds = gameBoardSpriteRenderer.bounds; Rect boardRect = Rect.MinMaxRect( gameBoardBounds.min.x, gameBoardBounds.min.y, gameBoardBounds.max.x, gameBoardBounds.max.y ); freeAreas.Clear(); freeAreas.Add(boardRect); boardArea = boardRect.width * boardRect.height; } private float GetCurrentBarThickness(bool isVerticalLine) { var barSegments = GameObject.FindGameObjectsWithTag("Bar"); foreach (var barSegment in barSegments) { if (barSegment == null) { continue; } var spriteRenderer = barSegment.GetComponent(); if (spriteRenderer == null) { continue; } float thickness = isVerticalLine ? spriteRenderer.bounds.size.x : spriteRenderer.bounds.size.y; if (thickness > geometryEpsilon) { return thickness; } } return fallbackBarThicknessWorldUnits; } private Rect FindActiveArea( Vector2 lineMidpoint, bool isVerticalLine, float lineCenter, float lineMin, float lineMax) { if (freeAreas.Count == 0) { InitializeFreeAreas(); } for (int i = 0; i < freeAreas.Count; i++) { if (RectContainsWithTolerance(freeAreas[i], lineMidpoint)) { return freeAreas[i]; } } Rect lineRect = isVerticalLine ? Rect.MinMaxRect(lineCenter - geometryEpsilon, lineMin, lineCenter + geometryEpsilon, lineMax) : Rect.MinMaxRect(lineMin, lineCenter - geometryEpsilon, lineMax, lineCenter + geometryEpsilon); float bestOverlap = -1.0f; Rect fallback = freeAreas[0]; for (int i = 0; i < freeAreas.Count; i++) { if (!TryGetRectIntersection(freeAreas[i], lineRect, out Rect overlap)) { continue; } float overlapArea = overlap.width * overlap.height; if (overlapArea > bestOverlap) { bestOverlap = overlapArea; fallback = freeAreas[i]; } } return fallback; } private void DetermineSideOccupancy( Queue balls, Rect activeArea, bool isVerticalLine, float lineCenter, float halfBarThickness, out bool sideAHasBall, out bool sideBHasBall) { sideAHasBall = false; sideBHasBall = false; if (balls == null) { return; } foreach (var ball in balls) { if (ball == null || !ball.activeInHierarchy) { continue; } Vector2 ballPosition = ball.transform.position; if (!RectContainsWithTolerance(activeArea, ballPosition)) { continue; } if (isVerticalLine) { if (ballPosition.x < lineCenter - halfBarThickness) { sideAHasBall = true; } else if (ballPosition.x > lineCenter + halfBarThickness) { sideBHasBall = true; } else { if (ballPosition.x <= lineCenter) { sideAHasBall = true; } else { sideBHasBall = true; } } } else { if (ballPosition.y < lineCenter - halfBarThickness) { sideAHasBall = true; } else if (ballPosition.y > lineCenter + halfBarThickness) { sideBHasBall = true; } else { if (ballPosition.y <= lineCenter) { sideAHasBall = true; } else { sideBHasBall = true; } } } if (sideAHasBall && sideBHasBall) { return; } } } private void ApplyWallTransform(Transform wallTransform, SpriteRenderer wallRenderer, Rect targetRect) { Vector2 baseSize = wallRenderer.bounds.size; if (baseSize.x <= geometryEpsilon || baseSize.y <= geometryEpsilon) { Debug.LogError("Cannot scale wall: base sprite size is invalid."); return; } Vector3 currentScale = wallTransform.localScale; float scaleX = currentScale.x * (targetRect.width / baseSize.x); float scaleY = currentScale.y * (targetRect.height / baseSize.y); wallTransform.localScale = new Vector3(scaleX, scaleY, currentScale.z); wallTransform.position = new Vector3(targetRect.center.x, targetRect.center.y, wallTransform.position.z); } private void UpdateFreeAreas(Rect occupiedArea) { List updatedAreas = new List(); for (int i = 0; i < freeAreas.Count; i++) { Rect freeArea = freeAreas[i]; if (!TryGetRectIntersection(freeArea, occupiedArea, out Rect overlap)) { updatedAreas.Add(freeArea); continue; } // Left remainder. AddValidRect(updatedAreas, Rect.MinMaxRect(freeArea.xMin, freeArea.yMin, overlap.xMin, freeArea.yMax)); // Right remainder. AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMax, freeArea.yMin, freeArea.xMax, freeArea.yMax)); // Bottom remainder. AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMin, freeArea.yMin, overlap.xMax, overlap.yMin)); // Top remainder. AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMin, overlap.yMax, overlap.xMax, freeArea.yMax)); } freeAreas = updatedAreas; } private void AddValidRect(List target, Rect candidate) { if (candidate.width <= geometryEpsilon || candidate.height <= geometryEpsilon) { return; } target.Add(candidate); } private bool TryGetRectIntersection(Rect a, Rect b, out Rect intersection) { float xMin = Mathf.Max(a.xMin, b.xMin); float xMax = Mathf.Min(a.xMax, b.xMax); float yMin = Mathf.Max(a.yMin, b.yMin); float yMax = Mathf.Min(a.yMax, b.yMax); if (xMax - xMin <= geometryEpsilon || yMax - yMin <= geometryEpsilon) { intersection = default; return false; } intersection = Rect.MinMaxRect(xMin, yMin, xMax, yMax); return true; } private bool RectContainsWithTolerance(Rect rect, Vector2 point) { return point.x >= rect.xMin - geometryEpsilon && point.x <= rect.xMax + geometryEpsilon && point.y >= rect.yMin - geometryEpsilon && point.y <= rect.yMax + geometryEpsilon; } private float PixelsToWorldUnits(float pixels, SpriteRenderer referenceRenderer) { float pixelsPerUnit = 100.0f; if (referenceRenderer != null && referenceRenderer.sprite != null && referenceRenderer.sprite.pixelsPerUnit > 0.0f) { pixelsPerUnit = referenceRenderer.sprite.pixelsPerUnit; } else if (gameBoardSpriteRenderer != null && gameBoardSpriteRenderer.sprite != null && gameBoardSpriteRenderer.sprite.pixelsPerUnit > 0.0f) { pixelsPerUnit = gameBoardSpriteRenderer.sprite.pixelsPerUnit; } return pixels / pixelsPerUnit; } private Rect ExpandRectInsideBounds(Rect rect, float padding, Rect boundsRect) { if (padding <= geometryEpsilon) { return rect; } Rect expanded = Rect.MinMaxRect( rect.xMin - padding, rect.yMin - padding, rect.xMax + padding, rect.yMax + padding ); if (TryGetRectIntersection(expanded, boundsRect, out Rect clampedExpanded)) { return clampedExpanded; } return rect; } }