This is the final post in my little DevSta {Challenge 2008} "miniseries". In previous posts, I described how the vehicle "AI" works to avoid the rocks and also how the explosion animations worked. In this post, I'll describe how the vehicle images are rotated so that they look as though they're driving in the right direction.
First of all, take a look at the screenshot below:
In this screenshot, you can see two vehicles at different angles - the blue line represents their current "heading" direction. The way I draw the actual image of the vehicle is pretty simple. I'm obviously no artist, so I simply drew one image of the vehicle pointing down, then at runtime I calculate the angle between the "down" vector and the direction I want to display the vehicle and rotate the image by that angle using the GDI+ RotateTransform method. Below is the code I used:
First, we calculate the angle between the "down" vector and the current heading (represented by the direction variable). Because the dot product basically always looks at the smaller angle, we need to negate the angle if it's on the "other side":
var down = new Vector(0, 1);
var angle = (float)Math.Acos(down.Dot(direction));
if (Direction.X > 0)
angle *= -1.0f;
Next, we create a temporary Bitmap to hold the rotated image. The new bitmap needs to be a bit bigger than the source because rotating the image can obviously make it a bit wider (you can see in the screenshot above, when it's at an angle, the corners of the source image [represented by the inner square] are wider than the original image). We then apply a rotation and a translate matrix to rotate the original image and then translate it into the middle of the larger image:
var rotated = new Bitmap((int)(img.Width * 1.75), (int)(img.Height * 1.75));
using (var tmp = Graphics.FromImage(rotated))
{
tmp.TranslateTransform(rotated.Width / 2.0f, rotated.Height / 2.0f);
tmp.RotateTransform(180.0f*angle/(float) Math.PI, MatrixOrder.Prepend);
Remember, the RotateTransform takes angles in degrees, while most math functions (in this case, Math.Acos return values in radians, so we need to convert. Once we've set up the transform, it's a simple matter of using DrawImageUnscaled to draw the source to the temporary image:
tmp.DrawImageUnscaled(img, (int)(-img.Width / 2.0f), (int)(-img.Height / 2.0f));
}
And then using that image as the final image and you're done! In the screenshot above, the two squares you can see represent the border around the original "source" image (before the transform is applied) and the square around the final image. It just gives you an indication of the rotation that took place.
Now, in my testing, I discovered that this was actually pretty quick - certainly fast enough that I didn't bother to optimize any further. The obvious optimization would be to pre-rotate the images and save off, say, 360 of them (one for each angle). But I didn't really have to because I was able to play up to level 25 (with 15-20 on screen at once) even on my lowly 1.6GHz laptop.
So that's pretty much it for this "series". The Missile Command game that I made is not particularly complicated in terms of gameplay or coding. The part that takes the longest, as I said before, was tuning all the elements to produce a game that "plays well" and isn't too hard or too easy. I hope that I've been able to get that worked out well!
Once the winners of DevSta have been announced, I'll release the game and source code here, so stay tuned :>>