Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using NUnit.Framework;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
- using static ProcGen;
- using static UnityEditor.PlayerSettings;
- public class ProcGen : MonoBehaviour
- {
- public Dictionary<Vector2Int, Cell> Grid = new Dictionary<Vector2Int, Cell>();
- public GameObject grassPrefab;
- public GameObject sandPrefab;
- public GameObject waterPrefab;
- public GameObject forestPrefab;
- //variables that determine grid size and distance between cells
- public int chunks = 10;
- public int amplitude = 100;
- public List<Chunk> AllChunks = new List<Chunk>
- {
- new //Grass Chunk
- (
- "Grass",
- new List<WeightedChunk> //AllowedLeft
- {
- new("Grass", 5f),
- new("Forest", 2f),
- new("Beach", 1f),
- new ("Water", 1f),
- },
- new List<WeightedChunk> //AllowedRight
- {
- new("Grass", 5f),
- new("Forest", 2f),
- new("Beach", 1f),
- new ("Water", 1f),
- },
- new List<WeightedChunk>
- {
- new("Grass", 5f),
- new("Forest", 2f),
- new("Beach", 1f),
- new ("Water", 1f),
- },
- new List<WeightedChunk>
- {
- new("Grass", 5f),
- new("Forest", 2f),
- new("Beach", 1f),
- new ("Water", 1f),
- }
- ),
- new //Forest Chunk
- (
- "Forest",
- new List<WeightedChunk> //AllowedLeft
- {
- new("Grass", 2f),
- new("Forest", 5f),
- new ("Water", 1f),
- new ("Beach", 1f),
- },
- new List<WeightedChunk> //AllowedRight
- {
- new("Grass", 2f),
- new("Forest", 5f),
- new ("Water", 1f),
- new ("Beach", 1f),
- },
- new List<WeightedChunk>
- {
- new("Grass", 2f),
- new("Forest", 5f),
- new ("Water", 1f),
- new ("Beach", 1f),
- },
- new List<WeightedChunk>
- {
- new("Grass", 2f),
- new("Forest", 5f),
- new ("Water", 1f),
- new ("Beach", 1f),
- }
- ),
- new //Beach Chunk
- (
- "Beach",
- new List<WeightedChunk> //AllowedLeft
- {
- new ("Grass", 1f),
- new ("Forest", 1f),
- new ("Water", 30f),
- new ("Beach", 10f),
- },
- new List<WeightedChunk> //AllowedRight
- {
- new ("Grass", 1f),
- new ("Water", 30f),
- new ("Beach", 10f),
- new ("Forest", 1f),
- },
- new List<WeightedChunk>
- {
- new ("Grass", 1f),
- new ("Water", 30f),
- new ("Forest", 1f),
- new ("Beach", 10f),
- },
- new List<WeightedChunk>
- {
- new ("Grass", 1f),
- new ("Water", 30f),
- new ("Beach", 10f),
- new ("Forest", 1f),
- }
- ),
- new //Water Chunk
- (
- "Water",
- new List<WeightedChunk> //AllowedLeft
- {
- new ("Grass", 1f),
- new ("Water", 10f),
- new ("Forest", 1f),
- new ("Beach", 3f),
- },
- new List<WeightedChunk> //AllowedRight
- {
- new ("Grass", 1f),
- new ("Water", 10f),
- new ("Forest", 1f),
- new ("Beach", 3f),
- },
- new List<WeightedChunk>
- {
- new ("Grass", 1f),
- new ("Water", 10f),
- new ("Forest", 1f),
- new ("Beach", 3f),
- },
- new List<WeightedChunk>
- {
- new ("Grass", 1f),
- new ("Water", 10f),
- new ("Forest", 1f),
- new ("Beach", 3f),
- }
- )
- };
- readonly Vector2Int[] directions = new Vector2Int[]
- {
- Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right
- };
- // Start is called once before the first execution of Update after the MonoBehaviour is created
- void Start()
- {
- for(var x = 0; x<chunks; x++) //creates grid
- {
- for (var y = 0; y<chunks; y++)
- {
- Grid[new Vector2Int(x, y)] = new Cell();
- Grid[new Vector2Int(x, y)].worldPos = new Vector2(x * amplitude, y * amplitude);
- print("Current cell at " + x + ", " + y + " is now set to worldPos " + x*amplitude + ", " + y*amplitude);
- }
- }
- int safety = 1000;
- while (Grid.Values.Any(cell => cell.placedChunk == null) && safety-- > 0)
- {
- GenerateChunks();
- }
- if (safety <= 0)
- {
- Debug.LogError("Infinite loop detected — generation did not finish.");
- }
- }
- // Update is called once per frame
- void Update()
- {
- }
- void GenerateChunks()
- {
- if (Grid.Values.All(cell => cell.possibleChunks.Count == 0))
- {
- Debug.LogError("All remaining cells have no possible chunks — generation failed.");
- return;
- }
- List<Vector2Int> lowestEntropyCells = new List<Vector2Int>();
- foreach (var kvp in Grid) //step 1: determine possible chunks at each cell
- {
- Vector2Int pos = kvp.Key;
- Cell cell = kvp.Value;
- if (cell.placedChunk != null) continue;
- cell.possibleChunks = GetPossibleChunksForCell(pos);
- }
- int entropy = int.MaxValue; //make a list of cells with lowest entropy
- foreach (var kvp in Grid)
- {
- Vector2Int pos = kvp.Key;
- Cell cell = kvp.Value;
- if (cell.possibleChunks.Count == 0) continue;
- if (cell.possibleChunks.Count < entropy)
- {
- entropy = cell.possibleChunks.Count;
- lowestEntropyCells = new List<Vector2Int>
- {
- pos
- };
- }
- else if (cell.possibleChunks.Count == entropy)
- {
- lowestEntropyCells.Add(pos);
- }
- }
- if (lowestEntropyCells == null || lowestEntropyCells.Count == 0)
- {
- Debug.LogError("No cells with valid options left. Stopping generation.");
- return;
- }
- //step 2: randomly pick a cell from the lowest possible cells
- PickChunk(lowestEntropyCells[Random.Range(0, lowestEntropyCells.Count)]);
- //step 4: determine possible chunks at said cell
- //step 3: randomly generate a cell based on weightings
- }
- void PickChunk(Vector2Int pos)
- {
- List<WeightedChunk> validChunks = GetWeightedoptionsForCell(pos);
- float weight = 0f;
- foreach (var kvp in validChunks)
- {
- weight += kvp.weight;
- kvp.weight = weight;
- }
- float rand = Random.Range(0, weight);
- foreach (var kvp in validChunks)
- {
- if (rand <= kvp.weight)
- {
- Grid[pos].placedChunk = AllChunks.First(c => c.name == kvp.name);
- print("Set chunk at " + pos.x + ", " + pos.y + " To " + kvp.name);
- return;
- }
- }
- }
- List<string> GetPossibleChunksForCell(Vector2Int cellPos)
- {
- List<string> validChunks = null;
- foreach(var dir in directions)
- {
- Vector2Int neighborPos = cellPos + dir;
- if (!Grid.ContainsKey(neighborPos)) continue;
- Chunk neighbor = Grid[neighborPos].placedChunk;
- if (neighbor == null) continue;
- List<WeightedChunk> allowed = GetAllowedChunksFrom(neighbor, dir);
- List<string> names = allowed.Select(wc => wc.name).ToList();
- if (validChunks == null)
- {
- validChunks = new List<string>(names);
- }
- else
- {
- validChunks = validChunks.Intersect(names).ToList();
- }
- }
- return validChunks ?? AllChunks.Select(wc => wc.name).ToList();
- }
- List<WeightedChunk> GetWeightedoptionsForCell(Vector2Int cellPos)
- {
- Dictionary<string, float> chunkWeights = new Dictionary<string, float>();
- foreach (var dir in directions)
- {
- Vector2Int neighborPos = cellPos + dir; //gets position of neighbor in current direction
- if (!Grid.ContainsKey(neighborPos)) continue; //skips if the selected grid position doesn't exist (edge)
- Chunk neighbor = Grid[neighborPos].placedChunk; //sets chunk to currently placed chunk at location
- if (neighbor == null) continue; //if there is no chunk there, skip it
- List<WeightedChunk> allowed = GetAllowedChunksFrom(neighbor, dir); //gets allowed chunk from neighbor if found
- foreach (var wc in allowed) //for each allowed chunk, add weight to chunkweights
- {
- if (!chunkWeights.ContainsKey(wc.name)) //if chunk weights doesnt have this chunk, add it and set it to it's weight
- chunkWeights[wc.name] = wc.weight;
- else //if chunk weights has this chunk already, add weight to it from the selected cell
- chunkWeights[wc.name] += wc.weight;
- }
- }
- if (chunkWeights.Count == 0) //if no chunks were found in neighbors, failsafe to set all chunks equally, so it randomly chooses the first chunk
- {
- return AllChunks.Select(chunk => new WeightedChunk(chunk.name, 1f)).ToList();
- }
- List<string> valid = GetPossibleChunksForCell(cellPos); //filter weights to remove all weights that aren't allowed by all neighbors
- return chunkWeights
- .Where(kvp => valid.Contains(kvp.Key)) //removed any weighted option that doesn't exist in valid
- .Select(kvp => new WeightedChunk(kvp.Key, kvp.Value)) //convert chunkWeights keys and values to weightedChunk format
- .ToList(); //combine to a list to return
- }
- List<WeightedChunk> GetAllowedChunksFrom(Chunk neighbor, Vector2Int dir)
- {
- if (dir == Vector2Int.up) return neighbor.AllowedBottom; // Because we're above
- if (dir == Vector2Int.down) return neighbor.AllowedTop; // Because we're below
- if (dir == Vector2Int.left) return neighbor.AllowedRight;
- if (dir == Vector2Int.right) return neighbor.AllowedLeft;
- return new List<WeightedChunk>();
- }
- }
- public class Chunk
- {
- public string name;
- public List<WeightedChunk> AllowedLeft;
- public List<WeightedChunk> AllowedRight;
- public List<WeightedChunk> AllowedTop;
- public List<WeightedChunk> AllowedBottom;
- public Chunk(string name, List<WeightedChunk> allowedLeft, List<WeightedChunk> allowedRight, List<WeightedChunk> allowedTop, List<WeightedChunk> allowedBottom)
- {
- this.name = name;
- AllowedLeft = allowedLeft;
- AllowedRight = allowedRight;
- AllowedTop = allowedTop;
- AllowedBottom = allowedBottom;
- }
- }
- public class Cell
- {
- public Vector2 worldPos;
- public Chunk placedChunk;
- public List<string> possibleChunks;
- public Cell()
- {
- this.possibleChunks = new List<string> {"Grass", "Water", "Beach", "Forest" };
- }
- }
- public class WeightedChunk
- {
- public string name;
- public float weight;
- public WeightedChunk(string name, float weight)
- {
- this.name = name;
- this.weight = weight;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement