Figuring out game architecture: using Scriptable Objects vs. MonoBehaviors

Hi all,

This is my first week working with Unity, and I’m just trying to wrap my head around how to structure my game architecture. I feel like the solution isn’t complicated, but I keep bouncing between solutions. Although there’s alot of info on the topic, I haven’t found something that works for me. So here’s how my game intends to work:

  1. Player has a party of units that have stats: simple attack and health. These are based on base units which can be bought.

  2. Units can be buffed (atk + hp) throughout the game during out-of-battle phases. Thus the party is persistent.

  3. Units will participate in battle where their health will change, and they can receive in-combat buffs (again, atk + hp) which can be temporary or permanent.

After much research into the use of Scriptable Objects vs. Monobehaviors and whatnot, I was thinking of having a database of base units which are scriptable objects. When the player buys a unit, they get a copy of the scriptable object which is added to a list in their player class. That way, this copy can be changed without messing up the database. Finally, in battle, a Monobehavior + game object is used to take on the stats of the scriptable object, unit gets perma/temp buffs, everything gets sorted at the end and perma buffs get added to the player scriptable object. In my prototype, it just directly references the SO, but that’s no good because the health changes in combat are then permanent.

So finally: A) can this be simplified? Is this straight up unnecessary? B) Are there any memory issues? C) How would I implement the battle phase object?

Hope this isn’t too much to ask, any help is appreciated. Thanks!

My general approach between the two is:

  • Monobehaviours are for stuff that’s generally mutable and are happy to live and die with the scene

  • Scriptable Objects are for when you need to author mostly plain data and this data doesn’t change

Of course there’s situations where I break those personally guidelines, particularly SO’s with mutable values, particularly for the purpose of transmitting data between scenes.

Most often you want monobehaviours for… monobehaviour stuff. Matters to do with Game Objects, and when you need Update, and any of the other monobehaviour exclusive Unity messages.

Don’t forget you can use plain old C# objects too (classes that don’t inherit from anything). If you want data immutable in one place, but mutable in another, wrap it all in a plain class, and have ways to duplicate the data from one place to another.

In any case, don’t get too caught up in this while you’re still learning. Throw yourself into it, and learn as you go.

4 Likes

Well,

Not a spec of light is showing
So the danger must be growing.

Whichever direction we are going
I have a tip for you.

when you copy base units, whether they be mono’s or scriptable; if you these unit clones contain lists of lists in association with their data, the clones of those objects do not automatically inherit the contents of those lists on copy. And so a copy-list-paste-list code alongside the instancing of clone has to be written to accompany.

yes the danger must be growing
As the rowers keep on rowing

Good Luck and have fun

I assume you have profound programming experience outside of Unity. If so, I’d recommend to make prototypes. Like 1-5 days per prototype.

Think of one solution that could work, implement it, and then try to perform or think of the work you’d have to do when you scale this architecture.

Then try another architecture and see how it relates to the other one.

And maybe a third.

The thing is: you’ll learn a lot about the pros and cons of these architectures in the context of your game, as well as learning a lot about Unity. I tend to go back and forth myself, and end up getting nowhere, or stuck after a longer time because the approach wouldn’t scale and refactoring is too daunting. Other times I might have the energy and willpower to refactor it all because it’s worth it.

But having made prototypes is especially helpful when I use some system of Unity or go for an architecture that I haven’t used or implemented before. There’s so much to learn, and you might end up realizing that the system everyone recommends isn’t actually so helpful in the context and scale of what I’m trying to do. While other times you may realize that for example GUI is a much bigger and more involved part than expected, and it is best to spend some time abstracting the usually direct connection of consumer (model) and the view (GUI) by introducing a single GUI event dispatcher (controller) or fine-grained scriptable object variables/events/database (model-controller).

As to your design:
ScriptableObjects are essentially global variables. You cannot “copy” them, but you can instantiate copies. I don’t think this is going to help much, besides being able to enter the base-stats of every unit. But you can achieve the same thing with a MonoBehaviour that shows a [Serializable] struct (or array of structs) in its Inspector, where the struct is simply UnitBaseStats and allows you to edit whatever stats the units need. When you instantiate a unit, you pass in that struct and since it is a value type, it automatically is a local copy for the instantiated unit.

If you have many units where editing within Inspector can become a chore I’d recommend to check out Odin Inspector to make in-editor editing more comfortable, or straight-out move your variables into a spreadsheet that you import on the fly (ie CSV reader) to fill in the values of structs matching the table layout.

You should not be concerned about memory, you’re going to add a mesh and textures for each unity, those will consume hundreds of times more memory than whatever code architecture you use.

Hi All,

Thanks to everyone for your responses, I really do appreciate it!

Yeah, I need to get over the analysis paralysis and just start trying implementations. I keep wanting to minimize future headaches, but I don’t know enough so I should just accept the inevitable refactoring. I don’t have a great coding background (never done OO programing, just Python for research), but I’ve been doing alot of reading. I’m going to try either a Monobehavior or a regular C# class like you suggest and see what works best. Thank you, your reply was thoughtful and very helpful. I’ll be referencing this thread as I work.

1 Like

You won’t.

I suggest you do lots of unity doodling instead!

Throw stuff in a scene.

Write some scripts to make them do things (move? blink? move under player control?)

Write some scripts to do other things when the objects touch (play a noise? give a score?)

Now delete it all.

Go do something else in a different scene. Don’t use any of the stuff you made in the first one.

Lather, rinse, repeat.

Be like this guy: ask yourself, “Can I … ?”

Imphenzia: How Did I Learn To Make Games:

https://www.youtube.com/watch?v=b3DOnigmLBY

2 Likes

Most likely a mix of all three will be needed. Usually I use plain classes or structs as the bits of data that get thrown around between all the Unity objects. After all you can easily create instances with new() and don’t have to worry about their lifetime, unlike anything derived from UnityEngine.Object.

I’ve been messing around with the prototype abit, and I’ve actually found that when a player first buys a unit, instantiating the base unit SO and adding it to a list in the player manager class seems to work great for what I want. Then making a Monobehavior that attaches all the properties of the player’s unique SO (attack, health, sprite, etc.) to a game object along with a displayUI script whenever I need the unit on screen seems like it’ll do the trick. So it goes Database of SO’s → List of Player SO’s (distinct from database ones) → Monobehavior + UI when on screen.

I’m hoping that repeatedly buying and selling units doesn’t result in too many unreferenced SO’s, but I think those would get cleaned up by the GC, no? From reading, that’s my understanding.

1 Like

No, anything deriving from UnityEngine.Object (which scriptable objects do), you need to manually clean up yourself. This is because they all have second life in the C++ managed land.

So all these instantiated scriptable objects, you need to manually Destroy() them when no longer needed.

Personally, this is why I don’t use SO’s for such a purpose, and instead stick with plain old C# objects. Either as a means to encapsulate data, or to ‘wrap’ around scriptable objects and have the wrapper manage the mutable data.

1 Like

Hmmm, okay, I guess that wouldn’t actually be a problem if in the sell function I just call Destroy(). It’s just so convenient to instantiate the SO, but I may try a plain C# script if it becomes too cumbersome. Thanks for all the insight.

1 Like

I’ll definitely give this a try at some point. I’m wrapped up in this project right now with what time I have, and it’s teaching me alot about scripting and data structuring. But I definitely see how this would be a great technique to learn, knowing where to start was pretty terrifying.

Also… before you go to deep down the rabbit hole, have a think about how you’re going to do save games.

Often times your architecture and data structures are determined by whether you want this data to be saveable or not. There’s a lot of quirks when it comes to saving in Unity… so best to learn them now before you do all this work and realise none of it is compatible with saving in the context of Unity.

1 Like

That’s a great point, I haven’t really been putting any thought into saves. I think I’ll finish a few prototypes and try to implement a save system into each one.

1 Like