Monday, 24 February 2014

Finite State Machine for handling input.

This is maybe obvious but for whatever reason I struggled to work out how to handle a simple left click of the mouse.  The problem being that a single left click could mean many different things depending what state your in.  Take selecting a unit as an example, it could be interpreted as, 1) select this unit 2) deselect current and now select new unit 3) move to 4) attack.  etc.  Keeping track of the state you're in then is vital, and I'm using an enum which is perfect for this.

enum pickingState { nothing, unitPicked, cityPicked, moveTo}
pickingState curPickingState = pickingState.nothing;

The next step was breaking down the methods for moving between states.  To go from (nothing --> unitPicked) we'll need a Select() method.  And likewise, to get back to nothing selected (unitPicked --> nothing)we need a DeSelect() method.  For the raycasting itself, Unity provides excellent collision checking for that, mine looks something like this.

//picking code looks something like this
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;

//left click
if (Input.GetMouseButtonDown(0) && Managers.OverGUI == false)
  //did we hit something
  bool hitResult = Physics.Raycast(ray, out hit, 100f);

  switch (curPickingState)
        Debug.Log("nothing selected state");
        handleSelection(hitResult, hit);
        handleDeselection(hitResult, hit);
        Debug.Log("in moveto state");
        handleMoveTo(hitResult, hit);


private void handleSelection(bool hitResult, RaycastHit hit)
//bring up gui elements here

private void handleDeselection(bool hitResult, RaycastHit hit)
//fade out gui elements here & call the picking code again

private void handleMoveTo(bool hitResult, RaycastHit hit)
//set target

Using a switch() statement with enums is a perfect match. as you can see, and neater than a big ol' pile of if() else() statements.  An element that may be a little confusing is calling handleDeselection() if we have a unit picked already.  This was a minor eureka moment for me, and when I worked out I neeeded to seperate out selection and deselection correctly.  So if the player has decided they wanted to select a different unit when one is already selected, you first need to correctly change back to the nothing selected state, then we simple call the picking code again and it will correctly interpret us back into the correct state.  It simple be the player clicked off map to deselect entirely, which would be hitReseult = false.

The last little piece is the Managers.OverGUI bool.  Unity handles interface input in the OnGUI() method for things like buttons etc.  You don't want to have clicks going through the GUI into the world and so for that we need to check each loop if the cursor is over a GUI element, and if so we don't want to handle any 3d world picking and instead let our GUI code handle it instead.  

I'm sure much of the above is second nature to some programmers but I really struggled getting all the bits and pieces working together correctly.