web 2.0

Recreating Frogger in Silverlight - Part 3

In Part 2 of this series I introduced the SpriteBase class. In addition I talked about how I initially populated the sprites on the screen using some random calculations. Now its time to discuss 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 move 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:

   1:  private void MoveSprites()
   2:  {
   3:      for (int i = 0; i < _sprites.Count; i++)
   4:      {
   5:          Boolean remove = false;
   6:   
   7:          SpriteBase sprite = _sprites[i];
   8:          double newX = sprite.X;
   9:   
  10:               //check the direction of the sprite and modify accordingly
  11:          if (sprite.RightToLeft)
  12:          {
  13:              newX -= sprite.XSpeed;
  14:              remove = (newX + sprite.ActualWidth < 0);
  15:          }
  16:          else
  17:          {
  18:              newX += sprite.XSpeed;
  19:              remove = (newX > this.Width);
  20:          }
  21:   
  22:          //when items go off the screen we replace them with a new random sprite
  23:          if (remove == true)
  24:          {
  25:              LayoutRoot.Children.Remove(sprite);
  26:              SpriteBase replacement;
  27:   
  28:              if (sprite.GetType() == typeof(Vehicle))
  29:                  replacement = new Vehicle((VehicleType)_randomizer.Next(0, 5), sprite.RightToLeft);
  30:              else if (sprite.GetType() == typeof(Log))
  31:                  replacement = new Log((LogSize)_randomizer.Next(0, 3), sprite.RightToLeft);
  32:              else
  33:                  replacement = new Turtle((TurtleType)_randomizer.Next(0, 3), sprite.RightToLeft);
  34:   
  35:              //find the min or max X position of the sprite in the same lane
  36:              var query = from x in _sprites
  37:                          where
  38:                             x.Lane == sprite.Lane
  39:                          orderby
  40:                             x.X ascending
  41:                          select
  42:                             x;
  43:   
  44:              SpriteBase lastSprite;
  45:              //right to left means you want the max because when the item wraps around the screen 
  46:              //it will appear in the higher range of X values
  47:              if (sprite.RightToLeft)
  48:              {
  49:                  lastSprite = query.Last();
  50:                  if ((lastSprite.X + lastSprite.ActualWidth) >= this.Width)
  51:                      newX = (lastSprite.X + lastSprite.ActualWidth) + _randomizer.Next(50, 150);
  52:                  else
  53:                      newX = this.Width;
  54:              }
  55:              else
  56:              {
  57:                  lastSprite = query.First();
  58:                  if (lastSprite.X <= 0)
  59:                      newX = (lastSprite.X) - _randomizer.Next(50, 150) - replacement.ActualWidth;
  60:                  else
  61:                      newX = 0 - replacement.ActualWidth;
  62:              }
  63:              replacement.XSpeed = sprite.XSpeed;
  64:              replacement.Lane = sprite.Lane;
  65:              replacement.Y = GetNewYLocation(replacement, sprite.Lane);
  66:   
  67:              _sprites[i] = replacement;
  68:              sprite = replacement;
  69:   
  70:              LayoutRoot.Children.Add(replacement);
  71:          }
  72:   
  73:               //when items start to move off the screen we clip part of the object so we do 
  74:               //not see it hanging off the screen
  75:          if ((newX + sprite.ActualWidth) >= this.Width)
  76:          {
  77:              if (sprite.X < this.Width)
  78:              {
  79:                  RectangleGeometry rg = new RectangleGeometry();
  80:                  rg.Rect = new Rect(0, 0, this.Width - sprite.X, sprite.ActualHeight);
  81:                  sprite.Clip = rg;
  82:                  sprite.Visibility = Visibility.Visible; //forces a repaint
  83:              }
  84:          }
  85:   
  86:               //if the frog is on a object in the river then move it at the same rate
  87:          if (_frog.WaterObject == sprite)
  88:          {
  89:              double frogX = _frog.X - (sprite.X - newX);
  90:              Point p = new Point(frogX, _frog.Y);
  91:              MoveFrog(p);                    
  92:          }
  93:   
  94:          sprite.X = newX;
  95:      }
  96:  }

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:

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:

   1:  private bool CheckForCollisions()
   2:          {
   3:              //check only the current lane to see if the frog is being hit by any vehicles.
   4:              //rely on the X coordinates only since the frog basically sits in the middle of the lane.
   5:              var query = from x in _sprites
   6:                          where
   7:                              x.Lane == _currentRow &&
   8:                              ((_frog.X >= x.X) &&
   9:                              (_frog.X <= (x.X + x.ActualWidth)) ||
  10:                              ((_frog.X + _frog.ActualWidth) >= x.X) &&
  11:                              ((_frog.X + _frog.ActualWidth) <= (x.X + x.ActualWidth)))
  12:                          select
  13:                              x;
  14:              return (query.Count() > 0);
  15:          }

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 in Part 2 of this series, 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 then the frog is road kill. However, when the frog is in Lanes 6 through 10 (the water) and the 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:

   1:  void _mainLoop_Tick(object sender, EventArgs e)
   2:  {
   3:      MoveSprites();
   4:   
   5:      //only check for collisions when the frog is on the road
   6:      if (_currentRow > 0 && _currentRow < 6)
   7:      {
   8:          if (CheckForCollisions() == true)
   9:          {
  10:              //frog is a pancake
  11:              KillFrog();
  12:          }
  13:      }
  14:   
  15:      //you are in the water
  16:      if (_currentRow > 6 && _currentRow < 12)
  17:      {
  18:          _frog.WaterObject = GetObjectUnderFrog();
  19:          if (_frog.WaterObject == null)
  20:          {
  21:              KillFrog();
  22:          }
  23:   
  24:          if (_frog.X > this.Width ||
  25:               _frog.X < 0)
  26:              KillFrog();
  27:      }
  28:      else
  29:      {
  30:          _frog.WaterObject = null;
  31:      }
  32:  }

This concludes my series on Recreating Frogger in Silverlight. The source code is available for download:

Download The Source Code - 2.94 MB

Play the Game (Opens in new window)

Comments

Code Capers , on 1/18/2010 3:22:06 PM Said:

trackback

Recreating Frogger in Silverlight - Part 2

Recreating Frogger in Silverlight - Part 2

Comments are closed