Structs in Unity

Structs in Unity (how and when to use them)

In Unity by John FrenchUpdated Leave a Comment

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:

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.

Serialized Struct in the Unity Inspector

Serializable Structs appear in the Inspector as values, just like a Vector 3 or a float would.

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.

A Reference variable type in Unity

Reference types typically allow you to select data that exists elsewhere, such as an object or an asset.

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.

An instance of an item in an inventory in Unity.

Serialized Class variables (that don’t inherit from Monobehaviour) will appear in the Inspector in the same way that structs do, which can be confusing when you’re trying to work out how structs and classes are different.

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:

An example array of a basic struct data type in Unity.

The whole point of structs is to group information together in one place, allowing you to use that new data type in functions, scripts and arrays.

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.

John French profile picture

by John French

Game audio professional and a keen amateur developer.

How was this content created

This article was written using first-hand research and experience, without the use of AI. For more information on how Game Dev Beginner articles are written, see my writing policy.

Get Game Development Tips, Straight to Your inbox

Get helpful tips & tricks and master game development basics the easy way, with deep-dive tutorials and guides.

My favourite time-saving Unity assets

Rewired (the best input management system)

Rewired is an input management asset that extends Unity's default input system, the Input Manager, adding much needed improvements and support for modern devices. Put simply, it's much more advanced than the default Input Manager and more reliable than Unity's new Input System. When I tested both systems, I found Rewired to be surprisingly easy to use and fully featured, so I can understand why everyone loves it.

DOTween Pro (should be built into Unity)

An asset so useful, it should already be built into Unity. Except it's not. DOTween Pro is an animation and timing tool that allows you to animate anything in Unity. You can move, fade, scale, rotate without writing Coroutines or Lerp functions.

Easy Save (there's no reason not to use it)

Easy Save makes managing game saves and file serialization extremely easy in Unity. So much so that, for the time it would take to build a save system, vs the cost of buying Easy Save, I don't recommend making your own save system since Easy Save already exists.

Leave a Comment