WPF C# Tutorial – Create a simple clicking game in Visual Studio
- Subject: WPF C# Tutorials
- Learning Time: 2 hour
Welcome to this new tutorial. In this tutorial we will be making a simple clicking game with WPF and C# inside of Visual Studio. This game will focus on the user clicking lots of circles inside of the canvas. When you click on a circle it will make a pop sound and disappear from the game and new ones will come out. Each time a new circle will spawn inside of the canvas it will start to grow slowly and when it gets bigger it pop on its own while making a pop sound. The idea behind this clicking game is to click and pop as many circles as possible before the health runs out. Each time a circle pops on its own it will reduce the in game health and when it reaches 0 a message box will pop up to tell you that the game has ended, you click OK on the message box to play again and it save your last score and display it on the screen while you are playing so you can aim to beat that score again on in the game.
Learning Objectives –
- Explore how to create a simple clicking game inside of Visual Studio using WPF and C#
- Spawn objects randomly to screen
- Scale objects using the timer inside of the C# using a dispatcher timer and timer events
- Link Mouse Click events to the Canvas inside of WPF
- Remove unused resources from the game
- import and play different sound files using media player class
- Keep score and keep a last played score inside of the game
- Make the game challenging by changing the speed, spawn rate and growth rates of the circles as the score increased
Full Video Tutorial –
XAML Code –
<Window x:Class="Clicking_Game.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:Clicking_Game" mc:Ignorable="d" Title="Clicky Game WPF MOO ICT" Height="450" Width="800"> <Canvas Name="MyCanvas" MouseLeftButtonDown="CanvasClicking" Background="Gray"> <Label Name="txtScore" FontSize="20" FontWeight="Bold" Content="Score: 0" /> <Label Name="txtLastScore" FontSize="20" FontWeight="Bold" Foreground="Purple" Content="Last Score: 0" Canvas.Left="180" /> <Rectangle Stroke="Black" Name="healthBar" Width="350" Fill="Red" Height="27" Canvas.Left="433" Canvas.Top="10" /> </Canvas> </Window>
In this XAML code above you will see we have added a CANVAS to this WPF app and we removed the original GRID tag from there. In this canvas we have 3 different items presented at the moment. There are two Labels inside here and there are2 labels and 1 rectangle presented. The two labels are showing the current score and the last score from your game play. The red rectangle will be used to show the current health inside of the game. In this game we will spawn and ellipse objects dynamically from the C# code and it will be used to show them in the screen and the users will be able click and interact with them. For the click events if you notice that we have those
C# 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; // import the threading namespace to use the dispatcher timer first namespace Clicky_Game_WPF_MOO_ICT { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { DispatcherTimer gameTimer = new DispatcherTimer(); // create a new instance of the dispatcher time called gameTimer List<Ellipse> removeThis = new List<Ellipse>(); // make a list of ellipse called remove this it will be used to remove the circles we click on from the game // below are all the necessary integers declared for this game int spawnRate = 60; // this is the default spawn rate of the circles int currentRate; // current rate will help add an interval between spawning of the circles int lastScore = 0; // this will hold the last played score for this game int health = 350; // total health of the player in the begining of the game int posX; // x position of the circles int posY; // y position of the circles int score = 0; // current score for the game double growthRate = 0.6; // the default growth rate for each circle in the game Random rand = new Random(); // a random number generator // below are the two media player classes one for the clicked sound and one for the pop sound MediaPlayer playClickSound = new MediaPlayer(); MediaPlayer playerPopSound = new MediaPlayer(); // below are the two URI location finder for both mp3 files we imported for this game Uri ClickedSound; Uri PoppedSound; // colour for the circles Brush brush; public MainWindow() { InitializeComponent(); // inside the main constructor we will write the instructors for the begining of the game gameTimer.Tick += GameLoop; // set the game timer event called game loop gameTimer.Interval = TimeSpan.FromMilliseconds(20); // this time will tick every 20 milliseconds gameTimer.Start(); // start the timer currentRate = spawnRate; // set the current rate to the spawn rate number // locate both of the mp3 files inside sound folder and add them to the correct URI below ClickedSound = new Uri("pack://siteoforigin:,,,/sound/clickedpop.mp3"); PoppedSound = new Uri("pack://siteoforigin:,,,/sound/pop.mp3"); } private void GameLoop(object sender, EventArgs e) { // this is the game loop event, all of the instructions inside of this event will run each time the timer ticks // first we update the score and show the last score on the labels txtScore.Content = "Score: " + score; txtLastScore.Content = "Last Score: " + lastScore; // reduce 2 from the current rate as the time runs currentRate -= 2; // if the current rate is below 1 if (currentRate < 1) { // reset current rate back to spawn rate currentRate = spawnRate; // generate a random number for the X and Y value for the circles posX = rand.Next(15, 700); posY = rand.Next(50, 350); // generate a random colour for the circles and save it inside the brush brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(1, 255), (byte)rand.Next(1, 255), (byte)rand.Next(1, 255))); // create a new ellipse called circle // this circle will have a tag, default height and width, border colour and fill Ellipse circle = new Ellipse { Tag = "circle", Height = 10, Width = 10, Stroke = Brushes.Black, StrokeThickness = 1, Fill = brush }; // place the newly created circle to the canvas with the X and Y position generated earlier Canvas.SetLeft(circle, posX); Canvas.SetTop(circle, posY); // finally add the circle to the canvas MyCanvas.Children.Add(circle); } // the for each loop below will find each ellipse inside of the canvas and grow it foreach (var x in MyCanvas.Children.OfType<Ellipse>()) { // we search the canvas and find the ellipse that exists inside of it x.Height += growthRate; // grow the height of the circle x.Width += growthRate; // grow the width of the circle x.RenderTransformOrigin = new Point(0.5, 0.5); // grow from the centre of the circle by resetting the transform origin // if the width of the circle goes above 70 we want to pop the circle if (x.Width > 70) { // if the width if above 70 then add this circle to the remove this list removeThis.Add(x); health -= 15; // reduce health by 15 playerPopSound.Open(PoppedSound); // load the popped sound uri inside of the player pop sound media player playerPopSound.Play(); // now play the pop sound } } // end of for each loop // if health is above 1 if (health > 1) { // link the health bar rectangle to the health integer healthBar.Width = health; } else { // if health is below 1 then run the game over function GameOverFunction(); } // to remov ethe ellipse from the game we need another for each loop foreach (Ellipse i in removeThis) { // this for each loop will search for each ellipse that exist inside of the remove this list MyCanvas.Children.Remove(i); // when it finds one it will remove it from the canvas } // if the score if above 5 if (score > 5) { // speed up the spawn rate spawnRate = 25; } // if the score is above 20 if (score > 20) { // speed up the growth and and spawn rate spawnRate = 15; growthRate = 1.5; } } private void ClickOnCanvas(object sender, MouseButtonEventArgs e) { // this click event is linked inside of the canvas, we need to check if we have clicked on the ellipse // if the original source clicked is a ellipse if (e.OriginalSource is Ellipse) { // create a local ellipse and link it to the original source Ellipse circle = (Ellipse)e.OriginalSource; // now remove that ellipse we clicked on from the canvas MyCanvas.Children.Remove(circle); // add 1 to the score score++; // load the clicked sound uri to the play click sound media player and play the sound file playClickSound.Open(ClickedSound); playClickSound.Play(); } } private void GameOverFunction() { // this is the game over function gameTimer.Stop(); // first stop the game timer // show a message box to the end screen and wait for the player to click ok MessageBox.Show("Game Over" + Environment.NewLine + "You Scored: " + score + Environment.NewLine + "Click Ok to play again!", "Moo Says: "); // after the player clicked ok now we need to do a for each loop foreach (var y in MyCanvas.Children.OfType<Ellipse>()) { // find all of the existing ellipse that are on the screen and add them to the remove this list removeThis.Add(y); } // here we need another for each loop to remove everything from inside of the remove this list foreach (Ellipse i in removeThis) { MyCanvas.Children.Remove(i); } // reset all of the game values to default including clearling all of the ellipses from the remove this list growthRate = .6; spawnRate = 60; lastScore = score; score = 0; currentRate = 5; health = 350; removeThis.Clear(); gameTimer.Start(); } } }