I'll be job hunting soon as a web dev, and over the holidays I played Wasteland 1 (the 1988 one) which sparked my curiosity over building a text-based RPG as a demo project to showcase. This really goes over some solid logic foundations I need to get started. Thanks so much for this!
Your videos are so valuable to me. I'm a decent coder, but I don't spend that much time researching coding solutions. I'll generally just use the knowledge I have to hack together a solution (that isn't necessarily the best). Every time my hacked together solution starts to buckle, I'll scroll through your videos to see if there is a better way, and usually there is. Your coding patterns videos and tutorials are some of the most valuable programming content on RUclips. Looking forward to your future content.
I had a thought to basically have a stat modifier stack, which is a list of stat modifier commands. You have your base stats, set in stone and serialised in the inspector. Then as buffs/debuffs are added the object it they're added to that stats modifier stack. Each time a stat is read it's pulled through the modifier stack to produce the actual value. Added benefit is it also stores which modifications have been applied to the state. Of course you would have a quick read buffer for the calculated stat when no recalc is required.
This is pretty close to what I ended up creating (in the next video). I didn't cache value but that could be a nice addition to my system with a little extra logic.
Upgrade Video: ruclips.net/video/HuI3vcMIggM/видео.html Stats in Unity Blog Post: onewheelstudio.com/blog/2022/11/8/how-i-do-stats Upgrades Blog Post: onewheelstudio.com/blog/2022/12/13/upgrade-system-stats-part-2 Odin Inspector: assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041?aid=1100lHSw Sirenix SO video: ruclips.net/video/W5ECIJyoW80/видео.html Code Monkey SO video: ruclips.net/video/5a-ztc5gcFw/видео.html
How I ended up here? Just like how you imagined, looking up how other people do it - though I'm pleasantly surprised you came to the same conclusion I did: Dictionaries. Great stuff, looking forward to the next video you teased here that'll include the problems you had with the ScriptableObjects and your upgrade system!
Hey, just writing a comment here on a more recent video, I was checking the observer pattern video and it was really clear. I might check this one later because I'm interested about it Thanks for explaining everything so clearly !
What I have also added to my statvalues: - unit (KG, HP, XP) - label for display - value limit [0..100] - but yes, what to do if I upgrade my hero's shield, how to raise the upper limit? needs a solution - valuechanged event - useful to change display scores/stats or check for ... death or level up events.
I use a combination of scriptableobjects and systems that consume them to sort of achieve instance-based entity-component-system architecture. each instance of unit (say, enemies) start with same stats, then different systems consume them (health, enemyMovement, targetDetection etc.) to monitor and upkeep instance-based values for each of these. It's not perfect but it's working.
I love scriptable objects also. For a Genetic AI unity project I have been making I couldn't figure out how to use it. I needed new stats that is a random value of two other game objects stats then a random chance of changing + or -. I was not able to figure out how to make a new scriptable object while in game. Only know in editor. So used stats that were part of the movement of the AI. Your videos are great. Just found your channel and used your RTS camera for this project.
Good job and thank for sharing! I am writing a stat system for a game now just like you I am doing a little research to see what is our there and that is how I found your video. If I may say, the one thing I did not like is when you used an ENUM for the Stat Types. I even saw that you added a couple of "Unused slots" in the enum in case you needed yo squeeze in more stats in the future. Personally I do not like to use ENUMs for things that may change in the future. When that is the case specifically in Unity I either set a arbitrary value to the enum items like 100, 200, 300, this way is very easy to squeeze in other values in the middle without others losing their prior values. Or even better, since you are already using Scriptable Objects here, you could also create a StatTypeSO that just defines the different stat types. This way you also get the benefit of being able to create new stat types entirely in the editor without having to refactor any code and add items to an ENUM. Either way, good video! Subscribed!
You’re totally right on the enums. I like the simplicity but there are potential big downsides. Assigning values is a great way to avoid most of those downsides. Love it.
Hey, around 3:25 onward you talk about if you upgrade your tower, all towers can be affected easily because they all use the same scriptable object. My question here is how? Are you changing your scriptable object's data at runtime (which I hear you typically don't want to do?) and polling it from the towers? Or what are you doing here to propagate the upgrades to all of your towers?
The real danger with SOs at runtime is thinking of them as a save system. It can also get a bit messy in the editor, but there are ways around it. So bascially I clean out any changes when the game stops. Probably other ways around it. I might restructure things but for now it's working.
In get method is best to throw an error. Not gently show message - it will be ignored with time. This thing need to blow up early. Also it can be written in four lines 'if (Try) return ...; return 0;' else are bad. In our project we name method with "Get" - that will throw error if it can't find a thing. And we call method with "Find" if it can't find and silently return default value. Also there are a problem, that you need to diffrentiate a static data and real data. You made a point in the end of the video about it, but it need to be more explicitly. You have two version of data - static - that never changes between run, and real data - that may change in run. Static data is your database, real data is your gameplay data. Before use, you may wan't to convert static data to real data. Point is that static data can be in other format - like in yours it is a some kind of dictionary in scriptable object, but real data can be anything realy. May be fields in class, objects from database (sprites/prefabs etc). So it will need to be initialized. We store our data in components, because we use ECS pattern with Entitas and this is just a convinient way. Nice video. Subscribbed :)
Scriptable object can be used as a short save option, like i make pick a character and i save this choice on a scriptable object, in every scene they share this scriptable object, no need to save this data if it's for one run. or temporary
Is scriptable objects the way to go for creating skill modifiers that changes the behavior of other things like projectiles and between eachothers modifiers? With Unreal Engine its really easy, to access classes directly without adding them as components, but I cant do the same in Unity with [SerializeField] to reference a class in the inspector. So in Unreal Engine it was as simple as adding a reference to for example a class SkillModifier and just add all SkillModifiers in a list and also the UI button itself could have reference directly to the class itself before it sent that class to my SkillManager to unlock or upgrade depending on the SkillLevel. In the Unreal Engine case i created a struct that held the class + level of the skill. This seems to me with Unity to be a bit more difficult so far, but I keep researching for a good solution and so far the SO might be the best option.
I think SOs are great for what you’re describing. I use them along with my stats system for upgrades and such. There’s a few gotchas like even private fields get serialized by default, but overall works really well. Over all I view SOs as little data prefabs.
@@OneWheelStudio Thank you for reaching back to me, I'll look into more of the SO solution for this. The way i seen SO before has been that they are purely data but it seems they can be more than that. My biggest thing is that i wanted everything to be designer friendly with being possible to drag and drop different modifiers like a reference to one of the classes in the inspector then communicate that back to a modifier manager of sort to "unlock" them. Kinda how i did in Unreal Engine via blueprints atm for a degree project. Button reference different modifiers class and communicate back to manager to unlock, lock and increase level if they already exist in an array of a custom struct that holds a class and a level of the modifier. Time to experiment and learn :D
@@B4NTO they definitely can be more than just data! Play around. There are so many uses for them. Also, check out Odin Inspector (I make their videos) super useful for inspector customization and Odin plays really nicely with SOs.
I don't really agree with you on the "SO's should not be used as a save system" statement. Serializing class based data as a save system is basically the same as serializing SO data, but SO's give you the option to give access to said data to a bunch of different places simultaneously like you mentioned. I've been using SO's as a framework for tying together systems using shared data and even events/signals for a while now in various client projects and I love the flexibility it gives you. With some special parsing I even use it for practically seamless streaming and storing said data to the cloud. I'd love to hear your thought though
To paraphrase @CodeMonkeyUnity video: The issue is that in the editor an SO can work like a save solution. Simply set the value of a variable and that value is there when you leave play mode or when you open up your project at some later date. But in a build, changing the value of a variable on an SO isn't persistent. You can't save a level or a player state. This can be a huge bummer if you've worked on a game for months before testing in a standalone build. This is the pitfall that myself and Code Monkey are trying to help folks avoid. I've got a link to the Code Monkey video in a pinned comment and in the video description. it's worth checking out.
Hi, I have a question. Where I see the source code of this video to be able to follow along better?. Im unable to find the SerializedScriptableObject part
i am having an issue where I used your code for the stats class, I copied your code exactly so that I could edit and alter it where needed but decided to just run a test(with all the upgrade information that is used in the next video mostly commented out!) and for some reason I am getting an error I cant seem to figure out where its not finding the namespace Stat which i find odd.....
I was using SO's for upgrading my stats & so that i can show them easily in UI just by referencing the data. But this caused me requirement to reset values every time i start the game meaning setting it to the default values too. But that caused to have "a lot of" values for defaults & currents.
Odin Inspector is a third party asset it's great: assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041?aid=1100lHSw As for the enum, they should show up if they are public or serialized. In my examples, I'm using an enum as the key to a dictionary - the dictionary only shows up because I am using the SerializedScriptableObject. Hope that helps.
In my project I've added other types, but that does mean I needed additional ways to access those types. In my case I had a list of terrain types. I just made the list public. Not fancy, but it does the job. But if you have colors or vectors you could create additional fields and or additional dictionaries depending on how many of each type you have and what makes sense. There are a ton of edge cases!
I Think you can get more generic by creating a generic class that holds the stat type and the stat data itself by the generic type. That way you only need a list of this stats class or a dictionary with the stat type as key. After that you need to create some methods like UpgradeFloat or UpgradeColor, for example, to do the calculations and only need to call for a Upgrade method on the upgrade class and with a generic parameter as the value to be changed.
The SO is per unit type. Not per unit in the scene - just to make sure we're on the same page. So the prefab of each unit has a reference to the SO for that type of object. Maybe this is what you were suggestion?
I know it sounds so cliche to read these comments, BUT I was JUST working on my current iteration of stats. I found this at the perfect time, YAY for not working too hard on it last night. I also needed a base stats and unit stats, but a contructor or something to give me the combine stats would work. (Yet to see how you handle this in your Upgrade video.) System.Linq can parse a list, a bit easier, not sure HOW much a performance improvement, but would mitigate the list use case. Thanks for coming out with GREAT videos, sometimes at the perfect time. My first shot, not tested. using System.Linq StatInfo localStatInfo = stats.Find(a => a.StatType == sType); if (localStatInfo != null) return localStatInfo.Value;
LINQ is definitely slower than a simple for/foreach loop (and most of the time allocates memory). Also Find is not a LINQ extension, but it's part of List, the equivalent (and slower) versions in LINQ are First/FirstOrDefault, Single/SingleOrDefault. TLDR: Avoid using LINQ in hot paths (Update()...etc).
I'll be job hunting soon as a web dev, and over the holidays I played Wasteland 1 (the 1988 one) which sparked my curiosity over building a text-based RPG as a demo project to showcase. This really goes over some solid logic foundations I need to get started. Thanks so much for this!
Your videos are so valuable to me. I'm a decent coder, but I don't spend that much time researching coding solutions. I'll generally just use the knowledge I have to hack together a solution (that isn't necessarily the best). Every time my hacked together solution starts to buckle, I'll scroll through your videos to see if there is a better way, and usually there is. Your coding patterns videos and tutorials are some of the most valuable programming content on RUclips. Looking forward to your future content.
I had a thought to basically have a stat modifier stack, which is a list of stat modifier commands. You have your base stats, set in stone and serialised in the inspector. Then as buffs/debuffs are added the object it they're added to that stats modifier stack. Each time a stat is read it's pulled through the modifier stack to produce the actual value. Added benefit is it also stores which modifications have been applied to the state. Of course you would have a quick read buffer for the calculated stat when no recalc is required.
This is pretty close to what I ended up creating (in the next video). I didn't cache value but that could be a nice addition to my system with a little extra logic.
Upgrade Video: ruclips.net/video/HuI3vcMIggM/видео.html
Stats in Unity Blog Post: onewheelstudio.com/blog/2022/11/8/how-i-do-stats
Upgrades Blog Post: onewheelstudio.com/blog/2022/12/13/upgrade-system-stats-part-2
Odin Inspector: assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041?aid=1100lHSw
Sirenix SO video: ruclips.net/video/W5ECIJyoW80/видео.html
Code Monkey SO video: ruclips.net/video/5a-ztc5gcFw/видео.html
How I ended up here? Just like how you imagined, looking up how other people do it - though I'm pleasantly surprised you came to the same conclusion I did: Dictionaries. Great stuff, looking forward to the next video you teased here that'll include the problems you had with the ScriptableObjects and your upgrade system!
Hey, just writing a comment here on a more recent video, I was checking the observer pattern video and it was really clear. I might check this one later because I'm interested about it
Thanks for explaining everything so clearly !
Good info on SOs, thanks. Looking fwd on how you tackled the "CodeMonkey" problem.
What I have also added to my statvalues:
- unit (KG, HP, XP)
- label for display
- value limit [0..100] - but yes, what to do if I upgrade my hero's shield, how to raise the upper limit? needs a solution
- valuechanged event - useful to change display scores/stats or check for ... death or level up events.
What a GOLDMINE of a video!!
I use a combination of scriptableobjects and systems that consume them to sort of achieve instance-based entity-component-system architecture. each instance of unit (say, enemies) start with same stats, then different systems consume them (health, enemyMovement, targetDetection etc.) to monitor and upkeep instance-based values for each of these. It's not perfect but it's working.
i like the way you cut the video with a slight "One Wheel" pause.
I love scriptable objects also. For a Genetic AI unity project I have been making I couldn't figure out how to use it. I needed new stats that is a random value of two other game objects stats then a random chance of changing + or -. I was not able to figure out how to make a new scriptable object while in game. Only know in editor. So used stats that were part of the movement of the AI. Your videos are great. Just found your channel and used your RTS camera for this project.
Good job and thank for sharing! I am writing a stat system for a game now just like you I am doing a little research to see what is our there and that is how I found your video.
If I may say, the one thing I did not like is when you used an ENUM for the Stat Types. I even saw that you added a couple of "Unused slots" in the enum in case you needed yo squeeze in more stats in the future. Personally I do not like to use ENUMs for things that may change in the future. When that is the case specifically in Unity I either set a arbitrary value to the enum items like 100, 200, 300, this way is very easy to squeeze in other values in the middle without others losing their prior values. Or even better, since you are already using Scriptable Objects here, you could also create a StatTypeSO that just defines the different stat types. This way you also get the benefit of being able to create new stat types entirely in the editor without having to refactor any code and add items to an ENUM.
Either way, good video! Subscribed!
You’re totally right on the enums. I like the simplicity but there are potential big downsides. Assigning values is a great way to avoid most of those downsides. Love it.
Hey, around 3:25 onward you talk about if you upgrade your tower, all towers can be affected easily because they all use the same scriptable object. My question here is how? Are you changing your scriptable object's data at runtime (which I hear you typically don't want to do?) and polling it from the towers? Or what are you doing here to propagate the upgrades to all of your towers?
The real danger with SOs at runtime is thinking of them as a save system. It can also get a bit messy in the editor, but there are ways around it. So bascially I clean out any changes when the game stops. Probably other ways around it. I might restructure things but for now it's working.
In get method is best to throw an error. Not gently show message - it will be ignored with time. This thing need to blow up early. Also it can be written in four lines 'if (Try)
return ...;
return 0;' else are bad. In our project we name method with "Get" - that will throw error if it can't find a thing. And we call method with "Find" if it can't find and silently return default value.
Also there are a problem, that you need to diffrentiate a static data and real data. You made a point in the end of the video about it, but it need to be more explicitly. You have two version of data - static - that never changes between run, and real data - that may change in run. Static data is your database, real data is your gameplay data. Before use, you may wan't to convert static data to real data. Point is that static data can be in other format - like in yours it is a some kind of dictionary in scriptable object, but real data can be anything realy. May be fields in class, objects from database (sprites/prefabs etc). So it will need to be initialized.
We store our data in components, because we use ECS pattern with Entitas and this is just a convinient way.
Nice video. Subscribbed :)
The upgrade system at 1:24 looks very cool, is there a video where it is being built?
Yep! Here you go: ruclips.net/video/HuI3vcMIggM/видео.html
@@OneWheelStudio so in the next video :D sorry for being impatient and cheers!
No worries. YT doesn’t do an awesome job helping with that kind of stuff.
Scriptable object can be used as a short save option, like i make pick a character and i save this choice on a scriptable object, in every scene they share this scriptable object, no need to save this data if it's for one run. or temporary
Is scriptable objects the way to go for creating skill modifiers that changes the behavior of other things like projectiles and between eachothers modifiers?
With Unreal Engine its really easy, to access classes directly without adding them as components, but I cant do the same in Unity with [SerializeField] to reference a class in the inspector.
So in Unreal Engine it was as simple as adding a reference to for example a class SkillModifier and just add all SkillModifiers in a list and also the UI button itself could have reference directly to the class itself before it sent that class to my SkillManager to unlock or upgrade depending on the SkillLevel.
In the Unreal Engine case i created a struct that held the class + level of the skill.
This seems to me with Unity to be a bit more difficult so far, but I keep researching for a good solution and so far the SO might be the best option.
I think SOs are great for what you’re describing. I use them along with my stats system for upgrades and such. There’s a few gotchas like even private fields get serialized by default, but overall works really well.
Over all I view SOs as little data prefabs.
@@OneWheelStudio Thank you for reaching back to me, I'll look into more of the SO solution for this. The way i seen SO before has been that they are purely data but it seems they can be more than that.
My biggest thing is that i wanted everything to be designer friendly with being possible to drag and drop different modifiers like a reference to one of the classes in the inspector then communicate that back to a modifier manager of sort to "unlock" them.
Kinda how i did in Unreal Engine via blueprints atm for a degree project. Button reference different modifiers class and communicate back to manager to unlock, lock and increase level if they already exist in an array of a custom struct that holds a class and a level of the modifier.
Time to experiment and learn :D
@@B4NTO they definitely can be more than just data! Play around. There are so many uses for them.
Also, check out Odin Inspector (I make their videos) super useful for inspector customization and Odin plays really nicely with SOs.
there is no SerializedScriptableObject in unity, my Dictionary is not displayed in inspector
It's a part of Odin Inspector
I don't really agree with you on the "SO's should not be used as a save system" statement. Serializing class based data as a save system is basically the same as serializing SO data, but SO's give you the option to give access to said data to a bunch of different places simultaneously like you mentioned.
I've been using SO's as a framework for tying together systems using shared data and even events/signals for a while now in various client projects and I love the flexibility it gives you.
With some special parsing I even use it for practically seamless streaming and storing said data to the cloud.
I'd love to hear your thought though
To paraphrase @CodeMonkeyUnity video: The issue is that in the editor an SO can work like a save solution. Simply set the value of a variable and that value is there when you leave play mode or when you open up your project at some later date. But in a build, changing the value of a variable on an SO isn't persistent. You can't save a level or a player state. This can be a huge bummer if you've worked on a game for months before testing in a standalone build.
This is the pitfall that myself and Code Monkey are trying to help folks avoid. I've got a link to the Code Monkey video in a pinned comment and in the video description. it's worth checking out.
Hi, I have a question. Where I see the source code of this video to be able to follow along better?. Im unable to find the SerializedScriptableObject part
SerializedScriptableObject is part of Odin Inspector.
enums can share the same integrer ?
i am having an issue where I used your code for the stats class, I copied your code exactly so that I could edit and alter it where needed but decided to just run a test(with all the upgrade information that is used in the next video mostly commented out!) and for some reason I am getting an error I cant seem to figure out where its not finding the namespace Stat which i find odd.....
8 months later...
I was using SO's for upgrading my stats & so that i can show them easily in UI just by referencing the data. But this caused me requirement to reset values every time i start the game meaning setting it to the default values too. But that caused to have "a lot of" values for defaults & currents.
SerializedScriptableObject is a class provided by Odin inspector? I wonder how did you provided an enum in the Inspector
Odin Inspector is a third party asset it's great: assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041?aid=1100lHSw
As for the enum, they should show up if they are public or serialized. In my examples, I'm using an enum as the key to a dictionary - the dictionary only shows up because I am using the SerializedScriptableObject.
Hope that helps.
I noticed that all your stats are of type float, how will you do if you need other type of stats, like for example vector3 or color ?
In my project I've added other types, but that does mean I needed additional ways to access those types. In my case I had a list of terrain types. I just made the list public. Not fancy, but it does the job.
But if you have colors or vectors you could create additional fields and or additional dictionaries depending on how many of each type you have and what makes sense. There are a ton of edge cases!
I Think you can get more generic by creating a generic class that holds the stat type and the stat data itself by the generic type. That way you only need a list of this stats class or a dictionary with the stat type as key. After that you need to create some methods like UpgradeFloat or UpgradeColor, for example, to do the calculations and only need to call for a Upgrade method on the upgrade class and with a generic parameter as the value to be changed.
Noice!!
Can I try to brainstorm a stat system with you on Discord? I have a few ideas along the lines of this, but with a few modifications.
For sure. Make a post in #gamedevhelp and ping me I’ll make sure to take a look.
@@OneWheelStudio I had to find this message before YT doesn't alert me to these. jeez lol
@@OneWheelStudio I am Danny Algorithmic
How do I ping you in Discord? I cannot find your name
creating instances of scriptable objects for every game unit sounds bad. Why not a class which have ref to blueprint scriptable object?
The SO is per unit type. Not per unit in the scene - just to make sure we're on the same page. So the prefab of each unit has a reference to the SO for that type of object. Maybe this is what you were suggestion?
@@OneWheelStudio ah, i miss this part. My bad
Honestly, this video is so confusing to me, that I don't even know what I'm looking at.
I know it sounds so cliche to read these comments, BUT I was JUST working on my current iteration of stats. I found this at the perfect time, YAY for not working too hard on it last night. I also needed a base stats and unit stats, but a contructor or something to give me the combine stats would work. (Yet to see how you handle this in your Upgrade video.)
System.Linq can parse a list, a bit easier, not sure HOW much a performance improvement, but would mitigate the list use case.
Thanks for coming out with GREAT videos, sometimes at the perfect time.
My first shot, not tested.
using System.Linq
StatInfo localStatInfo = stats.Find(a => a.StatType == sType);
if (localStatInfo != null) return localStatInfo.Value;
LINQ is definitely slower than a simple for/foreach loop (and most of the time allocates memory). Also Find is not a LINQ extension, but it's part of List, the equivalent (and slower) versions in LINQ are First/FirstOrDefault, Single/SingleOrDefault.
TLDR: Avoid using LINQ in hot paths (Update()...etc).