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.
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?
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.
Here’s an example of the implementation of one of my state.
public class BomboShoot : State<BomboAI>
{
public static BomboShoot Instance = new BomboShoot();
public override void Enter(BomboAI _c)
{
_c.CurrDir = Vector2.zero;
}
public override void Execute(BomboAI _c)
{
// Set the condition's name to a more descriptive one
bool isTheBomboFacingPlayer = _c.Fsm.StateConditionA;
// Rotate towards target!
var playerDir = _c.GetVectorToTarget(_c.transform.position);
_c.CurrDir = Vector2.Lerp(_c.CurrDir, playerDir, _c.Fsm.TimeInState);
if (_c.CurrDir == playerDir)
{
// start shooting
isTheBomboFacingPlayer = true;
}
}
public override void Exit(BomboAI _c)
{
}
}
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.
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.
This might look like the following (edited in browser, sorry for potential typos/ syntax errors):
public class BomboShoot : State<BomboAI>
{
public static BomboShoot Instance = new BomboShoot();
public override void Enter(BomboAI _c)
{
_c.CurrDir = Vector2.zero;
}
// this is new and only used as implementation details, thus private is enough for now
private bool IsFacingPlayer
{
get { return _c.Fsm.StateConditionA; }
set { _c.Fsm.StateConditionA = value; }
}
public override void Execute(BomboAI _c)
{
// Set the condition's name to a more descriptive one
// no longer needed here
//bool isTheBomboFacingPlayer = _c.Fsm.StateConditionA;
// Rotate towards target!
var playerDir = _c.GetVectorToTarget(_c.transform.position);
_c.CurrDir = Vector2.Lerp(_c.CurrDir, playerDir, _c.Fsm.TimeInState);
if (_c.CurrDir == playerDir)
{
// start shooting
// isTheBomboFacingPlayer= true;
// replaced with
IsFacingPlayer = true;
}
}
public override void Exit(BomboAI _c)
{
}
}
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
using System.Collections.Generic;
using UnityEngine;
public class FSM<TEntityType>
{
public bool DebugActivated = false;
/// <summary>
/// Conditions that are reset to false every time the FSM state changes.
/// </summary>
public bool StateConditionA;
public bool StateConditionB;
public bool StateConditionC;
public bool StateConditionD;
public bool StateConditionE;
public float StateAccuTimeA;
public float StateAccuTimeB;
public FSM (TEntityType _owner)
{
Owner = _owner;
}
public TEntityType Owner;
public List<KeyValuePair<string, float>> FinishedStates = new List<KeyValuePair<string, float>>(10);
public State<TEntityType> CurrentState { get; private set; }
public State<TEntityType> PreviousState { get; private set; }
public State<TEntityType> GlobalState { get; private set; }
public float TimeInState { get; private set; }
public void SetCurrentState(State<TEntityType> _s) { CurrentState = _s; }
public void SetCurrentStateWithEnterExecute(State<TEntityType> _s)
{
CurrentState = _s;
CurrentState.Enter(Owner);
}
public void SetPreviousState(State<TEntityType> _s) { PreviousState = _s; }
public void SetGlobalState(State<TEntityType> _s) { GlobalState = _s; }
public void UpdateFsm()
{
if (GlobalState != null) GlobalState.Execute(Owner);
if (CurrentState != null) CurrentState.Execute(Owner);
TimeInState += Time.deltaTime;
}
public void ChangeState(State<TEntityType> _s)
{
Debug.Assert(_s != null);
// TODO: Create event that notifies the state is changing!
ResetStateVariables();
if (DebugActivated) Debug.Log("Exiting: " + CurrentState);
CurrentState.Exit(Owner);
PreviousState = CurrentState;
CurrentState = _s;
CurrentState.Enter(Owner);
if (DebugActivated) Debug.Log("Entering: " + CurrentState);
}
private void ResetStateVariables()
{
TimeInState = 0;
StateConditionA = false;
StateConditionB = false;
StateConditionC = false;
StateConditionD = false;
StateConditionE = false;
FinishedStates.Insert(0, new KeyValuePair<string, float>(CurrentState.ToString(), TimeInState));
if (FinishedStates.Count > 10)
{
FinishedStates.RemoveAt(10);
}
StateAccuTimeA = 0;
StateAccuTimeB = 0;
}
public void RevertToPreviousState()
{
ChangeState(PreviousState);
}
public bool IsInState(State<TEntityType> _s)
{
return CurrentState.Equals(_s);
}
}
Here are the states:
State.cs
public abstract class State<T>
{
// _c for character
public abstract void Enter(T _c);
public abstract void Execute(T _c);
public abstract void Exit(T _c);
}
Here’s BomboAI.cs:
public class BomboAI : AIAgent
{
public FSM<BomboAI> Fsm;
[Header("Bombo Attributes"), Space]
public float AggroRange;
public float PatrolChangeDirectionTime;
[HideInInspector] public RotateAccordingToMovement RotateAccordingToMovement;
protected override void Start ()
{
base.Start();
Fsm = new FSM<BomboAI>(this);
Fsm.SetCurrentStateWithEnterExecute(BomboPatrol.Instance);
RotateAccordingToMovement = GetComponentInChildren<RotateAccordingToMovement>();
}
protected override void Update ()
{
base.Update();
Fsm.UpdateFsm();
// ABSORB FIRE DAMAGE + GROW ONCE
Move.SetDirectionalInputForAi(CurrDir);
Move.FlyVelocity();
Move.Move(false);
}
private void OnGUI()
{
var result = Fsm.GetStateDebug(5, "Bombo");
JeyGUI.OnGuiDraw(result, Move.Col, Vector2.down, 14);
}
}
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.
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.