Dood I was literally making a game rn, and figuring out how can I use scriptable objects to organize my enemy ai behaviors . Then this video got recommended to me like youtube is reading my mind and I realized this video was just uploaded 5 hours ago lol . What's more funny is I just subscribed to you like 15 hours ago. Anyways, keep up the good work man !!
I've just used this idea to remake the AI in a game project. What we expected to take a month to rebuild. We did a working base in less than 2 days. Best idea ever to make modular behaviours. Old AI was over 1500 lines. The new one with all behaviours so far combined 260 lines. Update. We still use this system as base for our new Enemy AI, but with all the stuff we've covered we found out a few little limits. Pro: -Making modular AI behaviours easy to switch with another. -Specific behaviours for specific enemies. Con: -You can't use Async or IEnumerator in a scriptable object. They do work but for exemple 2-3 enemies die at the exact time, only one will trigger the Async or IEnumerator. You need to put them in the main script and use the the scriptable object behaviours to call it. -Start and update function doesn't exist in scriptable obj. Possible to create a ''fake'' update function called from the main script calling the update on the active behaviours.
Great vid as always! This is my fave way to do AI in unity. You can also pass the target directly as a parameter. I also like to pass "context" (list of surrounding objects) as a parameter to allow boid like behaviors ☺
Honestly if I hadn't been subscribed already, that last bit would've earned it regardless haha! Keep up the good work and sense of humor! I have learned a great deal from your tutorials already!
This seems really good! I will try it. If I want to create different behaviors and have conditions for switching between them, would you contain the logic for switching between states within each scriptable object?
How do you make it that you want the enemy to go for an objective but if the player gets inside his range he go for the player and if the player ran (out of his range) he goes back to his objective? There is also when the enemy collides you can just push him back and it just does not chase you anymore.
Nice video! Thanks a lot for the tutorial, now my enemy becomes very flexible. But one problem tho, when I duplicate the enemy, all of the enemies suddently becomes very weird, but when I duplicate the behaviours, all of the enemy works fine. Is this becouse a behaviour act as all of the enemies brain? If so is there any way that I can make the enemy to have different brains without having to duplicate everything?
I don't like this system because of the assets duplication (base script + scriptable object per single behavior), but I use scriptable objects for stats management and let machine states or behavior nodes do the actual behavior, that's probably a way less cluttered method if you don't mind dealing with code to "register" each enemy behavior in a dictionary so they can be found by a name passed in a manager class
Would you mind explaining this to me? Im currently making an rpg and one thing im trying to figure out is how i wanna handle stats and other things ect.
Once again another amazing video !?! I was just wondering if you have any videos on this... I have a pick up item (call it a perk) that give me a super jump automatically.... but I wanted it so u could scroll through different perks and then picking one to enable the super jump... if that makes sense.. kinda like a weapon list but for a perk lol
Great explanation, only thing you probably dont want to do is the FindGameObjectWithTag() in the update. for me i went around this issue: by creating another function in my "Brain" and "ChaseBrain" scripts. i called it InitializeThink. inside the "ChaseBrain" i use this function to search for the player tag and pass it to the target variable. in Brain: "public abstract void InitializeThink(EnemyBehaviour behaviour);" in ChaseBrain: " public override void InitializeThink(EnemyBehaviour behaviour) { chaseTarget = GameObject.FindGameObjectWithTag(chaseTargetTag); }" inside "Enemy Thinker" i simply put "brain.InititalizeThink(this)" into the start function. so basically my InitializeThink is my ChaseBrains Start function now wich is pretty handy. in Enemy Thinker: "brain.InitializeThink(this);" of course you can do other stuff there as well. but its sure better than calling things every frame if we dont need to. i hope this helps someone,
@@user-ts3rv5hb7u it's been a while since i wrote this but I think it's in InitializeThink where you would give the enemy behavior as an argument I'm not a pro or anything but it worked for me
Nice video, however I really don't understand the difference with scriptable object vs regular scripts? It's just using an abstract class which we are inhereting from. This can be done using regular scripts. How is inhereting from scriptable objects different? (really trying to grasp it😂)
Id watch the talk i have linked in the description it shows the advantages with scriptable objects over monobehaviors. These things can definitely all be done with regular scripts but using scriptable objects gives you better modularity/testability in a lightweight serializable asset you can play around in in the unity editor. Because its an asset its not confined to being in a scene at runtime like monobehaviors
Interesting approach and one I might give it a try, I have to say though you do a GetComponent and FindGameObjectWithTag on Update which are both very heavy. It would be best to pass the parameters on Start to the Scriptable Object on its own parameters.
You are 100% right. passing it in is good, storing target results in a dictionary for constant time lookup is another good option - the architecture is the focus and more important than the actual demo logic
@@BMoDev yeah sure I know you were showing a demo and that part of the code was not important to Scriptable Objects. I feel, however, like it should be noted in case someone sees this it and tries to copy it
You could do something similar to Unity Atoms (register the object with the tag and then have a static dictionary, so you can do something like this in your brain once AtomTags.OnInitialization(() => target = AtomTags.FindByTag(_tagToTarget.Value).transform);
Running "find object by tag" inside "think" which is called every frame inside an Update function call? I don't see how this degree of abstraction is helpful (the example is too simple) and surely running gameobject.find on the update call of every enemy object is very wasteful? I just don't get it. I bailed at 7:30 once it became clear you were calling find so often - this could kill a game with lots of enemies?
The abstraction becomes beneficial the more types of AI behaviors you have, the more behaviors you have the more you can tweak parameters that define those behaviors (e.g. you can use a slider in the inspector to determine the range before an enemy ai will chase the player, or the variance in accuracy when shooting at the player) these things can be done without modifying a single line of code, and can be plugged into different enemy types at will. In regards to the performance that's just a fault of me trying to keep the tutorial under 30 mins without fleshing out more of a system, and the intent was to showcase the architecture rather than the implementation. Calling GameObject.FindObjectByTag can actually be perfectly fine in update, given on the first call you store the results in, say, a dictionary giving you constant time lookup, and as long as you have a value that lookup will not be performed again. Alternatively, it could be completely unnecessary because you could actually pass in the target without needing to perform a lookup at all. All things I could have illustrated better and explained more - much of this will have to be setup for your specific game, so the deeper into an example I go the less general the information becomes
Dood I was literally making a game rn, and figuring out how can I use scriptable objects to organize my enemy ai behaviors . Then this video got recommended to me like youtube is reading my mind and I realized this video was just uploaded 5 hours ago lol . What's more funny is I just subscribed to you like 15 hours ago.
Anyways, keep up the good work man !!
Haha thanks! Let me know of theres any specific topics youre looking for
Bmo big brain 🧠 super helpful AI tutorial!
I've just used this idea to remake the AI in a game project. What we expected to take a month to rebuild. We did a working base in less than 2 days. Best idea ever to make modular behaviours. Old AI was over 1500 lines. The new one with all behaviours so far combined 260 lines.
Update. We still use this system as base for our new Enemy AI, but with all the stuff we've covered we found out a few little limits.
Pro:
-Making modular AI behaviours easy to switch with another.
-Specific behaviours for specific enemies.
Con:
-You can't use Async or IEnumerator in a scriptable object. They do work but for exemple 2-3 enemies die at the exact time, only one will trigger the Async or IEnumerator. You need to put them in the main script and use the the scriptable object behaviours to call it.
-Start and update function doesn't exist in scriptable obj. Possible to create a ''fake'' update function called from the main script calling the update on the active behaviours.
Great vid as always! This is my fave way to do AI in unity. You can also pass the target directly as a parameter. I also like to pass "context" (list of surrounding objects) as a parameter to allow boid like behaviors ☺
You deserve so many more subs!
Great video
Honestly if I hadn't been subscribed already, that last bit would've earned it regardless haha! Keep up the good work and sense of humor! I have learned a great deal from your tutorials already!
Thank you youtube recommended for this blessing.
Showing love! 🔥🔥
Thanks!
Thank you for the tutorials. Making my way through them all, including the occasional BMO "Ted Talk" videos.
This seems really good! I will try it. If I want to create different behaviors and have conditions for switching between them, would you contain the logic for switching between states within each scriptable object?
Thanks! I didn't think you could use those with AI.
muito bom, justamente oque eu estava procurando.
How do you make it that you want the enemy to go for an objective but if the player gets inside his range he go for the player and if the player ran (out of his range) he goes back to his objective?
There is also when the enemy collides you can just push him back and it just does not chase you anymore.
BIG BRAIN!!!!!!!
Nice video! Thanks a lot for the tutorial, now my enemy becomes very flexible. But one problem tho, when I duplicate the enemy, all of the enemies suddently becomes very weird, but when I duplicate the behaviours, all of the enemy works fine. Is this becouse a behaviour act as all of the enemies brain? If so is there any way that I can make the enemy to have different brains without having to duplicate everything?
Awesome.
Does this concept replace the need for a State Machine then?
I don't like this system because of the assets duplication (base script + scriptable object per single behavior), but I use scriptable objects for stats management and let machine states or behavior nodes do the actual behavior, that's probably a way less cluttered method if you don't mind dealing with code to "register" each enemy behavior in a dictionary so they can be found by a name passed in a manager class
Would you mind explaining this to me? Im currently making an rpg and one thing im trying to figure out is how i wanna handle stats and other things ect.
Once again another amazing video !?! I was just wondering if you have any videos on this... I have a pick up item (call it a perk) that give me a super jump automatically.... but I wanted it so u could scroll through different perks and then picking one to enable the super jump... if that makes sense.. kinda like a weapon list but for a perk lol
Hmm not directly, you could modify something from my Powerup vid but Ill add this to my video idea list
@@BMoDev thank you so much ! Keep up the great work love your content !
Great explanation, only thing you probably dont want to do is the FindGameObjectWithTag() in the update. for me i went around this issue:
by creating another function in my "Brain" and "ChaseBrain" scripts. i called it InitializeThink. inside the "ChaseBrain" i use this function to search for the player tag and pass it to the target variable.
in Brain: "public abstract void InitializeThink(EnemyBehaviour behaviour);"
in ChaseBrain:
" public override void InitializeThink(EnemyBehaviour behaviour)
{
chaseTarget = GameObject.FindGameObjectWithTag(chaseTargetTag);
}"
inside "Enemy Thinker" i simply put "brain.InititalizeThink(this)" into the start function. so basically my InitializeThink is my ChaseBrains Start function now wich is pretty handy.
in Enemy Thinker: "brain.InitializeThink(this);"
of course you can do other stuff there as well. but its sure better than calling things every frame if we dont need to.
i hope this helps someone,
Where does it take enemy behaviour as an argument
@@user-ts3rv5hb7u it's been a while since i wrote this but I think it's in InitializeThink where you would give the enemy behavior as an argument
I'm not a pro or anything but it worked for me
good video, thanks. But, lots of using FindgameObjectswithTag and GetComponent in every Update call
yeah was thinking the same, but thats something we can easily improve on luckily
Nice video, however I really don't understand the difference with scriptable object vs regular scripts?
It's just using an abstract class which we are inhereting from. This can be done using regular scripts.
How is inhereting from scriptable objects different?
(really trying to grasp it😂)
Id watch the talk i have linked in the description it shows the advantages with scriptable objects over monobehaviors.
These things can definitely all be done with regular scripts but using scriptable objects gives you better modularity/testability in a lightweight serializable asset you can play around in in the unity editor. Because its an asset its not confined to being in a scene at runtime like monobehaviors
@@BMoDev thanks for the answer, I'll check it out :)
Speedrunning Scriptable Objects Any%
Neat concept, but calling GetComponent in Update makes my brain hurt (pun not intended). Don't do that if you care about performance.
FindObjectWithTag in the update function? Do better! That should be cached on start or only run if not target it found.
Interesting approach and one I might give it a try,
I have to say though you do a GetComponent and FindGameObjectWithTag on Update which are both very heavy. It would be best to pass the parameters on Start to the Scriptable Object on its own parameters.
You are 100% right. passing it in is good, storing target results in a dictionary for constant time lookup is another good option - the architecture is the focus and more important than the actual demo logic
@@BMoDev yeah sure I know you were showing a demo and that part of the code was not important to Scriptable Objects. I feel, however, like it should be noted in case someone sees this it and tries to copy it
You could do something similar to Unity Atoms (register the object with the tag and then have a static dictionary, so you can do something like this in your brain once
AtomTags.OnInitialization(() => target = AtomTags.FindByTag(_tagToTarget.Value).transform);
Running "find object by tag" inside "think" which is called every frame inside an Update function call? I don't see how this degree of abstraction is helpful (the example is too simple) and surely running gameobject.find on the update call of every enemy object is very wasteful? I just don't get it. I bailed at 7:30 once it became clear you were calling find so often - this could kill a game with lots of enemies?
The abstraction becomes beneficial the more types of AI behaviors you have, the more behaviors you have the more you can tweak parameters that define those behaviors (e.g. you can use a slider in the inspector to determine the range before an enemy ai will chase the player, or the variance in accuracy when shooting at the player) these things can be done without modifying a single line of code, and can be plugged into different enemy types at will.
In regards to the performance that's just a fault of me trying to keep the tutorial under 30 mins without fleshing out more of a system, and the intent was to showcase the architecture rather than the implementation.
Calling GameObject.FindObjectByTag can actually be perfectly fine in update, given on the first call you store the results in, say, a dictionary giving you constant time lookup, and as long as you have a value that lookup will not be performed again.
Alternatively, it could be completely unnecessary because you could actually pass in the target without needing to perform a lookup at all.
All things I could have illustrated better and explained more - much of this will have to be setup for your specific game, so the deeper into an example I go the less general the information becomes