Skip to content

Instantly share code, notes, and snippets.

@grofit
Created June 12, 2015 12:52
Show Gist options
  • Save grofit/01236764b64536ad99c0 to your computer and use it in GitHub Desktop.
Save grofit/01236764b64536ad99c0 to your computer and use it in GitHub Desktop.
A hybrid Strategy/Chain Of Responsibility style approach
public class Character
{
public EntityStates CurrentState { get; }
public void Update()
{
switch(CurrentState)
{
case EntityStates.Idle
{
// play an idle animation
// check for a timer to see if you need to do a random idle pose
// regen health
}
break;
case EntityStates.Walking
{
// move character
// play walking animation
// check for collisions
}
break;
case EntityStates.Running
{
// move character but faster
// play running animation
// check for collisions
// very similar to walking
}
break;
// More cases with more logic
}
}
}
/*
Now the above class is ok, but if you imagine every time we add a new state,
it will ideally need to go into the switch statement have have some logic put
in to handle the state.
Now you could improve this by moving the handling to private methods, or even
moving them completely to their own classes, but the main point here is that
every time you want to add a new state, you need to modify this class.
*/
public interface IStateHandler<T>
{
bool CanHandle(T data);
void Handle(T data);
}
public class CharacterIdleStateHandler : IStateHandler<Character>
{
public bool CanHandle(Character character)
{ return character.CurrentState == EntityStates.Idle; }
public void Handle(Character character)
{
// play an idle animation
// check for a timer to see if you need to do a random idle pose
// regen health
}
}
public class CharacterWalkingStateHandler : IStateHandler<Character>
{
public bool CanHandle(Character character)
{ return character.CurrentState == EntityStates.Walking; }
public void Handle(Character character)
{
// move character
// play walking animation
// check for collisions
}
}
public class CharacterRunningStateHandler : IStateHandler<Character>
{
public bool CanHandle(Character character)
{ return character.CurrentState == EntityStates.Walking; }
public void Handle(Character character)
{
// move character but faster
// play running animation
// check for collisions
// very similar to walking
}
}
public class Character
{
public EntityStates CurrentState { get; }
private IEnumerable<IStateHandler<Character>> characterStates;
public Character(IEnumerable<IStateHandler<Character>> states)
{ this.characterStates = states; }
public void Update()
{
foreach(var state in characterStates)
{
if(state.CanHandle(this))
{ state.Handle(this); } // optionally break
}
}
}
/*
We are being a bit overly specific with the states here and we can be more granular,
like for example you could have a MovementHandler and AnimationHandler and they
will react to different states differently rather than coping with everything at once.
However the main point here is that we have now used IoC to allow our state handling
to be passed into the Character and we have also now made it so we can easily add
new encapsulated handlers without changing the Character class, it will just
process whatever handlers it has, so this makes your code far more flexible
and maintainable as you can test your handlers in isolation as well as being
able to completely control behaviour at the DI level if required.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment