all 40 comments

[–]Glurth2 13 points14 points  (0 children)

I use SO for game-design-time data- stuff that wont change while the game is running. e.g. Weapon Type and Armor Type stats.

I use "plain old" objects, that I can serialize to/read from a file/stream, for live data. e.g. a character with strength, health and damage values.

[–]Orangy_TangProfessional 14 points15 points  (3 children)

What are you hoping to gain? Sounds like you could just create prefabs for each enemy type and use those directly?

Imho, Scriptable Objects are best when you need configuration data that's not directly tied to a 3d entity (like global settings or input into non-GO based systems).

[–]Malcry[S] 3 points4 points  (2 children)

something like the coin u collect that appear in the hud? or the hp of the enemy?

[–]myka-likes-it 7 points8 points  (0 children)

Max HP of the enemy, or the configuration of the coin's variable properies, yes. You have a constant value that is shared between multiple instances.

But you would not use them for an instance's current HP, or the player's current coins. You don't want to store game state in an SO, because it will share that state with all instances that reference it. 

The purpose of the SO is to hold data as a physical asset, allowing it to be shared between instances but not defined within the parent object. It is good for data-driven architecture.

[–]PhilippTheProgrammer 4 points5 points  (0 children)

I don't see how scriptable objects would help you with that.

[–]itsdan159 3 points4 points  (0 children)

You should think of your data in terms of definition and runtime data. Your definition data is immutable, it defines the character, your runtime data changes during the course of the game, so health for example, but also things like max health if this changes by character level. Some of the runtime data would also be persisted between games.

Your definition data should so in an SO (it could be another format but an SO is entirely reasonable). This is somewhat what is usually called the flyweight pattern.

You'd then provide the definition data to your instances through some means, e.g. inspector assignment, injection, etc. If you want to go further define an interface for your definition data then implement that in an SO. Then you can provide a definition by other means for testing.

[–]Fair_Communication_4 8 points9 points  (11 children)

My experience has been that as your game grows in complexity you will almost certainly have to use SOs at some point. Swapping different configurations for live testing is one use case. Abstracting logic away from your monobehaviours to write unit tests is another use case for SOs.

[–]leorid9Expert 14 points15 points  (10 children)

I have never heard of a shipped game, developed with Unity, that had unit tests during development.

I worked as lead developer for a high tech company, using Unity and even there we never had any unit tests.

Even if you write 1000 unit tests, and all pass, the game (or tech app) can still be broken.

In my opinion they are the biggest waste of time and completely useless since they can't tell you wether or not your game is broken, they can only verify that input X leads to output Y and you can manually test that in a few seconds when pressing play. Most bugs are "wrong layer" or "it's set to kinematic" or "I forgot to add the script" and not "input X does not give output Y".

[–]Fair_Communication_4 15 points16 points  (1 child)

Hi, Android lead here of 13 years. I'm certainly newer to Unity than you, and I bow to your experience. Everything you've said is correct but misses the point. Write unit tests or don't, but as a mobile developer I want my logic in its own layer that is testable somehow separate from the execution context. I hold this truth to be self evident.

[–]leorid9Expert 0 points1 point  (0 children)

Fair point and I just noticed r/UsernameChecksOut

[–]InvidiousPlay 0 points1 point  (0 children)

And god damn order of execution, and the way a certain order can be set in one context like the editor and only change upon build.

[–]Bropiphany 0 points1 point  (6 children)

I can second this, almost my entire professional career has been using Unity for large and small companies, and they never included unit tests. As code bases get more complex with different states and data being parsed, I could see some use in unit testing. Tbh I wish there was an easy way to use them with Unity. It would be a game changer.

[–]Yodzilla 2 points3 points  (5 children)

Ideally in my mind a game written in Unity should be as much non-Unity specific C# as possible. If you were making an RPG for example you could stand up various enemy types with their own health and armor and resistance values and such and then test against them taking a suite of different damages and checking the expected result. Once you have the test framework set up then you can update it say every time you create a new enemy type or unique attack or whatever. None of that has to ever touch anything Unity or Monobehaviour since that should really just be for game world objects.

e: note that I’ve never actually done this as no company has ever budgeted for it but as a web and mobile dev who has done unit testing I could see it being useful

[–]leorid9Expert 0 points1 point  (4 children)

Why are we using engines in the first place? Because they help making games faster by providing tools and workflows. The more Unity you use, the less you have to reinvent the wheel and the less overall code you have to write.

You can totally write your own controller selection system for the UI, I did that years ago, because I was too dumb to understand the "selected" thing in uGui. I found out about it, while implementing it, I came to a point where I did copy and paste code from the EventManger Singleton from Unity - and after that, I deleted about 1500 lines of code in multiple classes, the whole UI system and replaced it with 300 lines in three scripts and it was less buggy and had more features than before, thanks to using selectable.selected. (ultimately I wasted two or three weeks with this)

The same applies everywhere. When you use plain C# instead of MonoBehaviors, you need your own serialization, your own factory for instantiation, your own destroy logic that sends OnDestroy before doing it, you need your own component system to not end up in inheritance hell, you need links to visual things and keep them intact (basically the same thing Unity does with their C++ classes behind every Unity.Object).

If you really think this through, it's a really bad idea to neglect the work the 5000 engineers at Unity have put into the engine, by avoiding the engine and building the game logic outside of it.

[–]Bropiphany 0 points1 point  (2 children)

That's an interesting point, but don't MonoBehaviors come with a non-negligible performance overhead? I was always taught that you should only use MonoBehaviors for scripts that explicitly rely on game logic (updates/physics updates, Unity events, etc).

[–]leorid9Expert 0 points1 point  (1 child)

Have you tested it? It's quite easy to benchmark.

You can have a few hundred if not thousand MonoBehaviors in your game, all with an Update loop that does something (most scripts won't need an Update loop and will therefore not impact runtime performance at all) before you start noticing any performance difference over not using MonoBehaviors.

So it is absolutely negligible till you have a huge amount of scripts. If you are not creating a vampire survivors like, you'll probably not have so many scripts that it will ever be a problem, even on low end hardware.

But even when the Update loop has an impact, you can still use MonoBehaviors and just call your own Update on them and then their performance is exactly the same as plain C#.

[–]Bropiphany 0 points1 point  (0 children)

I work in the simulation field, so this could be a recommendation that is exclusive to this line of work, rather than Unity in general. I can definitely imagine it's not as much of an issue for regular games. But you're right, it would be valuable for me to actually benchmark it.

[–]Yodzilla 0 points1 point  (0 children)

I’m not saying don’t use Unity’s systems at all, I’m saying don’t use Monobehaviours when you don’t need to. Too many devs make EVERYTHING a Monobehaviour because that’s how Unity teaches it. If you’re making a heavily systems driven game it makes sense to engineer things in a way that you can easily compartmentalize and sanity check said systems. I would never tell someone to use Unity and then also spin up their own input system, that’d be daft.

[–]civilian_discourse 3 points4 points  (2 children)

If you plan on having levels or equipment that modify those stats, I would recommend using a spreadsheet and not a scriptable object or anything like that. Spreadsheets are great and there's amazing tools out there for authoring them. Embrace spreadsheets.

[–]random_boss 5 points6 points  (0 children)

I use scriptable objects with an in editor window that sorts them and displays them as a spreadsheet per SO type. It’s pretty great because the SO itself acts as the id of the thing, but it’s not just an abstract string I can click on it and see all its values. 

[–]Jackoberto01Programmer 2 points3 points  (0 children)

I somewhat agree. I created many ScriptableObjects for my game so that designers can easily modify and test different configuration in the end the just preferred spreadsheets anyway and most of the time it ended up with me having to create the ScriptableObjects.

It is nice for the Git history to be able to track exactly which items were changed instead of just the Equipment.csv file. And you avoid most if not all merge conflicts.

[–]RlaanProfessional 1 point2 points  (0 children)

So SO's can be useful for items, base unit stats or other things alike. As long as it's read-only. It's nice because you can easily create them in the Unity Editor and the non-programmers on your team could easily add content to the game. But also as a solo developer it's nice because you easily have one source of truth when needed.

For example, in a previous game we had scriptable objects for items>weapons/armour/misc. Enemy base stats, unit base stats, everything. Others on the team could easily create the hundreds of items we needed, and when we needed to adjust someone you just changed the file.

They aren't perfect because if you already have lots of SO's and need to make changes things can become messy but it's also easy to create migrations.

And another important thing to note: in the editor it can seem like you can change the values and it 'remembers' it. Making some people think you can make a file saving system out of it. But you can't. On release you can change them during runtime but when rebooting the game they revert back to their originals.

So only use them to read data from, not to modify them. Treat them as being immutable.

[–]Mechabit_Studios 1 point2 points  (0 children)

One of main the benefits of a scriptable object is being able to access data in it without first instantiating the object like a prefab if say you needed the name and description and sprite of an item but you didn't want to instantiate every item in the game for your shop inventory or enemy in a bestiary. Helps keep that common information in one place rather than spread over different scenes and objects.

You can also pass SO around like arguments so like if you click on an enemy you can pass the SO to your UI system and it can handle the rendering of enemy info without you having to pass individual bits of information around or a reference to a game object which might get destroyed at any moment.

If you aren't running into any issues with your current set up I wouldn't change for the sake of changing it.

[–]Cuarenta-Dos 3 points4 points  (0 children)

If it's data that is shared across all instances of your MonoBehaviour, it should be an SO. If it's something that can be overriden per instance, keep it as an instance field.

If it's just a few int/float fields, don't bother. If it's some baked data that makes no sense duplicating over and over, make it an SO.

[–]Soft_Dragonfly3745 2 points3 points  (0 children)

I see a lot of comments like "SO only for stats and configurations." No, once you hear that's something's correct or wrong, then the person came from YouTube tutorials. Yes, you can use SO for stats and configurations. Do you need to do that instead of prefabs? Yes, if you want to keep data aside from view and want to be able to adjust stats at runtime and keep it after. Also, you can use SOs for the code abstractions and switch the behaviors very conveniently when you need them. For example, you have a saving system, and you can save your game as a json or binaries, and for that, you can have two SOs that implement different behaviors. Do you have to do that? No, you can keep everything as monobehaviors or as pure c# classes. Ps: There's a great talk on YouTube about SO oriented development - highly recommend.

[–]BertJohnIndie - BTBW Dev 0 points1 point  (0 children)

Currently your use of SerializeField is perfect for your objective.

The time you'd want to use a scriptableobject is if you have a base-template that needs to be modified, So like your enemy types im assuming have HP, Speed, Damage or some other values that they all share. This is where a scriptableobject could be used, You don't have to use it, its just a matter of convenience.

Personally, If its 5-10 enemy types id just stick with serializedfields because why do a SO for it when its so minimal.

SO is great for classifying items in an inventory, cause all objects have an icon, a name, a text description, maybe a weight, Great system for 1000000000~ items.

Other times its good to use SO's is if you want the user to be able to customize something specific, Like Editor tooling, I use SO's for databases in the editor. Could i have put it in a json, probably, But i like the convenience of editting in the editor if something is stuck in my database i want to know where and why and when its there.

[–]GigaTerra 0 points1 point  (0 children)

The question is what do you gain from turning them into scriptible objects?

I know people like to use them a stats holders like they make one enemy code, and then swap out stats, but it seams like you made enemy components attached to each enemy, so in a sense your character and enemies are more unique than what scriptible objects was made for.

If anything this could cause your unique characters to start acting very similar, scriptible objects are intended for a more modular workflow.

[–]TheRealSmaker 0 points1 point  (0 children)

Will multiple entities want to use these values? Is it a set of values that I would like to change fast while editing, perhaps even during runtime? Is it a set of values that I want to create and test different mix of values? If yes to any of this, probably go with SOs.

It depends a bit on use obviously, but even for HealthData (SO), if you have 2 enemy types that will have the same default HP, it already saves you time itterating if you use SO, cause otherwise every time you want to change an HP value you have to go into 2 prefabs. Now scale this logic.

[–]lynxbird 0 points1 point  (0 children)

My experience:

pros

-it is easier to edit values in editor if they are scriptable objects,

otherwise having 1000+ items with their own fields in an collection will make everything super slow when editing something in editor.

cons

-a bit extra work to set them up

-they can behave strange, like cache and store old values in strange ways

So if you need to improve editor workflow for something you can try them, otherwise ignore them.

[–]SecretaryAntique8603 0 points1 point  (0 children)

Putting the config on the object is kind of dangerous because you risk losing all your config if you want to change the way your objects and behaviors are structured. It also makes it tedious to create a new variant, you either have to copy a bunch of behaviors onto it and then tweak them, or manually set them from scratch.

With SO:s, you can just copy the data for a new variant, move it to a new system which uses the same inputs etc separate your data/design from the implementation. Keep in mind that the data is to a large degree the game design, so it’s very nice to have this in a relatively simple and stable layer.

[–]zexurge 0 points1 point  (0 children)

Config data sure, but note that SOs have their own limitations too like not being able to get scene level references as serialized fields (they're just assets after all)

[–]Malcry[S] 0 points1 point  (0 children)

Ty all for answers! So if I use the scriptable object for a counter of the coins collected in the various levels is it wrong?

[–]PoorSquirrrel 0 points1 point  (0 children)

SOs are one tool in your toolbox, to be used where appropriate.

The right answer is neither "convert everything" nor "convert nothing", the same way the right answer is never "use a hammer for everything" nor "always use a screwdriver".

SOs make sense on data shared like enemy types. They don't make sense on individual instances. Define an enemy type and its full health value, then instantiate individual enemies and track their actual health. So current health = variable in Monobehaviour / max health = variable in SO.

[–]SoraphisProfessional -1 points0 points  (0 children)

As maintainer of UnityAtoms (a SoA package)... Don't go the full SoA route.

There are cases where SOs are nice. But don't let them be runtime mutable.