I’m currently working on a few unity tutorials and having some issues in how to use generics.
private void Awake()
{
Core = GetComponentInChildren<Core>();
StateMachine = new PlayerStateMachine();
IdleState = new PlayerIdleState(this, StateMachine, playerData, "idle");
MoveState = new PlayerMoveState(this, StateMachine, playerData, "move");
JumpState = new PlayerJumpState(this, StateMachine, playerData, "inAir");
InAirState = new PlayerInAirState(this, StateMachine, playerData, "inAir");
LandState = new PlayerLandState(this, StateMachine, playerData, "land");
...
}
Is it available to use generics and make it simple when there are constructors that overlaps this much? All the states above inherits PlayerState class.
I know that this information is too little for precise answers, but I can’t just copy and paste all my codes in this question.
Thank you in advance.
You can’t make a generic constructor, though you can make a class with a generic parameter, and have that as a paramater of said constructor, though it wouldn’t help you here honestly.
You can have a bass class that expresses an ‘initialise’ method, and so long as your derived class has a parameterless constructor, you can create then via generics.
Rough example:
public abstract class BaseState
{
private StateMachine _stateMachine;
private PlayerData _playerData;
public StateMachine StateMachine => _stateMachine;
public PlayerData PlayerData => _playerData;
public void Initialize(StateMachine stateMachine, PlayerData playerData)
{
_stateMachine = stateMachine;
_playerData = playerData;
}
public virtual void EnterState() { }
}
public sealed class IdleState : BaseState { }
// usage
public void EnterState<T>() when T : BaseState, new()
{
T state = new T();
state.Initialize(StateMachine, PlayerData);
state.EnterState();
}
Though, with state machine, usually you just instance the states on the fly and pass them to the state machine to enter them. Not sure why you’re instantiating all these states beforehand.
You can either shorten (?) the code a bit using the method (though this may not be what you intended).
IdleState = CreateState<PlayerIdleState>("idle");
MoveState = CreateState<PlayerMoveState>("move");
JumpState = CreateState<PlayerJumpState>("inJump");
InAirState = CreateState<PlayerInAirState>("inAir");
LandState = CreateState<PlayerLandState>("land");
T CreateState<T>(string name) where T : PlayerState, new()
{
return new T(this, StateMachine, playerData, name);
}
Another common thing for cases where you have a lot of the same data passed to an object is to use some sort of config object or struct where you simply group the necessary parameters into one object. The .NET framework does this in several cases. One is the ProcessStartInfo class. The Process class can be used to start another process / application and has several overloads of the Start method taking different combinations of arguments. However some of the more rare stuff can only be set through the ProcessStartInfo class. It essentially groups all necessary information into a single class.
Apart from that since you pass in “this” you could create a constructor overload that doesn’t require the other arguments like StateMachine and playerData and have the constructor pull that information from the “this” object. This may not be possible depending on what that argument type actually is. Though this could also be done through an interface that provides properties so the constructor can access the necessary information.
In any case it’s probably still a good idea to keep the more complex constructor to not be bound to this way of setting up the object. Keep in mind that you can “chain” constructors. Something like this:
public PlayerIdleState(SomeObject aObj, StateMachine aStateMachine, PlayerData aPlayerData, string aName)
{
// actual constructor stuff
}
public PlayerIdleState(IMyInterface aParent, string aName) : this((SomeObject)aParent, aParent.stateMachine, aParent.playerData, aName)
{
// this stays empty or could do additional stuff
}
So here the object would implement that IMyInterface interface (please, choose a meaningful and discriptive name) that provides read only properties for the necessary suff. We chain the constructors so the one taking the interface object simply calls the other constructor with the proper data.
Excellent use-case for a source generator. You decorate an enum or something with an attribute to create a state machine with those states and you generate all the fluff in a source generator. Easy, reusable, strictly remains on the static method calls in runtime, no virtual calls and easy to follow. These are the things I love meta programming for.
Actually, this was what I asked for. Just to shorten those constructors.
But Visual Studio spits out error saying that it cannot provide arguments when creating an instance of a variable type.
Maybe I can’t give any parameters when using generics…
That’s just because they all have different LogicUpdate functions and I thought creating states every time I access them can be inefficient. Anyway, I think this is the only way to accomplish what I wanted… Thx.
Why though?
They should all express a common base class/interface, and all interfaced through the same methods.
If the states should be more or less static and reused, I would probably go with ScriptableObjects. Also I would probably avoid creating tons of subclasses of the base state but use other ScriptableObjects as pluggable functions (Strategy pattern). So you can simply create your state instances in your project and assign a separate “behaviour” scriptable object.