Thursday, December 29, 2016

How to implement movement undo

It turns out that Skye still has a partner in crime on this project. I tend to keep to my hermitage, but from time to time, I feel it necessary to let people know I'm still here. Lately I've been focusing on building the code instead of the artwork, which is why there hasn't been much to show. However, I thought it might be interesting to talk about some of the design.

One of the most constructive bits of feedback we received from the Salt Lake gaming convention was that our movement system was not immediately intuitive. Because of that we decided to overhaul it and move to a point-and-click waypoint system similar to other tactical games. There are a few working parts to this, including the path finding algorithm, the grid system, the waypoints, and the actual movement. However, I'd like to focus more on the design of the waypoint system.

For those familiar with the fundamentals of programming, there is the concept of a stack. For those that know a bit more, there's another concept called the state pattern. A stack is essentially what it sounds like. You add things to the top and when you need something, you take it off the top, making it a "last in, first out" structure. The state pattern is a way to design the flow of a program. Essentially, there is one object that everything references and it delegates to state objects that behave differently. In order to implement a path (with waypoints) that can be undone, I employed a combination of these two concepts.

Here come the technical details, so if that makes you uneasy, feel free to stop here or skip to the last paragraph. Anyway, to start, I added an interface for what I refer to as an “action”. This action is essentially one of the state objects and any state in the state machine will need to have the same interface to its actions as the others in order for it to be reused. As I am using C#, the code looks something like this:


1
2
3
4
5
6
public interface IAction {
 void Act(Vector2 move);
 void Act(bool confirmation);
 ....
 ActionType GetActionType();
}


The Act() function accepts a number of different types of arguments. Each one will be implemented differently based on the state.
An example of one of the states might be something like this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class MovementSelection : IAction {
 private Vector2 location;
 public MovementSelection() {
  // Initiation stuff for this state
 }
 public void Act(Vector2 move) {
  // Check for stuff that might need to validated before the move actually happens
  // Now we have a move, so we need to store it and move to the next state
  location = move;
  ActionHandler.Instance.SetState(new MovementWaypoint(move));
 }
 ....
 public ActionType GetActionType() {
  return ActionType.MovementSelection;
 }
}


As you can see, the state is in charge of initializing stuff for itself, storing any relative data, and then moving to the next state if appropriate. It also can return the type of state it is for bookkeeping if necessary. Then the next state (MovementWaypoint) handles itself in a like manner. And of course different states will keep track of different stuff and behave differently.
Now you may be wondering where all of this is initiated from and where the main handler object comes in. One of the keys to the state pattern is that the states are essentially hidden from the rest of the application. The state handler and other states are the only ones that should know anything about states. Ultimately, the state handler becomes the interface to the rest of the application for this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ActionHandler : IAction {
 // The following makes this object a singleton
 private static ActionHandler instance;
 public static ActionHandler Instance { 
  get {
   if (instance == null)
    instance = new ActionHandler();
   return instance;
  } 
 }
 private ActionHandler() { /* any necessary initialization here */ }
  
 private Stack<IAction> stateStack;
 public void SetState(IAction newState) {
  // sets the current state or, for us, adds it to the stack of states that are used to keep track of state history
 }
 public void RevertState() {
  // Pop off the stack to have the previous state be the current
 }
 public ActionType GetActionType() {
  // get the type of the current state 
 }
 public void Act(Vector2 move) {
  // calls Act on the current state
 }
 public void Act(bool confirmation) {
  // calls Act on the current state
 }
 ....
} 


There’s kind of a lot going on here, so explain it a bit. The first part makes this into a singleton, which ultimately means that only one instance of this object will ever exist. I won’t go into too much detail on this concept, but this is important because we only want one occurrence of the state machine. The next part is the stack that I mentioned previously. This is used to keep track of a history of states, or in our case, moves. The MovementSelection state handles the initial selection and the MovementWaypoint state handles subsequent selections and can also close out the path. Since all of these states are kept track of in the handler, it can handle all of it and delegate calls to the states themselves. Then if we need to undo, we can just revert the state and have the previous state as our current.

The benefit of using a stack is relatively obvious as it provides a history of actions, however, the state pattern is another powerful tool. Instead of having to edit a lot of code that could potentially be in a giant if statement, we can just add a state and update who goes to that state and what that state’s behavior is. Additionally, the state pattern can be expanded to more than just the movement (hint: I used this for the entire battle flow). Anyway, I hope this has given some useful information to those curious about the process.

No comments:

Post a Comment