There is a minor bug at 6:10 You're just pushing that int to nowhere and always returning first item on list (items[0]) So it should be return items[index];
Great video! (This and the other on the ScriptableObjects Cookbook). After some days of watching videos on the subject, I must say this is one of the best. Please keep doing this kind of videos: spot-on, no series based, dynamic and fun!
This is such an important topic. You explain things so well, especially with the brief side-tangent examples/explanations as you do, it helps to elucidate this 'beyond beginner' concept. You have an excellent ability to come at topics in a very direct and usable way, from this one, and a couple of your previous videos I've seen(procedural tree and buildings/city generation). I really hope you continue making more videos, and in this same style as well. Cheers!
I'm probably using an anti-pattern, but I have a single SO called "Blackboard" which holds references to every important runtime object such as the player, the various managers, etc. This is then referenced by practically everything. Each of these managers, the player, etc. when spawned or enabled/awake, automatically set the references to themselves on the blackboard. I may eventually break this up into discrete SOs that contain references to specific groups of objects such as managers, but for now, it does the job well enough. Objects such as items automatically add and remove themselves from their respective managers which I've recently decided to convert to mono behaviors because each manager lives in the "GamePlay" scene which is always loaded along side the other scenes, and so items in the non game play scene are able to get the reference via the blackboard. By having zone / scene specific object instances automatically add and remove themselves, I can simply create prefabs and drop them in whatever scene I want without any further configuration.
Thanks a lot for this vid! I didn't understand the concept behind this (and I don't think that you have mentioned it explicitly) but basically with SO now our EnemyManager lives as .asset data so we can create a prefab out of the enemy and it will persist the connection to the SO while if we have a MonoBehaviour we would need to create / find it each time in our scene. Thanks!
I like the idea, though personally I would be inclined to jazz it up a little with a signal pattern. rather than dragging in a reference to the scriptable object, you could have the "AddGameObjectRuntimeSet" dispatch a Signals.Dispatch(this); and have a single scene instance scriptable binder that has the SO field assign in inspect, listen to that and perform the assignment, that way you can have one place in the scene to maintain which scriptable is the runtime set. you could even filter on GetType and drop them into separate sets. It also might be out of scope for this video as a tutorial, but I would add a custom indexer to the runtime set collection type so you can streamline the contract to playerSet[0].transform . great video.
Hm, this is interesting. Definitely gonna try and play around with this, but as you said, it's not a "one and only solution to everything" and I'm not super fond of the kinda hard-coded GetItem with and index. But it should be able to work around things like that. I do like the flexibility and the way dependencies will be resolved. Great video, keep it up Matt!
Thanks man! Yeah I think it could be improved for sure. This is more of just me pointing to the idea and opening a conversation. I definitely am going to experiment further myself as well. Thanks for checking it out!
This method is working great but my nav mesh agents seem to be losing the parent transform somewhere along the travel. They get close to the player transform but then so slightly beyond it and miss the trigger area I have set for them to setActice(false); Something I'm missing I think. Do I need to update the nav mesh at run time also?
This looks really useful. I've had some bad times with the race conditions you mentioned. Too many 'bool isInitialized' used and it's ugly. This seems like it would take some getting used to, but a useful trick. Thanks!
@@newbquesttv Considering starting yet another project that will likely remain unfinished, seems like a good candidate. Will do. Also, thought you might be interested in this: th-cam.com/video/nmhL32p1R0k/w-d-xo.html
You have a new subscriber! I found your videos while looking for a damage system for a little game I want to make. I was about to write my own but thought something as common as a Damage System has to be a solved problem and there would probably be tons of examples out there, right? There are and the more I look in to it, the more I think I do have to write my own because there are so many different ways to implement such a system. I may spend more time customizing someone elses vs writing my own.
Does this still work when you build your game? As far as I know saving data to Scriptable Objects works only in the editor, but I've seen similar solutions multiple times now. I'm just confused.
To actually save data persistently you'll need to serialize it to something like JSON. ScriptableObjects are better for passing references or for creating edit time data. You don't really want to write persistent data to them.
@@newbquesttv The question is I think, would code in this video work in build. Cause SO don't seem like they are changeable in build. You cannot change their values in runtime, once in build. So no adding nor removing from the list.
@@mikoajkubiak7775 Yes, it will work in a build. What won't work in the build is the serializing/deserializing of the data contained in the Scriptable Object. By this I mean that in the Unity Editor, we are accustomed to changing the values of variables on SOs either in the Inspector or via script, and they will be continue to persist even after you stop playing, unlike Monobehaviors. The persistence (the values being saved and carried over from play session to play session) is what won't work in a build. The values will be reset to the values that were set when you did the build. However, you can use a SO just like any other C# object - including modifiyng its values, in a build. The values will reset after you stop playing, but that's exactly the behavior you want for a runtime set. All that happens is they lose their ability to carry over values between play sessions (because in a build there is no Editor saving their state).
Hello Matt, I like seeing alternate uses for ScriptableObjects like this one. I hadn't encountered it before so thank you for showing it so clearly. I do wonder how this compares to, for example, the FSM of PlayMaker. In an FSM you can also register game objects to variables of that FSM. Does that suffer from the same order of operation issues as you described in the video? Keep up the great content!
Thanks, glad you found it clear! I wasn’t sure if I was jumping around too much :) I think playmaker and it’s use of assets does maybe have some similarities but I’ve only ever used it for tiny demos so can’t really speak too authoritatively on it. Glad you found the video interesting!
Well i tried to setup my enviroments a bit like this. Most of the time i just added a script dynamically with Component Add to the Instantiated GO and that script did most of the "spcial operations" like finding something etc. I used a lot of simple structures like Lists or Dictionaries to store information of GOs like what kind of enemy one GO is or stuff like that. Do`t know if that is an effective approach...
Another important use I think one could check out, is a modular scriptable object ways to check for collisions between 2 objects. I've been casually thinking about ways via Scriptable Object without having to disturb the other colliding object in the monobehaviour OnCollisionEnter() and co, callback functions.
Even with my adoption of a Modular Game Architecturing pattern via Scriptable Object, GameObjects still find themselves crossing each other's paths because of collision l, I just wish everything can be fully modular and during collision, every check and collision info is handled in Scriptable Objects
You mentioned at the end how this solves the problem of things being tied together (in the example of making a demo scene). But this setup seems like you'd run into a problem if you dropped an enemy into a scene with no player. In the enemy's Start(), wouldn't it get a null reference from the playerSet and then fail on every Update() due to a null reference exception?
Hey yeah that’s fair, I think my example here may not be perfect. Ideally the enemies wouldn’t be hard coded to chase the player from the beginning of the scene and have null checks and stuff. It’s an interesting question though of whether the player qualifies as a kind of singleton too with some of the same problems. I think it’s kind of rare you’d have scenes without any players but it’s possible. 🤔
I tend to not use scriptable object for this particular case, but more static classes with static methods, it's removing the need of passing scriptable object reference via the editor. Did you use both together sometimes and in which case?
Broadly I’d say what this helps to replace or provides an alternative to is the approach you describe with static singletons, like a game manager or enemy manager. I recommend checking our Ryan Hipple’s talk in the ScriptableCoolbook playlist for more context.
You can avoid your "race condition" problem by using the execution order attribute and give the singleton or managers (or whatever you want call them) a lower number (or even negative number) this assures that they will always be initialized first.
Many people consider this to be poor practice, as it's really easy to break things later on and not remember/realize why. If you move your code to a new project, it won't work unless you remember to set up the execution order the same way. Similarly, if you change the execution order later for some unrelated reason, you could end up breaking these things that rely on it. You're also relying on Unity not screwing with this feature later on. To the greatest extent possible, your code shouldn't depend on project settings to work.
no, dont do that, avoid doing that at all cost. Run away fast (racing). cuz down the road, you will have trouble remembering which and why you put the execution order that way when the project gets big...
Well obviously it shouldn't be overused. If you have to run your init logic in a specific order then you should only use this attribute on one class and this one class should initialize all other classes in the correct order and not add the attribute to all other classes, but in most cases you can avoid the "race" issue by properly splitting the logic between Awake and Start and / or use a class which handles the order of the initialization of other objects If you need to initialize your objects in a specific order then this is the "safest" way, because Awake / Start can run in a different order during editor / build, so don't write your code in a way that depends on the logic that the Start method of object A is called before the Start method of object B, just because it is called in that order in the editor, trust me this kind of code will cause you way more issues than the attribute with a explicit order. This is also the reason why some Unity classes use this attribute, because they want to assure that the order is correct and not "hope" that it will run in the correct order. - "you will forget why" IF you use this attribute then you should obviously add a comment to the attribute and write down why you need it and why this specific custom value as picked. If you have big projects you will have some documents which describe important details like this anyways, in addition to the comment in the specific class - "If you change it later, you can break things", I don't see the point here, why would you randomly change the value? If you change something important like that then you must have a good reason and review why you change it and to which number you change it - "Unity will break it". Not sure if you knew this but many of the Unity classes use this attribute and it has been there for many years, so they will not randomly break it. Or the change of breaking it is about the same as they could break anything else, but in this case it's easy to test if it works or not - "will not work if you move your code to a new project" by using the attribute the logic is independend of project settings, so yes it will work if you simply copy the code, but again, this attribute should only be used in rare cases. So hopefully you don't even need the code in the new project because you learned your lesson and set it up in a better way - "your code shouldn't depend on project settings" again, the attribute is part of the code, not a project setting, but overall a project can have many settings that have to be moved over if you want to have the same behvior in a new project for example tags, layers, physic settings etc. Btw singletones and global managers are also considered poor practice yet many Unity devs use them. I personally don't use them, but every project is different and requires different "tricks". This comment was only meant to make people aware that the attribute exists, I never stated that they should overuse it for every thing or that it is the best solution. Becaus adding the attribute is easier than refactoring your whole system, depending on how early you notice the "issue" ;)
I read: Stop Using Singletons And Runtime Set ScriptableObjects (Unity Tutorial) Notice the and? Yes, I was pretty confused (as I have heard the talk earlier)
I don’t really like doing linked series since people usually drop off pretty fast by second episode. That said something small and 2D that explores this in a focused way could be cool!
You are reinventing a dependency injection framework. It might be easier to just use an existing solution like Extenject and get familiar with this kind of architecture. Rolling your own DI framework takes some years of experience which obviously you don't have. Sorry, I'm too harsh, but you talk so confidently as if you know what you're doing. This is dangerous and misleads people.
Thanks for the feedback but if you watch my content I'm pretty clear that I'm not putting this out as "this is the architecture everyone should use" but instead "this is a tool and pattern I'm learning and experimenting with". If you have an approach you prefer or would like to see a different type of content I would love to see what you yourself would make. The magic of TH-cam is that we all can make whatever content we want to share with the world, and I think the world is a better place with more people learning and sharing. I don't think there's any world where making free programming tutorials is "dangerous" even if they're wildly or even subtly wrong.
@@newbquesttv You're right. It's true that we all can make whatever we want and share it. I just left my opinion here so that people know there is a better way. This approach brings accidental complexity to the project. This is the magic of comment-able content ;)
There is a minor bug at 6:10
You're just pushing that int to nowhere and always returning first item on list (items[0])
So it should be return items[index];
Yeah thanks, I saw that after I posted it 😅
Great video! (This and the other on the ScriptableObjects Cookbook). After some days of watching videos on the subject, I must say this is one of the best. Please keep doing this kind of videos: spot-on, no series based, dynamic and fun!
Thank you Gaspar! Yes I will definitely continue exploring this concept. Thanks!
@@newbquesttv sorry to bother, can't find this example on the repo. Just the enums ones are there, but not the Runtime Sets!
This is such an important topic. You explain things so well, especially with the brief side-tangent examples/explanations as you do, it helps to elucidate this 'beyond beginner' concept. You have an excellent ability to come at topics in a very direct and usable way, from this one, and a couple of your previous videos I've seen(procedural tree and buildings/city generation). I really hope you continue making more videos, and in this same style as well. Cheers!
Thanks so much for the kind words Eric! I really appreciate it :)
I'm probably using an anti-pattern, but I have a single SO called "Blackboard" which holds references to every important runtime object such as the player, the various managers, etc. This is then referenced by practically everything. Each of these managers, the player, etc. when spawned or enabled/awake, automatically set the references to themselves on the blackboard. I may eventually break this up into discrete SOs that contain references to specific groups of objects such as managers, but for now, it does the job well enough. Objects such as items automatically add and remove themselves from their respective managers which I've recently decided to convert to mono behaviors because each manager lives in the "GamePlay" scene which is always loaded along side the other scenes, and so items in the non game play scene are able to get the reference via the blackboard. By having zone / scene specific object instances automatically add and remove themselves, I can simply create prefabs and drop them in whatever scene I want without any further configuration.
Thanks a lot for this vid!
I didn't understand the concept behind this (and I don't think that you have mentioned it explicitly) but basically with SO now our EnemyManager lives as .asset data so we can create a prefab out of the enemy and it will persist the connection to the SO while if we have a MonoBehaviour we would need to create / find it each time in our scene.
Thanks!
I like the idea, though personally I would be inclined to jazz it up a little with a signal pattern. rather than dragging in a reference to the scriptable object, you could have the "AddGameObjectRuntimeSet" dispatch a Signals.Dispatch(this); and have a single scene instance scriptable binder that has the SO field assign in inspect, listen to that and perform the assignment, that way you can have one place in the scene to maintain which scriptable is the runtime set. you could even filter on GetType and drop them into separate sets. It also might be out of scope for this video as a tutorial, but I would add a custom indexer to the runtime set collection type so you can streamline the contract to playerSet[0].transform . great video.
Thanks yeah that’d be interesting to try. I haven’t worked much with signal patterns before. Appreciate the thoughtful comment!
Isn't this basically a view-model that you are describing?
This was a great tutorial! Definitely some great knowledge to have working with Unity.
Thank you! Appreciate your checking it out.
Would you please do an inventory/item system tutorial that utilizes these concepts?
Hm, this is interesting. Definitely gonna try and play around with this, but as you said, it's not a "one and only solution to everything" and I'm not super fond of the kinda hard-coded GetItem with and index. But it should be able to work around things like that. I do like the flexibility and the way dependencies will be resolved.
Great video, keep it up Matt!
Thanks man! Yeah I think it could be improved for sure. This is more of just me pointing to the idea and opening a conversation. I definitely am going to experiment further myself as well. Thanks for checking it out!
This method is working great but my nav mesh agents seem to be losing the parent transform somewhere along the travel. They get close to the player transform but then so slightly beyond it and miss the trigger area I have set for them to setActice(false); Something I'm missing I think. Do I need to update the nav mesh at run time also?
This looks really useful. I've had some bad times with the race conditions you mentioned. Too many 'bool isInitialized' used and it's ugly. This seems like it would take some getting used to, but a useful trick. Thanks!
Yeah exactly, in this case you know that things are there because they’re in the project. Give it a try and let me know how it goes!
@@newbquesttv Considering starting yet another project that will likely remain unfinished, seems like a good candidate. Will do. Also, thought you might be interested in this: th-cam.com/video/nmhL32p1R0k/w-d-xo.html
You have a new subscriber! I found your videos while looking for a damage system for a little game I want to make. I was about to write my own but thought something as common as a Damage System has to be a solved problem and there would probably be tons of examples out there, right? There are and the more I look in to it, the more I think I do have to write my own because there are so many different ways to implement such a system. I may spend more time customizing someone elses vs writing my own.
Glad to hear you found it useful thanks!
Does this still work when you build your game? As far as I know saving data to Scriptable Objects works only in the editor, but I've seen similar solutions multiple times now. I'm just confused.
To actually save data persistently you'll need to serialize it to something like JSON. ScriptableObjects are better for passing references or for creating edit time data. You don't really want to write persistent data to them.
@@newbquesttv The question is I think, would code in this video work in build. Cause SO don't seem like they are changeable in build. You cannot change their values in runtime, once in build. So no adding nor removing from the list.
@@mikoajkubiak7775 Yes, it will work in a build. What won't work in the build is the serializing/deserializing of the data contained in the Scriptable Object. By this I mean that in the Unity Editor, we are accustomed to changing the values of variables on SOs either in the Inspector or via script, and they will be continue to persist even after you stop playing, unlike Monobehaviors. The persistence (the values being saved and carried over from play session to play session) is what won't work in a build. The values will be reset to the values that were set when you did the build. However, you can use a SO just like any other C# object - including modifiyng its values, in a build. The values will reset after you stop playing, but that's exactly the behavior you want for a runtime set. All that happens is they lose their ability to carry over values between play sessions (because in a build there is no Editor saving their state).
Hello Matt,
I like seeing alternate uses for ScriptableObjects like this one. I hadn't encountered it before so thank you for showing it so clearly.
I do wonder how this compares to, for example, the FSM of PlayMaker. In an FSM you can also register game objects to variables of that FSM. Does that suffer from the same order of operation issues as you described in the video?
Keep up the great content!
Thanks, glad you found it clear! I wasn’t sure if I was jumping around too much :)
I think playmaker and it’s use of assets does maybe have some similarities but I’ve only ever used it for tiny demos so can’t really speak too authoritatively on it. Glad you found the video interesting!
Great video, very well explained
Thank you!
Hey, this is working in android? When I compile the game, I get all references to scriptable objects as null.
You have to either have it referenced some some scene (aka scene reference baking) or instantiate it before somehow referencing it.
Well i tried to setup my enviroments a bit like this. Most of the time i just added a script dynamically with Component Add to the Instantiated GO and that script did most of the "spcial operations" like finding something etc. I used a lot of simple structures like Lists or Dictionaries to store information of GOs like what kind of enemy one GO is or stuff like that. Do`t know if that is an effective approach...
Another important use I think one could check out, is a modular scriptable object ways to check for collisions between 2 objects. I've been casually thinking about ways via Scriptable Object without having to disturb the other colliding object in the monobehaviour OnCollisionEnter() and co, callback functions.
Even with my adoption of a Modular Game Architecturing pattern via Scriptable Object, GameObjects still find themselves crossing each other's paths because of collision l, I just wish everything can be fully modular and during collision, every check and collision info is handled in Scriptable Objects
You mentioned at the end how this solves the problem of things being tied together (in the example of making a demo scene). But this setup seems like you'd run into a problem if you dropped an enemy into a scene with no player. In the enemy's Start(), wouldn't it get a null reference from the playerSet and then fail on every Update() due to a null reference exception?
Hey yeah that’s fair, I think my example here may not be perfect. Ideally the enemies wouldn’t be hard coded to
chase the player from the beginning of the scene and have null checks and stuff. It’s an interesting question though of whether the player qualifies as a kind of singleton too with some of the same problems. I think it’s kind of rare you’d have scenes without any players but it’s possible. 🤔
I tend to not use scriptable object for this particular case, but more static classes with static methods,
it's removing the need of passing scriptable object reference via the editor.
Did you use both together sometimes and in which case?
Broadly I’d say what this helps to replace or provides an alternative to is the approach you describe with static singletons, like a game manager or enemy manager. I recommend checking our Ryan Hipple’s talk in the ScriptableCoolbook playlist for more context.
I saw, somewhere in youtube video how one guy had connected scriptable object like event machine, with less of code. What do you think about it? Matt.
Yeah I am going to do a video on scriptableobject events soon, just need to do some more research first
You can avoid your "race condition" problem by using the execution order attribute and give the singleton or managers (or whatever you want call them) a lower number (or even negative number) this assures that they will always be initialized first.
Many people consider this to be poor practice, as it's really easy to break things later on and not remember/realize why. If you move your code to a new project, it won't work unless you remember to set up the execution order the same way. Similarly, if you change the execution order later for some unrelated reason, you could end up breaking these things that rely on it. You're also relying on Unity not screwing with this feature later on. To the greatest extent possible, your code shouldn't depend on project settings to work.
no, dont do that, avoid doing that at all cost. Run away fast (racing).
cuz down the road, you will have trouble remembering which and why you put the execution order that way when the project gets big...
Well obviously it shouldn't be overused. If you have to run your init logic in a specific order then you should only use this attribute on one class and this one class should initialize all other classes in the correct order and not add the attribute to all other classes, but in most cases you can avoid the "race" issue by properly splitting the logic between Awake and Start and / or use a class which handles the order of the initialization of other objects
If you need to initialize your objects in a specific order then this is the "safest" way, because Awake / Start can run in a different order during editor / build, so don't write your code in a way that depends on the logic that the Start method of object A is called before the Start method of object B, just because it is called in that order in the editor, trust me this kind of code will cause you way more issues than the attribute with a explicit order. This is also the reason why some Unity classes use this attribute, because they want to assure that the order is correct and not "hope" that it will run in the correct order.
- "you will forget why" IF you use this attribute then you should obviously add a comment to the attribute and write down why you need it and why this specific custom value as picked. If you have big projects you will have some documents which describe important details like this anyways, in addition to the comment in the specific class
- "If you change it later, you can break things", I don't see the point here, why would you randomly change the value? If you change something important like that then you must have a good reason and review why you change it and to which number you change it
- "Unity will break it". Not sure if you knew this but many of the Unity classes use this attribute and it has been there for many years, so they will not randomly break it. Or the change of breaking it is about the same as they could break anything else, but in this case it's easy to test if it works or not
- "will not work if you move your code to a new project" by using the attribute the logic is independend of project settings, so yes it will work if you simply copy the code, but again, this attribute should only be used in rare cases. So hopefully you don't even need the code in the new project because you learned your lesson and set it up in a better way
- "your code shouldn't depend on project settings" again, the attribute is part of the code, not a project setting, but overall a project can have many settings that have to be moved over if you want to have the same behvior in a new project for example tags, layers, physic settings etc.
Btw singletones and global managers are also considered poor practice yet many Unity devs use them. I personally don't use them, but every project is different and requires different "tricks". This comment was only meant to make people aware that the attribute exists, I never stated that they should overuse it for every thing or that it is the best solution. Becaus adding the attribute is easier than refactoring your whole system, depending on how early you notice the "issue" ;)
i can do whatever i want sir!
I read: Stop Using Singletons And Runtime Set ScriptableObjects (Unity Tutorial)
Notice the and? Yes, I was pretty confused (as I have heard the talk earlier)
Good job ;)
Thanks 😊
To be honest you're creating a lot of other problems while trying to solve another one...
"Reads title"
Me: what?
@6:13 Ummm you might want to take a closer look at your code there because that is definitely not what it's doing...
yup, it's a bug, see the pinned comment
Umm, have you thought of making a 2d game series where your viewers can follow like a simple game while using this pattern?
I don’t really like doing linked series since people usually drop off pretty fast by second episode. That said something small and 2D that explores this in a focused way could be cool!
@@newbquesttv Yeah, that would be nice :) will be waiting hehe
You are reinventing a dependency injection framework. It might be easier to just use an existing solution like Extenject and get familiar with this kind of architecture. Rolling your own DI framework takes some years of experience which obviously you don't have. Sorry, I'm too harsh, but you talk so confidently as if you know what you're doing. This is dangerous and misleads people.
Thanks for the feedback but if you watch my content I'm pretty clear that I'm not putting this out as "this is the architecture everyone should use" but instead "this is a tool and pattern I'm learning and experimenting with".
If you have an approach you prefer or would like to see a different type of content I would love to see what you yourself would make. The magic of TH-cam is that we all can make whatever content we want to share with the world, and I think the world is a better place with more people learning and sharing. I don't think there's any world where making free programming tutorials is "dangerous" even if they're wildly or even subtly wrong.
@@newbquesttv You're right. It's true that we all can make whatever we want and share it. I just left my opinion here so that people know there is a better way. This approach brings accidental complexity to the project. This is the magic of comment-able content ;)
@@behnamrasooli8801 yup, understood. Thanks!