Should you use ScriptableObjects excessively in the game architecture?

Semi-amateur Unity dev here, please listen to my insane ramblings and tell me what’s wrong with them. I feel like I’ve been stuck in my head for too long on this question and I need an outside perspective of someone with actual experience. The post isn’t really structured, I’m just writing down my thoughts.

I’ve been stuck figuring out how to best make a good universal state machine for my characters for a while now and I’ve seen quite a few approaches to it, the most basic being a bunch of monobehaviour scripts existing on an entity. But there’s this official open source Unity game about a pig chef that also uses state machines and it basically runs entirely on scriptableobjects - they’re used to store conditions, transitions, states and all of that is eventually stored in a transition table used by a monobehaviour state machine attached to a character. I halfway through to transitioning to that system because it was aimed at better control in the editor, because thanks to all the important pieces being scriptableobjects, you can just plug in whatever conditions and states you want, but what bothers me is that this approach 1) requires you to create an instance of each scriptable object, so you’re basically doubling the number of stuff in your assets folder, 2) creates a whole bunch of scriptableobjects that are only used in one place, which I’m not sure whether that’s an ideal use for them.

In addition to all that, the thing i realized was that states themselves don’t even actually have to be neither monobehaviours, nor scriptableobjects logically speaking - they don’t need to have the Unity callbackslike Update, because you can have custom alternatives that are called in the state machine’s callbacks, so they don’t need to be monobehaviours and they don’t need to be used by multiple objects or store data between launches like scriptableobjects. So theoretically you can build an architecture, where all of the states are just regular C# classes that are created somewhere. But this approach comes with its own peroblems as well, it’ll be harder to use in the inspector, for example.

The next thing is that I need to solve an architectural issue with my simple statemachine, which is that only one state can be active at the same time. This is an issue because there are some values that are shared between states that need to be updated when only one state is active - for example the number of jumps the player has in the air that is shared between an InAir state and an InAirCrouching state. For a while now I’ve been using a simple architecture, where the more specific states like Idle are inherited from broader states like Grounded and the logic from the latter is just reused in the former with the latter never actually being created, it’s just an abstract thing. But I’ve started to think that actually instantiating those higher states and keeping that shared logic in them, updating them separately and then updating the actual child state that is currently in use might be a solution, kind of like a behaviour tree. And I can’t seem to figure out how to do this thing without knowing how to approach the general structure of this whole thing that I talked about above - should i make a whole bunch of scriptableobjects? Make an ugly monobehaviour tree in the scene? Make regular C# classes and put each classes’ children into the constructor though code?

I’m pretty much paralyzed with possibilities. I think I’m stuck with a case of “trying too hard to abstract everything and overcomplicating simple things”. I’d appreciate any help, even if it’s slap to the face and a loud “PLEASE STOP SAYING DUMBASS THINGS, YOU’RE NOT EVEN MAKING SENSE!”.

I love insane ramblings!! I do them myself all the time. Watch me… here I go again!

Back to ScriptableObjects… they’re just bags of data that can be edited in the Unity Editor trivially.

While ANY bag of data can be edited in the Unity Editor, you have to write the editor / insepctor code to edit that bag of data.

ScriptableObjects are special bags where the parts contained within that CAN be serialized by the editor WILL be easily editable, hence saving you time writing boring editor scripts.

Go do tutorials… lots of ultra simple game tutorials. As you do them the screaming raging flow in your brain will at first increase greatly and become even more chaotic, but then as you apply discipline and the Two Steps to Successfull Tutorial Doing below, it will slowly coalesce into a raving mass that you can at least work with in your brain.

Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

How to do tutorials properly, two (2) simple steps to success:

Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That’s how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.

Fortunately this is the easiest part to get right: Be a robot. Don’t make any mistakes.
BE PERFECT IN EVERYTHING YOU DO HERE!!

If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.

Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there’s an error, you will NEVER be the first guy to find it.

Beyond that, Step 3, 4, 5 and 6 become easy because you already understand!

Here are some great content creators:

Imphenzia: How Did I Learn To Make Games:

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

Imphenzia / imphenzia - super-basic Unity tutorial:

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

Jason Weimann:

https://www.youtube.com/watch?v=OR0e-1UBEOU

Brackeys super-basic Unity Tutorial series:

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

Sebastian Lague Intro to Game Development with Unity and C#:

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

YUCK. Don’t fall for that trap.

FSM finite state machines:

This is my position on finite state machines (FSMs):

https://discussions.unity.com/t/834040/6

I’m kind of more of a “get it working first” guy.

Your mileage may vary.

“Should you use scriptable objects excessively in your game architecture?”

Probably not. In the context of a ‘universal’ FSM, they’re probably better on a per-project basis. I’ve just always down states with plain classes, but if I needed something more advanced I’d probably make a node editor too hook up different states.

Beyond that scriptable objects have plenty of good uses, but shouldn’t be what you use for everything.

I know where you are. You stare at Unity and Unity stares back at you.

It’s a classic analysis-paralysis with this software because it has become huge, riddled with so many different approaches, architectures, and design challenges, all united under the same logo. Yes, we all know the name of the engine, but this is ridiculous.

I will try to address your problem not by commenting the exact approaches you mentioned, but by going meta, because I know too well the feeling of becoming lost when facing the ocean of seemingly endless possibilities, none of which sound optimal or at least adequate enough to carry you through the project without headaches.

The solution is (relatively) simple, and goes hand-in-hand with what Kurt said. Iterate!
Development is an iterative process, and you shouldn’t be afraid to start something knowing fully you’re going to throw it away. For that reason, start small, make an experiment, a test. Do it dirty, quickly, and try to solve issues as you go. Set some goal with the experiment, reach it, then you can upscale.

This process will paint a much better mental picture for you and you’ll begin to see the answers behind your questions. You can try and nibble your project from many different angles by doing this, and you will surely be in a much better position to grasp the actual territory and terrain of the project. But the most important thing is – because you aren’t fully committed yet, there is nothing to be disappointed about. In fact, you’ll likely become even more hungry to see things through, once the most optimal route for your project gets in your sight.

Just iterate.

This process is effectively the same thing as “refactoring”, however the logic is reversed. Because you’ve planned ahead to do this refactoring no matter what, this will never feel like any time was wasted, and you can be truly creative with your initial attempts, without having to pay attention to any kind of sanitation, documentation, or etiquette.

I personally go like this:

  1. dirty hacks and shameful smells that will forever fester on my computer
  2. gritty and unsanitary proof of concepts which can maybe remind me of some clever ideas sometimes
  3. well-intended but ultimately half-abandoned “wishful thinkings”
  4. grisly behemoths of low level I-am-too-smart-I-want-it-all-to-be-perfect
  5. glorious optimum of many shiny new things I’ve discovered on the way, pragmatically cobbled into something that works and reads well

If you see it like this, it makes sense that you want to accumulate as many of 5s as you possibly can, but sadly there is no cutting corners.

As a side note, I rarely use anything Unity recommends, unless it’s a) unavoidable/integral, b) something boring, c) a solution that saves heck of a lot time. I’m clearly a typical conservative programmer, I like to be in mental charge of my projects, and to know exactly what’s going on. (c) is obviously the hardest for me, because I have to see things the way others see them (which is usually abhorrent), and to adopt needlessly “fresh” paradigms and adjust my own views and habits. New APIs are to me like having to learn laws, you can’t or won’t possibly ever learn all of it – AND it’s constantly shapeshifting while you’re trying, month after month – but oh boy you’re going to jail if you do something wrong. But hey, welcome to the 21st century, where we all make huge sandcastles out of coarse quartz sand and pretend it’ll hold.

Just remember that analysis-paralysis stems from the fact that you don’t really want to waste your time doing something that will end up as a useless maintenance disaster that dies on your computer. So scout ahead instead, and always do one thing at a time. Iteration is a strategy.

2 Likes

This ^ ^ ^

Another great strategy is: do something, do anything, every day.

Even the tiniest silliest little piece of throwaway code can get your mind spinning and thinking.

And even the tiniest bit of code or scene setup is better than not doing anything.

Here’s a fun one: make a new blank Unity project and make a scene for today: September02.

Drag a cube in that scene. Drag another one in. Make a few materials. Make one cube brown and make it large and flat and low… that’s your ground. Make the other cube blue… that’s your player.

Now connect input to the player… it’s as simple as a script like this on the player:

void Update()
{
  float xmove = Input.GetAxis( "Horizontal");
  float ymove = Input.GetAxis( "Vertical");

// moves on the X/Z plane
  Vector3 motion = new Vector3( xmove, 0, ymove);

  motion *= 5;  // speed

  transform.position += motion * Time.deltaTime;
}

Put it on the cube. Save the scene. Play the cube around the level.

Keep going, keep adding stuff, when you get tired, save it and put it away.

Now do something tomorrow, but different, and save it as September03.

DO SOMETHING EVERY DAY!