| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- using UnityEngine;
- using System.Collections.Generic;
- public class TerrainGenerator
- {
- private NoiseGenerator noiseGenerator;
- private List<Vector2Int> originalRiverPositions;
- public TerrainGenerator()
- {
- noiseGenerator = new NoiseGenerator();
- originalRiverPositions = new List<Vector2Int>();
- }
- public void GenerateTerrain(MapData mapData)
- {
- // Clear previous river positions
- originalRiverPositions.Clear();
- // Generate base heightmap
- GenerateHeightmap(mapData);
- // Generate water bodies first (they take priority)
- GenerateOceans(mapData);
- GenerateLakes(mapData);
- GenerateRivers(mapData);
- // Store river positions after generation but before forests
- StoreRiverPositions(mapData);
- // Generate mountains (avoid water areas)
- GenerateMountains(mapData);
- // Generate forests (can overlap with rivers initially)
- GenerateForests(mapData);
- // Create river paths through forests (this cuts paths after forests are placed)
- CreateRiverPathsThroughForests(mapData);
- // Generate shores (around water)
- GenerateShores(mapData);
- }
- private void GenerateHeightmap(MapData mapData)
- {
- for (int x = 0; x < mapData.Width; x++)
- {
- for (int y = 0; y < mapData.Height; y++)
- {
- float height = noiseGenerator.GetNoise(x, y, 0.1f);
- mapData.GetTile(x, y).height = height;
- }
- }
- }
- private void GenerateOceans(MapData mapData)
- {
- // Generate one large ocean that extends to map edge
- int edgeChoice = Random.Range(0, 4); // 0=left, 1=right, 2=top, 3=bottom
- int oceanX, oceanY, oceanWidth, oceanHeight;
- switch (edgeChoice)
- {
- case 0: // Left edge ocean - cover entire left edge
- oceanX = 0;
- oceanY = 0;
- oceanWidth = Random.Range(40, 70);
- oceanHeight = mapData.Height;
- break;
- case 1: // Right edge ocean - cover entire right edge
- oceanWidth = Random.Range(40, 70);
- oceanX = mapData.Width - oceanWidth;
- oceanY = 0;
- oceanHeight = mapData.Height;
- break;
- case 2: // Top edge ocean - cover entire top edge
- oceanX = 0;
- oceanHeight = Random.Range(40, 70);
- oceanY = mapData.Height - oceanHeight;
- oceanWidth = mapData.Width;
- break;
- default: // Bottom edge ocean - cover entire bottom edge
- oceanX = 0;
- oceanY = 0;
- oceanWidth = mapData.Width;
- oceanHeight = Random.Range(40, 70);
- break;
- }
- // Create the main ocean shape
- List<Vector2Int> oceanTiles = new List<Vector2Int>();
- for (int x = oceanX; x < oceanX + oceanWidth && x < mapData.Width; x++)
- {
- for (int y = oceanY; y < oceanY + oceanHeight && y < mapData.Height; y++)
- {
- // Calculate distance from the map edge that should be ocean
- float distanceFromOceanEdge = 0f;
- if (edgeChoice == 0) distanceFromOceanEdge = x; // Left edge
- else if (edgeChoice == 1) distanceFromOceanEdge = mapData.Width - 1 - x; // Right edge
- else if (edgeChoice == 2) distanceFromOceanEdge = mapData.Height - 1 - y; // Top edge
- else distanceFromOceanEdge = y; // Bottom edge
- // Create probability that decreases from edge with some noise
- float maxDepth = Mathf.Min(oceanWidth, oceanHeight) * 0.8f;
- float edgeProbability = 1f - (distanceFromOceanEdge / maxDepth);
- // Add noise for coastline variation, but keep it more cohesive
- float noise = noiseGenerator.GetNoise(x, y, 0.1f) * 0.2f;
- float finalProbability = Mathf.Clamp01(edgeProbability + noise);
- // Ensure edge tiles are always ocean for proper ocean coverage
- if (distanceFromOceanEdge < 3 || finalProbability > 0.4f)
- {
- oceanTiles.Add(new Vector2Int(x, y));
- }
- }
- } // Apply ocean tiles
- foreach (var pos in oceanTiles)
- {
- mapData.GetTile(pos.x, pos.y).terrainType = TerrainType.Ocean;
- mapData.GetTile(pos.x, pos.y).isWalkable = false;
- }
- // Fill gaps more aggressively to reduce scattered cells
- FillTerrainGaps(mapData, TerrainType.Ocean, 3);
- }
- private void GenerateLakes(MapData mapData)
- {
- int lakeCount = Random.Range(3, 6);
- for (int i = 0; i < lakeCount; i++)
- {
- int attempts = 0;
- while (attempts < 20)
- {
- attempts++;
- int lakeX = Random.Range(10, mapData.Width - 10);
- int lakeY = Random.Range(10, mapData.Height - 10);
- int lakeSize = Random.Range(4, 10);
- // Check if area is suitable for a lake (no water nearby)
- if (IsAreaSuitable(mapData, lakeX, lakeY, lakeSize * 2, TerrainType.Plains))
- {
- CreateLake(mapData, lakeX, lakeY, lakeSize);
- break;
- }
- }
- }
- }
- private void CreateLake(MapData mapData, int centerX, int centerY, int size)
- {
- List<Vector2Int> lakeTiles = new List<Vector2Int>();
- for (int x = centerX - size; x <= centerX + size; x++)
- {
- for (int y = centerY - size; y <= centerY + size; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- float distance = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerY));
- if (distance <= size * Random.Range(0.8f, 1.2f))
- {
- lakeTiles.Add(new Vector2Int(x, y));
- }
- }
- }
- }
- // Apply lake tiles
- foreach (var pos in lakeTiles)
- {
- mapData.GetTile(pos.x, pos.y).terrainType = TerrainType.Lake;
- mapData.GetTile(pos.x, pos.y).isWalkable = false;
- }
- // Fill gaps
- FillTerrainGaps(mapData, TerrainType.Lake, 1);
- }
- private void GenerateRivers(MapData mapData)
- {
- var riverGenerator = new RiverGenerator();
- riverGenerator.GenerateRivers(mapData);
- }
- private void GenerateForests(MapData mapData)
- {
- // Generate one huge forest first (if possible)
- GenerateHugeForest(mapData);
- // Generate medium forests
- int mediumForestCount = Random.Range(3, 6);
- for (int i = 0; i < mediumForestCount; i++)
- {
- int attempts = 0;
- while (attempts < 20)
- {
- attempts++;
- int forestX = Random.Range(10, mapData.Width - 10);
- int forestY = Random.Range(10, mapData.Height - 10);
- int forestSize = Random.Range(8, 18);
- if (IsAreaSuitableForForest(mapData, forestX, forestY, forestSize))
- {
- CreateForest(mapData, forestX, forestY, forestSize);
- break;
- }
- }
- }
- // Generate small forests and forest patches
- int smallForestCount = Random.Range(5, 10);
- for (int i = 0; i < smallForestCount; i++)
- {
- int attempts = 0;
- while (attempts < 15)
- {
- attempts++;
- int forestX = Random.Range(5, mapData.Width - 5);
- int forestY = Random.Range(5, mapData.Height - 5);
- int forestSize = Random.Range(3, 8);
- if (IsAreaSuitableForForest(mapData, forestX, forestY, forestSize))
- {
- CreateForest(mapData, forestX, forestY, forestSize);
- break;
- }
- }
- }
- }
- private void GenerateHugeForest(MapData mapData)
- {
- int attempts = 0;
- while (attempts < 10)
- {
- attempts++;
- // Try to place a huge forest away from water and edges
- int forestX = Random.Range(25, mapData.Width - 45);
- int forestY = Random.Range(25, mapData.Height - 45);
- int forestSize = Random.Range(25, 40);
- if (IsAreaSuitableForForest(mapData, forestX, forestY, forestSize))
- {
- CreateHugeForest(mapData, forestX, forestY, forestSize);
- break;
- }
- }
- }
- private void CreateHugeForest(MapData mapData, int centerX, int centerY, int size)
- {
- List<Vector2Int> forestTiles = new List<Vector2Int>();
- // Create multiple overlapping forest areas for irregular huge forest
- int subForests = Random.Range(3, 6);
- for (int sf = 0; sf < subForests; sf++)
- {
- // Each sub-forest has its own center near the main center
- int subCenterX = centerX + Random.Range(-size / 3, size / 3);
- int subCenterY = centerY + Random.Range(-size / 3, size / 3);
- int subSize = Random.Range(size / 2, size);
- // Generate organic forest shape for this sub-forest
- for (int x = subCenterX - subSize; x <= subCenterX + subSize; x++)
- {
- for (int y = subCenterY - subSize; y <= subCenterY + subSize; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- float distance = Vector2.Distance(new Vector2(x, y), new Vector2(subCenterX, subCenterY));
- float maxDistance = subSize * Random.Range(0.7f, 1.3f);
- // Add some noise for irregular edges
- float noise = noiseGenerator.GetNoise(x, y, 0.2f);
- maxDistance += noise * (subSize * 0.2f);
- if (distance <= maxDistance)
- {
- MapTile tile = mapData.GetTile(x, y);
- // Allow forests on Plains and Rivers - we'll cut river paths later
- if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.River)
- {
- forestTiles.Add(new Vector2Int(x, y));
- }
- }
- }
- }
- }
- }
- // Apply forest tiles (only on Plains now, rivers are left as paths)
- foreach (var pos in forestTiles)
- {
- MapTile tile = mapData.GetTile(pos.x, pos.y);
- tile.terrainType = TerrainType.Forest;
- }
- // Fill gaps more aggressively for huge forests
- FillTerrainGaps(mapData, TerrainType.Forest, 2);
- }
- private bool IsAreaSuitableForForest(MapData mapData, int centerX, int centerY, int size)
- {
- int plainsCount = 0;
- int totalCount = 0;
- for (int x = centerX - size / 2; x <= centerX + size / 2; x++)
- {
- for (int y = centerY - size / 2; y <= centerY + size / 2; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- totalCount++;
- var tile = mapData.GetTile(x, y);
- if (tile.terrainType == TerrainType.Plains)
- {
- plainsCount++;
- }
- }
- }
- }
- return totalCount > 0 && (float)plainsCount / totalCount > 0.8f;
- }
- private void CreateForest(MapData mapData, int centerX, int centerY, int size)
- {
- List<Vector2Int> forestTiles = new List<Vector2Int>();
- // Determine forest type - some are dense, some are sparse
- bool isDenseForest = Random.value < 0.4f; // 40% chance for dense forest
- bool hasClearing = Random.value < 0.3f && size > 6; // 30% chance for clearing in larger forests
- // Create organic forest shape with variation
- for (int x = centerX - size; x <= centerX + size; x++)
- {
- for (int y = centerY - size; y <= centerY + size; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- float distance = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerY));
- float maxDistance = size * Random.Range(0.6f, 1.2f);
- // Add noise for irregular edges
- float noise = noiseGenerator.GetNoise(x, y, 0.15f);
- float noiseInfluence = isDenseForest ? 0.1f : 0.3f; // Dense forests are more regular
- maxDistance += noise * (size * noiseInfluence);
- // Create clearings in some forests
- bool inClearing = false;
- if (hasClearing)
- {
- float clearingDistance = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerY));
- inClearing = clearingDistance < size * 0.3f && Random.value < 0.7f;
- }
- if (distance <= maxDistance && !inClearing)
- {
- MapTile tile = mapData.GetTile(x, y);
- // Allow forests on Plains and Rivers - we'll cut river paths later
- if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.River)
- {
- // Dense forests fill more completely, sparse forests are more scattered
- float fillProbability = isDenseForest ? 0.85f : 0.65f;
- if (Random.value < fillProbability)
- {
- forestTiles.Add(new Vector2Int(x, y));
- }
- }
- }
- }
- }
- }
- // Apply forest tiles (only on Plains now, rivers are left as paths)
- foreach (var pos in forestTiles)
- {
- MapTile tile = mapData.GetTile(pos.x, pos.y);
- tile.terrainType = TerrainType.Forest;
- }
- // Dense forests get better gap filling
- int gapFillLevel = isDenseForest ? 2 : 1;
- FillTerrainGaps(mapData, TerrainType.Forest, gapFillLevel);
- }
- private void GenerateMountains(MapData mapData)
- {
- // Generate more varied mountain ranges with different sizes
- int mountainCount = Random.Range(2, 5); // Slightly more mountain ranges
- for (int i = 0; i < mountainCount; i++)
- {
- int attempts = 0;
- while (attempts < 20)
- {
- attempts++;
- int mountainX = Random.Range(15, mapData.Width - 15);
- int mountainY = Random.Range(15, mapData.Height - 15);
- // More varied mountain sizes - some small, some large
- int mountainSize;
- float sizeRoll = Random.value;
- if (sizeRoll < 0.3f)
- mountainSize = Random.Range(5, 10); // Small mountains
- else if (sizeRoll < 0.7f)
- mountainSize = Random.Range(10, 18); // Medium mountains
- else
- mountainSize = Random.Range(18, 28); // Large mountain ranges
- if (IsAreaSuitableForMountains(mapData, mountainX, mountainY, mountainSize))
- {
- CreateMountainRange(mapData, mountainX, mountainY, mountainSize);
- break;
- }
- }
- }
- }
- private bool IsAreaSuitableForMountains(MapData mapData, int centerX, int centerY, int size)
- {
- // Check if area is mostly plains (not water or already mountains)
- int suitableCount = 0;
- int totalCount = 0;
- for (int x = centerX - size; x <= centerX + size; x++)
- {
- for (int y = centerY - size; y <= centerY + size; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- totalCount++;
- var tile = mapData.GetTile(x, y);
- if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.Forest)
- {
- suitableCount++;
- }
- }
- }
- }
- return totalCount > 0 && (float)suitableCount / totalCount > 0.7f;
- }
- private void CreateMountainRange(MapData mapData, int centerX, int centerY, int size)
- {
- List<Vector2Int> mountainTiles = new List<Vector2Int>();
- // Create mountain range with multiple peaks - more varied
- int peaks = Random.Range(2, 6); // More variation in peak count
- for (int p = 0; p < peaks; p++)
- {
- // More random peak placement instead of evenly spaced
- float angle = Random.Range(0f, 360f) * Mathf.Deg2Rad;
- float peakDistance = Random.Range(0.2f, 0.5f) * size; // Varied distance from center
- int peakX = centerX + Mathf.RoundToInt(Mathf.Cos(angle) * peakDistance);
- int peakY = centerY + Mathf.RoundToInt(Mathf.Sin(angle) * peakDistance);
- // Each peak has its own size variation
- int peakSize = Random.Range(size / 3, size);
- for (int x = peakX - peakSize / 2; x <= peakX + peakSize / 2; x++)
- {
- for (int y = peakY - peakSize / 2; y <= peakY + peakSize / 2; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- float distance = Vector2.Distance(new Vector2(x, y), new Vector2(peakX, peakY));
- // More random distance threshold for irregular mountain shapes
- float maxDistance = peakSize * Random.Range(0.3f, 0.9f);
- // Add noise for more natural mountain edges
- float noise = noiseGenerator.GetNoise(x, y, 0.15f);
- maxDistance += noise * (peakSize * 0.2f);
- if (distance <= maxDistance)
- {
- MapTile tile = mapData.GetTile(x, y);
- if (!tile.IsWater()) // Don't override water
- {
- mountainTiles.Add(new Vector2Int(x, y));
- }
- }
- }
- }
- }
- }
- // Apply mountain tiles
- foreach (var pos in mountainTiles)
- {
- mapData.GetTile(pos.x, pos.y).terrainType = TerrainType.Mountain;
- mapData.GetTile(pos.x, pos.y).isWalkable = false;
- }
- // Fill gaps
- FillTerrainGaps(mapData, TerrainType.Mountain, 1);
- }
- private void FillTerrainGaps(MapData mapData, TerrainType terrainType, int maxGapSize)
- {
- for (int x = 0; x < mapData.Width; x++)
- {
- for (int y = 0; y < mapData.Height; y++)
- {
- MapTile tile = mapData.GetTile(x, y);
- if (tile.terrainType != terrainType)
- {
- // Check if this tile is surrounded by the target terrain type
- int neighborCount = 0;
- int targetNeighbors = 0;
- for (int dx = -maxGapSize; dx <= maxGapSize; dx++)
- {
- for (int dy = -maxGapSize; dy <= maxGapSize; dy++)
- {
- if (dx == 0 && dy == 0) continue;
- int checkX = x + dx;
- int checkY = y + dy;
- if (mapData.IsValidPosition(checkX, checkY))
- {
- neighborCount++;
- if (mapData.GetTile(checkX, checkY).terrainType == terrainType)
- {
- targetNeighbors++;
- }
- }
- }
- }
- // If most neighbors are the target terrain, fill this gap
- if (neighborCount > 0 && (float)targetNeighbors / neighborCount > 0.6f)
- {
- if (terrainType == TerrainType.Ocean || terrainType == TerrainType.Lake || terrainType == TerrainType.River)
- {
- tile.isWalkable = false;
- }
- if (terrainType == TerrainType.Mountain)
- {
- tile.isWalkable = false;
- }
- tile.terrainType = terrainType;
- }
- }
- }
- }
- }
- private void GenerateShores(MapData mapData)
- {
- for (int x = 0; x < mapData.Width; x++)
- {
- for (int y = 0; y < mapData.Height; y++)
- {
- MapTile tile = mapData.GetTile(x, y);
- if (tile.terrainType == TerrainType.Plains)
- {
- if (IsNearWater(mapData, x, y, 2)) // Reduced range for shores
- {
- tile.terrainType = TerrainType.Shore;
- }
- }
- }
- }
- }
- private bool IsNearWater(MapData mapData, int x, int y, int range)
- {
- for (int dx = -range; dx <= range; dx++)
- {
- for (int dy = -range; dy <= range; dy++)
- {
- int checkX = x + dx;
- int checkY = y + dy;
- if (mapData.IsValidPosition(checkX, checkY))
- {
- MapTile checkTile = mapData.GetTile(checkX, checkY);
- if (checkTile.IsWater())
- {
- return true;
- }
- }
- }
- }
- return false;
- }
- private bool IsAreaSuitable(MapData mapData, int centerX, int centerY, int size, TerrainType requiredType)
- {
- int suitableCount = 0;
- int totalCount = 0;
- for (int x = centerX - size; x <= centerX + size; x++)
- {
- for (int y = centerY - size; y <= centerY + size; y++)
- {
- if (mapData.IsValidPosition(x, y))
- {
- totalCount++;
- if (mapData.GetTile(x, y).terrainType == requiredType)
- {
- suitableCount++;
- }
- }
- }
- }
- return totalCount > 0 && (float)suitableCount / totalCount > 0.8f;
- }
- private void StoreRiverPositions(MapData mapData)
- {
- originalRiverPositions.Clear();
- for (int x = 0; x < mapData.Width; x++)
- {
- for (int y = 0; y < mapData.Height; y++)
- {
- var tile = mapData.GetTile(x, y);
- if (tile.terrainType == TerrainType.River)
- {
- originalRiverPositions.Add(new Vector2Int(x, y));
- }
- }
- }
- }
- private void CreateRiverPathsThroughForests(MapData mapData)
- {
- // Restore rivers that got covered by forests with more natural-looking paths
- foreach (var riverPos in originalRiverPositions)
- {
- var tile = mapData.GetTile(riverPos.x, riverPos.y);
- // If this river position got turned into forest, restore it as river
- if (tile.terrainType == TerrainType.Forest)
- {
- tile.terrainType = TerrainType.River;
- // Create narrower, more natural river paths
- // Only expand along the main river flow directions (not diagonally)
- for (int dx = -1; dx <= 1; dx++)
- {
- for (int dy = -1; dy <= 1; dy++)
- {
- // Skip center (already handled) and diagonal directions (keep forest)
- if ((dx == 0 && dy == 0) || (Mathf.Abs(dx) == 1 && Mathf.Abs(dy) == 1))
- continue;
- int x = riverPos.x + dx;
- int y = riverPos.y + dy;
- if (mapData.IsValidPosition(x, y))
- {
- var neighborTile = mapData.GetTile(x, y);
- // Only expand river into forests, and with lower probability for narrower channels
- if (neighborTile.terrainType == TerrainType.Forest && Random.value < 0.3f)
- {
- neighborTile.terrainType = TerrainType.River;
- }
- }
- }
- }
- }
- }
- }
- }
|