9
Feb
0

Silverlight – What you need to create a Game

I will write many stuff needed to write a game in Silverlight. Maybe you can find better solution but It works for me :)
It like a tutorial to create Game in silverlight.

Some of this tips are only for big games some others for any games. Use what you need.

Licence

The code here is free and open source. But please refer to the last part “Credit” before using it.

Frame Rate

On internet, a frame rate about 20 – 25 fps is enough!


public MainPage(){

//DEBUG
 //Application.Current.Host.Settings.EnableFrameRateCounter = true;
 //Application.Current.Host.Settings.EnableRedrawRegions = true;

 //limit fram rate
 Application.Current.Host.Settings.MaxFrameRate = 25;

Controller Pattern – Singleton like but more Easy!

In each UserControl you can add a controller if there is only one instance of this UserControl in your program. Example : The MainPage. The MainPage is instanced only one time, and at any time in your program there is only one MainPage. We can imagine that in your MainPage you have a Canvas to print warning (not in a MessageBox but) in a pretty Canvas.


<Grid x:Name="LayoutRoot" Background="White">
    <Canvas x:Name="WarningCanvas" Margin="0" Background="#7FFFFFFF">
        <TextBlock x:Name="WarningText" Height="123" Width="370" Canvas.Left="131" Canvas.Top="104" Text="TextBlock" TextWrapping="Wrap" TextAlignment="Center" LineHeight="8" FontFamily="Arial Black" FontSize="24"/>
         <Canvas x:Name="WarningButtonOk" Height="42" Width="168" Canvas.Left="234" Canvas.Top="231">
             <Canvas.Background>
                 <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                 <GradientStop Color="#FF989898" Offset="0"/>
                 <GradientStop Color="#FF949494" Offset="1"/>
                 </LinearGradientBrush>
             </Canvas.Background>
             <TextBlock Height="42" Width="152" Canvas.Left="8" Text="Ok" TextWrapping="Wrap" FontSize="24" TextAlignment="Center"/>
         </Canvas>
     </Canvas>
 </Grid>

Then, in every part of your program you need to give the reference of your MainPage to print warning. This is not easy and it adds many useless lines in your program. So a solution is to create a controller.


public partial class MainPage : UserControl
{
     public static MainPage Controller;

     public MainPage()
     {
         Controller = this;

         InitializeComponent();

         WarningButtonOk.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(WarningButtonOk_MouseLeftButtonDown);
 }

 private void WarningButtonOk_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
 {
     WarningCanvas.Visibility = Visibility.Collapsed;
 }

 public void PrintWarning(string message){
     WarningText.Text = message;
     WarningCanvas.Visibility = Visibility.Visible;
 }
}

To print a Warning :


    MainPage.Controller.PrintWarning("I Cannot Connect to Host");

It is like a singleton, but without instancing (it is automatically done).

The keyboard – Only MainPage can receive keys event

Even if you create several UserControl for your menu, etc. You cannot add event to listen to the keyboard. In fact, only MainPage can listen “keys up/down”.

There is two solutions to work with that problem. The first one is to create a “interface” that each UserControl can inherit. Then, your MainPage manages the event as shown in the fallowing example :


// Create an interface

public interface KeyListener
 {
     void KeyPressed(Key k);

     void KeyReleased(Key k);
 }

// Then in the MainPage

public partial class MainPage : UserControl
 {
     KeyListener currentWorker;

     public MainPage()
     {
         InitializeComponent();

         KeyDown += new System.Windows.Input.KeyEventHandler(MainPage_KeyDown);
         KeyUp += new System.Windows.Input.KeyEventHandler(MainPage_KeyUp);
     }

     private void MainPage_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
     {
         if( currentWorker != null ) currentWorker.KeyPressed(e.Key);
     }

     private void MainPage_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
     {
         if( currentWorker != null ) currentWorker.KeyReleased(e.Key);
      }

 }

// In your UserControls

public partial class MyUserControl : UserControl, KeyListener

The second solution is to listen to event everywhere you need. Then in your callback you use the Key only if the UserControl is Visible.


// MainPage

public partial class MainPage : UserControl
 {
     public static MainPage Controller;

     public MainPage()
     {
         Controller = this;

         InitializeComponent();
     }
 }

// Others

public partial class IListenToKey : UserControl
 {
     public IListenToKey()
     {
         InitializeComponent();

         MainPage.Controller.KeyDown +=new System.Windows.Input.KeyEventHandler(Controller_KeyDown);
         MainPage.Controller.KeyUp +=new System.Windows.Input.KeyEventHandler(Controller_KeyUp);
     }

     private void Controller_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
     {
     // TODO: Add event handler implementation here.
     }

     private void Controller_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
     {
     // TODO: Add event handler implementation here.
     }
 }

Please note that If you loose the focus you will not be able to listen to key released (the good solution is to clean key pressed in case of focus lost).

Sound FX for user interaction (Button, etc.)

When the user clicks on a button or select some thing you may want to play a sound. My advice it to put this sound in your MainPage (each sound in a Canvas).


<Canvas >
     <MediaElement x:Name="top_wma"  Source="/Data/Sounds/top.wma"  Stretch="Fill" AutoPlay="False" RenderTransformOrigin="0.5,0.5"/>
     <MediaElement x:Name="tip_wma"  Source="/Data/Sounds/tip.wma" Stretch="Fill" AutoPlay="False" RenderTransformOrigin="0.5,0.5"/>
 </Canvas>

Then in your cs file you need to reset the sound and play it. Every components of your program can play this sound.


// MainPage

public void PlaySimpleClick(){
     tip_wma.Position =  TimeSpan.Zero;
     tip_wma.Play();
 }

public void PlayDoubleClick(){
     top_wma.Position =  TimeSpan.Zero;
     top_wma.Play();
 }

// To Play A sound

MainPage.Controller.PlaySimpleClick();

Play A Sound In Loop

I use this for the motor Sound in KartoOne.

Please Refere to : http://community.irritatedvowel.com/blogs/pete_browns_blog/default.aspx

You have to know that most of the system you will find only works with .wav (and Wow wav file are tooo BIg).

Interacting With Your Server

You may want to upload/download ranking. My advice is to use a WebClient and Php. On your server you user Php (it is easy, it works everywhere including Linux server) and you use GET query with WebClient.


WebClient client = new WebClient();

client.DownloadStringCompleted +=new System.Net.DownloadStringCompletedEventHandler(client_DownloadStringCompleted);

client.DownloadStringAsync(new Uri(string.Format("http://mywebsite.fr/getranks.php?id_joueur={0}", UserPreference.Controler.Login)));

Animating – Storyboard

Well, if you can, use Expression Blend you spend less time and make thing prettiest than only coding.

But if you need, write the code. Here is an example of a storyboard that make a usercontrol visible and hide another one.


protected  void AnimateInOut(UserControl inControl, UserControl outControl){
     Duration duration = new Duration(TimeSpan.FromSeconds(0.2));

     DoubleAnimation inOpac = new DoubleAnimation();
     DoubleAnimation outOpac = new DoubleAnimation();

     inOpac.Duration = duration;
     outOpac.Duration = duration;

     Storyboard sb = new Storyboard();
     sb.Duration = duration;

     sb.Children.Add(inOpac);
     sb.Children.Add(outOpac);

     Storyboard.SetTarget(inOpac, inControl);
     Storyboard.SetTarget(outOpac, outControl);

     Storyboard.SetTargetProperty(inOpac,new PropertyPath("(UIElement.Opacity)"));
     Storyboard.SetTargetProperty(outOpac,new PropertyPath("(UIElement.Opacity)"));

     inOpac.To = 0;
     outOpac.To = 1;

    //if you need a callback
     //sb.Completed +=new System.EventHandler(sb_Completed);

    sb.Begin();
 }

Managing character

A good blog about that is : http://www.bluerosegames.com/silverlight-games-101/

Bill Reiss wrote a good class to manage that.


public class Sprite : UserControl
 {
     Point position;
     public Vector Velocity;
     RotateTransform _rotate;
     ScaleTransform _scale;

 public double CollisionRadius{
     get{ return Math.Max(Height, Width);}
 }

 public Sprite()
 {
     AddTransforms();
     Rectangle rect = new Rectangle();
     rect.Width = 50;
     rect.Height = 50;
     rect.Fill = new SolidColorBrush(Colors.Red);
     SetContent(rect);
 }

 public Point Origin
 {
     get;
     set;
 }

 public Sprite(FrameworkElement content)
 {
     AddTransforms();
     SetContent(content);
 }

 void AddTransforms()
 {
     _rotate = new RotateTransform();
     _scale = new ScaleTransform();
     TransformGroup transforms = new TransformGroup();
     transforms.Children.Add(_rotate);
     transforms.Children.Add(_scale);
     this.RenderTransform = transforms;
 }

 public virtual void SetContent(FrameworkElement content)
 {
     Content = content;
     this.Width = content.Width;
     this.Height = content.Height;
 }

 public virtual Point Position
 {
     set
     {
         position = value;
         this.SetValue(Canvas.LeftProperty, value.X - Origin.X);
         this.SetValue(Canvas.TopProperty, value.Y - Origin.Y);
     }
     get
     {
         return position;
     }
 }

 public virtual Vector Scale
 {
     set
     {
         _scale.ScaleX = value.X;
         _scale.ScaleY = value.Y;
     }
     get
     {
         return new Vector(_scale.ScaleX, _scale.ScaleY);
     }
 }

 public double RotationAngleRadian{
     get{ return MathHelpers.DegreesToRadians(RotationAngle);}
     set{ RotationAngle = MathHelpers.RadiansToDegrees(value);}
 }

 public double RotationAngle
     {
     get
     {
         return _rotate.Angle;
     }
     set
     {
         _rotate.Angle = value;
     }
 }

 public virtual void Update(TimeSpan ElapsedTime)
 {
     if (Velocity != Vector.Zero)
     Position += Velocity * ElapsedTime.TotalSeconds;
 }

}

// A centred sprite

public class CenteredSprite : Sprite
 {
     public CenteredSprite()
     : base()
     {
     }

     public CenteredSprite(FrameworkElement content)
     : base(content)
     {
     }

     public override void SetContent(FrameworkElement content)
     {
         base.SetContent(content);
         Origin = new Point(Width / 2, Height / 2);
         RenderTransformOrigin = new Point(.5, .5);
     }
 }

// Then you can use it by modifying
// RotationAngleRadian
// Position

Test Collision between two centred sprite :

  • In case of circular shape, just check radius (I let you do that)
  • In case of rectangle shape use the following code (work for centred sprite)

public static bool Collides(Sprite s1, Sprite s2)
 {
     Vector v = new Vector(s1.Position.X - s2.Position.X, s1.Position.Y - s2.Position.Y);
     if (s1.CollisionRadius + s2.CollisionRadius > v.Length)
     {
         return
Contains(s2.Width,s2.Height,rotate(-s2.RotationAngleRadian,translat(s2.Position,transpose(s1.Position,rotate(s1.RotationAngleRadian,new Point(s1.Width/2,s1.Height/2))))))
 || Contains(s2.Width,s2.Height,rotate(-s2.RotationAngleRadian,translat(s2.Position,transpose(s1.Position,rotate(s1.RotationAngleRadian,new Point(-s1.Width/2,s1.Height/2))))))
 || Contains(s2.Width,s2.Height,rotate(-s2.RotationAngleRadian,translat(s2.Position,transpose(s1.Position,rotate(s1.RotationAngleRadian,new Point(-s1.Width/2,-s1.Height/2))))))
 || Contains(s2.Width,s2.Height,rotate(-s2.RotationAngleRadian,translat(s2.Position,transpose(s1.Position,rotate(s1.RotationAngleRadian,new Point(s1.Width/2,-s1.Height/2))))))

 || Contains(s1.Width,s1.Height,rotate(-s1.RotationAngleRadian,translat(s1.Position,transpose(s2.Position,rotate(s2.RotationAngleRadian,new Point(s2.Width/2,s2.Height/2))))))
 || Contains(s1.Width,s1.Height,rotate(-s1.RotationAngleRadian,translat(s1.Position,transpose(s2.Position,rotate(s2.RotationAngleRadian,new Point(-s2.Width/2,s2.Height/2))))))
 || Contains(s1.Width,s1.Height,rotate(-s1.RotationAngleRadian,translat(s1.Position,transpose(s2.Position,rotate(s2.RotationAngleRadian,new Point(-s2.Width/2,-s2.Height/2))))))
 || Contains(s1.Width,s1.Height,rotate(-s1.RotationAngleRadian,translat(s1.Position,transpose(s2.Position,rotate(s2.RotationAngleRadian,new Point(s2.Width/2,-s2.Height/2))))));
 }
     else
     {
         return false;
     }
 }

 public static Point rotate(double angle,Point pt){
     return new Point( Math.Cos(angle) * pt.X - Math.Sin(angle) *pt.Y,
         Math.Sin(angle) * pt.X + Math.Cos(angle) *pt.Y);
 }

 public static Point translat(Point nouv,Point pt){
     return new Point( pt.X - nouv.X, pt.Y - nouv.Y);
 }

 public static Point transpose(Point nouv,Point pt){
     return new Point( pt.X + nouv.X, pt.Y + nouv.Y);
 }

 public static bool Contains(double width, double height, Point pt){
     width = width / 2;
     height = height / 2;
     return pt.X < width && pt.X > - width && pt.Y < height && pt.Y > -height;
 }

Create a Background And Move it

In your user control add a Canvas on your LayoutRoot


<Grid x:Name="LayoutRoot" Width="640" Height="480">
     <Canvas x:Name="Background" Margin="0,0,-860,-1020" Width="1500" Height="1500" RenderTransformOrigin="0,0">
         <Canvas.RenderTransform>
             <TransformGroup>
                 <ScaleTransform/>
                 <SkewTransform/>
                 <RotateTransform/>
                 <TranslateTransform x:Name="MoveMap" X="0" Y="0"/>
             </TransformGroup>
         </Canvas.RenderTransform>
     </Canvas>
 </Grid>

Then you can move the Background

<pre>CenteredSprite car;</pre>
...
MoveMap.X = -getIn(car.Position.X - LayoutRoot.Width/2, 0, Background.Width  - LayoutRoot.Width);
MoveMap.Y = -getIn(car.Position.Y - LayoutRoot.Height / 2, 0, Background.Height  - LayoutRoot.Height);
...
public static double getIn( double in_test, double in_min, double in_max ) {
    return ( in_test < in_min ) ? in_min : ( in_test >= in_max ? in_max - 1 : in_test );
}

Load & Save User Preference – IsolatedStorage

You may use IsolatedStorage to store preferences :


// SAVE

IsolatedStorageSettings apps = IsolatedStorageSettings.ApplicationSettings;
 if (apps.Contains("Play"))
        apps["Play"] = Play;
 else
        apps.Add("Play", Play);

//apps.save();

// LOAD

if(apps.Contains("Play"))  Play = (bool)( apps["Play"]!=null?apps["Play"]: false);

To save the preference you can use a callback called when the user close the silverlight window :


private void Current_Exit(object sender, System.EventArgs e)
 {
     UserPreference.Controler.Save();
 }

It you want you can use this two function to do that easily :

public static object RetrieveObjectFromLocalStorage(string KEY)
 {
 if (IsolatedStorageSettings.ApplicationSettings.Contains(KEY))
     return IsolatedStorageSettings.ApplicationSettings[KEY];

 return null;
 }

 public static void UpdateLocalStorageObject(string KEY, object value)
 {
 if (IsolatedStorageSettings.ApplicationSettings.Contains(KEY))
     IsolatedStorageSettings.ApplicationSettings[KEY] = value;
 else
     IsolatedStorageSettings.ApplicationSettings.Add(KEY, value);
 IsolatedStorageSettings.ApplicationSettings.Save();
 }

Game Loop

You can use a timer (a post about that is accessible in this blog) but I prefer :


CompositionTarget.Rendering +=new System.EventHandler(CompositionTarget_Rendering);

...

private void CompositionTarget_Rendering(object sender, System.EventArgs e)

{

...

}

Pixel API

I use the pixel API to create a virtual collision MAP. Here is the class I made to do that :


<UserControl
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 mc:Ignorable="d"
 x:Class="Project.PixelisedCollision"
 >

     <Image x:Name="img"/>
</UserControl>

public partial class PixelisedCollision : UserControl
 {
     /** <summary> Sylverlight Image Bitmap </summary> */
     protected BitmapImage _imgBitmapSource = null;
     /** <summary> Sylverlight Image Bitmap with Pixels access </summary> */
     protected WriteableBitmap _imgBitemap = null;

     /** <summary> Get the bitmap details and access to pixels </summary> */
     public WriteableBitmap ImgBitemap{
         get
         {
             return this._imgBitemap;
         }
     }

     public bool IsLoaded{
         get{ return _imgBitemap != null; }
     }

     /** <summary> width of the Image </summary> */
     protected double _width = 0;
     public double WidthPixel
     {
         get
         {
             return this._width;
         }
     }
     /** <summary> height of the Image </summary> */
     protected double _height = 0;
     public double HeightPixel
     {
         get
         {
             return this._height;
         }
     }

     public PixelisedCollision( )
     {
         // Required to initialize variables
         InitializeComponent();

     }

     public void LoadImage(string image){
         _width = 0;
         _height = 0;

         _imgBitmapSource = new BitmapImage(new Uri(image, UriKind.RelativeOrAbsolute));

         img.Source = (ImageSource) _imgBitmapSource;

         img.ImageOpened += new EventHandler<RoutedEventArgs>(this.ImageLoaded);
     }

     public void ReleaseImage(){
         _width = 0;
         _height = 0;
         _imgBitmapSource = null;
         _imgBitemap = null;
         img.Source = null;
     }

     protected virtual void ImageLoaded(object in_sender, RoutedEventArgs in_event)
     {

         _imgBitemap = new WriteableBitmap(_imgBitmapSource);

         _width = ((BitmapImage)img.Source).PixelWidth;
         _height = ((BitmapImage)img.Source).PixelHeight;
     }
 }

 public class PixelDescriptor{
     public byte r;
     public byte g;
     public byte b;
     public byte a;

     public PixelDescriptor( int in_pixel ) {
         a = (byte)( in_pixel >> 24 );
         r = (byte)( in_pixel >> 16 );
         g = (byte)( in_pixel >> 8 );
         b = (byte)in_pixel;
     }

     public PixelDescriptor(){
         a = 0xff;
         r = 0x00;
         g = 0x00;
         b = 0x00;
     }

     public void pixel( int in_pixel ) {
         a = (byte)( in_pixel >> 24 );
         r = (byte)( in_pixel >> 16 );
         g = (byte)( in_pixel >> 8 );
         b = (byte)in_pixel;
     }
     public int pixel() {
         return ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
     }

     public string ToString(){
         return string.Format("{0:x2}{1:x2}{2:x2}{3:x2}",a,r,g,b);
     }

 }

Then to use it :


PixelisedCollision colismap = new PixelisedCollision();

colismap.LoadImage("MyImage.jpg");

// YOU MUST PUT THE MAP IN A CANVAS, if you donot the image will never be loaded

//I put the map at the back position to make it "not visible"
 MyCanvas.Children.Insert(0,colismap);

// you can release  colismap.ReleaseImage();

// then

if( colismap.IsLoaded ){

    int pixel = colismap.ImgBitemap.Pixels[(int)( ( colismap.WidthPixel *
        (int)(pos.Y*colismap.HeightPixel/workingRace.HeightCanvas()))
         + (pos.X*colismap.WidthPixel/workingRace.WidthCanvas()))];

        PixelDescriptor px = new PixelDescriptor(pixel);

        // you can use px.r etc...

}

Server/Client using Socket

In my multi player game KartoOne I use socket in Sivlerlight, if you want to see code about that go in my previous post :

A chat in silverlight

Credit

Please leave comment if you need some help or to support me.

Also, I ask any people that use this post to put my name in their credit thanks ^^

Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.

Comments are closed.

Celadon theme by the Themes Boutique