State, state, state.
Kids, read this post as a cautionary tale and don’t do things the way I write about here — instead, use this design pattern.
I’ve said it before and I’ll say it again. If you want to do game programming you’d better be prepared to deal with state. That means making sure you know when something can happen and when it can’t. For instance, in Hexbgon, I set my bee on a moving path from point A to point B. While it is moving from point A to point B I should not be able to change that state (in this particular game; other games might have different needs — for example, if I click a target location in a game like Baldur’s Gate, then click at a different point on the ground while the characters are moving, they can/should retarget themselves for the new position).
So I did. I set it so my bee can’t move in mid flight.
I created a CanMove() method that checks whether the bee arrived at its target location, and if it had, returns a “true” value indicating it can move.
Then I have an external canMove boolean variable. When I click the mouse button, I set the value of canMove to what CanMove() method returns after it does this check. If it canMove == true, then it it goes on its merry way to the next target. However, this value is set to true when and only when the bee has landed on a target position, the second I click, and it senses canMove is true, then starts to move, the bee’s position is longer equal to the target position, thus it cannot move (i.e. the CanMove() method returns false).
This worked just fine.
Then after playing the other night, I did what one should always do when playing/testing. Try to break it.
In this case, instead of waiting to see what my next allowable moves were (remember, “canMove” is based upon whether I have a valid target position and whether my position is equal to that targetposition), I just clicked really fast (like a double click) on a nearby hexagon when “canmove” was in effect. Once I clicked on a (valid) target, if I clicked again quickly it would not have a new target for the next clicks displayed and that let me jump to any hex I wanted, and continued to eat up the board without consideration to the actual legal moves.
After playing around with trying to set a new boolean “canClick” (i.e. to set “canClick” to “false” when the bee is moving, so I can’t click when it’s in flight (not just can’t move, but the mouse won’t try to set a new target in between positions as well)), I didn’t create a method, I just had a canClick boolean variable. Fair enough I thought, when the player clicks the mousebutton, I’ll check if they “canClick”, and if so – continue on the rest of the methods way, and if not, well, they wouldn’t do that block of code (i.e. move to new target position). Of course, if canClick was true, I set it to false right after I entered the block to be executed: canClick == false.
This should work I thought, all I have to do is reset “canClick” to true once I have finished flying and landed on the target position. So I went to the end of the block after the bee flew, and set it to true.
What’s this, it didn’t work? I tried multiple locations to get it so canClick being set to true would do it properly, but no dice. Then I realized that perhaps it wasn’t carrying the false value that I set inside of the block to the outside. Sure as shit, I ran some debug logs and when I checked the value of canClick after setting it to False and then after I exited that block of code, false immediately went back to True.
This was due to the fact that booleans are Value types in C#. This means that they are placed on the stack (well not technically, there are other differences between “value” and “reference” types and this is a lazy explanation and a pedant would call me out on it, but for our purposes here, it’s close enough). So when I set the value of the boolean within a block it only kept that value change within the block, and even though I created the boolean outside the block it didn’t matter. If it were a reference type (similar to a pointer in C/C++, but again – not really the exact same), I could make the change. So my first inclination was to try to pass by using the ref keyword, but because it’s a Value type, I think it wasn’t doing it… It was still a value, not a reference.
This is one of those things where a proper understanding of language features saves a lot of time. Once I realized what was going on (and thank goodness I’ve been reading books on C# to know the difference between a Value and Reference type so that I knew it was an issue when I saw it)… if I hadn’t realized/known about it, I would have been barking up the wrong tree, or at least had to spend more time figuring out why the value of canClick was reverting outside the block of code, even though I wasn’t explicitly changing it.
So – I basically did precisely what I did with the “CanMove” issue above — create a method that does a check and returns a boolean. Only my question now was… What is this check? Well, I can’t click while the bee is moving/flying. Similar to the “canmove” it’s basically the inverse of canmove. It is mutually exclusive. If the bee canMove, I can’t click (return false), otherwise, return true.
Anyways, if you’ve read my code in the past you’ve probably seen me talk about how I feel that while it’s not spaghetti code, necessarily (I don’t use GOTO), it certainly isn’t well structured. This is the sort of thing that makes it not well structured. It’s fine for doing quick agile coding, but it needs to be refactored into a proper Stateful Design Pattern (see link above).
Because this is just 2 basic states, and, as the link above asks: what happens when you have more and more states? There’s just a tremendous amount of checking that one needs to do and it gets unwieldy the more you add. So you need to practice a proper design to expand into more functionality and to keep code manageable. Right now it’s hard to keep track of state 1 vs state 2, which function was is set in, etc…
So – my next goal is to learn more about these State Design Patterns and figure out how to implement it in my game(s).