When you first start working with scripts in Unity, you’ll probably find yourself using individual variables, such as single integers, floats, and Vector 3 values, for just about everything.
Which, if that’s all you need, is absolutely fine.
However, as you start to design the different parts of your game you might find that you need to combine two, or more, pieces of information together in the same place.
For example, what if you want to store a damage data value with the type of damage that was inflicted?
Or store a player’s name with their score on a scoreboard.
And what if you want to create a data type that stores an entire set of information about an event that’s just happened, that can be easily passed to other scripts?
In Unity, it’s possible to create unique data types by using Structs.
Structs are Data Structures, which are customisable containers that can be used to keep related values together or to organise functions and information in a single, encapsulated script that’s easy to pass around your game.
Which, at first, might sound a bit confusing.
But, even if you’re new to Unity, you’ve probably already worked with structs before.
For example, a Vector 3 value is a struct that contains 3 floats for the X, Y, and Z positions.
While the Raycast Hit struct allows you to get information about a collider that was hit by a raycast, all from one data reference that’s passed with the function when it happens.
Put simply, structs can be extremely useful, and can make building your game music easier.
In this article, you’ll learn how structs in Unity work, how to use them in your project, and how they compare with similar data types, such as plain C# classes.
Here’s what you’ll find on this page:
- How to use Structs in Unity
- How to make a Struct
- What can you put inside of a Struct?
- How to use Structs in scripts
- Structs vs Classes in Unity
- When to use a Struct in Unity
So how do structs in Unity work?
How to use Structs in Unity
Structs are, basically, custom data formats.
They’re a type of script that allows you to organise variables and information in a way that can be passed around as if it’s a value.
Which, essentially, it is.
A struct can be as simple and as small as two numbers that need to be stored together, or they can be used to create large data structures, with multiple variables and even functions inside of them.
For example, the Vector 3 struct contains 3 float position values which, when you use a vector 3, are visible in the Inspector and publicly accessible in scripting.
But, there’s actually a lot more going on inside the Vector 3 struct than you might have realised.
It also contains properties and functions that are used to get information about an instance of a vector 3 value, such as its Magnitude, as well as static properties and methods that can be used to work with vector 3’s in general.
All in one, seemingly small, data structure:
Vector3 position;
Putting all of this information in one place can be extremely helpful, as it allows you to keep everything you need for reading and working with a certain type of value, in the same place as the value itself.
So how can you use structs in your project?
How to make a Struct
Structs are created in the same way as any other script, except that, instead of the class keyword, you’ll use the struct keyword instead.
Like this:
public struct MyStruct
{
// your data goes here!
}
You can do this inside of a new C# Script file or you can add a struct to the end of another script.
Like this:
public class ExampleScript : MonoBehaviour
{
MyStruct myStruct;
}
struct MyStruct
{
// your data here!
}
Which can be useful when you’re going to use the struct inside of a particular script, as it means that you can edit it in one place, without opening a different file.
You can even create a struct entirely inside of another class.
Like this:
public class ExampleScript : MonoBehaviour
{
MyStruct myStruct;
struct MyStruct
{
// your data here!
}
}
Which can be useful when you need to create a data structure that only exists inside of the class that’s going to use it.
In this case, the struct won’t be accessible to other parts of your project which, if you don’t need it to be, can make it a little easier to work with by hiding data types that you don’t need to use elsewhere.
Just like with a regular class, you can then declare variables and functions inside of your struct.
But what works, and what doesn’t?
What can you put inside of a Struct?
You can put just about anything inside of a struct that you’d normally put inside of a normal class, including values, functions and even other structs.
Like this:
public struct PlayerInfo
{
float hp;
Vector3 position;
public int GetHealthAsInt()
{
return Mathf.RoundToInt(hp);
}
}
However, functions and properties that are specific to Monobehaviours, such as the gameobject, tag, or transform properties, or object-specific functions such as Invoke or Start Coroutine, can’t be used.
Event functions, that usually only work on game object scripts, such as Update, Start and Awake, also won’t be called.
This is because these functions and variables are specific to scripts that are attached to an actual object, meaning that a struct, which can’t be attached to an object, can’t call them.
But if a struct can’t be attached to an object, where does it exist, and how can you use it in a script?
How to use Structs in scripts
Structs are values, meaning that, like other values, such as numbers or booleans, they exist where they are created.
For example, if you create a basic struct that contains two numbers.
Like this:
[System.Serializable]
public struct MyStruct
{
public float a;
public float b;
}
Then you’ll be able to use that data type as a variable in a script.
Like this:
public MyStruct myStruct;
Then, you’ll be able to access the public variables and functions that exist within the struct by using the Dot Operator to access them.
Like this:
float result = myStruct.a + myStruct.b;
Alternatively, it’s possible to pass a struct into a function as a parameter:
public void AddBothNumbers(MyStruct myStruct)
{
float result = myStruct.a + myStruct.b;
Debug.Log(result);
}
Or as a return type:
[SerializeField] MyStruct myStruct;
public MyStruct GetMyStruct()
{
return myStruct;
}
And, so long as the struct is Serializable, if you create a value of that type inside of a script, you’ll see it, and any serializable values inside of it, in the Inspector.
This works because structs are Value Types.
This is different to Monobehaviour scripts and plain C# Classes which are both Reference types.
So what’s the difference?
And why does it matter?
Structs vs Classes in Unity
The basic difference between structs and classes is that structs are value types while classes are reference types.
This affects how they’re stored in memory and how they behave when they’re copied.
For example, when you copy a value type to a new variable, such as float,
Like this:
float number = 100;
float copy = number;
You’re copying the information as it exists when you read it.
This means that if the original number changes, the copy doesn’t.
However, when you copy a reference type, such as a game object,
Like this:
GameObject thisObject = gameObject;
GameObject copy = thisObject;
You’re not duplicating the object, you’re creating a second reference to the same allocation of memory.
This means that, if the game object changes, both of the references will as well, since they’re both simply pointing to one object elsewhere.
Which is why, in the Inspector, a value type will display its actual value, while a reference type will show up as an assignable reference to something else, such as an object or an asset, that can be dragged to the empty field or assigned using the Circle Select button.
Structs and Classes work in the same way.
Structs are values, meaning that their data is copied.
Classes are objects, meaning that their data is referenced.
As a result classes, just like other objects with references pointing to them, can cause garbage if you create new instances of them inside of functions, while creating a new local struct, which is stored on the stack, typically doesn’t.
But, there are some drawbacks to using structs.
For example, you can’t, typically, initialise a value that’s inside a struct in Unity like you can do with a class.
So this won’t work:
struct Number
{
float number = 4;
}
Which means that if you want to assign a value to a struct variable when it’s created, you’ll need to do it in a Constructor.
Like this:
public class OtherScript : MonoBehaviour
{
public Number number = new Number(3);
}
[System.Serializable]
public struct Number
{
public float number;
public Number(float num)
{
number = num;
}
}
What’s more, very large structs can cause performance issues if they’re repeatedly copied.
Meaning that, if you’re frequently copying the same complex struct to new values, it might make more sense to use a class instead which only exists in one place.
But what does any of this actually mean for you?
When should you use a class and when should you use a struct?
Generally speaking, the main difference between a struct and a class is that class references can be null, meaning that a class variable can be empty, while structs can’t, they simply exist as soon as they’re created, just like numbers and other value types do.
This means that if your data structure represents a type of object, something that may or may not exist, for example, you may want to use a class, not a struct.
An example of this might be an item slot in an Inventory Script.
Something like this:
public class Inventory : MonoBehaviour
{
public InventoryItem[] items = new InventoryItem[10];
}
public class InventoryItem
{
public ItemData itemType; // Scriptable Object item data
public int condition;
public int ammo;
}
Which, by using a class, not a struct, would allow you to check if a particular inventory slot is empty or not.
However, in the Unity editor, the difference between classes and structs can sometimes be difficult to notice.
This is because if you serialize a class reference, it will appear in the Inspector in the same way as a struct will.
And, because it’s been serialized, it’s no longer null, meaning that a serialized class reference will appear to behave in the same way as a struct.
As a result, if you are using plain classes to create object variables, and you want to be able to check if they’re empty or not, you won’t be able to serialize them, as this defeats the purpose of using a class in the first place.
Structs, in comparison, are good for storing and moving data around.
So how can that help you, and when should you be using one?
When to use a Struct in Unity
Generally, the point of a struct is to group data together, so that a set of related information can be used, and passed around, as a single entity.
At a very simple level, this might be to combine two pieces of information so that they are linked, creating a new type of value.
For example, you could pair a name with an associated score value.
Like this:
[System.Serializable]
public struct ScoreEntry
{
public string playerName;
public int score;
}
This would allow you to create a basic scoreboard by simply making an array of that data type.
Like this:
public ScoreEntry[] scoreEntries;
Which, in the Inspector, looks like this:
However, using structs, it’s possible to create much more complex data containers that, on the surface, provide basic information, but that work by processing more complex data behind the scenes.
Why would you want to do that?
Let’s say, for example, that you’re making a game that works on a grid.
Chances are, that to calculate positions on that grid, you might end up spending a lot of time converting raw vector 3 values to rounded grid values.
One way to do this would be to round the value directly whenever and wherever you needed it.
Like this:
Vector3 roundedPosition new Vector3(
Mathf.Round(originalPosition.x),
Mathf.Round(originalPosition.y),
Mathf.Round(originalPosition.z)
);
Which works, but isn’t very convenient, as you’d have to write out the function every time you wanted to use it, making it difficult to change.
An alternative might be to create a function that rounds the value for you, either locally or in a static class, and simply call that whenever you need a rounded value.
Like this:
Vector3 roundedPosition = SnapPositionToGrid(originalPosition);
Which makes a lot more sense and, a lot of the time, this might be all you need to do.
However, an alternative to this is to create a Grid Position data type using a Struct, where setting the raw grid position automatically updates a snapped version of the vector 3, which can then be read by other scripts, but not set directly.
Then, when the snapped position is updated, the struct can raise an event that other scripts can subscribe to, allowing them to only receive the snapped position of the value whenever it actually changes to something new.
Like this:
public struct GridPosition
{
Vector3 rawPosition;
public Vector3 RawPosition
{
get
{
return rawPosition;
}
set
{
rawPosition = value;
SnappedPosition = rawPosition;
}
}
public event Action<Vector3> OnNewSnappedPosition;
Vector3 snappedPosition;
public Vector3 SnappedPosition
{
get
{
return snappedPosition;
}
private set
{
Vector3 newSnappedPosition = SnapPositionToGrid(value);
bool snappedPositionIsNew = newSnappedPosition != snappedPosition;
snappedPosition = newSnappedPosition;
if (snappedPositionIsNew)
{
OnNewSnappedPosition?.Invoke(snappedPosition);
}
}
}
Vector3 SnapPositionToGrid(Vector3 position)
{
return new Vector3(
Mathf.Round(position.x),
Mathf.Round(position.y),
Mathf.Round(position.z)
);
}
}
This struct uses variables, Properties and Event Actions to give other scripts access to information in a limited way, making it easier to work with and harder to accidentally break.
But, there’s nothing here that can’t be done in a regular Monobehaviour script.
So why bother?
Why do any of it?
The point of using a struct is to keep related data together in one place so that it’s easier to use.
In practice, it means that all of the code that’s in the example above, when it’s created as a variable in a script, looks like this:
public GridPosition gridPosition;
Which is useful, as encapsulating data into a format that’s easy to use means that passing it around your game is generally easier to do as well.
It also allows you to avoid duplication by making common data types reusable between scripts.
Ultimately, however, how you use structs in your game will depend on what it is you’re trying to create and how you want to manage the data that your scripts will use.
But, generally speaking, structs can be incredibly useful.
So much so that, when you start using them, you’ll probably wonder how you ever lived without them.
Now it’s your turn
Now I want to hear from you.
How are you using structs in your project?
Have they made your game easier to build, or more complicated?
And what have you learned about making your own data structures in Unity that you know someone else would find useful?
Whatever it is, let me know by leaving a comment.
Comments
Your articles are among the best I have ever seen.
Thank you!