Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Making 2 variables' value connected in c#

Discussion in 'Scripting' started by FeastSC2, Aug 24, 2017.

  1. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Is it possible to have 2 bools that store the same value? So if I changed one bool's value, the other would have the same value.

    Code (csharp):
    1. private bool FieldBool = false;
    2.  
    3. void Start()
    4. {
    5.      bool localBool;
    6.      localBool = true; // This would also makes the FieldBool true
    7. }
     
    Last edited: Aug 24, 2017
  2. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    Code (CSharp):
    1.  private bool bool1;
    2.     private bool bool2;
    3.  
    4.     public bool Bool1
    5.     {
    6.         set
    7.         {
    8.             bool1 = value;
    9.             bool2 = value;
    10.         }
    11.     }
     
  3. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    I should have actually mentioned that the bool I'm changing is a not a member variable.


    1. Code (csharp):
      1. private bool Bool = false;
      2. [*]
      3.  
      4. [*]
      5.  
      6. [*]void Start()
      7. [*]{
      8.      bool localBool;
      9. [*]     localBool = true; // This would also makes the Bool true
      10. [*]}
     
  4. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    First question that comes to mind: What's the purpose? Do you actually need 2 variables for that?

    Anyway, yes. Use properties or setters if that's possible.

    Other ways would be dedicated reference types that you use instead or [warning] unsafe code. The latter is not recommended though, but can sometimes be the most efficient way, for instance for values such as dirty flags that otherwise take a long chain of setter/getters in complex and performance-critical hierarchies.
     
  5. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    you can't do that!!
     
  6. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    So, what's the purpose? If they're not some kind of "state" of an object, why can't you just use the boolean which is declared as member, as they stay the same all the time either way?
    Why would you declare a local boolean just to update the member on assignement?
     
  7. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974

    I have a Finite State Machine that has bools in it that reset automatically every time my entity goes into a new state. Since these bools can be for anyone they have generic names (like StateConditionA, StateConditionB, ...).

    So when I'm using them in my FSM for a specific case, like to say "You can now jump" setting StateConditionA to true is hard to keep track of, I need someway to be able to use a different name for that condition. Like canJumpNow = true; and it also changes the StateConditionA.

    Tell me if that's clear or not.
     
  8. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Here's an example of the implementation of one of my state.
    Code (csharp):
    1. public class BomboShoot : State<BomboAI>
    2. {
    3.     public static BomboShoot Instance = new BomboShoot();
    4.     public override void Enter(BomboAI _c)
    5.     {
    6.         _c.CurrDir = Vector2.zero;
    7.     }
    8.  
    9.     public override void Execute(BomboAI _c)
    10.     {
    11.         // Set the condition's name to a more descriptive one
    12.         bool isTheBomboFacingPlayer = _c.Fsm.StateConditionA;
    13.  
    14.         // Rotate towards target!
    15.         var playerDir = _c.GetVectorToTarget(_c.transform.position);
    16.  
    17.         _c.CurrDir = Vector2.Lerp(_c.CurrDir, playerDir, _c.Fsm.TimeInState);
    18.  
    19.         if (_c.CurrDir == playerDir)
    20.         {
    21.             // start shooting
    22.             isTheBomboFacingPlayer = true;
    23.         }
    24.     }
    25.  
    26.     public override void Exit(BomboAI _c)
    27.     {
    28.     }
    29. }
     
  9. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    It's not 100% sure yet.

    First of all, have you considered using enums or state objects? It's appears to be much easier.
    Either way, if you have some place to put specific "local" names, there's also a way to have properly named properties/methods instead that get or set the state of the FSM accordingly.
     
  10. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974

    I don't think enum would work in this case but I'm not a c# pro, I could create a class that has both a string and a bool in it. That could make it a little clearer but doing it like that seems more unclear than if it the localBool was a pointer (like in C++) to the private Bool.
     
  11. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    In regards to this, consider my post:
    This might look like the following (edited in browser, sorry for potential typos/ syntax errors):

    Code (csharp):
    1. public class BomboShoot : State<BomboAI>
    2. {
    3.     public static BomboShoot Instance = new BomboShoot();
    4.     public override void Enter(BomboAI _c)
    5.     {
    6.         _c.CurrDir = Vector2.zero;
    7.     }
    8.  
    9.     // this is new and only used as implementation details, thus private is enough for now
    10.     private bool IsFacingPlayer
    11.     {
    12.          get { return _c.Fsm.StateConditionA; }
    13.          set { _c.Fsm.StateConditionA = value; }
    14.     }
    15.  
    16.     public override void Execute(BomboAI _c)
    17.     {
    18.         // Set the condition's name to a more descriptive one
    19.         // no longer needed here
    20.         //bool isTheBomboFacingPlayer = _c.Fsm.StateConditionA;
    21.      
    22.         // Rotate towards target!
    23.         var playerDir = _c.GetVectorToTarget(_c.transform.position);
    24.  
    25.         _c.CurrDir = Vector2.Lerp(_c.CurrDir, playerDir, _c.Fsm.TimeInState);
    26.  
    27.         if (_c.CurrDir == playerDir)
    28.         {
    29.             // start shooting
    30.             // isTheBomboFacingPlayer= true;
    31.             // replaced with
    32.             IsFacingPlayer = true;
    33.         }
    34.     }
    35.  
    36.     public override void Exit(BomboAI _c)
    37.     {
    38.     }
    39. }
     
  12. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    @Suddoha Thanks!

    That looks like it would work! Normally, that won't make all my Bombo creatures share the same bools logically.
     
  13. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    yes I think @Suddoha is right

    other than that you cant compare 2 vectors with this "if (_c.CurrDir == playerDir)" use a dot product and check if it is near to 1f
     
    FeastSC2 likes this.
  14. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Oh actually it doesn't work because I don't have the variable _c outside of my methods.
     

    Attached Files:

  15. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Oh, didn't check for that. My bad and personal confusion, since I use the underscore '_' for members.
    If this was C++, there would be other ways to solve this easily without changing the actual structure of your program. :S
     
    FeastSC2 likes this.
  16. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    what State<BomboAI> should contains ? it is not the same BomboAI in the parent class
     
  17. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Here's the way my FSM works:

    FSM.cs:
    Code (csharp):
    1.  
    2.  
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class FSM<TEntityType>
    7. {
    8.     public bool DebugActivated = false;
    9.  
    10.     /// <summary>
    11.     /// Conditions that are reset to false every time the FSM state changes.
    12.     /// </summary>
    13.     public bool StateConditionA;
    14.     public bool StateConditionB;
    15.     public bool StateConditionC;
    16.     public bool StateConditionD;
    17.     public bool StateConditionE;
    18.  
    19.     public float StateAccuTimeA;
    20.     public float StateAccuTimeB;
    21.  
    22.     public FSM (TEntityType _owner)
    23.     {
    24.         Owner = _owner;
    25.     }
    26.  
    27.     public TEntityType Owner;
    28.  
    29.     public List<KeyValuePair<string, float>> FinishedStates = new List<KeyValuePair<string, float>>(10);
    30.  
    31.     public State<TEntityType> CurrentState { get; private set; }
    32.     public State<TEntityType> PreviousState { get; private set; }
    33.     public State<TEntityType> GlobalState { get; private set; }
    34.  
    35.     public float TimeInState { get; private set; }
    36.  
    37.     public void SetCurrentState(State<TEntityType> _s) { CurrentState = _s; }
    38.  
    39.     public void SetCurrentStateWithEnterExecute(State<TEntityType> _s)
    40.     {
    41.         CurrentState = _s;
    42.         CurrentState.Enter(Owner);
    43.     }
    44.     public void SetPreviousState(State<TEntityType> _s) { PreviousState = _s; }
    45.     public void SetGlobalState(State<TEntityType> _s) { GlobalState = _s; }
    46.  
    47.     public void UpdateFsm()
    48.     {
    49.         if (GlobalState != null) GlobalState.Execute(Owner);
    50.         if (CurrentState != null) CurrentState.Execute(Owner);
    51.         TimeInState += Time.deltaTime;
    52.     }
    53.  
    54.     public void ChangeState(State<TEntityType> _s)
    55.     {
    56.         Debug.Assert(_s != null);
    57.  
    58.         // TODO: Create event that notifies the state is changing!
    59.  
    60.         ResetStateVariables();
    61.  
    62.         if (DebugActivated) Debug.Log("Exiting: " + CurrentState);
    63.         CurrentState.Exit(Owner);
    64.         PreviousState = CurrentState;
    65.         CurrentState = _s;
    66.         CurrentState.Enter(Owner);
    67.         if (DebugActivated) Debug.Log("Entering: " + CurrentState);
    68.     }
    69.  
    70.     private void ResetStateVariables()
    71.     {
    72.         TimeInState = 0;
    73.  
    74.         StateConditionA = false;
    75.         StateConditionB = false;
    76.         StateConditionC = false;
    77.         StateConditionD = false;
    78.         StateConditionE = false;
    79.  
    80.         FinishedStates.Insert(0, new KeyValuePair<string, float>(CurrentState.ToString(), TimeInState));
    81.         if (FinishedStates.Count > 10)
    82.         {
    83.             FinishedStates.RemoveAt(10);
    84.         }
    85.  
    86.         StateAccuTimeA = 0;
    87.         StateAccuTimeB = 0;
    88.     }
    89.  
    90.     public void RevertToPreviousState()
    91.     {
    92.         ChangeState(PreviousState);
    93.     }
    94.  
    95.     public bool IsInState(State<TEntityType> _s)
    96.     {
    97.         return CurrentState.Equals(_s);
    98.     }
    99. }
    Here are the states:
    State.cs
    Code (csharp):
    1.  
    2. public abstract class State<T>
    3. {
    4.     // _c for character
    5.     public abstract void Enter(T _c);
    6.     public abstract void Execute(T _c);
    7.     public abstract void Exit(T _c);
    8. }
    9.  
    Here's BomboAI.cs:
    Code (csharp):
    1.  
    2.  
    3. public class BomboAI : AIAgent
    4. {
    5.     public FSM<BomboAI> Fsm;
    6.     [Header("Bombo Attributes"), Space]
    7.     public float AggroRange;
    8.     public float PatrolChangeDirectionTime;
    9.  
    10.     [HideInInspector] public RotateAccordingToMovement RotateAccordingToMovement;
    11.  
    12.     protected override void Start ()
    13.     {
    14.        base.Start();
    15.         Fsm = new FSM<BomboAI>(this);
    16.         Fsm.SetCurrentStateWithEnterExecute(BomboPatrol.Instance);
    17.        RotateAccordingToMovement = GetComponentInChildren<RotateAccordingToMovement>();
    18.  
    19.     }
    20.    
    21.     protected override void Update ()
    22.     {
    23.        base.Update();
    24.         Fsm.UpdateFsm();
    25.    
    26.         // ABSORB FIRE DAMAGE + GROW ONCE
    27.         Move.SetDirectionalInputForAi(CurrDir);
    28.         Move.FlyVelocity();
    29.         Move.Move(false);
    30.     }
    31.  
    32.     private void OnGUI()
    33.     {
    34.         var result = Fsm.GetStateDebug(5, "Bombo");
    35.         JeyGUI.OnGuiDraw(result, Move.Col, Vector2.down, 14);
    36.     }
    37. }
     
  18. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    It's just the type parameter that defines which type of AI is passed into the Execute, Enter and Exit.

    I think the issue you're encountering now is just a trade-off for the architectural design in combination with the high abstraction that you've chosen. The idea seems good, though it's a common trade-off of such an approach when dealing with different domains.
     
    Last edited: Aug 24, 2017
  19. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    One thing though.

    Your state class could as well be an interface, if it only defines abstract methods.

    Other than that, in regards to your actual problem:
    Looking at it again, your BomboAI class can provide the properly named properties that forward execution to the FSM. You could even split that up into interfaces, so that it's not a BomboAI, but a more general interface for similar entities.
    The state itself should not care about the BomboAI's internal management either way, i.e. should not care about how to set the FSM states. To the state itself, this can be considered an implementation details.

    Long story short, move the property to your BomboAI or an abstraction, so that you can access it with ease and via proper names.
     
  20. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    So the only way left is something of that sort? It's not really convenient.
    Code (csharp):
    1.  
    2. public class FSMBool
    3. {
    4.     private string Name;
    5.     private bool Value;
    6.  
    7.     public bool Get(string _name)
    8.     {
    9.         if (Name == _name) return Value;
    10.         Debug.LogError("Bad name");
    11.         return false;
    12.     }
    13.  
    14.     public void Set(string _name, bool _active)
    15.     {
    16.         if (Name == _name) Value = _active;
    17.         Debug.LogError("Bad name");
    18.     }
    19. }
     
  21. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Hm, may I make a suggestion.

    On the one hand, I do see what you're trying to do with this state machine structure, where you pass the AI module to the state for it to do something based on the AI's properties. So only one state object needs to exist.

    On the other hand, this also means that the state object itself is stateless as it's entirely dependent on the AI module's properties. Anything it needs, the AI module needs to expose to the state. For this reason, it's also a bit odd to see these StateConditionA-E members as they feel like they're sort of a hack around the stateless feature.

    The way I do this is I create a state machine instance for each AI module. This greatly reduces the complexity of the state machine design, since now the states can better contain data for that AI module type. After all, you're already creating a BomboShoot state that requires a BomboAI, so take advantage of that:

    Code (csharp):
    1.  
    2. public class BomboShoot : State<BomboAI>
    3.  
    In other words, it's code meant to specifically handle BomboAIs, so let each AI module contain a state machine, get rid of the StateConditionA-E members (which are too vague), and state transitions can be more straightforward.
     
  22. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    what about this

    Code (CSharp):
    1.  public abstract class State<T>
    2.     {
    3.         // _c for character
    4.         public abstract void Enter(T _c);
    5.         public abstract void Execute(T _c);
    6.         public abstract void Exit(T _c);
    7.  
    8.         public T Agent { get; protected set; }
    9.     }
    and you set the Agent in your Enter function
     
  23. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Ok, does that allow me to do something different?

    I thought about that but the issue then is that I can't have a reusability of the bools. I would have to make more bools because my entity has many states which would have different names for their bools.
    And so, if their names don't change per state, then their use becomes obscure. Basically I would have to make a lot more bools, because they're not reused per state.
     
  24. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    I'm not totally sure but if I do this, won't it make my Agent, always the last bombo that enters the specific State? I believe that's the reason why my State class must remain abstract.
     
  25. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    If only one state instance exists, then Agent might not work if this state object has to process 100 enemy AI modules each frame.
     
  26. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Do you have an example of this, something like code? I've seen a few FSM's and the one I'm using seemed particularily cool, but like you say it's a bit awkard to always have to use _c. to use my methods and to have to expose everything so that the state can access it.
     
  27. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Not really, except that you may have less trouble if you get to the point where you want to implement several reusable base states and suddenly need hybrids (mixture of both without creating a third type, fourth type etc).

    It does not change a lot, that's why i suggest it.
    Your state itself does not hold any state either way, so the name is already a little misleading. It's more of an state manipulator/modifier or whatever that would be called using proper terms.

    The FSM is already cached by the Entity, so the specific ones can grant access through properly named properties to the actual "generalized" state variables of the FSM.
    The state (modifier) that you're currently implementing does not need to know about how the entity manages its states, I'd simply move that and encapsulate it as an implementation details of the entity. Otherwise you'll need to re-structure everything quite a bit to achieve convenient access.

    There are other good arguments meanwhile, but I'm just trying to help with as few changes as possible since you've already implemented quite a lot and seem to be doing well so far.
     
    FeastSC2 likes this.
  28. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Thanks for all the help man, let me try and rephrase this to make sure I understand:

    So I need to implement a new kind of state per entity.
    That would result in something like this:
    Code (csharp):
    1. public class BomboShoot : BomboState<BomboAI> // instead of State<BomboAI>
    I could create that new State implementation in BomboAI.cs for example.

    Now in those new implemented state, what could I code up to fix my issue? Isn't it the same as if I had created bools in my BomboAI class?
     
  29. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    That's not what I meant.

    In your BomboAI class, add the properties such as the one that I've added to your state earlier, before i noticed that wouldn't work due to BomboAI being only an argument instead of the field.

    Consider the following:

    Code (csharp):
    1.  
    2. public class BomboAI : AIAgent
    3. {
    4.     public bool IsFacingPlayer
    5.     {
    6.         get { return FSM.StateConditionA; }
    7.         set { FSM.StateConditionA = value; }
    8.     }
    9.  
    10. // other implementation details
    11. }
    12.  
    Now the BomboAI offers the abstraction to manipulate the specific booleans, since it's got the reference to the FSM either way.

    To abstract this a little more, you can bundle everything BomboShoot needs to know about in reusable interfaces for different types of AI, you could make the BomboAI implement an an interface IPlayerShootingAI (if that name makes sense - sometimes I'm extremly bad at naming as well).

    Code (csharp):
    1.  
    2. public interface IPlayerShootingAI
    3. {
    4.     bool IsFacingPlayer { get; set; }
    5.     bool IsShootingPlayer { get; set; }
    6. }
    7.  
    BomboAI (or any other type of AI) could implement it:

    Code (csharp):
    1.  
    2. public class BomboAI : AIAgent, IPlayerShootingAI
    3. {
    4.     public bool IsFacingPlayer
    5.     {
    6.         get { return FSM.StateConditionA; }
    7.         set { FSM.StateConditionA = value; }
    8.     }
    9.  
    10. // other implementation details
    11. }
    12.  
    Finally, your BomboShoot could look like

    Code (csharp):
    1. public class BomboShoot : State<IPlayerShootingAI>
    2. {
    3.     // access members of any IPlayerShootingAI now
    4.     // no longer coupled to BomboAI, but usable for any AI that can shoot your player
    5. }
    6.  
    I'm not sure what exactly a "Bombo" is, so Ieft the name "BomboShoot" for now. Of course you could just leae the BomboAI as type parameter if that BomboShoot is specific to BomboAIs, but as I said, i can't really imagine what a bombo is meant to be :D
     
    FeastSC2 likes this.
  30. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Thanks for explaining that to me ;)
    That works but it seems to me that it's more trouble than it's worth because I don't expect my AI's to share similar behaviours in terms of their booleans. I think I'll end up leaving a comment on top of each of my state class.
    // Condition A = IsGoingToJump

    I'll take a look at unsafe coding first. Hopefully the answer lies there.
     
    Last edited: Aug 24, 2017
  31. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I wouldn't consider this to be a good option in this case, to be honest.

    If you don't want to take it that far, just ignore the interface and use the very first snippet.

    Or instead of the original solution in your state class, which looked like the following and did not work:

    Code (csharp):
    1. public class BomboShoot : State<BomboAI>
    2. {
    3.     // [...]
    4.     // this is new and only used as implementation details, thus private is enough for now
    5.     private bool IsFacingPlayer
    6.     {
    7.          get { return _c.Fsm.StateConditionA; }
    8.          set { _c.Fsm.StateConditionA = value; }
    9.     }
    10.     // [...]
    11. }
    You could try a helper methods, passing the FSM around...

    Code (csharp):
    1. public class BomboShoot : State<BomboAI>
    2. {
    3.     // [...]
    4.     // this is new and only used as implementation details, thus private is enough for now
    5.     private bool Get_IsFacingPlayer(FSM<BomboAI> fsm)
    6.     {
    7.          return fsm.StateConditionA;
    8.     }
    9.  
    10.     private void Set_IsFacingPlayer(FSM<BomboAI> fsm, bool value)
    11.     {
    12.         fsm.StateConditionA = value;
    13.     }
    14.     // [...]
    15. }
    It's an alternative, but it's also just shifting the problem to another place. But it does still offer what you were looking for: a way to have proper names for the access that is defined in a single place, so you don't need to remember it all the time.

    Problem: If you keep that "mapping" in your state modifying types, you have to ensure consistency once another state modifying type accesses the same boolean flag. It could get messed up again, you'd then either decide to code a helper utility or (in the end) move the mapping to your agent again...

    So many ways to look at a very specific issue, that's coding. It's all about the mixture of preference and a (re-)usable design at the same time.

    I'd have approached a FSM a little different i guess. But until now i did not get to the point to implement one.
     
  32. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    I found the solution!

    Code (csharp):
    1.  
    2. public struct FSMBool<TAi>
    3. {
    4.     public int ConditionNr { get; private set; }
    5.     private static FSM<TAi> Fsm;
    6.  
    7.     public FSMBool(ref FSM<TAi> _fsm) : this()
    8.     {
    9.         Fsm = _fsm;
    10.         _fsm.StateConditions.Add(false);
    11.         ConditionNr = _fsm.StateConditions.Count - 1;
    12.     }
    13.  
    14.     public bool Get()
    15.     {
    16.         return Fsm.StateConditions[ConditionNr];
    17.     }
    18.  
    19.     public void Set(bool _on)
    20.     {
    21.         Fsm.StateConditions[ConditionNr] = _on;
    22.     }
    23.  
    24.     public static void SetCurrentFsm(ref FSM<TAi> _fsm)
    25.     {
    26.         Fsm = _fsm;
    27.     }
    28. }
    29.  
    Every time I get into a new state in the FSM, I clear everything:
    Code (csharp):
    1.  
    2. public void ChangeState(State<TEntityType> _s)
    3.     {
    4. StateConditions.Clear();
    5. }
    Code (csharp):
    1.  
    2. public class BomboShoot : State<BomboAI>
    3. {
    4.     public static BomboShoot Instance = new BomboShoot();
    5.     private FSMBool<BomboAI> IsFacingPlayer;
    6.     private FSMBool<BomboAI> IsAttacking;
    7.  
    8.     public override void Enter(BomboAI _c)
    9.     {
    10.         FSMBool<BomboAI>.SetCurrentFsm(ref _c.Fsm);
    11.  
    12.         IsFacingPlayer = new FSMBool<BomboAI>(ref _c.Fsm);
    13.         IsAttacking = new FSMBool<BomboAI>(ref _c.Fsm);
    14.     }
    15.  
    16.     public override void Execute(BomboAI _c)
    17.     {
    18.         FSMBool<BomboAI>.SetCurrentFsm(ref _c.Fsm);
    19.  
    20.         // Rotate towards target!
    21.         var playerDir = _c.GetVectorToTarget(_c.transform.position);
    22.         _c.CurrDir = Vector2.Lerp(_c.CurrDir, playerDir, _c.Fsm.TimeInState);
    23.  
    24.         // start shooting
    25.         if (_c.CurrDir == playerDir)
    26.         {
    27.             Debug.Log("Called");
    28.             IsFacingPlayer.Set(true);
    29.             Debug.Log(_c.gameObject.name + " isFacingPlayer.Get(" + IsFacingPlayer.ConditionNr + ") : " + IsFacingPlayer.Get());
    30.             if (_c.DistanceToTarget() < 5)
    31.                 IsAttacking.Set(true);
    32.             Debug.Log(_c.gameObject.name + " IsAttacking.Get(" + IsAttacking.ConditionNr + ") : " + IsAttacking.Get());
    33.         }
    34.     }
    35.  
    36.     public override void Exit(BomboAI _c)
    37.     {
    38.         FSMBool<BomboAI>.SetCurrentFsm(ref _c.Fsm);
    39.  
    40.     }
    41. }
    42.  
    Now to make it better, I just have to find a way to make the SetCurrentFsm be automatic ;)
     
    Last edited: Aug 24, 2017
  33. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    @Suddoha Found the solution, I edited to post above to explain it. Tell me what you think about it when you got some time. I tested with 2 enemies and it works as expected. The key is
    Code (csharp):
    1.  FSMBool<BomboAI>.SetCurrentFsm(ref _c.Fsm);
     
  34. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    It actually appears to be a weird way of achieving your goal.
    One could say it's just another way of forwarding the manipulation of the FSM to a specific class (or rather a struct), while the helper properties/methods would just do the same.

    Working with this stateful approach (stateful due to the use of the static FSMBool<T_AI>SetCurrentFSM and the instances that refer to the currently set (static) currentFSM) will also turn out to be a huge downside if you ever decide to plan multi-threaded AIs, since you need to make such calls synchronized, which eliminates many advantages of MT.
    This is most-likely not an issue at the moment, as you access Unity'a API anyway and everything runs in sequence, but AIs often turn out to be a bottleneck once you either approach "better AIs" (quality) or at least "more AI" (quantity).

    Your old approach was less stateless and better in those regards, the "state" class just took an agent and operated on its particular state (and its FSM) without having a shared access point / without accessing shared state.
     
    FeastSC2 likes this.
  35. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    It would indeed become a problem if I used multithreading on those classes, I'll be careful when using Coroutines then.

    You think better AI's necessarily have multithreading required? I don't use them so far for my AI and I currently don't really see what the point would be. Do you have an example in mind?

    But let's say I was using them I could always play with the values that I stock in the BomboAI class. Create bools there that will be read by the multithreaded process, no? (Instead of reading them directly from the state of the BomboAI)
     
  36. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Well, there are a few approaches you can take. However, it looks like my suggestion would involve a lot of redesigning and rewriting on your end, so in the end, I'd say if you have a solution that works for you, then go for it.

    Here's just one example of what I mean about having an AI module contain a state machine instance:

    Code (csharp):
    1.  
    2.     public class ActionState<TResult>
    3.    {
    4.        public Action OnEnterState;
    5.        public Func<TResult> OnUpdateState;
    6.        public Action OnExitState;
    7.    }
    8.  
    9.    public class ActionStateMachine<TKey, TResult>
    10.    {
    11.        public TKey PreviousState { get; private set; }
    12.        public TKey CurrentState { get; private set; }
    13.        public TKey NextState { get; set; }
    14.  
    15.        private Dictionary<TKey, ActionState<TResult>> _stateDictionary;
    16.  
    17.        public ActionStateMachine()
    18.        {
    19.            _stateDictionary = new Dictionary<TKey, ActionState<TResult>>();
    20.        }
    21.  
    22.        public TResult Update()
    23.        {
    24.            bool isNextStateNullOrFalse = EqualityComparer<TKey>.Default.Equals(NextState, default(TKey));
    25.  
    26.            if (!isNextStateNullOrFalse && !NextState.Equals(CurrentState))
    27.            {
    28.                ActionState<TResult> currentActions;
    29.  
    30.                if (!EqualityComparer<TKey>.Default.Equals(CurrentState, default(TKey)))
    31.                {
    32.                    if (_stateDictionary.TryGetValue(CurrentState, out currentActions))
    33.                    {
    34.                        if (currentActions.OnExitState != null)
    35.                            currentActions.OnExitState();
    36.                    }
    37.                    else
    38.                    {
    39.                        Debug.LogError("Unknown action state key: " + CurrentState);
    40.                    }
    41.  
    42.                    Debug.Log("Exiting state: " + CurrentState);
    43.                }
    44.  
    45.                PreviousState = CurrentState;
    46.                CurrentState = NextState;
    47.  
    48.                Debug.Log("Entering state: " + CurrentState);
    49.  
    50.                if (_stateDictionary.TryGetValue(CurrentState, out currentActions))
    51.                {
    52.                    if (currentActions.OnEnterState != null)
    53.                        currentActions.OnEnterState();
    54.                }
    55.                else
    56.                {
    57.                    Debug.LogError("Unknown action state key: " + CurrentState);
    58.                }
    59.            }
    60.            else
    61.            {
    62.                if (!EqualityComparer<TKey>.Default.Equals(CurrentState, default(TKey)))
    63.                {
    64.                    ActionState<TResult> currentActions;
    65.  
    66.                    if (_stateDictionary.TryGetValue(CurrentState, out currentActions))
    67.                    {
    68.                        if (currentActions.OnUpdateState != null)
    69.                            return currentActions.OnUpdateState();
    70.                        else
    71.                            Debug.LogWarning("NULL update action for " + CurrentState);
    72.                    }
    73.                }
    74.  
    75.            }
    76.  
    77.            return default(TResult);
    78.        }
    79.  
    80.        public bool AddState(TKey stateKey, ActionState<TResult> stateActions)
    81.        {
    82.            Debug.Assert(stateActions != null);
    83.  
    84.            if (_stateDictionary.ContainsKey(stateKey))
    85.                Debug.LogWarning("ActionStateMachine - Key already exists. Overwriting... :" + stateKey);
    86.  
    87.            _stateDictionary[stateKey] = stateActions;
    88.  
    89.            return true;
    90.        }
    91.    }
    92.  
    Here's a very simple and dirty example of how that could be used:

    Code (csharp):
    1.  
    2.     public class MyAwesomeAI
    3.    {
    4.        enum AIStates
    5.        {
    6.            Idle,
    7.            Jump
    8.        }
    9.  
    10.        private ActionStateMachine<AIStates, bool> _stateMachine;
    11.  
    12.        public MyAwesomeAI()
    13.        {
    14.            _stateMachine = new ActionStateMachine<AIStates, bool>();
    15.            _stateMachine.AddState(AIStates.Idle, new ActionState<bool> { OnEnterState = OnEnterIdle, OnUpdateState = OnUpdateIdle, OnExitState = OnExitIdle});
    16.            _stateMachine.AddState(AIStates.Jump, new ActionState<bool> { OnEnterState = OnEnterJump, OnUpdateState = OnUpdateJump, OnExitState = OnExitJump });
    17.        }
    18.  
    19.        public void Update()
    20.        {
    21.             _stateMachine.Update();  // not really using the update return type here
    22.        }
    23.  
    24.        private void OnEnterIdle() { /* play idle animation, etc... */ }
    25.  
    26.        private bool OnUpdateIdle()
    27.        {
    28.            if (Input.GetKeyDown(KeyCode.Space))
    29.            {
    30.                _stateMachine.NextState = AIStates.Jump;
    31.                return true;
    32.            }
    33.  
    34.            return false;
    35.        }
    36.  
    37.        private void OnExitIdle() { /* perform any required cleanup, etc... */ }
    38.  
    39.        private void OnEnterJump() {  /* play jump animation, apply upward velocity, play mario-style sfx, etc... */ }
    40.  
    41.        private bool OnUpdateJump()
    42.        {
    43.            if ( /* jump ends */ )
    44.            {
    45.                _stateMachine.NextState = AIStates.Idle;
    46.                return true;
    47.            }
    48.  
    49.            return false;
    50.        }
    51.  
    52.        private void OnExitJump() { /* perform any required cleanup, etc... */ }
    53.    }
    54.  
    Some things to note: I'm not really taking advantage of the Update return types, it's there if you need it. Otherwise, remove it to make the code even simpler. I've also skipped creating a transition table since the AI itself knows what state to move to next. Otherwise you could abstract this away by creating a transition table that will need to know whether the Update() is finished and it's time to move on to the next state (whatever it is). You could even make it return the enum type of the state to move to next.

    I've also written another version of a state machine class using a class type instead of enums (think Idle class, Jump class, etc.) but the idea is still the same: Allow multiple instances of state machines where needed or even allow nested state machines (think application state machine + level progress state machine + player state machine + etc.) You might need to throw in a tutorial state machine as well.
     
    FeastSC2 likes this.
  37. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    That looks very useful, I'll implement this in a separate project just to give it a test run, I'm not that far into my project so it's still worth taking a look. It seems more flexible than mine but I'm not very knowledgeable in terms of Funcs or Actions so I'm not certain of what I see.
    Do you think the FSM you showed has any weaknesses compared to mine, or is it just plain better?
     
  38. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Hm. Maybe slightly more memory due to having a state machine for each AI. However, the state machine is so small it's pretty negligible. It's definitely way less code to read, which I like :D

    There's still the question of who/what manages the state transition. Sometimes I create a class for that. Sometimes I just say "Eh screw it" and let the AI module do it directly.

    However, if I were to do this properly, I'd move the state machine up a level and make it a component of the entity rather than the AI like so:

    Entity
    - State machine
    - AI
    - MeshRenderer
    - etc.

    The entity itself would have a script to deal with the state logic and use feedback from the AI to handle when transition(s) should happen.

    However, these are just design ideas, and it's up to you to determine what's best for your game. It's just a tool, I'm not sure I can objectively say that this tool is better or worse than any other tool.
     
    FeastSC2 likes this.
  39. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    974
    Thanks for helping me out Pete!
     
  40. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    No problem, good luck on your project!
     
    FeastSC2 likes this.