How can I constrain a reference passed in a function?

I am making a multiplayer game. But this question is all local. I am using hierarchical state machines for the player objects. So each behavior is its own class deriving from a class PlayerState. The players have a direct reference to two main states ‘Battle’ and ‘Build’. Those states each have references to their child states.

I would like to create a game manager that can put all the players on a client into either Battle or Build state when the client joins the game. To do this, I will have to create a function that essentially does something like this:

void UpdateAllPlayerStates(PlayerState someState)
{
     foreach (Player player in playerList)
     {
          player.statemachine.SetState(someState)
     }
}

Now here is my question. I can’t just pass any PlayerState into this, because the top of the state machine only can take Battle or Build. This won’t be a problem if I simply think carefully about how I use the function. But it makes me wonder, how can I limit the inputs here to make sure it only accepts certain classes that are derived from PlayerState?

So in fake code, I’m looking for something like this:

void UpdateAllPlayerStates(PlayerState someState) where someState is of type Battle or Build
{
     foreach (Player player in playerList)
     {
          player.statemachine.SetState(someState)
     }
}

As you seem to have indicated… you’re calling it so you have the control but if you are worried and/or just want to simplify things how about SetPlayersToBattle and SetPlayersToBuild methods and you don’t pass anything to them.

And perhaps pass the playerList instead which gives you direct control over which players to set.

1 Like

You’d need to define a common interface between these types in question, then use generics with a constraint to constrain the parameter to said interface type, and the concrete type.

Such as:

public interface IExampleInterface
{
	
}

private void UpdateAllPlayerStates<T>(T exampleState) where T : PlayerState, IExampleInterface
{
	// etc etc
}

The alternative is to do some validation within said method and log warnings when invalid parameters are provided.

1 Like

Note that you have conflicting naming here. The method tells me it’s “updating” the states, but in reality it’s merely “setting” the state. It should be called: SetStateForAllPlayers(PlayerState state)

And then this makes me question why you want to restrict the states that are settable? To me this indicates an architectural problem. If the players only accept specific types of states then these should have either the same interface as mentioned above but honestly, I would simply pass in the interface instead (ignoring any boxing issues): SetStateForAllPlayers(IPlayerState aPlayerState)

Or they use the same base class. Since these are player states I would assume that a player can accept all kinds of the base class PlayerState. And the base class could be StateBase in case you also need some EnemyState as well.

1 Like

Thanks for the response. I also feel like there is a architectural issue. I’m pretty green here, so I’m open to just about anything.

I have a class State, and then EnemyState and PlayerState derive from State. I then have specific classes for each player state that all derive directly from PlayerState, not each other. Logically, they are children of each other, but functionally, they are all distinct. Each of these player states is attached to an otherwise empty game object on the player. The hierarchy is all within the code itself. This is done, because I may have some behavior like ‘Jump’ that may need to be used as a child of more than one other state. For example, if I jump during the build phase or the battle phase, I don’t currently have a reason to make that jump behavior any different. So build and battle both can have a reference to jump, and could have it as a child state, but technically it’s the same instance of the jump class.

The states have a direct reference to their child states. Each level of the hierarchy also has a list of it’s own child states, for searching purposes. So the player object only has a reference to Build or Battle, like this:

class Player : Core // core is just the class for objects that have states, derived from networkbehaviour
{
     public PlayerBuildState buildState;
     public PlayerBattleState battleState;

     // the rest of the class
}

I followed a nice tutorial on youtube from AdamCYounis about hierarchical state machines to handle player and NPC behavior. To me, this structure felt effective. However, since then, I’ve been struggling to find the best ways to adjust the states or the branch of the hierarchy from external sources.

What I would like to do, is specify a PlayerState type, then recursively check each level of the hierarchy to see if it contains a child state of the specified class. I currently have a setup that looks something like this on the player and state classes:

public bool TrySetState<T>() where T : PlayerState
{
     // check for a child state of type T
     // if found, set it as the child state
     // else call a similar function on the current child state
     // returns true if the change was successful
}

This has issues though. As far as I can tell, generics cannot be sent over RPC, even if the specified type is network serializable. So if I want a server authoritative state change, I can’t use this setup to RPC.

I am open to suggestions or critiques if this seems like a bad approach to behavior trees.

Well, just a hunch because it strikes me as odd. I would not consider making individual states a class in itself. I guess you can do that but that’s not what I’ve been using historically.

The way I use a statemachine is to have classes for these, building on each other (indicated by indentation):

  • Statemachine
    • State
      • Transition
        • Conditions
        • Actions

State is merely a container of Transitions, which contain 0-n Conditions which, when all true, execute the Actions.
While you could wrap this up in unique, purpose-made State subclasses it would effectively make them non-modular and not reusable.

The power in such a system lies with the Conditions and Actions that initially require some extra work but eventually when you’ve been using simple modular code fragments like IsCardPlayed (condition) or PlayCard (action) a dozen times to build your logic, the workflow benefits from a speedy implementation and high reliability.