WPF C# Tutorial – Hit Testing with Multiple Rectangle Objects

This tutorial will show you how to make a WPF application that can detect hit test with multiple objects. This tutorial will be slightly similar to the platform drop down game but, in this case, we will only move one of the rectangles around the screen and use gravity to drop it between other rectangles on the screen. This small app will be made in Visual Studio 2019 using the WPF format. You can follow along with any other visual studio version that supports WPF.

Objectives:

  • Create a rectangle drop-down app in WPF and C# in Visual Studio 2019
  • Adding keyboard events in WPF
  • Adding timer and timer events in WPF
  • Animating Rectangle with key down and key up event
  • Creating for each loop and checking hit test with other rectangles
  • Using Rect class to determine if the objects are colliding
  • Moving player object left and right within the Canvas boundary
  • Identify which platform player lands on and show names of the objects colliding

Video Tutorial –

 

Written Tutorial –

Create a new WPF App under C# in Visual Studio.

Name the project MultipleHitTestExample, you can select where you save the project and click CREATE.

This is the default view of the WPF and the window below is the XML code. We can see the outlook of the app in the WPF part but we have to add the codes in the XML part of the code where you will be able to add buttons, labels, pictures and more.

This is the default XML code. We are going to make some changes to it first.

<Window x:Class="MultipleHitTestExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MultipleHitTestExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>

    </Grid>
</Window>

Notice there is a <GRID> </GRID> code in the XML. The kind of app we want to make we need to use <CANVAS> </CANVAS>. Grid allows you to make apps that are responsive and scale able however it’s not great with animation so we need to CANVAS that does animation pretty well.

Delete the GRID from the code and add the following –

<Window x:Class="MultipleHitTestExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MultipleHitTestExample"
        mc:Ignorable="d"
         Title="MainWindow" Height="600" Width="525" FocusManager.FocusedElement="{Binding ElementName=MyCanvas}">
    
    <Canvas Name="MyCanvas" KeyDown="Canvas_KeyDown" KeyUp="Canvas_KeyUp" Focusable="True" Margin="0,0,0.4,-59.6" HorizontalAlignment="Right" Width="518" >

        <Label FontSize="16" Canvas.Left="20">Landed on:</Label>
        <Label Name="infoTxt" FontSize="16" Canvas.Left="113">???</Label>

        <Rectangle Name="rec1" Width="50" Height="50" Margin="0" Canvas.Left="307" Canvas.Top="51" Fill="Blue" />
        <Rectangle Name="platform1" Width="337" Height="50" Margin="0" Canvas.Left="143" Canvas.Top="144" Tag="platform" Fill="Brown"/>
        <Rectangle Name="platform2" Width="337" Height="50" Margin="0" Canvas.Left="20" Canvas.Top="283" Tag="platform" Fill="Orange"/>
        <Rectangle Name="platform3" Width="337" Height="50" Margin="0" Canvas.Left="143" Canvas.Top="463" Tag="platform" Fill="Red"/>
    </Canvas>
</Window>

Lets go over the XML code first –

Title=”MainWindow” Height=”600″ Width=”525″ FocusManager.FocusedElement=”{Binding ElementName=MyCanvas}“>

First inside the title put the code above, this code will focus right on the Canvas when the app is loaded, this part of code will allow the program to respond key down and key up event.

<Canvas Name=”MyCanvas” KeyDown=”Canvas_KeyDown” KeyUp=”Canvas_KeyUp” Focusable=”True” Margin=”0,0,0.4,-59.6″ HorizontalAlignment=”Right” Width=”518″ >

This is the open canvas code, inside this tag we have several options included such as key down and key up events, whether this canvas is focusable when its loaded, margin, horizontal alignment and width.

<Label FontSize=”16″ Canvas.Left=”20″>Landed on:</Label>

<Label Name=”infoTxt” FontSize=”16″ Canvas.Left=”113″>???</Label>

These two lines of code above will add two labels to the screen. We will use them to see which platform the player character lands on.

<Rectangle Name=”rec1″ Width=”50″ Height=”50″ Margin=”0″ Canvas.Left=”307″ Canvas.Top=”51″ Fill=”Blue” />

This is the first rectangle; inside the tag we are giving this rectangle some properties. We need to give it a name and, in this case, its rec1. We give its width and height of 50, when we declare a number inside of this it calculates in pixels so this rectangle will be 50 pixels wide and 50 pixels high. We are leaving the margin to 0. We can set it to where we want it so in this case, we want this rectangle to be 307 pixels from the left and 51 pixels from the top.

<Rectangle Name=”platform1″ Width=”337″ Height=”50″ Margin=”0″ Canvas.Left=”143″ Canvas.Top=”144″ Tag=”platform” Fill=”Brown”/>

<Rectangle Name=”platform2″ Width=”337″ Height=”50″ Margin=”0″ Canvas.Left=”20″ Canvas.Top=”283″ Tag=”platform” Fill=”Orange”/>

<Rectangle Name=”platform3″ Width=”337″ Height=”50″ Margin=”0″ Canvas.Left=”143″ Canvas.Top=”463″ Tag=”platform” Fill=”Red”/>

Above the are 3 platforms for this app. I you look inside these tags you will notice they all have similar properties inside the rectangle tag. Each of them has their own names, height, width and location. We also have a TAG property in them, this is important because we will use this to detect the collision between these and the rec1.

</Canvas>

Lastly we end the canvas as we will not need to add anything else to it. Now lets get to the coding.

Adding Events

Right click on the Canvas_KeyDown and you will see the option Go to Definition. By clicking on this option Visual Studio will add the event got us. Nice right.

Right click and then click on Go to Definition.

Visual studio will open the C# script file that’s linked to this app and then add the Canvas_KeyDown event.

Now right click on the Canvas_KeyUp and Click on Go to definition.

As you can see both of the events are added to the code now.

On top of screen, under the line using System.Windows.Shapes; we need to add the line

using System.Windows.Threading;

By default, WPF forms app will not include the timer, but we can add it ourselves to use it in this app. We need the timer to use as the main engine for the app since there will be objects moving and colliding with each other we need a timer to keep track of all of these activities. By adding this line we will have access to the timer properties.

Now before the public MainWindow() line we need to add our global variables, these variables can be accessed from any function in this program this is why we call it GLOBAL.

int speed = 10; this is an integer variable which holds the number 10 inside of it. It will help us move the character.

int dropSpeed = 10; another integer which holds 10, this integer will be used to simulate gravity for this app.

bool goLeft, goRight; this is a short-handed way to declare variables. Both go left and right are Boolean type variables they only have two options, it can either be true or false. By default, it will be set to false when we declare it unless we set to true. Both of these variables will be used in the key down and up function to help us move the character left and right.

Inside the MainWindow() function we will declare the timer and initialize it. We are adding the following lines to this function, this is the function that runs first when the app will load so we want to initialize the timer when the app loads so the activities and instruction will be ready soon as its loaded on the screen.

DispatherTimer dispatcherTime = new DispatcherTimer(); this line is accessing the timer class from the Threading we added earlier. This line tells the app that we need this timer in this app so allows us to make a new instance of the class.

dispatcherTime.Tick += dispatcherTimer_Tick; this line is telling the program that the main timer will have this dispatcherTime_Tick event to run with each tick. So, each time the timer goes from 1 to 2 to 3 it will run this event. This event will hold most of the logic for this app.

dispatcherTimer.Interval = TimeSpan.FromMilliseconds(20); to make this app more fluent we need to make the timer tick faster, this line is telling the app we want to run the event linked to this timer every 20 milliseconds, so when we move the character it doesn’t look too choppy it looks better.

dispatcherTimer.Start(); this line will start the timer when then main app loads.

You will notice that the dispatcherTimer_Tick line will be red. This is ok for now because the program is looking for this event in the background and we haven’t created it just yet. Once we create it, the red line will go away.

Under the Canvas_KeyUp event you can now type the following event. Make sure you type it in before the last two curly brackets because it needs to be within the same class as the other function.

Type the function as it is above. This function is linked to the timer, so with every tick it run. Once you have typed this up the redline, we discussed earlier it will go away now.

Time to type up some instruction for all of the functions we added for this app so far.

Canvas Key Down Event –

        private void Canvas_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Left)
            {
                goLeft = true;
            }
            else if (e.Key == Key.Right)
            {
                goRight = true;
            }
        }

In this function we have two if statements inside of it, it will check for the right and left key presses. We are checking IF e.Key == Key.Left meaning if the app event catches the left key then we will change goLeft Boolean to true. We are doing the same for the right key.

Canvas Key Up Event –

        private void Canvas_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Left)
            {
                goLeft = false;
            }
            else if (e.Key == Key.Right)
            {
                goRight = false;
            }
        }

This event will trigger when we release the keys on the keyboard. Its similar to the key down event except we are changing the goLeft and goRight Booleans back to false. The idea behind this is we want the player to move left and right only when the keys are press and stop when the keys are released.

Dispatcher Timer Tick Event –

This event will control all of the aspects of this app, it may be slightly lengthy but go through the lines and comments are presented in the code in Green text so its easier to understand.

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // this line below will control the player drop speed
            Canvas.SetTop(rec1, Canvas.GetTop(rec1) + dropSpeed);

            // lets run a for each loop through all of the controls and find the rectangles we need
            foreach (var x in MyCanvas.Children.OfType<Rectangle>())
            {

                // if the rectangles found in the loop have a tag then we will do the following
                if (x.Tag != null)
                {
                    // create a platform id variable and save the tags for the platform in it
                    var platformID = (string)x.Tag;

                    // if the tag is a platform then we can do the following
                    if (platformID == "platform")
                    {

                        x.Stroke = Brushes.Black; // give the rectangles a border colour

                        // in order to do the hit test we need to define the elements for this app
                        // first define who the player is as in what object it is and how to calculate its height and width

                        Rect player = new Rect(Canvas.GetLeft(rec1), Canvas.GetTop(rec1), rec1.Width, rec1.Height);

                        // second we will need to do the same for the platforms
                        // since they are running inside a loop, we can use the X keyword to identify them and save their values
                        Rect platforms = new Rect(Canvas.GetLeft(x), Canvas.GetTop(x), x.Width, x.Height);

                        // now we can check if the player intersects or HITs the platforms if so, do we can do the following
                        if (player.IntersectsWith(platforms))
                        {
                            // change the drop speed to 0 so no gravity
                            dropSpeed = 0;

                            // push the player character atop of the platforms
                            Canvas.SetTop(rec1, Canvas.GetTop(x) - rec1.Height);

                            // show the platform name in the label on the top
                            infoTxt.Content = x.Name.ToString();
                        }
                        else
                        {
                            // if we are not hitting anything called platform then we can continue to drop
                            dropSpeed = 10;
                        }
                    }
                }
            }

            //character movement and boundaries code

            // if the characters height is the more than the applications height then we need to reset the character back to the top
            // so we don't lose the character even when you drop off the platforms
            if (Canvas.GetTop(rec1) + (rec1.Height * 2) > Application.Current.MainWindow.Height)
            {
                // here we are setting the characters top to a -50 so it will drop from the top when it goes off the bottom
                Canvas.SetTop(rec1, -50);
            }

            //moving left code
            // if the left button is pressed and characters left location is greater than 15 pixels from the border
            if (goLeft && Canvas.GetLeft(rec1) > 15)
            {
                // then we can move the character towards the left
                // this code will also help to set boundaries for the character
                // it will stop when it reaches 15 pixels
                Canvas.SetLeft(rec1, Canvas.GetLeft(rec1) - speed);
            }

            //moving right code
            // if the right button is pressed and characters right location plus the width of the character plus 10 is less than the applications width
            if (goRight && Canvas.GetLeft(rec1) + (rec1.Width + 10) < Application.Current.MainWindow.Width)
            {
                // we will move the character towards the right
                // this code is the if statement above will stop the character when it reaches the right side of the screen
                // with this code we can make sure that the character doesn't go off screen
                Canvas.SetLeft(rec1, Canvas.GetLeft(rec1) + speed);
            }
        }

// this line below will control the player drop speed

Canvas.SetTop(rec1, Canvas.GetTop(rec1) + dropSpeed);

This is the first line in the code, this line is setting the TOP or Y axis of the rectangle. The way this line works is in WPF, we cannot simply call rec1.LEFT or rec1.TOP as we do in windows form application. Since we are using CANVAS in WPF we need to evoke the Canvas class to make this object move in the scene. If we were to simply move rec1 to a selected location we can do this Canvas.SetTop(rec1, 200); This will move the REC1 rectangle to 200 pixels from the left of the scene. We want to move this rectangle dynamically we need to first get the Y axis of the rectangle and then add the drop speed to it, this is way we are first using the Canvas.SetTop linking the rec1 to it, then we are using Canvas.GetTop and accessing the current Y axis of rec1 and finally adding drop speed integer to it.

// lets run a for each loop through all of the controls and find the rectangles we need

foreach (var x in MyCanvas.Children.OfType<Rectangle>())

{

}

This line is the for each loop. For each loop are used to identify objects that are inside of the app. In this loop we are looking for Rectangle objects in this app. In this code above we are creating a variable called X this will contain all of the rectangles from this canvas, this way we can access each of them from the app and it will make it easier to check for collisions and give further instructions. The foreach loop starts and ends with its own curly brackets. Make sure you add them first before typing the code inside of it.

// if the rectangles found in the loop have a tag then we will do the following

if (x.Tag != null)

{

}

// create a platform id variable and save the tags for the platform in it

var platformID = (string)x.Tag;

In this line above we are creating a variable called platformID it will identify the platforms which has a tag and save it inside. We can use this to check if the player has landed on it or no.

// if the tag is a platform then we can do the following

if (platformID == "platform")

{

}

We are starting a new if statement here, if the platform ID contains the word platform then we can run the instructions from inside.

x.Stroke = Brushes.Black; // give the rectangles a border colour

When we find the platform icons in the Canvas we will first give it a black order around it, so its more visually identifiable and it shows us the loop is working.

// in order to do the hit test we need to define the elements for this app

// first define who the player is as in what object it is and how to calculate its height and width

Rect player = new Rect(Canvas.GetLeft(rec1), Canvas.GetTop(rec1), rec1.Width, rec1.Height);

// second we will need to do the same for the platforms

// since they are running inside a loop we can use the X keyword to identify them and save their values

Rect platforms = new Rect(Canvas.GetLeft(x), Canvas.GetTop(x), x.Width, x.Height);

I think the comments explain these lines very well, but there are few more things we need to mention here. First Rectangles as an object in C# doesn’t have any interests with function to see if its overlapping any other rectangle in the canvas, so we are going with the Rect class instead. First we made a Rect player = new Rect(); <- this line is going to create a new instance of the Rect class called player then we are sending some information in the new player rect. With each Rect object we can send the width, height, X position and Y position of the object. This is perfect for us because we can link it directly with our rect1 object. So, inside the brackets we are giving it Canvas.GetLeft(rec1), Canvas.GetTop(rec1), rec1.Width, rec1.Height);

If you look at the platforms Rect we created right under the player one, its slightly different because we are sending the x values such as height, width, x and y with it. This is because the loop we are running is saving all of the platform information inside X variable so we can access all of the data from one instead of writing more lines of code. Neat right.

Now we can check if they are colliding at any point. We are going to do this by using a IF statement. Take a look below.

// now we can check if the player intersects or HITs the platforms if so, do we can do the following

if (player.IntersectsWith(platforms))

{

// change the drop speed to 0 so no gravity

dropSpeed = 0;

// push the player character atop of the platforms

Canvas.SetTop(rec1, Canvas.GetTop(x) - rec1.Height);

// show the platform name in the label on the top

infoTxt.Content = x.Name.ToString();

}

else

{

// if we are not hitting anything called platform then we can continue to drop

dropSpeed = 10;

}

}

In this if statement we are checking IF PLAYER Rect intersects with platform Rect then

We change the drop speed integer value to 0, so player does fall down any more.

Canvas.SetTop(rec1, Canvas.GetTop(x) - rec1.Height);

This line above is setting the player above the platforms, so we can set the top of the player object rect1 by getting the top position of X (platform) and deducting the height of the player object. So, this will make it look like the player object has landed on the platform and is sitting right on top of it.

infoTxt.Content = x.Name.ToString();

This line will show which platform we have landed on. If we have landed on a platform then it will show the name on the Canvas.

IF none of the above has happened then we can simply keep the player object falling until it hits something.

// if the characters height is the more than the applications height then we need to reset the character back to the top

// so, we don't lose the character even when you drop off the platforms

if (Canvas.GetTop(rec1) + (rec1.Height * 2) > Application.Current.MainWindow.Height)

{

// here we are setting the characters top to a -50 so it will drop from the top when it goes off the bottom

Canvas.SetTop(rec1, -50);

}

In the if statement above, we are checking if the player has dropped below the actual height of the window, if so, we will set the rect1 back to top so it looks like its dropping from the top. By setting the Y value to – 50 it will drop back from top of the screen.

//moving left code

// if the left button is pressed and characters left location is greater than 15 pixels from the border

if (goLeft && Canvas.GetLeft(rec1) > 15)

{

// then we can move the character towards the left

// this code will also help to set boundaries for the character

// it will stop when it reaches 15 pixels

Canvas.SetLeft(rec1, Canvas.GetLeft(rec1) - speed);

}

This if statement will move the player character towards left of the screen however we don’t want the player to disappear from left of the screen so we want to make sure the player is at least 15 pixels away from the left then we can move it towards the left, if player isn’t 15 pixels then it will simply stop there. Player object will only move when its position is more than 15 pixels from the left else it will stop.

//moving right code

// if the right button is pressed and characters right location plus the width of the character plus 10 is less than the applications width

if (goRight && Canvas.GetLeft(rec1) + (rec1.Width + 10) < Application.Current.MainWindow.Width)

{

// we will move the character towards the right

// this code is the if statement above will stop the character when it reaches the right side of the screen

// with this code we can make sure that the character doesn't go off screen

Canvas.SetLeft(rec1, Canvas.GetLeft(rec1) + speed);

}

In the if statement above we are checking the right movement of the player object, IF go right Boolean is true and player object’s left position + player width + 10 is less than applications windows total width then we can move the character towards right of the screen, otherwise it will stop.

Now hit that green play (debug) button to see how it runs.

You should be able to able land on the platforms and it will show the platform name on the screen along with moving left and right, also when we drop of the platform it should bring the player character back to top of the screen.

Full C# Source Code –

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Windows.Threading; // for the timer and events

namespace MultipleHitTestExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        int speed = 10; // set the speed for the player

        int dropSpeed = 10; // set the gravity drop speed

        bool goLeft, goRight; // moving player left and right with these booleans

        public MainWindow()
        {
            InitializeComponent();

            // set up the timer and its event
            DispatcherTimer dispatcherTimer = new DispatcherTimer(); // create a new instance of the timer
            dispatcherTimer.Tick += dispatcherTimer_Tick; // link to the dispatcher timer event
            dispatcherTimer.Interval = TimeSpan.FromMilliseconds(20); // we want this to run every 20 milliseconds
            dispatcherTimer.Start(); // start the timer when the application loads
        }

        private void Canvas_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Left)
            {
                goLeft = true;
            }
            else if (e.Key == Key.Right)
            {
                goRight = true;
            }
        }

        private void Canvas_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Left)
            {
                goLeft = false;
            }
            else if (e.Key == Key.Right)
            {
                goRight = false;
            }
        }


        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // this line below will control the player drop speed
            Canvas.SetTop(rec1, Canvas.GetTop(rec1) + dropSpeed);

            // lets run a for each loop through all of the controls and find the rectangles we need
            foreach (var x in MyCanvas.Children.OfType<Rectangle>())
            {

                // if the rectangles found in the loop have a tag then we will do the following
                if (x.Tag != null)
                {
                    // create a platform id variable and save the tags for the platform in it
                    var platformID = (string)x.Tag;

                    // if the tag is a platform then we can do the following
                    if (platformID == "platform")
                    {

                        x.Stroke = Brushes.Black; // give the rectangles a border colour

                        // in order to do the hit test we need to define the elements for this app
                        // first define who the player is as in what object it is and how to calculate its height and width

                        Rect player = new Rect(Canvas.GetLeft(rec1), Canvas.GetTop(rec1), rec1.Width, rec1.Height);

                        // second we will need to do the same for the platforms
                        // since they are running inside a loop we can use the X keyword to identify them and save their values
                        Rect platforms = new Rect(Canvas.GetLeft(x), Canvas.GetTop(x), x.Width, x.Height);

                        // now we can check if the player intersets or HITs the platforms if so do we can do the following
                        if (player.IntersectsWith(platforms))
                        {
                            // change the drop speed to 0 so no gravity
                            dropSpeed = 0;

                            // push the player character atop of the platforms
                            Canvas.SetTop(rec1, Canvas.GetTop(x) - rec1.Height);

                            // show the platform name in the label on the top
                            infoTxt.Content = x.Name.ToString();
                        }
                        else
                        {
                            // if we are not hitting anything called platform then we can continue to drop
                            dropSpeed = 10;
                        }
                    }
                }
            }

            //character movement and boundaries code

            // if the characters height is the more than the applications height then we need to reset the character back to the top
            // so we don't lose the character even when you drop off the platforms
            if (Canvas.GetTop(rec1) + (rec1.Height * 2) > Application.Current.MainWindow.Height)
            {
                // here we are setting the characters top to a -50 so it will drop from the top when it goes off the bottom
                Canvas.SetTop(rec1, -50);
            }

            //moving left code
            // if the left button is pressed and characters left location is grater than 15 pixels from the border
            if (goLeft && Canvas.GetLeft(rec1) > 15)
            {
                // then we can move the character towards the left
                // this code will also help to set boundaries for the character
                // it will stop when it reaches 15 pixels
                Canvas.SetLeft(rec1, Canvas.GetLeft(rec1) - speed);
            }

            //moving right code
            // if the right button is pressed and characters right location plus the width of the character plus 10 is less than the applications width
            if (goRight && Canvas.GetLeft(rec1) + (rec1.Width + 10) < Application.Current.MainWindow.Width)
            {
                // we will move the character towards the right
                // this code is the if statement above will stop the character when it reaches the right side of the screen
                // with this code we can make sure that the character doesn't go off screen
                Canvas.SetLeft(rec1, Canvas.GetLeft(rec1) + speed);
            }
        }


     }
}

Double check the code if you haven’t already, any errors can be tracked with this source code and make sure the spelling for the objects are correct. Hopefully you had fun with this and will see you on the next one.




Comments are closed.