Some ideas randomly filter in and out of my head. One of them that would not go away was the procedural generation of materials.
I wanted to create a way that a computer would consistently generate the same material, with a random name, toughness, sharpness, and some fantasy stats like magic conductivity.
The Beginning
Starting out I had to define all of the variables I wanted to use. These changed over time, but this is what I have currently:
I needed a lot of variables that are static. These are all of the lists from prefix to sentenceAddatives.
These allowed me to choose random phrases and mash them together for the description generation. Or mash syllables together for name generation.
From top to bottom, the variables do:
- tier alters values to become bigger, basically if something a higher-tier material, it will be better
- num is currently not in use due to its use being moved to another file (I need to grab the information from the other file)
- seed is the seed used for generation (more on this later)
- hardness is used to describe the hardness of the object
- sharpness is used to describe the brittleness of the object
- magicConductivity is used to describe the magic proficiency of the object
- value is used to define the worth of the object
- redValue, greenValue, and blueValue are the values from 0.0f to 1.0 on the RGB scale the material falls under
- loadThis decides whether or not on load if the class should immediately generate a material
- materialName is the name of the material generated with the prefix suffix and names lists
- description is the description generated with the lists that start with the description and location
- type decides whether or not the material is a crystal or metal
- prefab is the generated prefab for the material
- material is the generated material for the object
- materialCrystalList and materialOreList are lists that define what materials (the one used for 3d objects) can be used for the respective type
- crystalList and oreList are used to define what prefabs can be used for each type
- people and sentence additives are used in the generation of the description
Start()
The start function is very short so I will go over it briefly.
The start function tries to use generate() if loadThis is equal to -1 else, it will use load.
There was another function on Start() and that was for testing. I had a button that would just let me generate materials as many times as I wanted.
Now you might be curious to see generate()
generate()
generate is the main function where everything goes down. This calls all other functions in the class, so I will go over them in the order that generate() calls them.
As I mentioned before, generate() mostly calls other functions to do its work, but I will break down the things it does without the help of other functions.
First, if a seed isn’t given, generate creates a random seed.
What is a seed?
Seeds allow one to create seeded generation. Seeded generation will have the same results every time with the same seed. This means that someone can send someone else the seed to something they find cool, or developers can whitelist only seeds that produce good content.
Unity allows seeded generation by calling Random.InitState(seed); If you are generating multiple randoms (for example if you are generating two random numbers between 0 and 1) you need to consistently change the seed if you want to generate a different number.
generate() continued
After initializing the seed, generate initializes int randomNum, which is used in a few random generations in generate(); It then generates the hardness, sharpness, value, and magic conductivity.
Then generate calls up the name generator and description generator to help it create the name and description, both of these functions are void functions so no inputs are needed.
generate() then decides if the material is a crystal or a metal, this is important for the rest of the generation’s choices, so it does it now. After that, it does the material and prefab generation which is chosen from the list that the user has to input.
The RGB values are chosen in the way they are so that they do not produce the same number. If the only colors chosen are the same (0.4, 0.4, 0.4) they are all just going to be grey with different brightness.
After this loadThis is set to -2 so that the start() function never happens to run again (this is mostly a just in case or a save load check.) Then the generate() function runs the save() and load() functions.
nameGenerator()
The name generator uses the three lists defined at the top and smashes their syllables together. It then returns the combined string. Here is the simple code that does that:
This code first decides whether or not it’s going to use a suffix or prefix, and if it does it generates it. It will always use a syllable from the name list.
It then returns the combination of the syllables it decided to generate.
descriptionGenerator()
The description generator is a lot more complex than the name generator. This is because the order of the phrases is random, and there needs to be a conjunction connecting them.
Let’s start from the top as this can get a bit confusing (without the comments that I forgot to add).
descriptionGenerator() has 6 variables it needs to keep a track of. These make sure that it never duplicates any part of the description and that it never misses any part.
Whenever any of the parts are successfully generated, its corresponding variable is set to true so that the code skips that generation next time.
Inside each generation a set of if statements decide whether or not the corresponding stat is good or bad for the given tier. It is done by dividing the generated value by the tier. This works because tiers scale linearly. It then compares with numbers arbitrarily chosen by me that are close to the minimum and maximum possible generation. Then if it is not the last thing to be generated in the description, it will add a sentence conjunction.
The location works a bit differently from the others by creating its own nonsense information by combining a location and person to create a possible origin for the material.
The unTest at the bottom is temporary for a possible 6th stat to keep track of.
save()
The save function took me a bit to figure out and is most likely to change in the future. It uses Unity’s built-in JSON manipulation functions to create JSON and then load it later.
Here is where num is used, but it is currently set to NULL, so I am surprised it works.
I use the BinaryFormatter to take the JSON created with the JSON Utility and serialize it. This makes it so that no player can cheat by manipulating the material’s value.
load()
load() is slightly more complex, as it also creates the prefab in the world.
load() starts off by doing a similar thing to save, but it has to check if the file exists. If the file does not, an error will be thrown out into the logs. The important difference is the initiation of the prefab.
The prefab is instantiated after the information is loaded from FromJsonOverwrite (which takes the JSON information and overwrites the object’s information). It then takes the material and color information and writes that onto the prefab. Currently, other information is not added to the material.
What is update() doing??
After looking at my code, some people may be wondering what the heck update() is doing.
The short answer is that update() does nothing. The comments were for a request by a friend for me to search out a specific name that my name generator could make.
Understanding my gameManager()
My gameManager is pretty simple. It just counts up until reaching a count of 5, then resetting back at 0, but with the tier one higher. It then runs generate() with the updated information.
This allows the generator to create 5 of the same tier and then move on, allowing the player to savor each tier a bit. I connected a button to tierCounter() for now, but it can easily be removed.
What to take away
Overall what I need to take away and do after this is add more comments. There is a few pieces of code that do nothing that I need to remove. I will make the num in MaterialObject.cs equal the counter in gameManager.cs
If you haven’t tried experimenting with procedural generation, it is fun and an interesting problem.
The code will be available below to check out, and you can always do whatever with it using GitHub’s features, including offering pull requests.