Files
decay-grid/Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs

197 lines
7.7 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Core.Domain;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Random = UnityEngine.Random;
namespace Infrastructure.Unity
{
public class LevelGenerator : MonoBehaviour
{
[Header("Prefabs")]
[SerializeField] private TileViewAdapter tilePrefab;
[SerializeField] private ParticleSystem tileBreakVfxPrefab;
[SerializeField] private JumpPadAdapter jumpPadPrefab;
[SerializeField] private TeleporterAdapter teleporterPrefab;
[Header("Level")]
[SerializeField] private LevelDefinition levelDefinition;
[Header("Settings")]
[SerializeField] private float decayTime = 0.5f;
[SerializeField] private float fallingTime = 2.0f;
[SerializeField] private float minimumDistanceBetweenTeleporters = 3f;
public LevelDefinition Definition => levelDefinition;
private TilePool _tilePool;
public IEnumerator GenerateAsync(SoundManager soundManager, TileRegistry registry, CameraController camera, RumbleManager rumble, Action onComplete)
{
if (!levelDefinition)
{
Debug.LogError("LevelGenerator: levelDefinition is not assigned. Assign a LevelDefinition asset in the Inspector.");
onComplete?.Invoke();
yield break;
}
_tilePool = new TilePool(tilePrefab, transform);
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < levelDefinition.FloorCount; i++)
{
var coords = GetCoordsForFloor(levelDefinition.Floors[i].pattern);
yield return GenerateFloorAsync(i, coords, soundManager, registry, camera, rumble, stopwatch);
}
stopwatch.Stop();
onComplete?.Invoke();
}
private List<Vector2Int> GetCoordsForFloor(FloorPatternType pattern)
{
var sizeX = levelDefinition.GridSizeX;
var sizeY = levelDefinition.GridSizeY;
return pattern switch
{
FloorPatternType.Square => MapPatterns.GenerateSquare(sizeX, sizeY),
FloorPatternType.Donut => MapPatterns.GenerateDonut(sizeX, Mathf.FloorToInt(sizeX / 3f)),
FloorPatternType.Circle => MapPatterns.GenerateCircle(sizeX),
_ => MapPatterns.GenerateSquare(sizeX, sizeY)
};
}
private IEnumerator GenerateFloorAsync(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager,
TileRegistry registry, CameraController camera, RumbleManager rumble, Stopwatch stopwatch)
{
var yOffset = -(floorIndex * levelDefinition.FloorHeightDistance);
var xOffset = levelDefinition.GridSizeX / 2f;
var zOffset = levelDefinition.GridSizeY / 2f;
const long frameBudgetMs = 5;
foreach (var coord in coordinates)
{
var pos = new Vector3(coord.x - xOffset, yOffset, coord.y - zOffset);
CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, registry, camera, rumble);
if (stopwatch.ElapsedMilliseconds > frameBudgetMs)
{
stopwatch.Reset();
stopwatch.Start();
yield return null; // Wait for next frame
}
}
if (floorIndex > 0 && jumpPadPrefab)
{
var validSpot = coordinates[Random.Range(0, coordinates.Count)];
var padPos = new Vector3(validSpot.x - xOffset, yOffset + 0.6f, validSpot.y - zOffset);
Instantiate(jumpPadPrefab, padPos, Quaternion.identity, transform);
}
if (floorIndex > 0 && teleporterPrefab && coordinates.Count > 5)
{
// Spawn a pair of teleporters
var indexA = Random.Range(0, coordinates.Count);
int indexB;
do
{
indexB = Random.Range(0, coordinates.Count);
} while (indexB == indexA);
var spotA = coordinates[indexA];
var spotB = coordinates[indexB];
var posA = new Vector3(spotA.x - xOffset, yOffset + 0.6f, spotA.y - zOffset);
var posB = new Vector3(spotB.x - xOffset, yOffset + 0.6f, spotB.y - zOffset);
// check if positions are not too close
if (Vector3.Distance(posA, posB) < minimumDistanceBetweenTeleporters)
{
// find a new position for teleporter B
for (var i = 0; i < coordinates.Count; i++)
{
if (i == indexA) continue;
var newSpotB = coordinates[i];
var newPosB = new Vector3(newSpotB.x - xOffset, yOffset + 0.6f, newSpotB.y - zOffset);
if (Vector3.Distance(posA, newPosB) >= minimumDistanceBetweenTeleporters)
{
posB = newPosB;
break;
}
}
}
var telA = Instantiate(teleporterPrefab, posA, Quaternion.identity, transform);
var telB = Instantiate(teleporterPrefab, posB, Quaternion.identity, transform);
telA.Initialize(telB.transform);
telB.Initialize(telA.transform);
System.Action onTeleport = () =>
{
camera?.Shake(0.2f, 0.2f);
rumble?.PulseMedium();
};
telA.OnTeleport += onTeleport;
telB.OnTeleport += onTeleport;
}
}
private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager,
TileRegistry registry, CameraController camera, RumbleManager rumble)
{
var go = _tilePool.Get();
go.transform.position = position;
go.transform.rotation = Quaternion.identity;
go.transform.localScale = Vector3.one * 0.95f;
// 15% chance to be Fragile
var isFragile = Random.value < 0.15f;
var type = isFragile ? TileType.Fragile : TileType.Normal;
var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime, type);
go.Initialize(tileLogic, (t) => _tilePool.Return(t));
tileLogic.OnStateChanged += (t) =>
{
if (t.CurrentState == TileState.Warning)
{
soundManager?.PlayTileWarning(position);
}
else if (t.CurrentState == TileState.Falling)
{
soundManager?.PlayTileBreak(position);
if (t.Type == TileType.Fragile)
{
camera?.Shake(0.1f, 0.05f);
rumble?.PulseLight();
}
if (tileBreakVfxPrefab)
{
var vfx = Instantiate(tileBreakVfxPrefab, position, Quaternion.identity);
var main = vfx.main;
var currentColor = go.MeshRenderer.material.color;
main.startColor = currentColor;
Destroy(vfx.gameObject, 2f);
}
}
};
registry.Register(tileLogic, go);
}
}
}