Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Idealogy Behind Creating "Rules" for Noise Generation - Fully Commented C# Code

Discussion in 'Scripting' started by keenanwoodall, Feb 11, 2015.

  1. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    595
    I've been following an excellent tutorial by Sebastian Lague and something he did interested me. To start of the noise generation he created a standard 2d array and randomly filled each index with a value of either 0 or 1. Then he ran all of the indices through a loop and applied a "rule" to it. The fact that he said rule made me think, "why not create more rules to choose from?". His rule turns a static looking array of values into a smoother looking terrain. His rules was a simple set of if/else statements. I threw that into another if statement and made an enum to hold different rule names. I names his angularize because I couldn't think of anything else, then I started trying to think of other simple rules that I could create and try out. I'm working on one called "Puffy". It will look at the given index and make all of it's surround indices solid. That's where I'm stuck. I have to check to see if a neighbor is null so that I don't try and make something solid that doesn't exist. Now that I'm typing this, I'm not sure if this post is mainly to assist me in this small problem, or to ask what people think of the concept of applying rules to an otherwise boring set of noise data. Is what I'm talking about a novel concept? Is it interesting? Does anyone know how to solve my simple problem? Anyways, here's the link to Sebastian's awesome tutorial and my code is pasted below. Unity is telling me that the index I'm trying to access is out of range, but it shouldn't be accessing it if it's null so...That's where I'm lost. <--Tutorial-->
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5.  
    6. public class MapGenerator : MonoBehaviour
    7. {
    8.     //The map's size
    9.     public int width;
    10.     public int height;
    11.  
    12.     //The amount of times the rule is applied
    13.     public int ruleIterations = 5;
    14.  
    15.     //The amount that the rule affects the noise per iteration
    16.     [Range(0, 8)]
    17.     public int ruleValue = 4;
    18.  
    19.     public enum Rule{Angularize, Puff};
    20.     public Rule specialNoiseRule = Rule.Angularize;
    21.    
    22.     //The map will be generated from a seed so that the same map can be recreated via its seed
    23.     public string seed;
    24.     //If we don't want to use a custom seed we can have 1 randomly generated
    25.     public bool useRandomSeed;
    26.     //When the map is randomly filled it needs to obey a loose guideline of how much should be solid
    27.     [Range(0, 100)]
    28.     public int randomFillPercent;
    29.  
    30.     //This defines a grid of integers, any tile that is equal to 0 will be empty, and any tile that is equal to 1 will be solid
    31.     //This grid of numbers will be filled with random 0s and 1s
    32.     private int[,] map;
    33.  
    34.     void Start()
    35.     {
    36.         GenerateMap();
    37.     }
    38.  
    39.     void Update()
    40.     {
    41.         GenerateMap();
    42.     }
    43.  
    44.     void GenerateMap()
    45.     {
    46.         //Map is a new integer array and is size is width by height
    47.         map = new int[width, height];
    48.         //RandomFillMap is called to fill map with  basic data
    49.         RandomFillMap();
    50.  
    51.         for(int i = 0; i < ruleIterations; i++)
    52.         {
    53.             ApplyRule();
    54.         }
    55.     }
    56.  
    57.     //This method creates the rough data for ApplyRule to smooth
    58.     void RandomFillMap()
    59.     {
    60.         //If a random seed is wanted-
    61.         if(useRandomSeed)
    62.             //Seed is set to the amout of time that the game has been running
    63.             seed = Time.time.ToString();
    64.         //This will return a unique integer, simulating randomness
    65.         //Prng stands for Pseudo Random Number Generator
    66.         System.Random prng = new System.Random(seed.GetHashCode());
    67.  
    68.         //Each index in the maps array of numbers is looped through
    69.         //For every x index-
    70.         for(int x = 0; x < width; x++)
    71.         {
    72.             //Every y index will be iterated through as well
    73.             for(int y = 0; y < height; y++)
    74.             {
    75.                 //The current index will be set to either 0 or 1
    76.                 //If the current index is on a corner or far side-
    77.                 if(x == 0 || x == (width - 1) || y == 0 || y == (height - 1))
    78.                 {
    79.                     //It'll be set to 1; solid
    80.                     map[x, y] = 1;
    81.                 }
    82.                 //Otherwise the current index isn't on the side and it's value will be randomly generated
    83.                 else
    84.                 {
    85.                     //Using the if/else notation, the current index will be set to 1 if the generated number is less than randomFillPercent,
    86.                     //otherwise it will be set to 0
    87.                     map[x, y] = (prng.Next(0, 100) < randomFillPercent) ? 1 : 0;
    88.                 }
    89.             }
    90.         }
    91.     }
    92.  
    93.     //ApplyRule lets the user choose from and create custom rules that will be applied to the map
    94.     //Withought this method, the map would just be boring static
    95.     void ApplyRule()
    96.     {
    97.         //Once again, every index in the map's array is looped through
    98.         for(int x = 0; x < width; x++)
    99.         {
    100.             for(int y = 0; y < height; y++)
    101.             {
    102.                 //Now some simple rules will be made up based on the amount of wall tiles
    103.                 //----RULES----//
    104.  
    105.                 //ANGULARIZE:
    106.                     //This rule finds all of the surrounding indices; if enough are solid the current index is solid as well
    107.                     //Otherwise it will be made empty
    108.                 if(specialNoiseRule == Rule.Angularize)
    109.                 {
    110.                     //We want to know how many neighboring tiles does the current tile index have that are walls
    111.                     //This will be found via the method GetSurroundingWallCount below
    112.                     int surroundingWalls = GetSurroundingWallCount(x, y);
    113.  
    114.                     //If the current index is surrounded by a certain amount (ruleValue) of solid tiles it'll be made solid as well
    115.                     if(surroundingWalls > ruleValue)
    116.                     {
    117.                         map[x, y] = 1;
    118.                     }
    119.                     //Otherwise it'll be made empty, set to 0
    120.                     else if(surroundingWalls < ruleValue)
    121.                     {
    122.                         map[x, y] = 0;
    123.                     }
    124.                 }
    125.                 //PUFF:
    126.                     //This rule finds all of the neighbor indices and makes them solid
    127.                 else if(specialNoiseRule == Rule.Puff)
    128.                 {
    129.                     //We want to get all of the neighboring walls and make them solid
    130.                     //Top Left
    131.                     if(map[x - 1, y + 1] != null)
    132.                         //----------------------------------------------THE ERROR TAKES ME TO THE LINE BELOW
    133.                         map[x - 1, y + 1] = 1;
    134.                     //Top
    135.                     if(map[x, y + 1] != null)
    136.                         map[x, y + 1] = 1;
    137.                     //Top Right
    138.                     if(map[x + 1, y + 1] != null)
    139.                         map[x + 1, y + 1] = 1;
    140.                     //Right
    141.                     if(map[x + 1, y] != null)
    142.                         map[x + 1, y] = 1;
    143.                     //Bottom Right
    144.                     if(map[x + 1, y - 1] != null)
    145.                         map[x + 1, y - 1] = 1;
    146.                     //Bottom
    147.                     if(map[x, y - 1] != null)
    148.                         map[x, y - 1] = 1;
    149.                     //Bottom Left
    150.                     if(map[x - 1, y - 1] != null)
    151.                         map[x - 1, y - 1] = 1;
    152.                     //Left
    153.                     if(map[x - 1, y] != null)
    154.                         map[x - 1, y] = 1;
    155.                 }
    156.             }
    157.         }
    158.     }
    159.  
    160.     //This method will look at all of the surrounding indices of a given index and see how have a value of 1 (how many are solid)
    161.     int GetSurroundingWallCount(int indexX, int indexY)
    162.     {
    163.         //This variable will store the amount of surrounding indices that are solid
    164.         int wallCount = 0;
    165.  
    166.         //A 3x3 grid centered on (indexX, indexY) is looped through
    167.         //Each index is checked to see if it is equal to 1
    168.         for(int nX = (indexX - 1); nX <= indexX + 1; nX++)
    169.         {
    170.             for(int nY = (indexY - 1); nY <= indexY + 1; nY++)
    171.             {
    172.                 //Because the surrounding indices will be looped through,
    173.                 //the current index cannot be on the edge of the map or in a corner, if it was Unity would give a null error
    174.  
    175.                 //If this isn't an edge tile-
    176.                 if(nX >= 0 && nX < width && nY >= 0 && nY < height)
    177.                 {
    178.                     //If the current index isn't the middle one (the one who's neighbors are being checked)-
    179.                     if(nX != indexX || nY != indexY)
    180.                     {
    181.                         //If the current index is 1, 1 will be added
    182.                         //If it is 0, 0 will be added
    183.                         wallCount += map[nX, nY];
    184.                     }
    185.                 }
    186.                 //Otherwise-
    187.                 else
    188.                 {
    189.                     //The amount of surrounding walls will be increased
    190.                     wallCount++;
    191.                 }
    192.             }
    193.         }
    194.         return wallCount;
    195.     }
    196.  
    197.     //To check that everything is working, for every index of the map, a gizmo cube will be created
    198.     void OnDrawGizmos()
    199.     {
    200.         //Check that the map exists so that Unity doesn't try to draw null
    201.         if(map != null)
    202.         {
    203.             //Each index in the maps array of numbers is looped through
    204.             //For every x index-
    205.             for(int x = 0; x < width; x++)
    206.             {
    207.                 //Every y index will be iterated through as well
    208.                 for(int y = 0; y < height; y++)
    209.                 {
    210.                     //Using if/else notation, if the current maps index is equal to 1, the cube will be set to white, otherwise it'll be set to 0
    211.                     Gizmos.color = ((map[x, y] == 1) ? Color.black : Color.white);
    212.                     //A new Vector3 is created to hold the position for where the gizmo should be drawn
    213.                     Vector3 position = new Vector3((-width / 2) + x + 0.5f, 0f, (-height / 2) + y + 0.5f);
    214.                     Gizmos.DrawCube(position, Vector3.one);
    215.                 }
    216.             }
    217.         }
    218.     }
    219. }
     
    Last edited: Feb 11, 2015
  2. meatpudding

    meatpudding

    Joined:
    Jan 28, 2015
    Posts:
    39
    I'm not 100% sure what you're trying to do, but when x = 0, x-1 = -1, so that's where you're out of range.
     
  3. SebastianLague

    SebastianLague

    Joined:
    Aug 31, 2014
    Posts:
    111
    Hi, you seem to be confused about the difference between 'out of range' and null. If you have an array of ints called x[], x[-1] is not null, it's out of range. So I think the easiest way to fix your error is to replace all those if statements with a for loop (like was done in GetSurroundingWallCount):

    Code (CSharp):
    1.         for(int nX = (x - 1); nX <= x + 1; nX++) {
    2.                         for(int nY = (y - 1); nY <= y + 1; nY++) {
    3.                             if (nX >= 0 && nX < width && nY >= 0 && nY < height) {
    4.                                 map[nX, nY] = 1;
    5.                             }
    6.                         }
    7.                     }

    I'm not sure what you're expecting from this puffy rule set though, because as far as I can see it's just setting all tiles to walls? Anyway, I think it's really cool that you're experimenting with the rules and I'm sure if you keep at it you'll get some really interesting results!
     
    keenanwoodall likes this.
  4. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Yes, out of range.
    But i don't know what you are expecting this to do?
    If you iterate over every cell, and turn all neighbors to 1, then you end up with every cell being 1...?

    It might be worth noting that the original 'rule' is almost Conway's game of life
     
  5. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    595
    Thanks for the help. I"m pretty sure your right about my lack of logic. I'm just messing around trying to make rules, procedural stuff is new to me so I didn't have the foresight. I'll try to edit "Puffy" so that it actually does something interesting. Once I get everything working I'll post the code so that anyone who's curious can give it a test run.
     
  6. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    595
    I couldn't figure out how to get the "Puffy" rule to work (big surprise), but thanks to hpjohn I learned about Conway's Game of Life. I looked it up on wikipedia and learned the steps to create it.
    • Any live cell with fewer than two live neighbours dies, as if caused by under-population.
    • Any live cell with two or three live neighbours lives on to the next generation.
    • Any live cell with more than three live neighbours dies, as if by overcrowding.
    • Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

    I think I replicated it pretty well. I'd suggest copying and pasting my code into an empty class and adding it to a game object because the results are pretty cool and with some tweaking of the values you can get some strange formations!
    Code (cs):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5.  
    6. public class MapGenerator : MonoBehaviour
    7. {
    8.     //The map's size
    9.     public int width;
    10.     public int height;
    11.  
    12.     //The amount of times the rule is applied
    13.     [Range(0, 20)]
    14.     public int ruleIterations = 5;
    15.  
    16.     //This enumeration will hold all of the different rules that can be applied to te base data
    17.     public enum Rule{GameOfLifeLague, GameOfLife};
    18.     public Rule specialNoiseRule = Rule.GameOfLifeLague;
    19.  
    20.     //The amount that the rule affects the noise per iteration
    21.     [Range(0, 8)]
    22.     public int ruleValue = 4;
    23.  
    24.     //The map will be generated from a seed so that the same map can be recreated via its seed
    25.     public string seed;
    26.     //If we don't want to use a custom seed we can have 1 randomly generated
    27.     public bool useRandomSeed;
    28.     //When the map is randomly filled it needs to obey a loose guideline of how much should be solid
    29.     [Range(0, 100)]
    30.     public int randomFillPercent;
    31.  
    32.     //This defines a grid of integers, any tile that is equal to 0 will be empty, and any tile that is equal to 1 will be solid
    33.     //This grid of numbers will be filled with random 0s and 1s
    34.     private int[,] map;
    35.  
    36.     void Start()
    37.     {
    38.         GenerateMap();
    39.     }
    40.  
    41.     void Update()
    42.     {
    43.         GenerateMap();
    44.     }
    45.  
    46.     void GenerateMap()
    47.     {
    48.         //Map is a new integer array and is size is width by height
    49.         map = new int[width, height];
    50.         //RandomFillMap is called to fill map with  basic data
    51.         RandomFillMap();
    52.  
    53.         //The map's data will be modified by the rule a set amount of times
    54.         for(int i = 0; i < ruleIterations; i++)
    55.         {
    56.             ApplyRule();
    57.         }
    58.     }
    59.  
    60.     //This method creates the rough data for ApplyRule to smooth
    61.     void RandomFillMap()
    62.     {
    63.         //If a random seed is wanted-
    64.         if(useRandomSeed)
    65.         {
    66.             //Seed is set to the amout of time that the game has been running
    67.             seed = Time.time.ToString();
    68.         }
    69.         //This will return a unique integer, simulating randomness
    70.         //Prng stands for Pseudo Random Number Generator
    71.         System.Random prng = new System.Random(seed.GetHashCode());
    72.      
    73.         //Each index in the maps array of numbers is looped through
    74.         //For every x index-
    75.         for(int x = 0; x < width; x++)
    76.         {
    77.             //Every y index will be iterated through as well
    78.             for(int y = 0; y < height; y++)
    79.             {
    80.                 //The current index will be set to either 0 or 1
    81.                 //If the current index is on a corner or far side-
    82.                 if(x == 0 || x == (width - 1) || y == 0 || y == (height - 1))
    83.                 {
    84.                     //It'll be set to 1; solid
    85.                     map[x, y] = 1;
    86.                 }
    87.                 //Otherwise the current index isn't on the side and it's value will be randomly generated
    88.                 else
    89.                 {
    90.                     //Using the if/else notation, the current index will be set to 1 if the generated number is less than randomFillPercent,
    91.                     //otherwise it will be set to 0
    92.                     map[x, y] = (prng.Next(0, 100) < randomFillPercent) ? 1 : 0;
    93.                 }
    94.             }
    95.         }
    96.     }
    97.  
    98.     //ApplyRule lets the user choose from and create custom rules that will be applied to the map
    99.     //Withought this method, the map would just be boring static
    100.     void ApplyRule()
    101.     {
    102.         //Once again, every index in the map's array is looped through
    103.         for(int x = 0; x < width; x++)
    104.         {
    105.             for(int y = 0; y < height; y++)
    106.             {
    107.                 //Now some simple rules will be made up based on the amount of wall tiles
    108.                 //----RULES----//
    109.              
    110.                 //ANGULARIZE:
    111.                 //This rule finds all of the surrounding indices; if enough are solid the current index is solid as well
    112.                 //Otherwise it will be made empty
    113.                 if(specialNoiseRule == Rule.GameOfLifeLague)
    114.                 {
    115.                     //We want to know how many neighboring tiles does the current tile index have that are walls
    116.                     //This will be found via the method GetSurroundingWallCount below
    117.                     int surroundingWalls = GetSurroundingWallCount(x, y);
    118.                  
    119.                     //If the current index is surrounded by a certain amount (ruleValue) of solid tiles it'll be made solid as well
    120.                     if(surroundingWalls > ruleValue)
    121.                     {
    122.                         map[x, y] = 1;
    123.                     }
    124.                     //Otherwise it'll be made empty, set to 0
    125.                     else if(surroundingWalls < ruleValue)
    126.                     {
    127.                         map[x, y] = 0;
    128.                     }
    129.                 }
    130.                 //PUFF:
    131.                 //This rule carries out John Horton Conway's cellular automation
    132.                 else if(specialNoiseRule == Rule.GameOfLife)
    133.                 {
    134.                     //We want to know how many neighboring tiles does the current tile index have that are walls
    135.                     //This will be found via the method GetSurroundingWallCount below
    136.                     int surroundingWalls = GetSurroundingWallCount(x, y);
    137.  
    138.                     //If the cell is solid-
    139.                     if(map[x, y] == 1)
    140.                     {
    141.                         //If the cell has less than 2 neighbors-
    142.                         if(surroundingWalls < (ruleValue - 1))
    143.                         {
    144.                             //It will be made empty (as if caused by under-population)
    145.                             map[x, y] = 0;
    146.                         }
    147.                         //Otherwise, if the cell has 2 or 3 neighbors-
    148.                         else if(surroundingWalls > (ruleValue - 1) && surroundingWalls < 4)
    149.                         {
    150.                             //The cell will be made solid (as if it lived on to the next generation)
    151.                             map[x, y] = 1;
    152.                         }
    153.                         //Otherwise, the cell has more than three neighbors-
    154.                         else
    155.                         {
    156.                             //And it will be made empty (as if by over-population)
    157.                             map[x, y] = 1;
    158.                         }
    159.                     }
    160.                     //Otherwise, the index is empty
    161.                     else
    162.                     {
    163.                         //If the cell has 3 neighbors
    164.                         if(surroundingWalls > 2 && surroundingWalls < 4)
    165.                         {
    166.                             //It will be made solid (as if by reproduction)
    167.                             map[x, y] = 1;
    168.                         }
    169.                     }
    170.                 }
    171.             }
    172.         }
    173.     }
    174.  
    175.     //This method will look at all of the surrounding indices of a given index and see how have a value of 1 (how many are solid)
    176.     int GetSurroundingWallCount(int indexX, int indexY)
    177.     {
    178.         //This variable will store the amount of surrounding indices that are solid
    179.         int wallCount = 0;
    180.      
    181.         //A 3x3 grid centered on (indexX, indexY) is looped through
    182.         //Each index is checked to see if it is equal to 1
    183.         for(int nX = (indexX - 1); nX <= indexX + 1; nX++)
    184.         {
    185.             for(int nY = (indexY - 1); nY <= indexY + 1; nY++)
    186.             {
    187.                 //Because the surrounding indices will be looped through,
    188.                 //the current index cannot be on the edge of the map or in a corner, if it was Unity would give a null error
    189.              
    190.                 //If this isn't an edge tile-
    191.                 if(nX >= 0 && nX < width && nY >= 0 && nY < height)
    192.                 {
    193.                     //If the current index isn't the middle one (the one who's neighbors are being checked)-
    194.                     if(nX != indexX || nY != indexY)
    195.                     {
    196.                         //If the current index is 1, 1 will be added
    197.                         //If it is 0, 0 will be added
    198.                         wallCount += map[nX, nY];
    199.                     }
    200.                 }
    201.                 //Otherwise-
    202.                 else
    203.                 {
    204.                     //The amount of surrounding walls will be increased
    205.                     wallCount++;
    206.                 }
    207.             }
    208.         }
    209.         return wallCount;
    210.     }
    211.  
    212.     //To check that everything is working, for every index of the map, a gizmo cube will be created
    213.     void OnDrawGizmos()
    214.     {
    215.         //Check that the map exists so that Unity doesn't try to draw null
    216.         if(map != null)
    217.         {
    218.             //Each index in the maps array of numbers is looped through
    219.             //For every x index-
    220.             for(int x = 0; x < width; x++)
    221.             {
    222.                 //Every y index will be iterated through as well
    223.                 for(int y = 0; y < height; y++)
    224.                 {
    225.                     //Using if/else notation, if the current maps index is equal to 1, the cube will be set to white, otherwise it'll be set to 0
    226.                     Gizmos.color = ((map[x, y] == 1) ? Color.black : Color.white);
    227.                     //A new Vector3 is created to hold the position for where the gizmo should be drawn
    228.                     Vector3 position = new Vector3((-width / 2) + x + 0.5f, 0f, (-height / 2) + y + 0.5f);
    229.                     Gizmos.DrawCube(position, Vector3.one);
    230.                 }
    231.             }
    232.         }
    233.     }
    234. }
     
    Last edited: Feb 11, 2015
  7. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Something more to note then, about the game of life
    You can't modify the current state of the grid when applying the rules, because it affects the outcomes of checks elsewhere in the grid
     
  8. keenanwoodall

    keenanwoodall

    Joined:
    May 30, 2014
    Posts:
    595
    Ok I see, so I need to reference how many neighbors a cell has from the original grid, bot from the most recent update of the grid (at least if I want to replicate the grid of life perfectly). I'll just call my messed up Game of Life rule "Keenan's Game of Life" so that it seems like I did it on purpose. As I was doing this I had another though, would it be possible to layer the rules? Apply a new rule to the end result of what another rule did when applied to the base set of data in the array...I worded the previous sentence awfully but hopefully someone intelligent can decipher it and understand what I'm getting at. :) If you look at the screenshot at this link (sorry for the double monitor screenshot, I didn't feel like cropping) you can see an example map that I generated. After generating that, could I then apply another rule that sees if a index is completely surrounded, if it is then it'll be made solid. That way I can generate a nice looking map and then tweak it slightly to get the desired effect. In this case I would, as I just said, apply a rule that fills in the specs of white with black.
     
    Last edited: Feb 12, 2015