Game Development Tutorial

Everything a total beginner needs for game programing!

26. Pythagoras, Firefighers and Math

The last and, in my oppinion, the coolest part of this tutorial is making our doggy splash water from his fire-hose. There are a lot of ways to do this… My first idea was to make him splash a stream of water in a horizontal line… But I wasn't satisfied. Then, i figured out how to make him splash wather at an angle… But that didn't make me satisfied either. Then I decided to renew my high school math knowlege, and to force this dog to splash water to the exact point where the player would click the mouse… And, how does this look in theory? Here's a drawing…

 

Here's a little legend: Point B is the point where player clicked. Point A is the starting point of the splash. So, that makes the blue line c our splash. Angle alpha is the angle of the splash. It can be positive, or negative. So, how can we figure out the lenght of the splash (c), and it's angle (alpha)??? Just a little math. Our points are defined with x and y coordinates. So, if we know the horizontal coordinate for B, and the width of our game field, then we know the lenght of the red line b. And on the same priciple, we can find the lenght of the red line a. Calculating splash lenght c is just a matter of Pythagoras. Remember that jibberish your math teacher filled your hed with? I hate to admit it, but, it came useful. So, here's the equation:

(Pythagoras' theorem):

That's how we would write it on a peace of paper. And to make our C# understand it, we can write it like this:

int c = (int)Math.Sqrt(a * a + b * b); 

And if we have two sides of the triangle calculated, we can determine the angle with another math operation… Uff… Here's the equation:

Again, translated into C# il would look like this:

float alpha = (float)Math.Atan
((double)a / (double)b); 

To rotate an image, we'll use matrix translations… More math… Ah well… So, now when we finally have everything, we can write the code. Here it is. Enjoy it, it's your last peace of code in this tutorial. 

private void Splash(int MouseY, int MouseX)
{
  // Point A:
  Point pStart = new Point
  (picGameField.Width - 1, 195);

  // Calculating the lenght of the splash,
  // and other sides of the triangle:

  int a = (picGameField.Height - MouseX
  - (picGameField.Height - 215));

  int b = (picGameField.Width - MouseY);
  int c = (int)Math.Sqrt(a * a + b * b);
  
  // Calculating the angle of the splash:
  float alpha = (float)Math.Atan
  ((double)a / (double)b);

  
  // Convert from Radians to Degrees
  alpha = alpha * (float)(180.0 / Math.PI);
  
  int x = picGameField.Width - c;
  int y = 195;

  // Defining the position of our splash image:
  Rectangle rSplash = new Rectangle(x,y,c,50);
  Graphics gSplash = picGameField.CreateGraphics();

  // Matrix rotations:
  Matrix X = new Matrix();
  X.RotateAt(alpha, pStart, MatrixOrder.Append);
  gSplash.Transform = X;
  
  // Drawing our image to the screen:
  gSplash.DrawImage(picSplash.Image, rSplash);
  // Pause for a moment:
  System.Threading.Thread.Sleep(100);
  // Getting rid of our splash image:
  gSplash.Dispose();
}

Obviously, we need some mouse coordinates for this code to work... In the next (probably the last) post - I'll post the whole source code for the project. With a download link... :) I know I skipped a few parts of the code, so, you'll be able to 'grab' them from actual project files! :)

25. Bubble bath

Up to now, we displayed the blocks as squares filled with color. From now on, we'll display little icon images instead. If you remember, we used rectangles to define the size, and the position for each field. And that suits us just fine! So, here's a part of our code that we wrote in chapter 7:

(This is a part of the code from chapter 7, method DrawField())

switch (Field[v, s])
{
  case 0:
    // Draw empty field...
    break;
  case 1:
    // I've decided that color #1 is Blue. So, if
    // the field holds a value of 1 – We'll color it
    // blue. And we'll do the same for all
    // other colors.

    gField.FillRectangle    (Brushes.Blue, rtgField);
    break;
  case 2:
    // If the field contains value 2:
    // we'll fill it red.

    gField.FillRectangle    (Brushes.Red, rtgField);
    break;
  case 3:
    // If the field contains value 3:
    // we'll fill it green.

    gField.FillRectangle    (Brushes.Green, rtgField);
    break;
  case 4:
    // If the field contains value 4:
    // we'll fill it yellow.

    gField.FillRectangle    (Brushes.Yellow, rtgField);
    break;   case 5:
    // If the field contains value 5:
    // we'll fill it purple.

    gField.FillRectangle    (Brushes.Purple, rtgField);
    break;
  case 6:
    // If the field contains value 6:
    // we'll fill it black.

    gField.FillRectangle    (Brushes.Black, rtgField);
    break;
  case 7:
    // If the field contains value 7:
    // we'll fill it orange.

    gField.FillRectangle    (Brushes.Orange, rtgField);
    break;
  case 8:
    // If the field contains value 8:
    // we'll fill it gray.

    gField.FillRectangle    (Brushes.Gray, rtgField);
    break;
  case 9:
    // If the field contains value 9:
    // we'll fill it dark blue.

    gField.FillRectangle    (Brushes.DarkBlue, rtgField);
    break;
  case 10:
    // If the field contains value 10:
    // we'll fill it pink.

    gField.FillRectangle    (Brushes.Pink, rtgField);
    break;
  default:
    // If our program comes to this     // point of the code,
    // something's gone wrong! ;) XD

    break;
}

We'll replace that FillRectangle statement with a new one: DrawImage. But before we do that, we have to store our images somewhere. And, lucky us, there is a control made just for that. Go to your toolbox, and find the Components group. In there you'll find the ImageList control. Double-click it, and it will apear next to your timers. Nice. Set the properties like this:

ImageList

  • (name):ilTiles
  • ColorDepth:  Depth32Bit
  • TransparencyColor:  Web > Transparent
  • Images:  [Load 10 different bubble images]
  • ImageSize:  32; 32

Now, add add our bubble graphics files into the list. You can reorder them however you want. After all, we'll display the right picture by calling it's index number. Ready? Ok… Here is the revised code for the Switch in the 'DrawField()' method. See the difference? Nice… How about trying it out?

switch (Field[v, s])
{
  case 0:
    //Draw empty field...
    break;
  case 1:
    gField.DrawImage    (ilTiles.Images[0], rtgField);
    break;
  case 2:
    gField.DrawImage    (ilTiles.Images[1], rtgField);
    break;
  case 3:
    gField.DrawImage    (ilTiles.Images[2], rtgField);
    break;
  case 4:
    gField.DrawImage    (ilTiles.Images[3], rtgField);
    break;
  case 5:
    gField.DrawImage    (ilTiles.Images[4], rtgField);
    break;
  case 6:
    gField.DrawImage    (ilTiles.Images[5], rtgField);
    break;
  case 7:
    gField.DrawImage    (ilTiles.Images[6], rtgField);
    break;
  case 8:
    gField.DrawImage    (ilTiles.Images[7], rtgField);
    break;
  case 9:
    gField.DrawImage    (ilTiles.Images[8], rtgField);
    break;
  case 10:
    gField.DrawImage    (ilTiles.Images[9], rtgField);
    break;
  default:
    break;
}


It looks a lot like the screenshot from chapter 1, doesn't it? Great! So, in the next chapter, we'll add one more extremly cool feature, to complete our game. We'll make our doggy splash water. And that's it. The game will be complete, and this tutorial will be almost over. As the last topc, I'll cover packaging and distribution of your game. And that's it… ;) Finally…

24. Preparing the show-off

Right, before we add all that fancy graphics, we have to prepare our form first. We'll add some additional picture boxes to hold our images. Like the one to hold the firefighter dalmatian doggy, etc… So, add the controls, and modify their properties as shown in the following tables.

PictureBox

  • (name):picDoggy
  • BackColor:  Web > Transparent
  • Image:  [Load firefighter doggy image]
  • Location: 477; 186
  • Size: 131; 201

PictureBox

  • (name):picSplash
  • Anchor:Right
  • BackColor:  Web > Transparent
  • Image:  [Load water splash image]
  • Location: 45; 81
  • Size: 407; 50
  • SizeMode: StretchImage
  • Visible: False

And now, adjust your form's properties so it'll show the background image:

Form - frmMain

  • BackgroundImage: [Load background image]

If you did everything correctly, you should get your game looking quite similar to the one I've made. But we still have to replace our regular buttons with some fancy ones. If you're really going for the 'wow' effect, you can use pictureboxes combined with labels to replace the buttons. So, remove your buttons (select them and click 'Delete') add some more controls to your form: 

Note: Our new picture boxes that'll replace the buttons will be called same as the buttons we're replacing. That way, we won't have to change our old code.

PictureBox

  • (name):butGame
  • BackColor:  Web > Transparent
  • Image:  [Load cloud image]
  • Location: 12; 6
  • Size: 126; 53
  • SizeMode: StretchImage

PictureBox

  • (name):butHighScores
  • BackColor:  Web > Transparent
  • Image:  [Load cloud image]
  • Location: 144; 6
  • Size: 126; 53
  • SizeMode: StretchImage

PictureBox

  • (name):butNG
  • BackColor:  Web > Transparent
  • Image:  [Load cloud image]
  • Location: 276; 6
  • Size: 126; 53
  • SizeMode: StretchImage

Now, we'll add some labels to our clouds:

Label

  • (name): lblGameCloud
  • BackColor:  Web >White
  • Location: 23; 26
  • Size: 107; 20
  • Text: New Game
  • TextAlign: MiddleCenter

Label

  • (name): lblHighScore
  • BackColor:  Web >White
  • Location: 155; 26
  • Size: 107; 20
  • Text: High Scores
  • TextAlign: MiddleCenter

Label

  • (name): lblNG
  • BackColor:  Web >White
  • Location: 286; 26
  • Size: 107; 20
  • Text: New Game
  • TextAlign: MiddleCenter

It would be cool if the label inside the picture box would pop-up when the player moves the mouse over the picture box… Here's the code for all three buttons:

private void butGame_MouseEnter
(object sender, EventArgs e)
{
  // Set label font to bold:
  lblGameCloud.Font = new Font
  (lblGameCloud.Font, FontStyle.Bold);
}

private void butGame_MouseLeave
(object sender, EventArgs e)
{
  // Set label font to normal:
  lblGameCloud.Font = new Font
  (lblGameCloud.Font, FontStyle.Regular);
}

private void butHighScores_MouseEnter
(object sender, EventArgs e)
{
  // Set label font to bold:
  lblHighScore.Font = new Font
  (lblHighScore.Font, FontStyle.Bold);
}

private void butHighScores_MouseLeave
(object sender, EventArgs e)
{
  // Set label font to normal:
  lblHighScore.Font = new Font
  (lblHighScore.Font, FontStyle.Regular);
}

private void butNG_MouseEnter
(object sender, EventArgs e)
{
  // Set label font to bold:
  lblNG.Font = new Font
  (lblNG.Font, FontStyle.Bold);
}

private void butNG_MouseLeave(
object sender, EventArgs e)
{
  // Set label font to normal:
  lblNG.Font = new Font
  (lblNG.Font, FontStyle.Regular);
}

To make this code work, go to your form designer window, then go to your properties window, and click on the 'Events' icon (lightning) on the top of the box. Select your new controls, one by one and find events called MouseEnter and MouseLeave. Click on the little dropdown arrow and you'll see our new code method names in the list. I hope you can find and match the right ones… If not, here's a list that shows you how to match the righ stuff.

  • Control - Event - EventName
  • butGame - Click - butGame_Click
  • butGame - MouseEnter - butGame_MouseEnter
  • butGame - MouseLeave- butGame_MouseLeave
  • butHighScores - Click - butHighScores_Click
  • butHighScores - MouseEnter - butHighScores_MouseEnter
  • butHighScores - MouseLeave- butHighScores_MouseLeave
  • butNG - Click - butNG_Click
  • butNG - MouseEnter - butNG_MouseEnter
  • butNG- MouseLeave- butNG_MouseLeave
  • lblGameCloud - Click - butGame_Click
  • lblGameCloud- MouseEnter - butGame_MouseEnter
  • lblGameCloud- MouseLeave- butGame_MouseLeave
  • lblHighScore - Click - butHighScores_Click
  • lblHighScore - MouseEnter - butHighScores_MouseEnter
  • lblHighScore - MouseLeave- butHighScores_MouseLeave
  • lblNG - Click - butNG_Click
  • lblNG - MouseEnter - butNG_MouseEnter
  • lblNG- MouseLeave- butNG_MouseLeave

You probably noticed in the table that we added the 'Click' event to our old button code… That's a nice touch in VS.net, that spares us from retyping the code… (or c/p-ing ;) Nice… Hope you did everything right… And one other cool thing is, that we added the same code to our labels and pictureboxes... Before you try the stuff, let's finally add some graphics.

Ok, what next? Obviously, we have to replace those blocks with that nice, round bubbles… So, that's next on our agenda.

23. Graphics!

So, we're finally here! You had enough time to design your own graphics. Hehe... I know, I know... Your dog ate the memory stick on wich you saved the files... :P Just kidding! Feel free to download the graphics i used for the game. Here's the link:

Link! (*.rar)

Everything is in there!

Take a look at them, and we'll continue in a couple of days!

I'm really sorry that I can't write posts more frequentley. I hope things will improve soon.
Enjoy!

22. Hall of fame

There's nothing better than prooving to everybody that you're the best! And you certanly can't do that if your score isn't saved. We are now going to make a high score list. For it to work, we'll need a file, that we'll write the scores into. The scores will be consisted of the following: Score, Level, and Player Name. We'll need a new command button to show the list, and a new label to display the results in it. Add them to your game's form, and apply the next properties to them: 

Button

  • (name): butHighScores
  • Location: 144; 6
  • Size: 126; 53
  • Text: High Scores

Label

  • (name):lblHighScoreList
  • AutoSize:  True
  • BackColor:  Web > Transparent
  • Font:  Courier New; 14,25pt; style=Bold
  • ForeColor:  Web > OrangeRed
  • Location: 17; 81
  • Size: 0; 22
  • Text: [BLANK]
  • Visible: False
Ok, and now the code for the button:

// If the High Score list
// isn't visible - show it.
// If it's visible - hide it.

if (lblHighScoreList.Visible
== false)
{
  // Show High scores.
  // We'll read the high scores,
  // but won't try to process 
  // the current score:

  // First, hide the game field:

  picGameField.Hide();
  // Call our function that reads
  // the high scores:

  HighScore(false);

  // Reset the text in our label:
  lblHighScoreList.Text = "";

  // This is the cool part:
  // Strings are so fun! You can
  // do just about anything with
  // them in C#! Here, we format
  // the string with PAD functions.
  // Padding means adding characters
  // (or spaces) in front (or back)
  // of the string. Google for more
  // info!


  // Format the header line:
  lblHighScoreList.Text =
  "#".PadLeft(3) + " " +
  "Score".PadRight(8) + " " +
  "Level".PadLeft(5) + " " +
  "Name".PadRight(22) + " " +
  "\n\r"; //New line

  //Read all highscores from the list:
  for (int h = 0; h < ScoreList.Count; h++)
  {
    int p = h + 1;
    lblHighScoreList.Text +=
    p.ToString().PadLeft    (3) + " " +
    ScoreList[h].iScore    .ToString().PadLeft
    (8, '0') + " " +
    ScoreList[h].iLevel    .ToString().PadLeft
    (5) +
" " +
    ScoreList[h].sName.PadRight
    (22) + " " +
    "\n\r"; //New line
  }
  // Show the filled label:
  lblHighScoreList.Visible = true;
}
else
{
  // Hide the High Score list,
  // and show the game field:

  picGameField.Show();
  lblHighScoreList.Visible = false;
  picGameField.Refresh();
}

But, we're not done yet. We have to give our player some way to input his name, assuming he made it into the top ten... So, add the next controls, and change their properties as shown:

Panel

  • (name): IOBox
  • BackColor:  Web > Transparent
  • BorderStyle:  FixedSingle
  • Location: 301; 6
  • Size: 311; 53
  • Visible: False

Text Box  !!! PUT IT INSIDE IOBox Panel !!!

  • (name):txtHS
  • BackColor:  Web > Tan
  • BorderStyle:  FixedSingle
  • Font:  Microsoft Sans Serif; 12pt; style=Bold
  • ForeColor:  Web >Green
  • Location: 3; 22
  • MaxLenght: 20
  • Size:199; 26
  • Visible: False

Button  !!! PUT IT INSIDE IOBox Panel !!!

  • (name): butIO
  • Location: 208; 22
  • Size: 100; 26
  • Text: Yea!
  • Visible: False

Label  !!! PUT IT INSIDE IOBox Panel !!!

  • (name): lblHS
  • Autosize: True
  • Font:  Microsoft Sans Serif; 9pt; style=Bold
  • Location: 0; -1
  • Size: 312; 15
  • Text: You have a high score! Please enter your name:
  • TextAlign: TopCenter
  • Visible: False
Ok, and now the rest of the code - put it in private void HighScore(bool bCalculate)!

// Clear the score list:
ScoreList.Clear();


// Read High scores from file:
// We'll use a file named "wGames.hs".
// It's basicaly a normal text file.

FileStream fileR = 
new FileStream
("wGames.hs", FileMode.OpenOrCreate);
StreamReader reader =
new
StreamReader(fileR);

string sRead;
while ((sRead = reader.ReadLine()) != null)
{
  // Read the file line by line.
  // The scores in the file are
  // ordered from the highest to
  // the lowest. A line in the file
  // is in the next form:
  // SCORE;LEVEL;NAME

  // Declare an array of strings,
  // and fill it with the parts
  // of the high-score line,
  // wich we split with the C#
  // built in Split() function!

  string[] aRead =   sRead.Split(';');
  
  // Let's write our scores in a
  // struct.

  stScores ScoreStruct =
  new stScores();
  ScoreStruct.iLevel =   int.Parse(aRead[1]);
  ScoreStruct.iScore =   int.Parse(aRead[0]);
  ScoreStruct.sName =   aRead[2];

  // And add the struct to the
  // ScoreList:

  ScoreList.Add(
ScoreStruct);

  // Now, we can sort the Score
  // List:

  ScoreList.Sort(delegate
  (stScores s1, stScores s2)
  { return s2.iScore.CompareTo
  (s1.iScore); });
}

// Close the file:
reader.Dispose();
fileR.Dispose();

// This code checks if we need
// to process the current score,
// because we use this function
// for both, processing, and just
// showing the high score.

if (bCalculate == true)
{
  // If the list contains less than
  // ten scores, we add the current
  // score in the list automatically.

 
if (ScoreList.Count > 9)
  {
    // If the list contains ten or
    // more scores, we have to check
    // if the current score is high
    // enough.

    if (ScoreList[9].iScore
    < iScore)
    {
      // We display the HighScore
      // entry controls!

      IOBox.Visible = true;
      lblHS.Visible = true;
      txtHS.Visible = true;
      butIO.Visible = true;
    }
  }
  else
  {
    // We don't show the controls
    // for high score entry!

    IOBox.Visible = true;
    lblHS.Visible = true;
    txtHS.Visible = true;
    butIO.Visible = true;
  }
}

And now, add this code to the butIO's click method:

// Put the current score and
// level number in a struct:

stScores NewScore = new stScores();
NewScore.iScore = iScore;
NewScore.iLevel = iLevel;
// Also, put the player name
// in the same struct. If the
// Player didn't enter a name,
// just call him 'J. Doe'.
// (we don't know if J. stands
// for John or Jane :)

if (txtHS.Text.Trim().Length == 0)
{
  NewScore.sName = "J. Doe";
}
else
{
  // We use ; as a delimiter in
  // our high score file. So, if 
  // the user would enter it in
  // the text box - it would crash
  // our game. That's why we'll
  // replace all the ; with commas.

  txtHS.Text.Replace(';', ',');
  NewScore.sName =   txtHS.Text.Trim();
}

// Add the struct to the list:
ScoreList.Add(NewScore);

// Sort the list:
ScoreList.Sort(delegate
(stScores s1, stScores s2) 
{ return s2.iScore.CompareTo
(s1.iScore); });

// Write the list back into the 
// file. We'll overwrite the old
// file.

StreamWriter writer = new StreamWriter("wGames.hs", false);

// See how many scores we have
// to save to the file. If we
// have more than ten scores
// (full list + new score),
// then save just the top
// ten scores!

int lc = ScoreList.Count;
if (lc > 10) lc = 10;

// Write scores to the file,
// line by line!

for (int w = 0; w < lc; w++)
{
  writer.WriteLine
  (ScoreList[w].iScore.ToString()
  + ";" +
  ScoreList[w].iLevel.ToString()
  + ";" +
  ScoreList[w].sName);
}
writer.Dispose();

// And hide the dialog after
// saving the score:

lblHS.Visible = false;
txtHS.Visible = false;
butIO.Visible = false;
IOBox.Visible = false;

And to make this work, we'll have to add a couple of global variables and stuff...( Look at the top of your code, remember?)

//List that holds score structs
List<stScores> ScoreList
= new List<stScores>();

//Struct that holds scores
struct stScores
{
  public int iScore;
  public int iLevel;
  public string sName;
}

Cool? Ok, test your game. It might just work! ;o)

Right! The game's fully functional now (= I hope =). And now comes the 'fun' part - design! :) (as I later found out - not so fun, because I'm 100% graphically challenged)... :c)



21. Time for a coffe break

Ok, we programmed almost everything that I wanted to see in our gameplay. But this tutorial is far from over. You remember the game screenshot from the first chapter? Our game doesn't look anything like that... In the next few chapters we'll add some practical functions to our game, like 'Pause' button, 'High Score' list, and then we'll move on to the decoration!

Almos every game has a 'Pause' button. It's almost normal that you can't play any game without interuption. Maybe your wife sends you to the store to fetch some ingredients for lunch, or your two-year-old starts pulling the mouse out of your hand, or something else comes in your way... In every case, you don't want your game to go on... So, to satisfy our players, we'll add a pause button. Well, not actualy add - we'll just use our existing 'New Game' button, and programatically make it a multifunctional 'gadget'.
Ok, now, we'll update our code to give our game/pause button the funcionality that it needs. Basically, we'll just stop our timer when the user clicks on the button, and start it again when the user clicks 'Continue'. We won't add a new button for the 'Continue' command. We'll use our current 'Pause' button to do this...

Here's the code:
(overwrite your existing 'GameButton_Click' code!)

if (butGame == "New Game")
{
  //NEW GAME
  picGameField.Show();
  picGameField.Refresh();
  NewGame();
  butGame.Text = "Pause";
}
else if (butGame.Text == "Pause")
{
  //GAME PAUSED
  Game.Stop();
  picGameField.Visible = false;
  lblDisplay.Text = "Game Paused";
  lblDisplay.Visible = true;
  butGame.Text = "Continue";
}
else
{
  //CONTINUE GAME
  picGameField.Show();
  butGame.Text =
"Pause";
  picGameField.Visible = true;
  Game.Start();
  butNG.Visible = false;
}


You probably noticed that we hide the game field when the player clickes 'Pause'. That's because we don't want our players to cheat, and have extra time to plan their moves... Wicked, right? ;)

Now, add another button to the game, and apply it's properties:

Button

  • (name): butNG
  • Location: 276; 6
  • Size: 126; 53
  • Text: New Game

This one is going to offer our player the possibility to start a new game. Let's add some code to it. Double click on the new button, and put this code there:

//NEW GAME
picGameField.Show();
picGameField.Refresh();
NewGame();
butGame.Text = "Pause";
butNG.Visible = false;

 

Ok, let's go on - in the next chapter we'll add a High Scores list to our game. Just so our players can brag about their achievements, and to give them a little bit of motivation for playing the game (if they don't hold the #1 spot on the list!). :)

20. How about a raise?

To make things a little interesting, we'll give the player bonuses after he completes each level. I've thought of two bonuses: One that is awarded for empty columns, and one that is awarded on top, if there are no blocks left on the game field. To calculate the 'Empty Column Bonus' I used this formula: Level * # of Empty Columns * 100. For the second bonus, I used this formula: Level * 1000. So, If a player manages to clear all block in level 4, he would recieve (4*15*100) 6000 points from the Empty Column Bonus, and additional (4*1000) points from the Cleared Level Bonus. That's 10000 points! Nice! To show the player how many points he was awarded, we'll use our existing label (lblDisplay). And we'll pause the game for a few seconds between the levels. In the end, we'll create a method to switch us between levels. Ok, enough talking.

Let's start coding!This is the code that goes into the LevelScore() method:

// A variable for holding
// TEMPORARY empty column
// bonus:

int iColBonus = 0;
//Stop the game play...
Game.Stop();
// Calculate the bonus 
// for empty columns


// Go trough all columns:
for (int c = 0; c < iWidth; c++)
{
  // If the column is empty, 
  // increase empty column
  // bonus. The column is
  // empty whene there's no
  // block in the lowest field.
  // (Since we have gravity)

  if (Field[iHeight - 1, c] == 0)
  {
    // You can play with this
    // code to adjust how many
    // points are awarded for
    // every empty column:

    iColBonus += (100 * iLevel);
  }
}

// Add the empty column
// bonus to the total
// score, and show it:

iScore += iColBonus;
lblScore.Text = iScore.ToString();

// Calculate the bonus for
// cleared level :)
// (Mission impossible)

// Temporary variable:

int iLvlClrBonus = 0;
if (iColBonus ==
iWidth * (100 * iLevel))
{
  // Code that defines
  // and shows the bonus:

  iLvlClrBonus = iLevel * 1000;
  iScore += iLvlClrBonus;
  lblScore.Text = iScore.ToString();
}

// Inform the user that 
// the level is over.

lblDisplay.Text = "Level Complete!";
lblDisplay.Visible = true;
this.Refresh();

// Show bonuses, 
// if they exist:

if (iColBonus > 0)
{
 
// Pause 2 seconds:
  System.Threading.Thread.Sleep(2000);
  // Display first bonus:
  lblDisplay.Text = "Empty Column Bonus: "
  + iColBonus.ToString();
  this.Refresh();
}

if (iLvlClrBonus > 0)
{
  // Pause 2 seconds:
  System.Threading.Thread.Sleep(2000);
  // Display second bonus:
  lblDisplay.Text = "Clear Level Bonus: "
  + iLvlClrBonus.ToString();
  this.Refresh();
}

// Pause 2 seconds:
System.Threading.Thread.Sleep(2000);
// Reset the display
// label, and hide it.

lblDisplay.Visible = false;
lblDisplay.Text =
"GAME OVER!";

So, let's move to the next chapter and see how we can play with the parameters of our gameplay to make things more interesting.

Update: I finally managed to sort out the feed for this site. (Before, the feed for this site was copyed from my other blog... uh...)

19. How to get a promotion

Almost all games have levels. As the player advances, the game becomes more and more difficult. We'll do just that. We'll speed up our gameplay from level to level, and add a new color to the blocks every three or four levels... That should spice things up nicely... Uh! Oh! Another idea: let's increase the number of columns from level to level! So, that would mean that the player needs to survive 15 columns in the first level to move on, 16 in the second, and so on... Our game will not have a finall level. If the player is skilled enough, he could play trough an infinite number of levels. But hey, that's almost impossible. Reaching level 10 will be almost like winning in a lottery ;) Hehe...

So, when the player starts a new game we should reset the level count, score, speed, colors.. So we'll fill our 'NewGame' method.

// Reset the level and
// score value, and the
// number of colors in the game:

iLevel = 0;
lblLevel.Text = iLevel.ToString();
iScore = 0;
lblScore.Text = iScore.ToString();
iColor = 3;
iColors = 0;

// Reset the game speed
Game.Interval = 4000;

// We don't have the IOBox yet,
// so, the next line is commented
// out:
// IOBox.Visible = false;

lblDisplay.Visible = false;

NewLevel();

Now, add a new label to the form. Here are the settings:

Label

  • (name): lblLevel
  • BackgroundColor: Web > Transparent
  • Location: 555;81
  • Font: Microsoft Sans Serif; 9,75pt; style=Bold
  • Text:1

And here's the code for going from level to level.
(Put it in the NewLevel() method)

//Increase the level number.
iLevel++;

// Increase the number of 
// columns needed to complete
// the level. You can play
// with this code to make the
// game harder or easier.

iColumns = iWidth - 1 + iLevel;

// Reset the column count.
iColumn = 0;

// Increase the number of colors.
// But only to 10. (because we
// didn't prepare more colours in
// our DrawField method)

if (iColor < 10)
{
  // Do it every three levels...
  // The number '3' in the next 
  // line tells the game how
  // many levels to skip before
  // adding a new color:

  if (iColors == 3)
  {
    iColor++;
    iColors = 0;
  }
  else
  {
    iColors++;
  }
}

// Speed up the game.
// We speed it up for 50 
// miliseconds from level
// to level. This, probably,
// will stay unnoticed by
// the player. But you can
// change this to increase
// the difficulty.

if(iLevel > 1 &&
Game.Interval > 1500)
{
Game.Interval =
Game.Interval - 50;
}

// Display level text
lblLevel.Text =
iLevel.ToString();

// Create our new level's
// game field (empty):

CreateField();
// And the first column:
CreateNewColumn();

// Show it on the screen:
Redraw();

// Start the game:
Game.Start();

And the last thing is to change your 'New Game' button's code to this:

NewGame()

In the next chapter, we'll add some nice bonuses, that'll show between levels, and give the player to 'breathe' for a second or two... Ok, moving on!

18. Keeping score

Every game has a way to put a value to the player's effort. In racing games that's done with time (in most cases). In tycoon games, that would be done with money. In our game, we'll just use numbers. So how should we evaluate our players effort? First of all, we have to give him points for every block he destroys. We could make our game give him more points with every new level, and some extra points for the number of different block colors for that level. And some extra scoring math: We could make every block color more valuable... So, let's start with adding a new label to our form, so we could display the score to the player.

The score label properties:

Label

  • (name): lblScore
  • BackgroundColor: Web > Transparent
  • Location: 555;65
  • Font: Microsoft Sans Serif; 9,75pt; style=Bold
  • Text: 0

Then, add this code to your project:
(put it in the DoTheScoreMath() method.)

// We'll process the score only
// if there's multiple blocks
// in the 'Connected List'.

if (ConnectedList.Count > 1)
{
  // I've declared some
  // variables to hold
  // some values I intend
  // on using to calculate
  // the score.

  int a = ConnectedList.Count;
  int b = iLevel;
  int c = iColor;
  int d = ConnectedList[0].iFieldColor;

  // Scoring formula...
  // You can play with it
  // as you like :)

  int e = a * (b + c) * d;
  
  // Just don't forget to
  // add it to the total
  // score:

  iScore += e;
}
// Oh, and to be a little
// naughty, we'll substract
// a point for every click
// the user makes...
// BUAHAAHAAHHAAAAA!

iScore--;

// Display the total score:
lblScore.Text = iScore.ToString();

Now, add the call in the 'picGF_MouseClick' method, after the existing code:

DoTheScoreMath()

Done? Cool! Ok, what now? Levels would be cool... Right, then! Next chapter: Levels!

17. To help, or not to help

So, our game is almost playable… Well, it is, but there's a lot of stuff to add… Like, random blocks appearing on the game field. The game I 'borrowed' the idea from, had these blocks apearing at radnom time intervals and random places on the game field… And those fields were cool, because, sometimes, they helped to clear you some additional blocks, and sometimes, they filled that valuable empty columns… Ok, back to the drawing board. Litteraly. Here are two examples, first one that shows when the block is helpful, and another one wich shows when it's not that helpful.

Take a look at the picture. The player has only two empty columns left. Let's think about the next situaion:  

a) Let's imagine that in this moment, our game 'spawns' a red field at [4,5]. That would help the player, since he would be able to clear that red field, and the field one block to the right. (the one under [5,4]). The result would be - three empty columns.

b) If we would spawn a blue field at [4,5] - that wouldn't affect the player a lot - he would clear the field, and the blue one on the left. The result would be the same: Two empty columns.
 
c) If we wolud spawn a green field at that location, that would be the worst case scenario for the player. He would be stuck with only one empty column.
 
Looking at the Field[5,4], the best thing for the player would be, if we created a red block there. In that case, he could empty the #5 column. Spawning a green block wouldn't make a big difference. When the columns would be moved to the right, the green fields could be cleared. Creating a blue field there would be the worst thing for the player, but that wouldn't mean any disadvantages for him. The empty column count would stay the same.

So, the code for creating a 'random' block:(Put it in the DropBlock() method)

// Once again, we'll use the
// random function. We'll
// generate a random color,
// and a random position for
// our new block.

Random Rndm = new Random();

// Increasing the random range
// decreases the chance to drop
// the block.

int R = Rndm.Next(1, 3);

switch (R)
{
  case 1:
    // Drop the block!
    // Generate the position:

    int RX = Rndm.Next(iHeight - 1);
    int RY = Rndm.Next(iWidth - 2);

    // We dont allow the new block to
    // spawn in the last column - so
    // we don't cause possible gameover's ;)


    // We have to see if the field is empty...
    // So we don't overwrite some other field.
    // This also affects the possibility
    // of creating a random block...

    if (Field[RX, RY] == 0)
    {
      // Fill the block with a random
      // color;

      Field[RX, RY] =
      Rndm.Next(1, iColor + 1);
      // Make it fall down...
      GoGravity();
    }
    break;

    default:
      break;

 
In the next chapter, we'll make a score method, so we can let the player know how succesful he was...

16. Thing that makes apples fall

Uhm, I admit – I lost a whole day to figure this stupid stuff out. And in the end, i ended up with just a few lines of code. It's true that simplicity is the best way. And after writing, like, fifty lines of code that didn't work, i decided to delete it, and to start over. Basically, I took every single block in the game, and checked if it's floating. If it was, I moved it down… This would be the result:

This is the code that goes in the GoGravity() method:

// So, bassicaly, i just use 
// a couple of loops to go trough 
// all the fields.


// Looping through columns:
for (int s = 0; s < iWidth; s++)
{
  // Looping through rows:
  for (int l = 0; l < iHeight; l++)
  {
    for (int v = iHeight - 1; v > 0; v--)
    {
      // In every column, I start  
      // checking from the bottom.

      if (Field[v, s] == 0)
      {
        // If the field is empty, I  
        // drop the field above it 
        // down one block.

        Field[v, s] = Field[v - 1, s];
        Field[v - 1, s] = 0;
      }
    }
  }
}
// And to show the changes, 
// I call the Redraw() method:

Redraw();


And this is the code that goes into the Redraw() method:

//Drawing our field and grid:
DrawGrid()
DrawField()


15. Destroying the blocks

Like I said, this is going to be pretty straight-forward chapter. To destroy the blocks, all we need to do is set their value to 0. And, since we have a list of blocks to destroy, we'll just use a simple FOR loop. The only thing we have to check is that the list contains more than one field. If not, the user clicked on a field that is not connected to any fields of the same color. Nothing fancy here. Here's the code (put it in your DestroyBlocks() method!):

// First, we'll check if the
// List contains more than
// one block. If it does, we
// destroy all the fields in
// it by setting their value
// to 0.

if (ConnectedList.Count > 1)
{
  for (int d = 0;
  d < ConnectedList.Count;
  d++)
  {
    Field[ConnectedList[d].iXcoordinate,
    ConnectedList[d].iYcoordinate] = 0;
  }
}

Simple, right? So, if you remember the picture from the last chapter – here's how that field would look now, after destroying the selected blocks:

In the next chapter, we'll create some gravity. Gravity will force all those floating blocks to fall down. See the floating blocks? (Hint: Field[0,0], Field[0,1] and Field[1,0]). And after that, our game will be almost playable. When we finish gravity, we'll start working on different 'bonus' stuff, levels (like promissed), scoring (and high score lists), design and graphics, and a little 'Drop Block' feature wich will randomly create a single block somewhere in our game field… So, click on, and discover the secrets of Gravity!

14. Connections make the world spin

So, to explain what we need to do – I've drawn an image:

Let's say that the player clicks on the Field[1,1]. Our game should detect that the [1,1] field is connected to two more blue fields (Field[2,1] and Field[2,0]). In our game, the rule will be that fields are connected horizontal and vertical, but not diagonal. How do we do this? First of all, we'll create a list of connected fields. We'll put in the field user clicked. And then, we'll check fields above it, below, to the right and to the left to see if they hold the same (color) value. If they do, we'll put them too in our list. And then we'll go trough our list, field by field, searching for connections for every field in our list. If we find any connections, we'll check if they are already in the list. If no, we'll add them. Sounds complicated. Even I can't understand what I just wrote. :o) I'll try to write this as a step by step list:


  1. Player clicks Field[1,1]
  2. We add Filed[1,1] to our list
  3. We check the field above it (Field[0,1]) to see if it's the same color. Since it's not, we ignore it.
  4. We check the field below if (Field[2,1]) to see if it's the same color. It is, so we add it to the list.
  5. We check the field on the left (Field[1,0]) – it's not the same color, so we ignore it.
  6. We check the field on the right (Field[1,2]) – it's not the same color, so we, once again, ignore it.
  7. We move to the next item (Field[2,1]) in our list, and repeat the steps for it…
  8. We check the field above it (Field[1,1]), but we ignore it since it's already in our list…
  9. Eventualy we find the Field[2,0], add it to our list, and that's it…

So, how does this look like, code wise? First we'll have to declare our list, and then create a couple of methods for checking connections: A method that goes up, down, left and right, another one that cheks if the field is already in our list, and another one that makes sure that we check connections for every field from the list. There's one new thing here, thoug – structs. Think of them as some sort of containers that can hold a lot of different variables… So, first declare our struct. Put the declaration on top of the code, where you have your other public declarations.

// Struct for our  connected
// fields.
struct stField
{
public int iFieldColor;
public int iXcoordinate;
public int iYcoordinate;
}

And then declare the list to hold the connected fields:

// List of our connected fields.
List<stField> ConnectedList =
new
List<stField>();

Right. Now, check the code - the method & function declarations are a little different... So, be careful :)

private void CheckConnected
(int Xpos, int Ypos, bool bClick)
{
// This is the struct that will hold the
// Connected field position and value.

stField stField = new stField();
stField.iFieldColor = Field[Xpos, Ypos];
stField.iXcoordinate = Xpos;
stField.iYcoordinate = Ypos;

// bClick actualy means that the field
// was clicked on by player, so we add
// it to the list automatically.

if (bClick == true)
{
// So, we add the field to our list,
// we check if it's already in the list,
// and we add it if not. This isn't
// necesary here, since logic thinking
// tells us that the list is empty at
// this point. But, just to be safe...

if (CheckIfInList
(stField.iXcoordinate, 
stField.iYcoordinate) ==
false)
{
ConnectedList.Add(stField);
}
}

//Checks fields up and down from 
//the desired field.

if (stField.iXcoordinate > 0)
{
if (Field[stField.iXcoordinate - 1,
stField.iYcoordinate] == 
Field[stField.iXcoordinate, 
stField.iYcoordinate])
{
// Field above has the same value(color).
// We add it to the list.

stField.iXcoordinate = 
stField.iXcoordinate - 1;
if (CheckIfInList(stField.iXcoordinate,
stField.iYcoordinate) == false)
{
ConnectedList.Add(stField);
}
stField.iXcoordinate = 
stField.iXcoordinate + 1;
}
}

if (stField.iXcoordinate < iHeight - 1)
{
if (Field[stField.iXcoordinate + 1,
stField.iYcoordinate] == 
Field[stField.iXcoordinate, 
stField.iYcoordinate])
{
// Field below has the same value(color).
// We add it to the list.

stField.iXcoordinate = 
stField.iXcoordinate + 1;
if (CheckIfInList(stField.iXcoordinate,
stField.iYcoordinate) == false)
{
ConnectedList.Add(stField);
}
stField.iXcoordinate = 
stField.iXcoordinate - 1;
}
}

//Checks fields left and right
//from the desired field.

if (stField.iYcoordinate > 0)
{
if (Field[stField.iXcoordinate,
stField.iYcoordinate - 1] ==
Field[stField.iXcoordinate,
stField.iYcoordinate])
{
// Field to the left has the same
// value(color). We add it to the list.

stField.iYcoordinate = 
stField.iYcoordinate - 1;
if (CheckIfInList(stField.iXcoordinate, 
stField.iYcoordinate) == false)
{
ConnectedList.Add(stField);
}
stField.iYcoordinate = 
stField.iYcoordinate + 1;
}
}

if (stField.iYcoordinate < iWidth - 1)
{
if (Field[stField.iXcoordinate, 
stField.iYcoordinate + 1] ==
Field[stField.iXcoordinate,
stField.iYcoordinate])
{
// Field to the right has the same 
// value(color). We add it to the list.

stField.iYcoordinate = 
stField.iYcoordinate + 1;
if (CheckIfInList(stField.iXcoordinate, 
stField.iYcoordinate) == false)
{
ConnectedList.Add(stField);
}
stField.iYcoordinate = 
stField.iYcoordinate - 1;
}
}
}

private void CheckAll()
{
// This method goes trough the list,
// and re-checks connections for
// every field in it.

if (ConnectedList.Count > 1)
{
for (int l = 0; l < 
ConnectedList.Count; l++)
{
CheckConnected
(ConnectedList[l].iXcoordinate,
ConnectedList[l].iYcoordinate, 
false);
}
}
}

private bool CheckIfInList
(int PXpos, int PYpos)
{
// This function checks if the
// field is in the list or not.
// If the field is in the list,
// the function returns boolean
// 'TRUE'. If not, it returns
// boolean 'FALSE'.


for (int l = 0; 
l < ConnectedList.Count; l++)
{
if (ConnectedList[l].iXcoordinate 
== PXpos && ConnectedList[l].iYcoordinate
== PYpos)
{
// The field is in the list:
return true;
}
}
// The field is not in the list:
return false;
}


Right. Hope this works… So, now we know what the user clicked. And if there are more than one blocks connected, we should destroy them. That's going to be easy. After destroying the blocks, we'll have to make the gravity. So we don't wind up with floating blocks… Moving on…

13. Chasing the mouse

In our game, we'll use the mouse as an input device. Player will be able to click anywhere in the game field, and we'll have to figure out if he clicked a block, or an empty field. If the player clicks on the block, we'll have to check if it's connected with any other block of the same color, and we'll have to destroy the blocks if so. And to add a little feature, we'll take away one point for every mouse click. (Wich reminds me… We'll have to make a score counter, real soon.) So, how the hell can we find out where the user clicked? Lucky for us, the Picture Box in wich we draw our game field, has a MouseClick event. So, we'll use it to see if the user clicked anywhere on the game field. But how to find out on what block the user clicked? A little basic math will help us solve this problem. We already have defined that the block field is 32 pixels in width/height. And lucky for us, VS.net can give us the coordinates of the mouse pointer on the screen, and in relation to the clicked object. That means, that VS will give us the distance from the top and left borders of the picture box, measured in pixels. So, those are the ingreedients. Let's mix them all together and see how they work. First of all, go to your design window, select the Picture Box, and then, go to your Properties window. Just above the properties list ther is a little icon of lightning. Click on it, and you'll see the events list. Find the MouseClick event, and double-click on it's name. This should give you the code editor window with the method already in place. Here's the code for it:

// We find the X and the Y coordinate of 
// the mouse using the mouse event arguments
// (called 'e'). We'll store them into some 
// variables. Note, how we switched X and Y's 
// place – that's because we declared our game
// field in the opposite way – X for height 
// and Y for width. I know… Goofy…

int mx = e.Location.Y;
int my = e.Location.X;

// The next step is to divide the mouse 
// coordinates with the block/field size,
// so we'll get the coordinates of our field.

decimal x = mx / iFieldSize;
decimal y = my / iFieldSize;

/ And to round them up (since they came 
// back as numbers with decimals) we 
// convert them right back into intigers.
// Not to mention that we need them as 
// integers, to define our fields.

int ix = (int)x;
int iy = (int)y;

This code will give you exact coordinates of the block player clicked. Ok, what's next? Obviously, we'll have to see if the block player clicked, is actualy connected to other blocks of the same color. And we'll do just that – in the next chapter.

Downloads

Downloads
Finished Bubble Splash Game

Followers

Translate

Blog Directories

Programming Blogs - BlogCatalog Blog Directory
blog directory
Software Computers Business Directory - BTS Local
Top Blogs GoLedy.com
TopOfBlogs DigNow.net
Blog Directory blog search directory
Top Computers blogs Add to Technorati Favorites
Computers Blogs Blog Directory
blogsbycategory.com Blog Directory & Search engine
Blog Flux Directory Programming Blog Directory
Join My Community at MyBloglog! BlogRankers.com
Technology Blogs - Blog Rankings Blog Directory
C# Game programming tutorial Blogs lists and reviews
Bloglisting.net - The internets fastest growing blog directory Blog Directory - photarium
blogarama - the blog directory Blog Toplist
Blog directory Computers (Linux) - TOP.ORG
Blog Ratings si.blogs
Blogz game, development, programming, c#, visual studio, tutorial, source code
On our way to 1,000,000 rss feeds - millionrss.com
RSSMountain
TopBlogLists.com - Blog Search