using UnityEngine; using System.Collections.Generic; public class RiverGenerator { public void GenerateRivers(MapData mapData) { // Find water bodies to connect var waterBodies = FindWaterBodies(mapData); Debug.Log($"Found {waterBodies.Count} water body tiles for river generation"); // Generate fewer, thinner rivers between some water bodies int riverCount = Mathf.Min(3, waterBodies.Count - 1); // Maximum 3 rivers riverCount = Mathf.Max(1, riverCount); // Force at least 1 river if possible Debug.Log($"Attempting to generate {riverCount} rivers"); for (int i = 0; i < riverCount && i < waterBodies.Count - 1; i++) { CreateRiver(mapData, waterBodies[i], waterBodies[i + 1]); Debug.Log($"Created river {i + 1} from {waterBodies[i]} to {waterBodies[i + 1]}"); } } private List FindWaterBodies(MapData mapData) { List waterBodyCenters = new List(); bool[,] visited = new bool[mapData.Width, mapData.Height]; // Find distinct water bodies (groups of connected water tiles) for (int x = 0; x < mapData.Width; x++) { for (int y = 0; y < mapData.Height; y++) { MapTile tile = mapData.GetTile(x, y); if (!visited[x, y] && (tile.terrainType == TerrainType.Ocean || tile.terrainType == TerrainType.Lake)) { // Found a new water body, find its center var waterTiles = FloodFillWaterBody(mapData, visited, x, y); if (waterTiles.Count > 5) // Only consider substantial water bodies { Vector2Int center = CalculateCenter(waterTiles); waterBodyCenters.Add(center); Debug.Log($"Found water body center at {center} with {waterTiles.Count} tiles"); } } } } return waterBodyCenters; } private List FloodFillWaterBody(MapData mapData, bool[,] visited, int startX, int startY) { List waterTiles = new List(); Queue queue = new Queue(); queue.Enqueue(new Vector2Int(startX, startY)); while (queue.Count > 0) { Vector2Int current = queue.Dequeue(); if (visited[current.x, current.y]) continue; visited[current.x, current.y] = true; waterTiles.Add(current); // Check neighbors for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; int newX = current.x + dx; int newY = current.y + dy; if (mapData.IsValidPosition(newX, newY) && !visited[newX, newY]) { MapTile tile = mapData.GetTile(newX, newY); if (tile.terrainType == TerrainType.Ocean || tile.terrainType == TerrainType.Lake) { queue.Enqueue(new Vector2Int(newX, newY)); } } } } } return waterTiles; } private Vector2Int CalculateCenter(List tiles) { if (tiles.Count == 0) return Vector2Int.zero; float avgX = 0f; float avgY = 0f; foreach (var tile in tiles) { avgX += tile.x; avgY += tile.y; } return new Vector2Int(Mathf.RoundToInt(avgX / tiles.Count), Mathf.RoundToInt(avgY / tiles.Count)); } private void CreateRiver(MapData mapData, Vector2Int start, Vector2Int end) { var riverPath = FindRiverPath(mapData, start, end); foreach (var point in riverPath) { if (mapData.IsValidPosition(point.x, point.y)) { MapTile tile = mapData.GetTile(point.x, point.y); if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.Shore) { tile.terrainType = TerrainType.River; tile.isWalkable = false; } } } // Smart diagonal blocking - only fill specific diagonal gaps BlockDiagonalCrossings(mapData, riverPath); } private void BlockDiagonalCrossings(MapData mapData, List riverPath) { foreach (var riverPoint in riverPath) { // Check for diagonal gaps that would allow diagonal crossing for (int dx = -1; dx <= 1; dx += 2) // -1 and 1 { for (int dy = -1; dy <= 1; dy += 2) // -1 and 1 { Vector2Int diagonal = new Vector2Int(riverPoint.x + dx, riverPoint.y + dy); Vector2Int horizontal = new Vector2Int(riverPoint.x + dx, riverPoint.y); Vector2Int vertical = new Vector2Int(riverPoint.x, riverPoint.y + dy); if (mapData.IsValidPosition(diagonal.x, diagonal.y) && mapData.IsValidPosition(horizontal.x, horizontal.y) && mapData.IsValidPosition(vertical.x, vertical.y)) { // If diagonal tile is river and both adjacent tiles are not river, // there's a crossable diagonal gap if (mapData.GetTile(diagonal.x, diagonal.y).terrainType == TerrainType.River && mapData.GetTile(horizontal.x, horizontal.y).terrainType != TerrainType.River && mapData.GetTile(vertical.x, vertical.y).terrainType != TerrainType.River) { // Fill one of the adjacent tiles to block diagonal crossing MapTile fillTile = mapData.GetTile(horizontal.x, horizontal.y); if (fillTile.terrainType == TerrainType.Plains || fillTile.terrainType == TerrainType.Shore) { fillTile.terrainType = TerrainType.River; fillTile.isWalkable = false; } } } } } } } private List FindRiverPath(MapData mapData, Vector2Int start, Vector2Int end) { List path = new List(); Vector2Int current = start; while (Vector2Int.Distance(current, end) > 1) { path.Add(current); // Move towards end with some meandering Vector2Int direction = new Vector2Int( end.x > current.x ? 1 : (end.x < current.x ? -1 : 0), end.y > current.y ? 1 : (end.y < current.y ? -1 : 0) ); // Add meandering if (Random.value < 0.4f) { direction.x += Random.Range(-1, 2); direction.y += Random.Range(-1, 2); direction.x = Mathf.Clamp(direction.x, -1, 1); direction.y = Mathf.Clamp(direction.y, -1, 1); } Vector2Int next = current + direction; if (mapData.IsValidPosition(next.x, next.y)) { current = next; } else { break; } } return path; } private void FillRiverDiagonals(MapData mapData, int x, int y) { // Check and fill diagonal gaps to prevent crossing without a bridge for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 || dy == 0) continue; // Skip cardinal directions int checkX = x + dx; int checkY = y + dy; if (mapData.IsValidPosition(checkX, checkY)) { MapTile neighbor = mapData.GetTile(checkX, checkY); if (neighbor.terrainType == TerrainType.River) { // Fill the gap int fillX = x + (dx > 0 ? 1 : 0); int fillY = y + (dy > 0 ? 1 : 0); if (mapData.IsValidPosition(fillX, fillY)) { MapTile fillTile = mapData.GetTile(fillX, fillY); if (fillTile.terrainType == TerrainType.Plains) { fillTile.terrainType = TerrainType.River; fillTile.isWalkable = false; } } } } } } } }