Hello fellow game developers, I’m pretty new to Unity and C# and I was wondering if there is any alternative to structs. Today, I was following Sebastian Lague’s terrain generation tutorial and was dumbing it down as I went along, none of that class stuff. I’m trying to make the code as simple as possible so I can edit it later and understand it. This might be a stupid question but thanks anyway…
Thanks,
Alek
What’s your use case? Structs and classes can be adapted for most use cases.
There are classes, structs, and more recently, records in C#. Unity likely doesn’t support records yet though, so your options are classes & structs.
What is it you’re actually trying to do though?
https://www.youtube.com/watch?v=RDQK1_SWFuc
Skip to 1:08. I’m trying to create an array or a list of colours
What do you have so far?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class noise : MonoBehaviour
{
public int mapWidth;
public int mapHeight;
public float noiseScale;
public int octaves;
[Range(0,1)]
public float persistance;
public float lacunarity;
public int seed;
public Vector2 offset;
public Renderer textureRender;
public terrainType[] regions;
// Start is called before the first frame update
void Start()
{
}
void OnValidate()
{
if(mapWidth < 1)
{
mapWidth = 1;
}
if (mapHeight < 1)
{
mapHeight = 1;
}
if (lacunarity < 1)
{
lacunarity = 1;
}
if (octaves < 0)
{
octaves = 0;
}
}
[System.Serializable]
public struct terrainType
{
public string name;
public float height;
public Color color;
}
public float[,] genNoise(int mapWidth, int mapHeight, int seed, float scale, int octaves, float persistance, float lacunarity, Vector2 offset)
{
float[,] noiseMap = new float[mapWidth, mapHeight];
Vector2[] octaveOffsets = new Vector2[octaves];
for (int i = 0; i < octaves; i++)
{
System.Random prng = new System.Random(seed);
float offsetX = prng.Next(-100000, 100000) + offset.x;
float offsetY = prng.Next(-100000, 100000) + offset.y;
octaveOffsets[i] = new Vector2(offsetX, offsetY);
}
if(scale <= 0)
{
scale = 0.0001f;
}
float maxNoiseHeight = float.MinValue;
float minNoiseHeight = float.MaxValue;
float halfWidth = mapWidth / 2f;
float halfHeight = mapHeight / 2f;
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
float amplitude = 1;
float frequency = 1;
float noiseHeight = 0;
for (int i = 0; i < octaves; i++)
{
float sampleX = (x - halfWidth) / scale * frequency + octaveOffsets[i].x;
float sampleY = (y - halfHeight) / scale * frequency + octaveOffsets[i].y;
float perlinVal = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
noiseHeight += perlinVal * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
}
if(noiseHeight > maxNoiseHeight)
{
maxNoiseHeight = noiseHeight;
}
else if (noiseHeight < minNoiseHeight)
{
minNoiseHeight = noiseHeight;
}
noiseMap[x, y] = noiseHeight;
}
}
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
noiseMap[x, y] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
}
}
return noiseMap;
}
public void generateMap()
{
float[,] noiseMap = genNoise(mapWidth, mapHeight, seed, noiseScale, octaves, persistance, lacunarity, offset);
drawNoise(noiseMap);
}
public void drawNoise(float[,] noiseMap)
{
int width = noiseMap.GetLength(0);
int height = noiseMap.GetLength(1);
Texture2D texture = new Texture2D(width, height);
Color[] colorMap = new Color[width * height];
for(int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
colorMap[y * width + x] = Color.Lerp(Color.black, Color.white, noiseMap[x, y]);
}
}
texture.SetPixels(colorMap);
texture.Apply();
textureRender.sharedMaterial.mainTexture = texture;
textureRender.transform.localScale = new Vector3(width, 1, height);
}
// Update is called once per frame
void Update()
{
generateMap();
}
}
And what’s the problem? Does that not work or are you just theorising a “better” solution?
Yeah, I’m trying to come up with a simpler solution for two reasons:
- I get to learn more by understanding it
- It’s easier to change in the future when I know what I’m doing
Sorry to be a pain
Well honestly the way you are doing it now is fine, and it’s probably the best way to do it.
If you want to learn more about structs then play with the fields within it, introduce a new field into your terrainType struct and see what you can do with that value.
Thanks for the help, just wanted to make sure I wasn’t doing it wrong from someone more advanced at coding than me
You can use Tuple Types, but they won’t show in the inspector.
public (string Name, float Height, Color Color)[] Regions;
Using structs and plain classes to break down and compartmentalise data and functionality is the complete norm. Expect to be doing it ad nauseum throughout your coding ventures.
If you want to try something new, you could look at moving those big functions into a static class as helper and extension methods.
If you want a crude but effective solution, you could use several arrays – one for each field in the struct – known as “parallel arrays”. Instead of having that array, regions, with terrainType items inside of it (name, height and color), use three arrays:
string regionNames[];
float regionHeights[];
Color regionColors[];
For example, the old region[4].name is now regionNames[4]. And the old region[9] (which had 3 parts) is now spread over regionNames[9], regionLengths[9] and regionColors[9]. Basically, the arrays are faking a struct.
The current version with a struct is much easier to read and work with, for most programmers, but if you want to avoid thinking about structs and things with [ ]'s and dots together, such as region*.height
, parallel arrays are time-tested.*
Structs are what you want. Alternatives exist as has been mentioned in this thread but they’re not going to be more readable than just using a struct, and more importantly in my opinion for cases where you are processing large sets of data they’re not going to be more performant than structs as alternatives are often built on classes.
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct
For example the tuple type is implemented using classes.
After reading through all the comments, I might just stick with using structs. Thanks everyone
When I started to use C# I tried to use structs like in C++ where they are very useful. In C# it doesn’t work that well. Ultimately I decided to use classes only, because it makes everything simpler and more consistent.
That just means you don’t really understand the purpose of a struct when you compare C++ and C#. In C++ a struct and a class is identical with the only exception that the default visibility of classes is “private” and structs have the default visibility “public”. In C++ you can allocate both (structs or classes) either on the stack or the heap. That is not the case in C#. Classes are always allocated on the heap and always are reference types while structs are value types and allocated “inplace”. That means their content is literally stored where the variable is located since its content is the value of the variable. So having local variables of structs inside a method means the struct lives on the stack. However having a struct variable inside a class as a class member, the struct lives inside the class itself on the heap.
The same is true for arrays of struct / values types. Arrays always live on the heap. They are reference types. However that means when you have an array of a struct, its elements live on the heap tightly packed one after the other.
That’s a completely misguided decision which is just based on not understanding the purpose of structs.
In C# structs are VERY limited and I don’t like that.
in C# they are just a different tool and like classes work differently then there C++ counterparts. Types made with classes are pass by reference and and live on the heap, structs for the most part are pass by value and live on the stack. They are best used for small data types, where you need value semantics or where you want to ensure you are not mutating objects held both other things.
This can sometimes be quite problematic in terms of performance. For example, i would never use classes for voxels. That would be a worst case for memory layout and garbage collector