Click here to Skip to main content
15,879,028 members
Articles / Web Development / HTML

Recreating Frogger in Silverlight

Rate me:
Please Sign up or sign in to vote.
4.91/5 (81 votes)
11 May 2009CPOL10 min read 124.9K   1.1K   116   33
A tutorial on how to recreate a classic arcade game in Silverlight.

Image 1

Introduction

Classic games are so much fun to play, and they are even more fun to try to recreate. A few months ago, I decided to build an asteriods clone in Silverlight in order to familiarize myself with the technology. I had so much fun with the project that I decided to build a Frogger clone this time. This article will walk you through the steps that I used to recreate this arcade classic.

How the Game was Built

Since I am by no means a graphic designer, I needed to find some pre-made images for my game. I figured that I would be able to at least find a few screenshots of an older Frogger game using Google image search. Luckily, I was able to find the Xbox live arcade screenshots which gave me the base images I needed. Now that I had something to work with, I opened the screenshot in GIMP and started cropping out images. This process took a while because I needed to crop out each unique image and make the background transparent. I ended up with five different vehicles, three different size logs, three different turtles, a frog, the frog head, and finally a plain background image. Here are some examples of the images that were cropped out:

froghead.png froghead, tractor.png tractor, and frog.png frog (our main character).

Now that I have the images out of the way, it is time to start coding. For my project I used Visual Studio 2008 with the Silverlight Tools Add-on. This add-on allows you to create Silverlight projects directly from Visual Studio, which means you do not have to download any of the Expression products.

new_silverlight_project.png

Once you get the add-on installed, it is time to fire up Visual Studio and create a new project using the 3.5 Framework. Then, select either VB or C# as your language. Finally, choose to create a new Silverlight Application. Follow the prompts, and when the Add Silverlight Application dialog appears, choose the option to Automatically generate a test page to host Silverlight at build time. I like this option because when you run your project from Visual Studio, an Internet Explorer instance will appear so you can test out your application.

Now that your project has been created, you can navigate to the Solution Explorer and open up the design surface for Page.xaml. My first step was to take the background image that I created from the original and set it as the background image for the page. This was done by adding an Image element to the Canvas.

XML
<Image Source="media/background.png" Stretch="None" Canvas.Top="50"/>

frogger_board_mappings.png

I also wanted to add some space to the top and bottom of the page for displaying the score and some other information that is relevant to the game. Therefore, I changed the width and the height of the canvas to be large enough to contain the background image and also have enough margin for the footer and header. Now that the basic elements were on the page, I needed to think about how I was going to program the game. Just for some background information, the whole idea of the game is for the frog to safely make it to the “homes” shown at the very top of the screen. In order to get home, the frog has to make it through the five lane highway by avoiding contact with the cars. Then, the frog must hop on the logs to safely make it to home. So, with that bit of information, I figured I needed a way to define “lanes”. The lanes would basically create physical boundaries for the sprites to move in. I ended up using Rectangles (these are represented by the red outlines you see on the image to the left) to achieve this affect. After creating all the Rectangles, here is what my screen ended up looking like. Also, you will notice that there are some additional rectangles at the top which define the goals where you want the frog to end up once he crosses all the obstacles.

Sprites

Now, it is time to discuss the mechanics of the game engine. Let me start by giving you the definition of a Sprite (from Wikipedia):

In computer graphics, a sprite is a two-dimensional/three-dimensional image or animation that is integrated into a larger scene.

Sprites were originally invented as a method of quickly compositing several images together in two-dimensional video games using special hardware. As computer performance improved, this optimization became unnecessary, and the term evolved to refer specifically to the two dimensional images themselves that were integrated into a scene. That is, figures generated by either custom hardware or by software alone were all referred to as sprites. As three-dimensional graphics became more prevalent, the term was used to describe a technique whereby flat images are seamlessly integrated into complicated three-dimensional scenes.

More specifically, in my game, the logs, vehicles, and turtles are all considered sprites. In order to make my things simpler to program, I leveraged a common base class called SpriteBase. Using a common base class allows you to leverage code reuse and more importantly polymorphism. This is an important factor when you are trying to move around large amounts of objects and applying collision detection algorithms to each one. For example, if you want to move a sprite around on the screen, you need to do it by changing the X and Y coordinates of that object. Therefore, the SpriteBase class has an X and Y property:

C#
/// <summary>
/// The Y position of the sprite
/// </summary>
public virtual double Y
{
   get { return (double)this.GetValue(Canvas.TopProperty); }
   set { this.SetValue(Canvas.TopProperty, value); }
}

Moving on, we have now established that the sprites are the basic building blocks of the game. So, the first step is to get the sprites on to the screen. Since we want the game to be challenging, we do not want to hard code the locations of each sprite; therefore, we will have to use a randomizer. The randomizer will be used to make the cars to move at different speeds, determine if items move left to right or right to left. In addition, we want a variety of different cars, logs, and turtles on the screen.

Previously, we discussed how I used Rectangles to create logical boundaries for how the various sprites would move within the game. Basically, what I did was overlay Rectangles on the background image to create “lanes” for the road and water. Each Rectangle was defined in the XAML file and has a unique name. I reference each unique name in my code and add it to an array inside my code. Now, I realize that I could have just dynamically created these items in code, but I like being able to see the layout of the game in design mode. Below is the section of the XAML file which creates the lanes on the road. You will notice that the lanes all have a red outline around them. This is so I can see the lines at design time. When the application loads, I loop over the Rectangles and set the Stroke to a transparent color.

XML
<Rectangle x:Name="StreetLane5" Stroke="Red" 
  StrokeThickness="1" Canvas.Left="0" 
  Canvas.Top="440" Width="600" Height="50"/>

<Rectangle x:Name="StreetLane4" Stroke="Red" 
  StrokeThickness="1" Canvas.Left="0" 
  Canvas.Top="490" Width="600" Height="50"/>
    
<Rectangle x:Name="StreetLane3" Stroke="Red" 
  StrokeThickness="1" Canvas.Left="0" 
  Canvas.Top="540" Width="600" Height="50"/>
  
<Rectangle x:Name="StreetLane2" Stroke="Red" 
  StrokeThickness="1" Canvas.Left="0" Canvas.Top="590" 
  Width="600" Height="50"/>

<Rectangle x:Name="StreetLane1" Stroke="Red" 
  StrokeThickness="1" Canvas.Left="0" 
  Canvas.Top="640" Width="600" Height="50"/>

Now that I have an array with the Rectangles added to it, I can rely on the X and Y coordinates of those Rectangles to control the placement of each sprite. Now, I create a nested for loop that iterates over each Rectangle and adds a random number of vehicles to each “lane”. Each lane is assigned a random speed and direction for the vehicles to move in. Also, the cars in each lane are randomly assigned. Here is the code:

C#
private void CreateVehicles()
{
   //add some vehicles to each lane using some random logic
   for (int i = 1; i <= 5; i++)
   {
      Boolean rightToLeft = (_randomizer.Next(0, 11) % 2 == 0);
      Double startX = _randomizer.Next(0, 150);
      Double speed = (double)_randomizer.Next(5, 20) * .1;
      
      for (int j = 0; j < 4; j++)
      {
         VehicleType vType = (VehicleType)_randomizer.Next(0, 5);
         Vehicle vehicle = new Vehicle(vType, rightToLeft);
         
         if ((startX + vehicle.ActualWidth) >= this.Width) break;
         
         vehicle.Lane = i;
         vehicle.X = startX;
         vehicle.XSpeed = speed;
         vehicle.Y = GetNewYLocation(vehicle, i);
         this.LayoutRoot.Children.Add(vehicle);
         _sprites.Add(vehicle);
         
         int spacing = _randomizer.Next((int)(_frog.ActualWidth * 3), 
                                        (int)(_frog.ActualWidth * 4));
         startX += (vehicle.ActualWidth + spacing);
      }
   }
}

You will notice that the cars are randomly spaced apart. I used the width of the frog as a basic unit of measure for this. After all, the frog needs to be able to fit between the cars if it wants to make across the road. Also, note that each new sprite is assigned a lane. This is important because it helps me determine what items are in which lane. Since the frog moves forwards or backwards only one lane at a time, I can easily determine which objects the frog can potentially collide with. The logs and turtles are created in a very similar fashion. The newly created sprites are all added to a private list named _sprites. Having the sprites in a collection allows me to easily iterate over them for animation and collision detection purposes.

Animation and Collision Detection

In order to animate the sprites, I create a new System.Windows.Threading.DispatcherTimer class and implement the Tick event. In the Tick event, I call a method called MoveSprites(). This method iterates over the list of sprites that were added to the screen and updates their X and/or Y coordinates. In addition, it also detects if a sprite moves off of the screen. When a vehicle, log, or turtle moves off the screen, they are replaced by a new random sprite. This makes the game a little more interesting. Finally, if the frog happens to be hopping across the river, I detect which object the frog is sitting on and move the frog at the same speed as that object. Let’s take a look at the code:

C#
private void MoveSprites()
{
   for (int i = 0; i < _sprites.Count; i++)
   {
      Boolean remove = false;
      SpriteBase sprite = _sprites[i];
      double newX = sprite.X;
      
      //check the direction of the sprite and modify accordingly
      if (sprite.RightToLeft) {
         newX -= sprite.XSpeed;
         remove = (newX + sprite.ActualWidth < 0);
      }
      else {
         newX += sprite.XSpeed;
         remove = (newX > this.Width);
      }

  
      //when items go off the screen we replace them with a new random sprite
      if (remove == true){
         LayoutRoot.Children.Remove(sprite);
         SpriteBase replacement;

         if (sprite.GetType() == typeof(Vehicle))
            replacement = new Vehicle((VehicleType)_randomizer.Next(0, 5), 
                                       sprite.RightToLeft);
         else if (sprite.GetType() == typeof(Log))
            replacement = new Log((LogSize)_randomizer.Next(0, 3), sprite.RightToLeft);
         else
            replacement = new Turtle((TurtleType)_randomizer.Next(0, 3), 
                                      sprite.RightToLeft);
 
         //find the min or max X position of the sprite in the same lane
         var query = from x in _sprites
                     where
                        x.Lane == sprite.Lane
                     orderby
                        x.X ascending
                     select
                        x;
         SpriteBase lastSprite;
         //right to left means you want the max because 
         //when the item wraps around the screen 
         //it will appear in the higher range of X values
         if (sprite.RightToLeft){
            lastSprite = query.Last();
            if ((lastSprite.X + lastSprite.ActualWidth) >= this.Width)
               newX = (lastSprite.X + lastSprite.ActualWidth) + _randomizer.Next(50, 150);
            else
               newX = this.Width;
         }
         else{
            lastSprite = query.First();
            if (lastSprite.X <= 0)
               newX = (lastSprite.X) - _randomizer.Next(50, 150) - replacement.ActualWidth;
            else
               newX = 0 - replacement.ActualWidth;
         }
         replacement.XSpeed = sprite.XSpeed;
         replacement.Lane = sprite.Lane;
         replacement.Y = GetNewYLocation(replacement, sprite.Lane);

         _sprites[i] = replacement;
         sprite = replacement;
         LayoutRoot.Children.Add(replacement);
      }
      //when items start to move off the screen we clip part of the object so we do 
      //not see it hanging off the screen
      if ((newX + sprite.ActualWidth) >= this.Width){
         if (sprite.X < this.Width) {
            RectangleGeometry rg = new RectangleGeometry();
            rg.Rect = new Rect(0, 0, this.Width - sprite.X, sprite.ActualHeight);
            sprite.Clip = rg;
            sprite.Visibility = Visibility.Visible; //forces a repaint
         }
      }

      //if the frog is on a object in the river then move it at the same rate
      if (_frog.WaterObject == sprite){
         double frogX = _frog.X - (sprite.X - newX);
         Point p = new Point(frogX, _frog.Y);
         MoveFrog(p);                    
      }
      sprite.X = newX;
   }
}

Since objects really only move in a horizontal direction, I never recalculate the Y position of a sprite after it is placed on the screen. I am only recalculating the X position. Towards the middle of the function, you see some LINQ code. The LINQ code is used to figure out where to position the “replacement” sprite when something moves off the screen. Because the sprites have different lengths, I have to dynamically determine how far to the left or right an object needs to be placed when it is added to the screen. If the objects in a lane are moving right-to-left, we need to find the maximum X value; if left-to-right, we will look for the minimum X value. The diagram below will help clarify this logic:

offscreen_diagram.png

So, now that you understand how the objects move around the screen, lets talk about collision detection. Collision detection in this game is very simple. Since the frog can only be in one lane at a time, I only perform collision detection on a particular group of objects at a time. Once again, I use LINQ to simplify the task:

C#
private bool CheckForCollisions()
{
   //check only the current lane to see if the frog is being hit by any vehicles.
   //rely on the X coordinates only since the frog basically sits in the middle of the lane.
   var query = from x in _sprites
               where
                  x.Lane == _currentRow &&
                  ((_frog.X >= x.X) &&
                  (_frog.X <= (x.X + x.ActualWidth)) ||
                  ((_frog.X + _frog.ActualWidth) >= x.X) &&
                  ((_frog.X + _frog.ActualWidth) <= (x.X + x.ActualWidth)))
               select
                  x;
   return (query.Count() > 0);
}

My collision detection algorithm is primary concerned only with X coordinates. Because the frog sits in the middle of a lane, I can rely on the fact that objects in the same lane are already within the same Y range of values. As I mentioned before, when the frog is on the road, he will die if he is hit by a vehicle. Therefore, when the frog is in lanes 1 through 5 (the road) and the result of the CheckForCollisions() method is true, the frog is road kill. However, when the frog is in lanes 6 through 10 (the water) and a collision occurs, then the frog is OK because that means he is sitting on top of a log or turtle. For this reason, when the frog is moving through water, I have a method called GetObjectUnderFrog() which will return a reference to the sprite the frog is on top of. If the frog is not sitting on anything, the method will return null, which means the frog fell in the water. If a sprite is returned, then a property called WaterObject is set so I keep a reference to the object the frog is sitting on. This property is used in the MoveSprites() method (shown above) to help move the frog at the same rate as the object it is sitting on. This gives the appearance that the frog is taking a ride. Here is the code:

C#
void _mainLoop_Tick(object sender, EventArgs e)
{
   MoveSprites();
   
   //only check for collisions when the frog is on the road
   if (_currentRow > 0 && _currentRow < 6) {
     if (CheckForCollisions() == true){
        //frog is a pancake
        KillFrog();
     }
   }

  
   //you are in the water
   if (_currentRow > 6 && _currentRow < 12) {
      _frog.WaterObject = GetObjectUnderFrog();
      if (_frog.WaterObject == null) {
         KillFrog();
      }

      
      if (_frog.X > this.Width || _frog.X < 0)
         KillFrog();
   }
   else
   {
      _frog.WaterObject = null;
   }
}

This concludes my article on Recreating Frogger in Silverlight. If you enjoyed this tutorial, then please leave a comment. I really appreciate your feedback.

History

  • May 11, 2009 - Initial revision.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Concepts2Code
United States United States
Michael is the co-founder and master consultant for Concepts2Code, a software consulting company based in Buffalo, New York. He's been programming since the early 1990's. His vast programming experience includes VB, Delphi, C#, ASP, ASP.NET, Ruby on Rails, Coldfusion and PHP. Michael also is a Microsoft Certified Application Developer and a Certified Technology Specialist for SQL Server.

Visit his blog.

Comments and Discussions

 
Questionc# to VB Pin
Member 1135487012-Feb-15 11:25
Member 1135487012-Feb-15 11:25 
QuestionCan I used your codes for Ultrabook contest? Pin
BigWCat18-Oct-12 5:44
professionalBigWCat18-Oct-12 5:44 
AnswerRe: Can I used your codes for Ultrabook contest? Pin
Michael Ceranski18-Oct-12 8:44
Michael Ceranski18-Oct-12 8:44 
GeneralRe: Can I used your codes for Ultrabook contest? Pin
BigWCat18-Oct-12 10:01
professionalBigWCat18-Oct-12 10:01 
QuestionVery good! Pin
hairy_hats23-Aug-11 4:58
hairy_hats23-Aug-11 4:58 
QuestionCan't download source? Pin
NeCroFire12-Aug-11 0:03
NeCroFire12-Aug-11 0:03 
GeneralCompile Error Pin
Jooooe3-Feb-10 4:33
Jooooe3-Feb-10 4:33 
GeneralRe: Compile Error Pin
Michael Ceranski3-Feb-10 4:48
Michael Ceranski3-Feb-10 4:48 
GeneralRe: Compile Error Pin
Jooooe3-Feb-10 5:48
Jooooe3-Feb-10 5:48 
GeneralRe: Compile Error Pin
Michael Ceranski3-Feb-10 5:58
Michael Ceranski3-Feb-10 5:58 
GeneralRe: Compile Error Pin
Jooooe3-Feb-10 6:10
Jooooe3-Feb-10 6:10 
GeneralRe: Compile Error Pin
Michael Ceranski3-Feb-10 6:17
Michael Ceranski3-Feb-10 6:17 
GeneralRe: Compile Error Pin
Jooooe3-Feb-10 6:22
Jooooe3-Feb-10 6:22 
GeneralSIMPLE MODIFICATIONS NEEDED TO MAKE IT RUN Pin
troyf_satx31-Oct-09 12:14
troyf_satx31-Oct-09 12:14 
GeneralMy vote of 1 Pin
yoke5-Aug-09 23:13
yoke5-Aug-09 23:13 
Generalwon't compile w/Silverlight 3 after conversion - fix here Pin
Jenkins Technology LLC5-Aug-09 17:21
Jenkins Technology LLC5-Aug-09 17:21 
Generalcan't get sample code to compile Pin
yoke4-Aug-09 20:31
yoke4-Aug-09 20:31 
GeneralWow Silverfrogging Pin
Wimmo30-Jul-09 22:08
Wimmo30-Jul-09 22:08 
GeneralLove it Pin
Michael Brookie21-Jun-09 2:57
Michael Brookie21-Jun-09 2:57 
GeneralAwesome. Pin
enzoenzo114-Jun-09 11:47
enzoenzo114-Jun-09 11:47 
GeneralI like it Pin
Dr.Luiji24-May-09 21:47
professionalDr.Luiji24-May-09 21:47 
Generalhehe Pin
nørdic20-May-09 6:29
nørdic20-May-09 6:29 
General5 on title alone Pin
Jeff Circeo16-May-09 13:39
Jeff Circeo16-May-09 13:39 
GeneralGreat Job Pin
Oussema Zarrai16-May-09 2:13
Oussema Zarrai16-May-09 2:13 
GeneralWTF(What the Frogger, lol) Pin
Dewey15-May-09 13:36
Dewey15-May-09 13:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.