Advertisement
TannerBoBanner

procgen

Jun 8th, 2025
318
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 11.71 KB | None | 0 0
  1. using NUnit.Framework;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. using static ProcGen;
  7. using static UnityEditor.PlayerSettings;
  8.  
  9. public class ProcGen : MonoBehaviour
  10. {
  11.     public Dictionary<Vector2Int, Cell> Grid = new Dictionary<Vector2Int, Cell>();
  12.  
  13.     public GameObject grassPrefab;
  14.     public GameObject sandPrefab;
  15.     public GameObject waterPrefab;
  16.     public GameObject forestPrefab;
  17.  
  18.     //variables that determine grid size and distance between cells
  19.     public int chunks = 10;
  20.     public int amplitude = 100;
  21.  
  22.     public List<Chunk> AllChunks = new List<Chunk>
  23.     {
  24.     new //Grass Chunk
  25.         (
  26.         "Grass",
  27.         new List<WeightedChunk> //AllowedLeft
  28.         {
  29.             new("Grass", 5f),
  30.             new("Forest", 2f),
  31.             new("Beach", 1f),
  32.             new ("Water", 1f),
  33.         },
  34.         new List<WeightedChunk> //AllowedRight
  35.         {
  36.             new("Grass", 5f),
  37.             new("Forest", 2f),
  38.             new("Beach", 1f),
  39.             new ("Water", 1f),
  40.         },
  41.         new List<WeightedChunk>
  42.         {
  43.             new("Grass", 5f),
  44.             new("Forest", 2f),
  45.             new("Beach", 1f),
  46.             new ("Water", 1f),
  47.         },
  48.         new List<WeightedChunk>
  49.         {
  50.             new("Grass", 5f),
  51.             new("Forest", 2f),
  52.             new("Beach", 1f),
  53.             new ("Water", 1f),
  54.         }
  55.         ),
  56.     new //Forest Chunk
  57.         (
  58.         "Forest",
  59.         new List<WeightedChunk> //AllowedLeft
  60.         {
  61.             new("Grass", 2f),
  62.             new("Forest", 5f),
  63.             new ("Water", 1f),
  64.             new ("Beach", 1f),
  65.         },
  66.         new List<WeightedChunk> //AllowedRight
  67.         {
  68.             new("Grass", 2f),
  69.             new("Forest", 5f),
  70.             new ("Water", 1f),
  71.             new ("Beach", 1f),
  72.         },
  73.         new List<WeightedChunk>
  74.         {
  75.             new("Grass", 2f),
  76.             new("Forest", 5f),
  77.             new ("Water", 1f),
  78.             new ("Beach", 1f),
  79.         },
  80.         new List<WeightedChunk>
  81.         {
  82.             new("Grass", 2f),
  83.             new("Forest", 5f),
  84.             new ("Water", 1f),
  85.             new ("Beach", 1f),
  86.         }
  87.         ),
  88.     new //Beach Chunk
  89.         (
  90.         "Beach",
  91.         new List<WeightedChunk> //AllowedLeft
  92.         {
  93.             new ("Grass", 1f),
  94.             new ("Forest", 1f),
  95.             new ("Water", 30f),
  96.             new ("Beach", 10f),
  97.         },
  98.         new List<WeightedChunk> //AllowedRight
  99.         {
  100.             new ("Grass", 1f),
  101.             new ("Water", 30f),
  102.             new ("Beach", 10f),
  103.             new ("Forest", 1f),
  104.         },
  105.         new List<WeightedChunk>
  106.         {
  107.             new ("Grass", 1f),
  108.             new ("Water", 30f),
  109.             new ("Forest", 1f),
  110.             new ("Beach", 10f),
  111.         },
  112.         new List<WeightedChunk>
  113.         {
  114.             new ("Grass", 1f),
  115.             new ("Water", 30f),
  116.             new ("Beach", 10f),
  117.             new ("Forest", 1f),
  118.         }
  119.         ),
  120.     new //Water Chunk
  121.         (
  122.         "Water",
  123.         new List<WeightedChunk> //AllowedLeft
  124.         {
  125.             new ("Grass", 1f),
  126.             new ("Water", 10f),
  127.             new ("Forest", 1f),
  128.             new ("Beach", 3f),
  129.         },
  130.         new List<WeightedChunk> //AllowedRight
  131.         {
  132.             new ("Grass", 1f),
  133.             new ("Water", 10f),
  134.             new ("Forest", 1f),
  135.             new ("Beach", 3f),
  136.         },
  137.         new List<WeightedChunk>
  138.         {
  139.             new ("Grass", 1f),
  140.             new ("Water", 10f),
  141.             new ("Forest", 1f),
  142.             new ("Beach", 3f),
  143.         },
  144.         new List<WeightedChunk>
  145.         {
  146.             new ("Grass", 1f),
  147.             new ("Water", 10f),
  148.             new ("Forest", 1f),
  149.             new ("Beach", 3f),
  150.         }
  151.         )
  152.  
  153.     };
  154.  
  155.     readonly Vector2Int[] directions = new Vector2Int[]
  156.     {
  157.         Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right
  158.     };
  159.  
  160.  
  161.  
  162.     // Start is called once before the first execution of Update after the MonoBehaviour is created
  163.     void Start()
  164.     {
  165.         for(var x = 0; x<chunks; x++) //creates grid
  166.         {
  167.             for (var y = 0; y<chunks; y++)
  168.             {
  169.                 Grid[new Vector2Int(x, y)] = new Cell();
  170.                 Grid[new Vector2Int(x, y)].worldPos = new Vector2(x * amplitude, y * amplitude);
  171.                 print("Current cell at " + x + ", " + y + " is now set to worldPos " + x*amplitude + ", " + y*amplitude);
  172.             }
  173.         }
  174.  
  175.         int safety = 1000;
  176.         while (Grid.Values.Any(cell => cell.placedChunk == null) && safety-- > 0)
  177.         {
  178.             GenerateChunks();
  179.         }
  180.  
  181.         if (safety <= 0)
  182.         {
  183.             Debug.LogError("Infinite loop detected — generation did not finish.");
  184.         }
  185.  
  186.        
  187.     }
  188.  
  189.     // Update is called once per frame
  190.     void Update()
  191.     {
  192.        
  193.     }
  194.  
  195.     void GenerateChunks()
  196.     {
  197.         if (Grid.Values.All(cell => cell.possibleChunks.Count == 0))
  198.         {
  199.             Debug.LogError("All remaining cells have no possible chunks — generation failed.");
  200.             return;
  201.         }
  202.  
  203.         List<Vector2Int> lowestEntropyCells = new List<Vector2Int>();
  204.  
  205.         foreach (var kvp in Grid) //step 1: determine possible chunks at each cell
  206.         {
  207.             Vector2Int pos = kvp.Key;
  208.             Cell cell = kvp.Value;
  209.  
  210.             if (cell.placedChunk != null) continue;
  211.             cell.possibleChunks = GetPossibleChunksForCell(pos);
  212.  
  213.         }
  214.  
  215.         int entropy = int.MaxValue; //make a list of cells with lowest entropy
  216.         foreach (var kvp in Grid)
  217.         {
  218.             Vector2Int pos = kvp.Key;
  219.             Cell cell = kvp.Value;
  220.  
  221.             if (cell.possibleChunks.Count == 0) continue;
  222.  
  223.             if (cell.possibleChunks.Count < entropy)
  224.             {
  225.                 entropy = cell.possibleChunks.Count;
  226.                 lowestEntropyCells = new List<Vector2Int>
  227.                 {
  228.                     pos
  229.                 };
  230.             }
  231.             else if (cell.possibleChunks.Count == entropy)
  232.             {
  233.                 lowestEntropyCells.Add(pos);
  234.             }
  235.         }
  236.  
  237.         if (lowestEntropyCells == null || lowestEntropyCells.Count == 0)
  238.         {
  239.             Debug.LogError("No cells with valid options left. Stopping generation.");
  240.             return;
  241.         }
  242.  
  243.  
  244.  
  245.         //step 2: randomly pick a cell from the lowest possible cells
  246.  
  247.         PickChunk(lowestEntropyCells[Random.Range(0, lowestEntropyCells.Count)]);
  248.        
  249.         //step 4: determine possible chunks at said cell
  250.         //step 3: randomly generate a cell based on weightings
  251.     }
  252.  
  253.     void PickChunk(Vector2Int pos)
  254.     {
  255.         List<WeightedChunk> validChunks = GetWeightedoptionsForCell(pos);
  256.  
  257.         float weight = 0f;
  258.         foreach (var kvp in validChunks)
  259.         {
  260.             weight += kvp.weight;
  261.             kvp.weight = weight;
  262.         }
  263.  
  264.         float rand = Random.Range(0, weight);
  265.  
  266.         foreach (var kvp in validChunks)
  267.         {
  268.             if (rand <= kvp.weight)
  269.             {
  270.                 Grid[pos].placedChunk = AllChunks.First(c => c.name == kvp.name);
  271.                 print("Set chunk at " + pos.x + ", " + pos.y + " To " + kvp.name);
  272.  
  273.                
  274.                 return;
  275.             }
  276.         }
  277.     }
  278.  
  279.     List<string> GetPossibleChunksForCell(Vector2Int cellPos)
  280.     {
  281.         List<string> validChunks = null;
  282.  
  283.         foreach(var dir in directions)
  284.         {
  285.             Vector2Int neighborPos = cellPos + dir;
  286.  
  287.             if (!Grid.ContainsKey(neighborPos)) continue;
  288.  
  289.             Chunk neighbor = Grid[neighborPos].placedChunk;
  290.  
  291.             if (neighbor == null) continue;
  292.  
  293.             List<WeightedChunk> allowed = GetAllowedChunksFrom(neighbor, dir);
  294.             List<string> names = allowed.Select(wc => wc.name).ToList();
  295.  
  296.             if (validChunks == null)
  297.             {
  298.                 validChunks = new List<string>(names);
  299.             }
  300.             else
  301.             {
  302.                 validChunks = validChunks.Intersect(names).ToList();
  303.             }
  304.         }
  305.         return validChunks ?? AllChunks.Select(wc => wc.name).ToList();
  306.     }
  307.  
  308.     List<WeightedChunk> GetWeightedoptionsForCell(Vector2Int cellPos)
  309.     {
  310.         Dictionary<string, float> chunkWeights = new Dictionary<string, float>();
  311.  
  312.         foreach (var dir in directions)
  313.         {
  314.             Vector2Int neighborPos = cellPos + dir; //gets position of neighbor in current direction
  315.  
  316.             if (!Grid.ContainsKey(neighborPos)) continue; //skips if the selected grid position doesn't exist (edge)
  317.  
  318.             Chunk neighbor = Grid[neighborPos].placedChunk; //sets chunk to currently placed chunk at location
  319.             if (neighbor == null) continue; //if there is no chunk there, skip it
  320.  
  321.             List<WeightedChunk> allowed = GetAllowedChunksFrom(neighbor, dir); //gets allowed chunk from neighbor if found
  322.             foreach (var wc in allowed) //for each allowed chunk, add weight to chunkweights
  323.             {
  324.                 if (!chunkWeights.ContainsKey(wc.name)) //if chunk weights doesnt have this chunk, add it and set it to it's weight
  325.                     chunkWeights[wc.name] = wc.weight;
  326.                 else //if chunk weights has this chunk already, add weight to it from the selected cell
  327.                     chunkWeights[wc.name] += wc.weight;
  328.             }
  329.  
  330.         }
  331.         if (chunkWeights.Count == 0) //if no chunks were found in neighbors, failsafe to set all chunks equally, so it randomly chooses the first chunk
  332.         {
  333.             return AllChunks.Select(chunk => new WeightedChunk(chunk.name, 1f)).ToList();
  334.         }
  335.  
  336.         List<string> valid = GetPossibleChunksForCell(cellPos); //filter weights to remove all weights that aren't allowed by all neighbors
  337.         return chunkWeights
  338.             .Where(kvp => valid.Contains(kvp.Key)) //removed any weighted option that doesn't exist in valid
  339.             .Select(kvp => new WeightedChunk(kvp.Key, kvp.Value)) //convert chunkWeights keys and values to weightedChunk format
  340.             .ToList(); //combine to a list to return
  341.     }
  342.  
  343.     List<WeightedChunk> GetAllowedChunksFrom(Chunk neighbor, Vector2Int dir)
  344.     {
  345.         if (dir == Vector2Int.up) return neighbor.AllowedBottom;   // Because we're above
  346.         if (dir == Vector2Int.down) return neighbor.AllowedTop;    // Because we're below
  347.         if (dir == Vector2Int.left) return neighbor.AllowedRight;
  348.         if (dir == Vector2Int.right) return neighbor.AllowedLeft;
  349.         return new List<WeightedChunk>();
  350.     }
  351. }
  352.  
  353. public class Chunk
  354. {
  355.     public string name;
  356.  
  357.     public List<WeightedChunk> AllowedLeft;
  358.     public List<WeightedChunk> AllowedRight;
  359.     public List<WeightedChunk> AllowedTop;
  360.     public List<WeightedChunk> AllowedBottom;  
  361.  
  362.     public Chunk(string name, List<WeightedChunk> allowedLeft, List<WeightedChunk> allowedRight, List<WeightedChunk> allowedTop, List<WeightedChunk> allowedBottom)
  363.     {
  364.         this.name = name;
  365.         AllowedLeft = allowedLeft;
  366.         AllowedRight = allowedRight;
  367.         AllowedTop = allowedTop;
  368.         AllowedBottom = allowedBottom;
  369.     }
  370. }
  371.  
  372. public class Cell
  373. {
  374.     public Vector2 worldPos;
  375.     public Chunk placedChunk;
  376.     public List<string> possibleChunks;
  377.  
  378.     public Cell()
  379.     {
  380.         this.possibleChunks = new List<string> {"Grass", "Water", "Beach", "Forest" };
  381.     }
  382. }
  383.  
  384. public class WeightedChunk
  385. {
  386.     public string name;
  387.     public float weight;
  388.  
  389.     public WeightedChunk(string name, float weight)
  390.     {
  391.         this.name = name;
  392.         this.weight = weight;
  393.     }
  394. }
  395.  
  396.  
  397.  
  398.  
  399.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement