Now that I have the graphics plumbed in the next step is to start to implement the rotation animation (after all that is what the game is named).
Before I go into how I have implemented the animations, a quick word about what has changed in the solution. I have tidied up some of the code. Firstly I have put the code that gets the textures for the drawable items behind a factory, this allows that code to be abstracted away. Next I have introduced a constants class. This class is responsible for setting different things like the colours of the tiles, by having a constants class I have one place in which I can tweak the game. Obviously being a constants class all of these variables get compiled into the code.
Last post I naively stated that I was doing a lot of redundant drawing by redrawing every single item for every single frame. I have since read that this is the way that the XNA framework works. Normally it is more efficient to just redraw everything in a frame rather than calculate what has moved and just redraw that. This makes sense, but means that I’ve had to go a slightly different path as to how I am going to do the animations.
When the player presses a button to rotate the squares this key press gets picked up in the update method. Then the rotate right or left method is called on the square rotator and the squares are updated instantly in the board in memory. This means that the next time the draw method gets called (i.e. a maximum of 1/60 of a second away) the board will instantly get redrawn with the tiles in the new position. This is not what we want. We want the tiles to animate round and finish in their final resting place in a smooth animation.
The way that the XNA framework implements rotations is in one of the overloads to the spritebatch’s draw method. The overload takes a vector to represent the sprites position in space, a vector for the origin of rotation and a float for the amount of rotation in radians.
With the above knowledge of how the XNA framework handles rotations the trick to get our animations to work is to change the way that we draw our sprites for the tiles on the board. The first implementation that we saw in part 9 just looped round all of the tiles and drew them at a specific point in space i.e. the first tile was drawn at 0, 0 the next at 40, 0 (40 being the width of a tile) and so on. What we need to do is change this and use the overload of the draw method. Except that we need to specify a position of the centre of the currently selected square, an origin of the offset from the currently selected square back to the point in space where we want to draw the square and a rotation of 0 degrees (for now).
For example when the centre square is selected (as is the default starting position) we set the position of the top left square to 180, 180 which is the exact centre of the selected square (tile). A square is 40×40 so its 4.5 squares to the centre in both directions (4.5×40 = 180). Next we need to get the offset from this point back to the top left corner of where we want to draw the square and pass that in as the origin. In this case it’s -180, -180. Obviously the degrees of rotation is 0.
To calculate these values I have introduced two new classes SquarePositionCalculator and SquareOriginCalculator. These classes both have one method and are responsible for calculating a square’s position and a square’s origin respectively according to the currently selected square. To see the full unit tests and implementation of both of these classes see the source code. Now we have these classes all we need to do is inject them into the SquareDrawer class and call them to get the vectors out for when we draw the square.
Now that we have overcome that obstacle all that is left to do to animate the selection is to wrap the draw method with something that works out which squares are selected and slowly changes the angle of rotation.
To do this I have setup some new interfaces. Firstly I have change the IDrawableItem to IAnimatableItem as for now everything that is animatable is drawable so IDrawableItem became redundant. One might argue that I could need IDrawableItem again in the future. That is true and if I need it again then I will add it back in. But I don’t like leaving dead code hanging round in case I might need it in the future. Doing this leads to code smell and can mean when people look at the code in the future they have a hard time following it when there are unused methods and classes all over the place.
The next interface that I need to define is a type of animation. For now there is only one type of animation a rotation animation, the interface I have defined is IRotationAnimationItem that has two properties a direction (clockwise/anti-clockwise) and an angle of rotation (I’m going to work in degrees as I find that easier than radians and I can simply convert this before I call draw). IRotationAnimationItem also implements the IAnimatableItem interface (obviously as if an item supports the rotation animation then it is obviously animatable).
To get the animation for the current item I have again used the factory pattern. I have defined an interface IItemAnimator that has two methods CanAnimate and Animate. This pattern is almost identical to how the item drawers worked. I have a factory class that takes the currently animatable item and returns a collection of animators that can animate all of the animations applied to the current animatable item. Then I loop around the animators and call animate on each one. For now obviously I only have one item animator the RotationAnimator. This class simply checks the direction of the item to see if it is currently being rotated. If it is then it checks the direction and either increments or decrements the angle by the amount defined in the constants class. Defining the amount in the constants easily allows me to tweak the animation speed. If we are incrementing the angle and we head above 0 then we set it to 0 and set the direction to none and vice versa if we are decrementing the angle.
Next we have to update the SquareRotation class so that the left and right methods also set the direction and angle of rotation. So when the user rotates left it sets the direction to anti-clockwise and the angle to 90 degrees. Obviously we need to update the SquareRotator unit tests to check this.
Now that we have all of those pieces in place it is simply a matter of calling them once per frame and voila we have animations. I have moved all of the code for animating and drawing the items into the AnimationEngine class. This provides a nice clean wrapper for getting all of the animatable items, applying all of the animations and then finally drawing them all. It also helps to keep the code in the game class nice and clean.
The game is starting to take shape. If you want to get the latest version of the code then simply get the latest version from the github repository and checkout the part10 branch. For details of how to do that see this page.