Enhance Your Game: EASY Enemy Logic Using Scriptable Objects (State Machine PART 2) | Unity Tutorial

Поділитися
Вставка
  • Опубліковано 17 лис 2024

КОМЕНТАРІ • 56

  • @MarushiaDark316
    @MarushiaDark316 Рік тому +9

    Very cool. Very modular. One thing to make this even tidier is, since you're using inheritance, you don't need to override any method whose only purpose is to call the base function. You only need to override things that would be different in child classes. In fact, you can probably get away with a root SO class that has all the base functions like Enter, Exit, Trigger, Update, etc. and override those in child SO classes as well since not every state will necessarily have all of them, but they could. Just saves on duplicate code.

  • @aiden1010
    @aiden1010 11 місяців тому +2

    You're a savior, this and the previous video were the only ones that have actually worked for me. Saved my school project, mad respect
    😌

  • @Diablokiller999
    @Diablokiller999 Рік тому +1

    Nice, easy, not too complicated.
    I'm using state machines with substates, a bit more advanced but gives you more fine tune between states.

  • @scolate_
    @scolate_ Рік тому +3

    4:25 my bones broke. You are against the DRY principle

  • @yaboijagerlive7923
    @yaboijagerlive7923 10 місяців тому +2

    Great video, this is actually how I'm currently implementing my own state machine. Although I have some doubts about instantiating the scriptable object states. I love how using scriptable object makes it easier to create new states but It just somehow feels hacky to me, anyone else feels the same?

  • @NightRunStudio
    @NightRunStudio Рік тому +2

    Love this! I totally would love to see more content like this. Keep it up!

  • @eduardscobioala9645
    @eduardscobioala9645 Рік тому +3

    another extremely great and useful video, thanks

  • @chadmoberly7044
    @chadmoberly7044 Рік тому +1

    Congrats on 5K subs!

  • @Lutojar1
    @Lutojar1 Рік тому

    Thanks so much! I watched about 20 vids about FSM on youtube and this one was the most useful.

  • @shockingchris9809
    @shockingchris9809 11 місяців тому

    I've already commented on the first video, and this is no different, AMAZING structure and I love the compartmentalization of scripts. I'm noticing that in the TriggerChecks you are setting the Aggro property when entering and exiting. You have logic to go from Idle to Chase and chase to Idle, but I don't see the change from Attack back to Chase or Chase back to Idle. It... kind of looks like your enemies in your preview are changing from attack to chase and chase to idle, but maybe I just missed that.
    I assume that it is as simple as adding an if on the AttackSOBase like so:
    public virtual void DoFrameUpdateLogic()
    {
    if (!enemy.IsWithinStrikingDistance)
    {
    enemy.StateMachine.ChangeState(enemy.ChaseState);
    }
    }
    and an else if on the ChaseSOBase that checks if the IsAggroed is false (exits the trigger collider area) to change to the Idle state again. But I also think that this seems too simple. Thank you again!! Incredible video!

    • @shockingchris9809
      @shockingchris9809 11 місяців тому

      Nope. I know the solve 😅 I just needed to do some adjusting of the distance to leave (Which I haven't switch out yet), noticing that it was just 3 times bigger than the trigger range. It just has to be set to something understandable as a leaving distance.

  • @12bhan
    @12bhan 5 місяців тому

    I'm stuggling to understand which script to put various logic in (in general, not the specific tutorial). Especially between the idle script derived from the EnemyState class and the one that is derived from the scriptableobject EnemyIdleSOBase class. Feels like it needs to go in the script that I derive from the scriptableobject class. The various states derived from EnemyClass appear to serve as intermediaries there to run the methods of the scriptableobjects. Great videos!

  • @muriilouwu
    @muriilouwu 2 місяці тому

    great tutorial, thanks!

  • @midniteoilsoftware
    @midniteoilsoftware Рік тому

    Great stuff Brandon!

  • @sdflogan2010
    @sdflogan2010 5 місяців тому

    Hi! Absolutely I LOVE IT. This is just what I was looking for.
    Just one quick question: You mentioned that each state will generate it own instance loading it on memory. Can we have a problem on mobile games using this solution with a lot of spawned enemies at the same time or the memory consumed is very low?

  • @AstralNostalgia
    @AstralNostalgia 4 місяці тому +1

    I think , how to deal tiwht Animators class? sincronize the animator ? the coolddowns and the state machine?

  • @Unity-wb9wv
    @Unity-wb9wv Рік тому +6

    Hi, can you help me understand why this differs from creating instances of classes per behavior? What is the benefit of the SO in this instance (aside from tweaking at runtime), since we can make public fields on the enemy to drag/drop different idle/attack/chase classes with varied logic as well? I'm a bit unclear what having these as SO's gives us over monobehaviours? Thanks for the videos!😀

    • @sasquatchbgames
      @sasquatchbgames  Рік тому +6

      That's a great question.
      You could certain just make Monobehavior scripts and slap them on different prefab versions of enemies. But that doesn't give you the benefit of a state machine with the easier debugging and better performance.
      As for dragging and dropping different behaviors into public fields, you actually can't do that using monobehaviors. I tried that first.
      Let's say you create a class, called EnemyIdleBehaviorBase, which inherits from Monobehavior, and then you create 3 different idle behavior classes which inherit from EnemyIdleBehaviorBase...if you set a [SerializedField] private EnemyIdleBehaviorBase enemyIdleBehavior..you can't just drag and drop any of the 3 behaviors on there. Serialization via Inheritance doesn't work like that in Unity's inspector from a Monobehavior. But you CAN do that with ScriptableObjects. (There's likely a method to make this work using generics, but I like ScriptableObjects, and I'm not too comfortable with generics yet.)

    • @kpickett9128
      @kpickett9128 Рік тому +2

      Agreed, I like the SO approach here purely bc it's immediately so easy to understand at a glance in the inspector lol
      The abstraction of "player has one central script where you can drag and drop add different abilities" is very intuitive compared to something like, "player has a bunch of scripts and one of them is the state machine and the others are abilities used by the state machine but they all look the same in the inspector at a glance unless you dig into the code".
      Maybe there is a simple drag and drop approach that can somehow be done using only monobehaviours that gets around the inheritance wrinkle, but don't know it either... as a noob this setup strikes me as a nice and intuitive way to get started that is easy to work with later in the pipeline as a designer as well!

    • @Unity-wb9wv
      @Unity-wb9wv Рік тому

      @@sasquatchbgames Thank you!

  • @davidvarga2916
    @davidvarga2916 4 місяці тому

    This would be so much easier if you could just drag and drop Mono Behaviours like SO's into Serialized fields.

  • @Xiimo_
    @Xiimo_ Рік тому +2

    i have a little question. at the 8:16 . I get that the timer for the shots should be handled inside the update logic but wouldnt it cause problems to handle de rigidbody velocity of a bullet outside the fixedUpdate logic?

    • @WeaselOnaStick
      @WeaselOnaStick Рік тому

      Not really, unless the enemy is hugging and shooting the wall, then for one frame there could be some bugs. But in this case we're not affecting any physics over several frames, only once to give the bullet its initial velocity (with any later bullet moving handled by unity's physics engine)

  • @MZ-sr2xr
    @MZ-sr2xr 7 місяців тому

    thank👏you👏so👏much👏

  • @JanTGTX
    @JanTGTX 8 місяців тому +1

    I'm probably too late to the party to get a response, but maybe someone reads this...
    The tutorial is very good as it stands, although my head spins. XD Well, it works, and that's cool.
    I've been programming for 3 years, but all this advanced stuff is kind of overwhelming. I'll probably figure it out over time, but for now, it still feels a bit like a black box.
    My question is: What's up with this AnimationTriggerEvent stuff that is set up here?
    I understand what those are, but as it is right now, you're just assigning enums and don't perform any method, no?
    How or where would I implement the actual logic for those AnimationTriggerEvents? Like for example, playing a sound when a foot hits the ground in the animation.
    My monkey ass brain would probably set this up in the Enemy script, but that can't be right, as different enemies will have different footstep sounds... well, I dunno...

    • @JanTGTX
      @JanTGTX 8 місяців тому +1

      Welp, it didn’t take me too long to intuitively figure stuff out. :D
      For anyone wondering, I've found what worked for animation trigger events using this method.
      You just create an "event" in the enum and then write it as a condition into either an SOBase or a child SO, depending on where its use is relevant, within the "DoAnimationTriggerEventLogic".
      Then pass the enum in as a trigger event at the desired frame in the animation and voila.
      I don't know if this is the intended approach, but it works. And it's state bound as well, which is kind of insane for me.
      This tutorial has probably been the most helpful I've seen on Unity programming yet. I didn't immediately understand all of it, but it's a hell of a game changer for noobs like me.
      I was quite stoked to implement this into my game code and it somehow is a hell of a lot of fun to work with. Something must be wrong with me. 😂

    • @Playeroth
      @Playeroth Місяць тому

      maybe switches and checking the type of selected enum. not sure but thats something i will have to worry later..

  • @brandonmitchell-kiss2533
    @brandonmitchell-kiss2533 8 місяців тому

    Shouldn't you make the reference to player static??

  • @tivasthegamer9817
    @tivasthegamer9817 Рік тому

    Awesome!

  • @Notreal76
    @Notreal76 Рік тому

    Very nice system :)

  • @midniteoilsoftware
    @midniteoilsoftware Рік тому +1

    By initializing the Player in the Enemy state using FindObjectWithTag() in the Initialize() method, what happens if the player is killed (i.e. destroyed and respawned)? Wouldn't any enemies on the board lose their reference to the player?

    • @Diablokiller999
      @Diablokiller999 Рік тому +2

      Only if you destroy the player object, you can just kill the player off and not delete its instance, rather just resetting it for a new scene load.

  • @roygatz
    @roygatz Рік тому

    Is using task and subscription an more simplified way to achieve similar results? What would be the down side if true?

  • @ItsDan123
    @ItsDan123 Рік тому

    What would you do for an enemy that should keep chasing while in attack range? Duplicating the movement behavior in two states feels wrong, you could have a combo state that is checking range which feels better but also feels like it's reintroduction the "if block" mess that the state machine should reduce.

    • @shauas4224
      @shauas4224 11 місяців тому

      bit late of a reply but the way I see it you have two options:
      1) let's suppose your enemy has a cooldown after attack. And you could change state to chase right after attack and add check for cooldown in your if block which changes to attack state. Meh, little bit messy and adds dependencies between states but not the worst option.
      2) you could use NFSM - non final state machine which is basically SM that can be in multiple states at the same time. More complex, more complicated but more flexible.
      3) and the third option is to use a Behaviour tree which is more upgraded and fancier kind of nfsm.

  • @lsysun1
    @lsysun1 Рік тому

    "Thank you for the videos, they have been very helpful. However, I have some questions. For instance, I would like different enemies to have different movement speeds. Should I set the movement speed in each Enemy.cs script and then assign it during EnemyIdleSOBaseInstance.Initialize(gameObject, this)? Or is there a better approach?

    • @grumpycrouton2279
      @grumpycrouton2279 6 місяців тому

      With this method you could write a base IdleSO just like it was shown here, then create a separate SO asset for each enemy, which can then all have their own speeds.

  • @WeaselOnaStick
    @WeaselOnaStick Рік тому +2

    This is the final boss of boilerplate, oof. Are you certain we need this mane script files for every single entity behaviour? I feel like you could get away with much less code

    • @puntalic
      @puntalic 6 місяців тому

      Its not about the amount of code, its about maintainability.
      Technicaly you could have something like Helldivers 2 in just 1 script. Debugging and modifying would an undoable feet.
      You often hear: less is more... here more is less (headaches)

  • @connorious455
    @connorious455 Рік тому

    Hey the video is great and very helpful but how would I go about adding more than one attack on a single enemy? Would I just have to put more than one attack on the scriptable object script and set conditions to change between them or would it be better to add another state?

    • @sasquatchbgames
      @sasquatchbgames  Рік тому +1

      If its just 1 more attack, updating what's already there seems fine. Though if it's more than that I'd suggest adding a new state. Whichever is more readable and manageable for you

    • @connorious455
      @connorious455 Рік тому

      @@sasquatchbgames okay I will try to do that thank you

  • @JUNA-ANUJ_YADAV
    @JUNA-ANUJ_YADAV Рік тому

    @SasquatchBStudios
    Pls make proper video on version controlling ,Save our project after every new addition with the use of source tree and git hub etc

    • @sasquatchbgames
      @sasquatchbgames  Рік тому

      Seeing as how i literally just learned how to do that today (with github), I might just. Thanks for the idea!

  • @rafarodriguez4765
    @rafarodriguez4765 6 місяців тому

    I tested it in detail, and I have to say that it is not modular...Despite you use another class (Ghost) inheriting from the base Enemy, Enemy is hardcoded with the ScriptableObjects and Custom Classes that derive from EnemyState, so all kinds of Enemies (Ghotst, Dog,...whatever) having the same number of slots serialized despite only use one or four states, and you can not serialize the Scriptable Objects in the Ghost class as the EnemyState classes (Idle, Chase...) call to Enemy.
    Also, the Transitions between States are hardcoded into the Scriptable Objects
    ....Summarizing it is really hardcoded
    Maybe you should avoid using Custom Classes, instead Scriptable Objects to keep Actions and Transitions in a modular way, as it does the father of this kind of tutorial that is the Tank one from Unity

  • @wuushan5961
    @wuushan5961 Рік тому

    🎉

  • @Arcann_bhp
    @Arcann_bhp Рік тому +1

    WHERE TF IS PART ONE

    • @beshkekart3909
      @beshkekart3909 6 місяців тому

      Yep, bro you find that?

    • @beshkekart3909
      @beshkekart3909 6 місяців тому

      A Better Way to Code Your Characters in Unity | Finite State Machine | Tutorial

  • @sebastianhall8150
    @sebastianhall8150 Рік тому +1

    First

  • @fireraccoon_
    @fireraccoon_ Рік тому

    I love your videos and comments. I also love enemy moving everywhere. Is there a possibility that you will paste here Idle/Patrol move on Horizontal or vertical ways? i tryed this but when i implelemt it i cannot assign any "object" becasue i get message "object missmatch"
    [SerializeField] public float moveSpeed = 1f;
    [SerializeField] public float startWaitTime;
    private float waitTime;
    //public Transform[] moveSpots; //Schemat patrolu
    public Transform moveSpot;
    //private int randomSpot; //Schemat patrolu
    public float minX;
    public float maxX;
    public float minY;
    public float maxY;
    private void Start()
    {
    waitTime = startWaitTime;
    //randomSpot = Random.Range(0, moveSpots.Length); //Schemat patrolu
    moveSpot.position = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
    }
    private void Update()
    {
    //transform.position = Vector2.MoveTowards(transform.position, moveSpots[randomSpot].position, moveSpeed * Time.deltaTime); //Schemat patrolu
    transform.position = Vector2.MoveTowards(transform.position, moveSpot.position, moveSpeed * Time.deltaTime);
    //if (Vector2.Distance(transform.position, moveSpots[randomSpot].position) < 0.2f) //Schemat patrolu
    if (Vector2.Distance(transform.position, moveSpot.position) < 0.2f)
    {
    if (waitTime < 0)
    {
    moveSpot.position = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY));
    //randomSpot = Random.Range(0, moveSpots.Length); //Schemat patrolu
    waitTime = startWaitTime;
    }
    else
    {
    waitTime -= Time.deltaTime;
    }
    }
    }

    • @fireraccoon_
      @fireraccoon_ Рік тому

      Ok I did it!!!:) i changed private Transform moveSpot to [SerializeField] public and put prefab object. Worked!!!:)

  • @Coco-gg5vp
    @Coco-gg5vp Рік тому +1

    First