Pages Menu
TwitterRssFacebook
Categories Menu

Posted by on 15th March, 2009

Silverlight 2.0 Moonlander game…

Silverlight 2.0 Moonlander game…

It has been a while since I have posted an experiment and mostly because I have been quite busy and maybe the little project I started was a bit more than just an afternoon (as is usually the case…). I really wanted to write a simple 1980’s type of game, such as asteroids, but someone had already done that in silverlight. So I figured I’d try something else, like the 1980’s Moonlander arcade game.

I know that writing a game is much more than just an experiment as it has much more to handle than just a single purpose. I have several times considered scrapping it altogether and starting on something else, but after many weeks on the back burner, I figured, I’d just get it over with and finish it. I have to say that the hardest part here was the collision detection and the gravity calculation.

I was shockingly surprised that Silverlight 2.0 does not have an obvious built-in HitTest or collision detection method for individual objects and the known workaround is quite clever, but somewhat less than elegant nor efficient, especially when checking many objects at the same time.

I haven’t really formed an opinion yet, since I may have overlooked features that are in the Silverlight 2.0 box and will post so, if I come across such feature. In any case, here is the game.

It is functional, and not completely finished, but I want to move on to other projects…

Note: You have to click on the game for the keys to function (it needs focus, the first time around) and then use left arrow, right arrow and the up arrow to maneuver your moon lander. Also, increase the difficulty by changing the variables at the bottom…

moonlander.xaml.cs

using System;  
using System.Windows.Controls;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Animation;  
using System.Windows.Shapes;  
namespace Silverlight_Moonlander_1  
{  

 public partial class Moonlander : UserControl  
 {  
  private Storyboard StoryBoardMoonLander = new Storyboard();  
  private bool isKeyUpPressed   = false;  
  private bool isKeyLeftPressed    = false;  
  private bool isKeyRightPressed   = false;  
  private double velocityVertical  = 0.00001;  
  private double velocityHorizontal   = 0.00001;  
  private double angleOfLander  = 0.00001;  
  private double fuel     = 100.0001;  
  private double consumptionMain   = 0.05;  
  private double consumptionSteering  = 0.02;  
  const double gravity    = 0.003;  
  const double velocityLimitTerminal  = 0.3;  
  const double velocityLimitThrust = 0.2;  
  const double velocityLimitLateral   = 0.8;  
  const double angleLimitOfLander  = 6;  
  private TimeSpan timeElapsed;  
  private DateTime now;  
  private Canvas gameData;  
  private Canvas gamePlay;  

  public Moonlander(Canvas _gameData, Canvas _gamePlay)  
  {  
   InitializeComponent();  
   this.now = DateTime.Now;  
   this.gameData = _gameData;  
   this.gamePlay = _gamePlay;  
   this.StoryBoardMoonLander.Duration  = TimeSpan.FromMilliseconds(1);  
   this.StoryBoardMoonLander.Completed += 
    new EventHandler(ThrusterStoryBoard_Completed);  
   this.StoryBoardMoonLander.Begin();  
   this.LanderRotateTransform.CenterX = this.Width/2;  
   this.LanderRotateTransform.CenterY = this.Height/2;  
  }  

  private void ThrusterStoryBoard_Completed(object sender, EventArgs e)  
  {  
   if (fuel > 0)  
   {  
    if (isKeyUpPressed)  
    {  
     velocityVertical -= gravity;  
     fuel -= consumptionMain;  
    }  
    if (isKeyRightPressed)  
    {  
     if (velocityHorizontal < velocityLimitLateral)  
     {  
      velocityHorizontal += gravity;  
     }  
     if (angleOfLander < angleLimitOfLander)  
     {  
      angleOfLander += 0.3;  
     }  
     fuel -= consumptionSteering;  
    }  
    if (isKeyLeftPressed)  
    {  
     if (velocityHorizontal > velocityLimitLateral * (-1))  
     {  
      velocityHorizontal -= gravity;  
     }  
     if (angleOfLander > angleLimitOfLander * (-1))  
     {  
      angleOfLander -= 0.3;  
     }  
     fuel -= consumptionSteering;  
    }  
   }  
   else  
   {  
    fuel = 0;  
   }  

   //normalize vertical speed  
   if (!isKeyUpPressed)  
   {  
    if (velocityVertical < velocityLimitTerminal)  
    {  
     velocityVertical += gravity;  
    }  
   }  

   //normalize horizontal speed and lander angle  
   if (!isKeyLeftPressed || 
       !isKeyRightPressed || 
       (isKeyLeftPressed && isKeyRightPressed))  
   {  
    if (velocityHorizontal > 0.001)  
    {  
     velocityHorizontal -= gravity / 5;  
    }  
    else if (velocityHorizontal < -0.001)  
    {  
     velocityHorizontal += gravity / 5;  
    }  
    else  
    {  
     velocityHorizontal = 0.0001;  
    }  
    if (angleOfLander > 0.001)  
    {  
     angleOfLander -= 0.1;  
    }  
    else if (angleOfLander < -0.001)  
    {  
     angleOfLander += 0.1;  
    }  
    else  
    {  
     angleOfLander = 0.0001;  
    }  
   }  
     
   //apply RenderTransform data  
   LanderTranslateTransform.X += velocityHorizontal;  
   LanderTranslateTransform.Y += velocityVertical;  
   LanderRotateTransform.Angle = angleOfLander;  

   //populate GameData panel  
   this.timeElapsed = DateTime.Now - now ;  

   if (fuel <= 0)  
   {  
    this.MainThruster.Visibility = System.Windows.Visibility.Collapsed;  
    this.StarboardThruster.Visibility = System.Windows.Visibility.Collapsed;  
    this.PortThruster.Visibility = System.Windows.Visibility.Collapsed;  
   }  
   this.StoryBoardMoonLander.Begin();  
  }  

  public void CanvasMoonlander_KeyDown(object sender, KeyEventArgs e)  
  {  
   if (e.Key == Key.Up)  
   {  
    this.isKeyUpPressed = true;  
    this.MainThruster.Visibility = System.Windows.Visibility.Visible;  
   }  
   if (e.Key == Key.Left)  
   {  
    this.isKeyLeftPressed = true;  
    this.StarboardThruster.Visibility = System.Windows.Visibility.Visible;  
   }  
   if (e.Key == Key.Right)  
   {  
    this.isKeyRightPressed = true;  
    this.PortThruster.Visibility = System.Windows.Visibility.Visible;  
   }  
  }  

  public void CanvasMoonlander_KeyUp(object sender, KeyEventArgs e)  
  {  
   if (e.Key == Key.Up)  
   {  
    this.isKeyUpPressed = false;  
    this.MainThruster.Visibility = System.Windows.Visibility.Collapsed;  
   }  
   if (e.Key == Key.Left)  
   {  
    this.isKeyLeftPressed = false;  
    this.StarboardThruster.Visibility = System.Windows.Visibility.Collapsed;  
   }  
   if (e.Key == Key.Right)  
   {  
    this.isKeyRightPressed = false;  
    this.PortThruster.Visibility = System.Windows.Visibility.Collapsed;  
   }  
  }  

  public double Angle   
  {  
   get { return angleOfLander; }  
  }  
  public double Fuel  
  {  
   set { fuel = value; }  
   get { return fuel; }  
  }  

  public double HorizontalVelocity  
  {  
   get { return velocityHorizontal; }  
  }  

  public double VerticalVelocity  
  {  
   get { return velocityVertical; }  
  }  

  public TimeSpan TimeElapsed  
  {  
   get { return timeElapsed; }  
  }  

  public void Stop()  
  {  
   this.StoryBoardMoonLander.Stop();  
  }  

  public ImageSource CrashLanderImage  
  {  
   set  
   {  
    this.MoonLanderShipImage.Source = value;  
   }  
  }  
 }  
} 
&#91;/code&#93;
&#91;/learn_more&#93;

&#91;learn_more caption="page.xaml.cs" state="open" class="codebox"&#93;
&#91;code lang="csharp"&#93;
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Net;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Animation;  
using System.Windows.Shapes;  
using System.Windows.Media.Imaging;  

namespace Silverlight_Moonlander_1  
{  

 public partial class Page : UserControl  
 {  
  private Moonscape MoonScape;  
  private Moonlander MoonLander;  
  private Storyboard StoryBoardCollision = new Storyboard();  
  private bool isContact;  
  public bool collisionResult;  
  private Point ptCheck = new Point();  
  private bool isGoAngle;  
  private bool isGoXspeed;  
  private bool isGoYspeed;  
  private double fuelTank;  
  private TimeSpan timeAllotted = new TimeSpan();  
  private TimeSpan timeLeft = new TimeSpan();  
  private int moonLanderCenterOffset = 10;  

  public Page()  
  {  
   InitializeComponent();  
   this.StoryBoardCollision.Duration = TimeSpan.FromMilliseconds(10);  
   this.StoryBoardCollision.Completed += 
    new EventHandler(StoryBoardCollision_Completed);  
   InitializeMoonsLanderGame(); 
  }  

  private void InitializeMoonsLanderGame()  
  {  
   this.MoonScape = new Moonscape(  
    Convert.ToInt32(
     ((ComboBoxItem)MoonscapePointsCombo.SelectedItem).Content.ToString()),   
       Convert.ToInt32(
        ((ComboBoxItem)LandingWidthCombo.SelectedItem).Content.ToString())); 
   
   //moonscape points, landingspot width  
   this.MoonLander = new Moonlander(this.CanvasGameData, this.CanvasGamePlay);  
   this.CanvasGamePlay.Children.Add(this.MoonScape);  
   this.CanvasGamePlay.Children.Add(this.MoonLander);  
   this.fuelTank = MoonLander.Fuel;  
   this.timeAllotted = new TimeSpan(0,0,30);  
   this.StoryBoardCollision.Begin();  
  }  

  private void UserControl_KeyDown(object sender, KeyEventArgs e)  
  {  
   if (!isContact && MoonLander.Fuel > 0)  
   {  
    MoonLander.CanvasMoonlander_KeyDown(sender, e);  
   }  
   else  
   {  
    MoonLander.CanvasMoonlander_KeyUp(sender, e);  
   }  
  }  

  private void UserControl_KeyUp(object sender, KeyEventArgs e)  
  {  
   if (!isContact)  
   {  
    MoonLander.CanvasMoonlander_KeyUp(sender, e);  
   }  
  }  

  private void StoryBoardCollision_Completed(object sender, EventArgs e)  
  {  
   this.isContact = 
    CheckForImpact(
     MoonLander.CanvasMoonlander, 
     MoonLander.MoonLanderShip, 
     MoonScape.CanvasMoonscape, 
     MoonScape.GroundScapePath);  
   if (this.isContact)  
   {  
    if (
      MoonLander.LanderTranslateTransform.X + 10 >= MoonScape.LandingSpotPoint.X &&  
      MoonLander.LanderTranslateTransform.X + MoonLander.Width - 10 <= 
      MoonScape.LandingSpotPoint.X + MoonScape.LandingSpotWidth)  
    {  
     if (isGoAngle && isGoXspeed && isGoYspeed)  
     {  
      this.endGame("landed");  
     }  
    }  
    else  
    {  
     this.endGame("crashed");  
    };  
   }  

   //fills three warning ellipses green if all ok  
   this.rectOkContainer.Fill = (isGoAngle && isGoXspeed && isGoYspeed) ? 
    new SolidColorBrush(Colors.White):new SolidColorBrush(Colors.Transparent);  

   //animate the fuel and timer gauges  
   if (MoonLander.Fuel > 0)  
   {  
    this.rectFuelGauge.Width = 
     this.rectFuelGaugeBox.Width / this.fuelTank * MoonLander.Fuel;  
   }  
   //else  
   //{  
   // this.endGame("out of Fuel");  
   //}  
   this.timeLeft = this.timeAllotted.Subtract(MoonLander.TimeElapsed);  
   if (timeLeft.TotalSeconds > 0)  
   {  
    this.rectTimeGauge.Width = 
     this.rectTimeGaugeBox.Width / this.timeAllotted.TotalSeconds * 
       timeLeft.TotalSeconds;  
   }  
   else  
   {  
    //if time runs out, cut off fuel supply....haha hahaaha hahaaa!  
    MoonLander.Fuel = 0;  
    this.timeLeft = new TimeSpan(0,0,0);  
   }  

   //populate GameData Panel  
   this.txtLevel.Text = "MoonLander 1.0";  
   this.txtFuel.Text = 
    "Fuel:  "  + 
     (MoonLander.Fuel.ToString().Length >= 
       4?MoonLander.Fuel.ToString().Remove(4):"0") + "%";  
   this.txtTime.Text = 
    "Time: "   + 
     this.timeLeft.Minutes + ":" + this.timeLeft.Seconds + 
       ":" + this.timeLeft.Milliseconds;  
   this.txtVerticalSpeed.Text = 
    "Xspeed: " + 
     (MoonLander.HorizontalVelocity * 10).ToString().Remove(4) + " m/s";  
   this.txtHorizontalSpeed.Text = 
    "Yspeed: " + 
     (MoonLander.VerticalVelocity * 10).ToString().Remove(4) + " m/s";  
   this.txtMoonLanderAngle.Text = 
    "Angle: "  + 
     MoonLander.Angle.ToString().Remove(4) + " deg";  
   if (MoonLander.Fuel > 0)  
   {  
    this.rectFuelGauge.Fill = 
      getFuelGaugeColor(this.fuelTank, MoonLander.Fuel);  
   }  
   if (timeLeft.TotalSeconds > 0)  
   {  
    this.rectTimeGauge.Fill = 
      getTimeGaugeColor(this.timeAllotted, this.timeLeft);  
   }   
     
   this.rectOkAngle.Fill = getAngleWarningColor(MoonLander.Angle);  
   this.rectOkHorizontalSpeed.Fill = 
    getHorizontalSpeedWarningColor(MoonLander.HorizontalVelocity);  
   this.rectOkVerticalSpeed.Fill  = 
    getVerticalSpeedWarningColor(MoonLander.VerticalVelocity);  
   this.StoryBoardCollision.Begin();  
  }  

  private bool CheckForImpact(FrameworkElement _canvasMoonLander, 
   FrameworkElement _moonLanderShip, 
   FrameworkElement _canvasMoonScape, 
   FrameworkElement _moonGroundScape)  
  {  
   double MoonLanderX = Convert.ToInt32(MoonLander.LanderTranslateTransform.X);  
   double MoonLanderY = Convert.ToInt32(MoonLander.LanderTranslateTransform.Y);  
   this.collisionResult = false;  
   //check for out of bounds first   
   if (MoonLanderX + moonLanderCenterOffset < 0 || 
    MoonLanderX - moonLanderCenterOffset + MoonLander.Width > 
     CanvasGamePlay.Width || MoonLanderY < 0)  
   {  
    this.collisionResult = true;  
   }  
   else  
   {  
    for (int x = Convert.ToInt32(MoonLanderX) + moonLanderCenterOffset; 
     x < Convert.ToInt32(MoonLanderX + MoonLander.Width - 
       moonLanderCenterOffset); x++)  
    {  
     this.ptCheck.X = x;  
     this.ptCheck.Y = MoonLanderY + 100;  
     List<UIElement> collisionList = 
      VisualTreeHelper.FindElementsInHostCoordinates(ptCheck, _canvasMoonScape)
       as List<UIElement>;  
     if (collisionList.Contains(_moonGroundScape))  
     {  
      this.collisionResult = true;  
      break;  
     }  
    }  
   }  
   return collisionResult;  
  }  

  private SolidColorBrush getFuelGaugeColor(double _fuelTank, 
    double _fuelRemaining)  
  {  
   int _fuelPercentage = 
    Convert.ToInt32(_fuelRemaining);
    //Convert.ToInt32((_fuelRemaining / _fuelTank) * 100);  
   if (_fuelPercentage < 75 && _fuelPercentage > 35)  
   {  
    return new SolidColorBrush(Colors.Yellow);  
   }  
   else if (_fuelPercentage < 35)  
   {  
    return new SolidColorBrush(Colors.Red);  
   }  
   else  
   {  
    return new SolidColorBrush(Colors.Green);  
   }  
  }  

  private SolidColorBrush getTimeGaugeColor(
    TimeSpan _timeAllotted, TimeSpan _timeRemaining)  
  {  
   double _timePercentage = 
    Convert.ToInt32((_timeAllotted.TotalSeconds/100) / 
      _timeRemaining.TotalSeconds) ;  
     
   if (_timePercentage < 75 && _timePercentage > 35)  
   {  
    return new SolidColorBrush(Colors.Yellow);  
   }  
   else if (_timePercentage < 35)  
   {  
    return new SolidColorBrush(Colors.Red);  
   }  
   else  
   {  
    return new SolidColorBrush(Colors.Green);  
   }  
  }  

  private SolidColorBrush getAngleWarningColor(double _angle)  
  {  
   this.isGoAngle = false;  
   if (_angle < -1 || _angle > 1)  
   {  
    if (_angle < -3 || _angle > 3)  
    {  
     return new SolidColorBrush(Colors.Red);  
    }  
    return new SolidColorBrush(Colors.Yellow);  
   }  
   else  
   {  
    this.isGoAngle = true;  
    return new SolidColorBrush(Colors.Green);  
   }  
  }  

  private SolidColorBrush getHorizontalSpeedWarningColor(double _hSpeed)  
  {  
   this.isGoXspeed = false;  
   if (_hSpeed < -0.1 || _hSpeed > 0.1)  
   {  
    if (_hSpeed < -0.2 || _hSpeed > 0.2)  
    {  
     return new SolidColorBrush(Colors.Red);  
    }  
    return new SolidColorBrush(Colors.Yellow);  
   }  
   else  
   {  
    this.isGoXspeed = true;  
    return new SolidColorBrush(Colors.Green);  
   }  
  }  

  private SolidColorBrush getVerticalSpeedWarningColor(double _vSpeed)  
  {  
   this.isGoYspeed = false;  
   if (_vSpeed > 0.2)  
   {  
    if (_vSpeed > 0.3)  
    {  
     return new SolidColorBrush(Colors.Red);  
    }  
    return new SolidColorBrush(Colors.Yellow);  
   }  
   else  
   {  
    this.isGoYspeed = true;  
    return new SolidColorBrush(Colors.Green);  
   }  
  }  

  private void endGame(string _reason)  
  {  
   this.MoonLander.Stop();  
   this.StoryBoardCollision.Stop();  
   this.MoonLander.MainThruster.Visibility = Visibility.Collapsed;  
   this.MoonLander.PortThruster.Visibility = Visibility.Collapsed;  
   this.MoonLander.StarboardThruster.Visibility = Visibility.Collapsed;  
   Uri uri = new Uri(_reason+".png", UriKind.Relative);  
   ImageSource imgSource = new BitmapImage(uri);  
   this.MoonLander.CrashLanderImage = imgSource;  
  }  

  private void PlayAgainButton_Click(object sender, RoutedEventArgs e)  
  {  
   this.MoonLander.Stop();  
   this.StoryBoardCollision.Stop();  
   InitializeMoonsLanderGame();  
  }  
 }  
} 
Enjoy!