Developers guide to writing a simple Silverlight game
Collision detection
Ah my old friend (and enemy), collision detection. This is where you realise Silverlight isn't a game framework because there is no out-of-the-box hit test. Ok not to worry, what we do have is the rather strange concept of the canvas. Like a container, a canvas is used to group a set of controls and help position them. But unlike a container the controls are not constrained by their canvas, you can draw off the canvas...weird. So really the name canvas is a misnomer, it's really a grouping & relative positioning container...Ok maybe canvas will do ;) But what this means is that the canvas can provide a nice, if primative, form of collision detection. Using the canvas and the detectors bounding box means we can not only provide a very efficient hit test but we can also easily exclude graphical assets (such as smoke or spray) from creating a collision. Just in case you're not familiar with such tests this is the one I used;
public static bool Collision(Sprite sprite1, Sprite sprite2)
{
double left1 = (double)sprite1.GetValue(Canvas.LeftProperty);
double left2 = (double)sprite2.GetValue(Canvas.LeftProperty);
double top1 = (double)sprite1.GetValue(Canvas.TopProperty);
double top2 = (double)sprite2.GetValue(Canvas.TopProperty);
double actualWidth = sprite1.ActualWidth;
double actualHeight = sprite1.ActualHeight;
GetActualSizesForTransformedSprite(sprite1, ref actualWidth, ref actualHeight);
//See if the sprite rectangles overlap
bool collision = !(left1 > left2 + sprite2.ActualWidth
|| left1 + actualWidth < left2
|| top1 > top2 + sprite2.ActualHeight
|| top1 + actualHeight < top2);
return collision;
}
The observant amongst you may have noticed GetActualSizesForTransformedSprite, this represented my biggest problem. One of our characters is a big jet plane that acts as a end of level boss. Although it moves around the screen using the same techniques as any other character the plane also zooms in and out using a transform. This caused a big problem with the collision detection. When I asked for it's actual size Silverlight would always tell me the size of the sprite in it's normal form, regardless of if it was currently fully zoomed in (or out). I have to thank dcstraw from the Silverlight.net forums for the core of the solution;
//game controller
private static void GetActualSizesForTransformedSprite(Sprite sprite1, ref double actualWidth,
ref double actualHeight)
{
PlaneControl plane = sprite1 as PlaneControl;
if (plane != null)
{
Rect bounds = plane.GetRenderBounds(Application.Current.RootVisual);
actualWidth = bounds.Width;
actualHeight = bounds.Height;
}
}
?
// Plane control
public Rect GetRenderBounds(UIElement relativeTo)
{
var transform = LayoutRoot.TransformToVisual(relativeTo);
var topLeft = transform.Transform(new Point(0, 0));
var topRight = transform.Transform(new Point(ActualWidth, 0));
var bottomRight = transform.Transform(new Point(ActualWidth, ActualHeight));
var bottomLeft = transform.Transform(new Point(0, ActualHeight));
double boundsLeft = Math.Min(topLeft.X, Math.Min(topRight.X,
Math.Min(bottomRight.X, bottomLeft.X)));
double boundsTop = Math.Min(topLeft.Y, Math.Min(topRight.Y,
Math.Min(bottomRight.Y, bottomLeft.Y)));
double boundsRight = Math.Max(topLeft.X, Math.Max(topRight.X,
Math.Max(bottomRight.X, bottomLeft.X)));
double boundsBottom = Math.Max(topLeft.Y, Math.Max(topRight.Y,
Math.Max(bottomRight.Y, bottomLeft.Y)));
return new Rect(boundsLeft, boundsTop, boundsRight - boundsLeft,
boundsBottom - boundsTop);
}