Give Metro UI Elements Space! Writing Your Own Collision Detection Handler in C#

Note: the code APIs discussed here were written at the time of the Windows 8 Release Preview. We also only consider 2D space.

Here I'm going to purposefully do things the hard way because…well, I often do that. Instead of using a pre-existing game library for handling collision detection in a Metro app, I am going to discuss my experience writing my own collision code! I chose this approach as my experience in collision detection was limited, and I really wanted a deep understanding of the design considerations at hand.

If you wish to follow along, I recommend using the Visual Studio 2012 RC IDE along with Windows 8 Release Preview. Here, the focus will be on the C# programming language and using the XAML designer. Ready? OK, let's begin!

1) I started with the XAML designer. I created a Canvas type as the parent, with several children images within it. Why? I liked the fact that with a canvas, you can explicitly specify coordinates of children objects relative to the parent. In other words, the coordinate (0,0) is the origin of the Canvas, not necessarily the device's screen.

Here is some sample XAML code I wrote:


    < Canvas Name="ImageCanvas" Width="1100" Margin="0,0,266,0">

      < Canvas.Background>
      < LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

        < GradientStop Color="Black"/>
        < GradientStop Color="#FF208984" Offset="1"/>

      < /LinearGradientBrush>
      < /Canvas.Background>

    < !-- Row 1 cells for the game table-->

      < Image x:Name="c11" Height="100" Width="100" Canvas.Top="0" Canvas.Left="100" />
      < Image x:Name="c12" Height="100" Width="100" Canvas.Top="0" Canvas.Left="200" />
















See how in the snippet, each image has a specification for both Canvas.Top and Canvas.Left ? This is how we specify the image positions relative to the parent. This at times may be more suitable than simply specifying the row and column indices.


2) At runtime, my images float around the screen. I won't get into the physics details here, but as a heads up, moving UI Elements dynamically is rather simple. If you wish to change the position of a child UI element (in this case, an Image) at runtime, you can simply do the following, assuming you wish to set the top coordinate of image c11 to be 10 units in the positive axial direction relative to your Canvas' origin:

Canvas.SetTop(c11, 10);


Like SetTop, you can use SetLeft as well. You may even want to consider the width of the image, in which case you can use the image's Width property. To no surprise, you can also use GetTop and GetLeft methods as well; as discussed below, these will be needed for collision detection.


3) OK, so now we have a basic gist of using a Canvas (in designer view) and moving UI elements at run-time (C# "code-behind"). Now, it is time for the fun part: how do we deal with collisions? Well, I considered two cases: collisions with walls, and collisions with other images.


    a) I chose to pass a reference to the actual Canvas parent into the collision handler routine. You can use the canvas' ActualHeight and ActualWidth properties to robustly determine whether an image is sneaky enough to want to go outside your Canvas bounds. Robustness is key here as we don't want different side effects when the same app is run on different devices with varying screen dimensions.

    When dealing with images colliding with each other, we need to use the "Get" methods I previously mentioned to find out the position of the UI Elements. With my sample app, I made life simpler by making all images square and of the same dimension.

    b) Additionally, I use four "flags" that are simple Boolean types. These indicate that before moving the current image of consideration, are flags set to indicate that there is a collision to the top, bottom, left, or right? This is important in that it meant two things for me. If an image has a collision to, say, the right, I'd reverse the movement direction for it if it were to overlap the other image on the next frame. The trickier part was: what if collision flags are set for both directions of either the x or y axes? In this case, I would then make the image stay still until traffic clears. The reason is that in my app, images always move diagonally; it's always the case that both x and y positions will be updated during motion. Of course, your usage model (and respective collision checking) may differ.





4) Here is a sample of my collision handler checking for an image-to-image collision in just one direction (the left direction, other directions aren't discussed as they are too similar):

//don't compare image distance to self or to blank image


    if ( ( (x != i) || (y != j) ) && images[i,j].Source != null)
    {

      ij_left = Canvas.GetLeft(images[i, j]);
      ij_top = Canvas.GetTop(images[i, j]);

      //compute the distance between this image's left edge and other image's right edge
      //if too close, make this image now move right
      if (Math.Abs(xy_left - (ij_left + images[i, j].Width)) <= (CLOSENESS + vel_offset))
      {

        //collision must occur in x component only if y components are close enough as well
        if (Math.Abs(xy_top - ij_top) <= images[x, y].Height)
        {

          collision_detected_left = true;
          del_x = SPEED + vel_offset;


        }






Here is what my code basically does. First, when considering the "current" image, images[x,y], we will only check for collision with images[i,j] images that are both distinct from this image and are not null (eg: it doesn't make sense to check for collision with a blank space). Note: your code may vary depending on your image movement you have defined. Also, this code is simply reflective of my experience and may in fact not be the optimal way to do things. For example, one may choose to use dynamic programming where we only compare with images known to be on one side of the current image, etc.

As reference, take a look at the following screenshot:




Here, images[x,y] is the "1" tile while images[i,j] is the "2" tile. The red line shows the horizontal width between the two images. Obviously, they are at risk for collision. This will cause the second if statement in the code above to evaluate to "true" since the check for tile "1" will find that to its left, there is an image that is too close. Further, since the vertical spacing between the tiles is so small, the final check passes, thus ensuring that tile "1" will move right to avoid the collision (the last code statement in the snippet above). Here I also locally store image properties to ij_left and ij_top.

For the if statement nested most deeply, I also made the realization that for images "too close" in the x direction, the images must be close enough in the y direction as well. Otherwise, you could falsely indicate collisions for images that would not actually collide due to too large of a y coordinate gap! So, when all conditions are met, I use del_x to set the new speed magnitude and direction as "positive" (greater than zero in my case) which means that, since this image (x,y) was going to collide with something to its left, make it move right now to avoid collision.


5) Similarly, I present to you my little code snippet describing one simple scenario for Canvas boundary collision detection:



    //make sure image doesn't go too left
    if (Canvas.GetLeft(images[x, y]) <= left)
    {
    Canvas.SetLeft(images[x,y], left+SPEED);

      del_x = SPEED + vel_offset;
      collision_detected_left = true;

    }





In the following picture:



The "4" tile is getting really close to the black wall to its left. Note that we are still safe; the left edge of the tile is to the right of the wall's edge, which will cause the condition in the code snippet to be "false". However, if the image continues moving to the left, upon impact, its movement direction will be made to be positive (eg: to the right, away from the wall).

Here, left indicates the left origin coordinate of the canvas. It is left up to the reader to use the ActualHeight and ActualWidth canvas properties to detect collisions with the other canvas edges. Like previously, I set collision flag and adjust movement direction accordingly.

5) In my game, I actually implemented a fling gesture where a flung image will have increased velocity for some time in comparison to other inert images, and for each frame to follow, the speed dampens until inertial velocity is reached. This is where vel_offset kicks in; when I check to see if two images are "too close", I take into account speed at all times to account for faster moving images.

6) Now, when images are moving diagonally, if at least one of the following two cases holds:



    collision_detected_left == collision_detected_right == true




or:


    collision_detected_top == collision_detected_bottom == true





I set a property to indicate that the image must wait for clearance. Recall that this is specific to my implementation in that images move diagonally. You may implement the check differently.

Well, that's it! I hope these snippets give you a good head start on writing your own Metro collision code if you are interested. While there are great libraries out there that do the work for you, sometimes it helps to reinvent the wheel for more of an academic approach. Feel free to comment with thoughts, suggestions, questions, etc. Thanks for reading!


For more complete information about compiler optimizations, see our Optimization Notice.

Comments

//make sure image doesn't go too left
if (Canvas.GetLeft(images[x, y]) <= left)
{
Canvas.SetLeft(images[x,y], left+SPEED);

del_x = SPEED + vel_offset;
collision_detected_left = true;
}
i don't understood


< Canvas Name="ImageCanvas" Width="1100" Margin="0,0,266,0">

< Canvas.Background>
< LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

< GradientStop Color="Black"/>
< GradientStop Color="#FF208984" Offset="1"/>
< /LinearGradientBrush>
< /Canvas.Background>
< !-- Row 1 cells for the game table-->

< Image x:Name="c11" Height="100" Width="100" Canvas.Top="0" Canvas.Left="100" />
< Image x:Name="c12" Height="100" Width="100" Canvas.Top="0" Canvas.Left="200" />

hmm..... thank you


Hello, apologies for the

Hello, apologies for the delay. We moved blog sites and there was delay in re-publishing this blog. For the first comment above, the code is doing the following. If in the next frame I predict that the image will be to the left of the left edge boundary if it moves one frame at its current speed, this means that a collision must have occurred....therefore, before letting the image go through a wall, we ensure that it won't improperly go through a wall.