March 2008 - Posts

Addicted to Silverlight? There's still hope...

Try some Silverlight Rehab, it looks like Microsoft is giving back to the community by providing help for the addicts. You can see some candid video here:

http://on10.net/blogs/tina/Silverlight-Rehab/

Posted by Bill Reiss | with no comments

The Microsoft MVP Summit 2008 starts 2 weeks from today

The Microsoft MVP Summit is a yearly conference where MVPs from around the world come to Seattle and Redmond and meet with other MVPs and Microsoft employees, including some of the people on the product teams that they interact with. I'm fortunate to be able to attend this year, and I look forward to meeting people that I've seen around the same online haunts that I have.

I have officially been switched over from a XNA/DirectX MVP to a Client App Dev MVP which is where the Silverlight and WPF MVPs are currently being put. I am still interested and somewhat active in the XNA community, but I have been spending more of my time on the Silverlight side of things so it made sense to make the switch official. So that means I'll be hanging out during the day with the other Silverlight focused MVPs and the Silverlight team, so I'm pretty excited about that.

So if you're going, try to seek me out, I'd love to meet you. I'll be wearing a Blue Rose Games T-shirt some of the time which should help you recognize me.

Posted by Bill Reiss | 1 comment(s)

Keyboard Input for Silverlight 2 Games

Source code for this tutorial: http://silverlightrocks.com/cs/files/folders/silverlight_2_tutorials_source_code/entry342.aspx

Now that we can rotate the ship using the game loop, let's add a way to control this rotation using the keyboard. You main forms of human input in a Silverlight application will typically be the keyboard or the mouse, I'll write a tutorial on mouse input in the future, but the mouse buttons generally behave like the keyboard buttons, and are actually a bit simpler.

Trapping Keyboard Events

Keyboard handling in Silverlight 2 consists of handling the KeyDown and KeyUp events on a FrameworkElement that has focus. The needs of a game are typically a bit different than regular text input since what we're really interested in is whether a key is currently pressed or not. To help with this, I have added another class to the BlueRoseGames.Helpers library. This class in the BlueRoseGames.Helpers.Keyboard.KeyHandler class and it keeps track of the KeyDown and KeyUp events so that all the caller has to do is call a IsKeyPressed method to see if a key is currently pressed.

Here is the source code for the KeyHandler class:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
 
namespace BlueRoseGames.Helpers.Keyboard
{
    public class KeyHandler
    {
        Dictionary<Key, bool> isPressed = new Dictionary<Key, bool>(); 
        FrameworkElement targetElement = null;
        public void ClearKeyPresses()
        {
            isPressed.Clear();
        }
 
        public KeyHandler(FrameworkElement target)
        {
            ClearKeyPresses();
            targetElement = target;
            target.KeyDown += new KeyEventHandler(target_KeyDown);
            target.KeyUp += new KeyEventHandler(target_KeyUp);
            target.LostFocus += new RoutedEventHandler(target_LostFocus);
        }
 
        void target_KeyDown(object sender, KeyEventArgs e)
        {
            if (!isPressed.ContainsKey(e.Key))
            {
                isPressed.Add(e.Key, true);
            }
        }
 
        void target_KeyUp(object sender, KeyEventArgs e)
        {
            if (isPressed.ContainsKey(e.Key))
            {
                isPressed.Remove(e.Key);
            }
        }
            
        void target_LostFocus(object sender, RoutedEventArgs e)
        {
            ClearKeyPresses();            
        }
 
        public bool IsKeyPressed(Key k)
        {
            return isPressed.ContainsKey(k);
        }
    }
}

Basically how it works is that the constructor accepts a FrameworkElement and the KeyUp and KeyDown events are wired up for that FrameworkElement. Then as keys are pressed and released, the current list of pressed keys is kept in the Dictionary. We're only really using the key of the dictionary and not the value. This gives us an indexed quick lookup of the keys that are currently pressed. Then in your game loop, you would call the IsKeyPressed() method for each key that you care about. The LostFocus event is important since if a key is being pressed when you lose focus, you won't see the KeyUp event and the key will get "stuck" in the down position. So on LostFocus, KeyHandler clears all of the currently pressed keys.

So now to use this in the SpaceRocks game.

The hardest part of this whole thing was getting focus to the Silverlight control. If the Silverlight control doesn't have focus, then you're not going to see the key presses. There have been some discussions on the forums lately about how to force focus to the control, and there doesn't seem to be a good way that works cross browser so we're stuck with making the user click on the control to make it get focus.

First we need some text to indicate that the user has to click. We'll put this after the Canvas in the Page.xaml but still inside the Grid element:

<TextBlock x:Name="clickToStart" Width="250" Height="80" Text="Click to Start!" Foreground="White" VerticalAlignment="Top" Margin="50" FontSize="35" TextAlignment="Center"/>

The game should now look like this:

clicktostart

We'll need a using statement in the Page.xaml.cs so that we can access the KeyHandler class:

using BlueRoseGames.Helpers.Keyboard;

Now we won't start our timer until after the control has focus, otherwise the player won't be able to control the ship when the game starts. We'll also want to stop the timer if the control loses focus, which will pause the game. When the control receives focus, we'll want to hide the TextBlock that tells the user to click, and on a loss of focus, we'll display it again.

One last thing we'll need to do is actually handle checking for which keys are pressed. We'll check for A or the Left Cursor Key to turn left, and D or the Right Cursor Key to turn right. This is the updated Page class in Page.xaml.cs:

public partial class Page : UserControl
{
    StoryboardGameLoop gameLoop;
    //   DispatcherTimerGameLoop gameLoop;
    KeyHandler keyHandler;
 
    double rotationSpeed = 150;
    public Page()
    {
        InitializeComponent();
        this.GotFocus += new RoutedEventHandler(Page_GotFocus);
        this.LostFocus += new RoutedEventHandler(Page_LostFocus);
        this.MouseLeftButtonDown += new MouseButtonEventHandler(Page_MouseLeftButtonDown);
        gameLoop = new StoryboardGameLoop(this);
        //  gameLoop = new DispatcherTimerGameLoop();
        gameLoop.Update += new GameLoop.UpdateHandler(gameLoop_Update);
        keyHandler = new KeyHandler(this);
    }
 
    void Page_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        clickToStart.Visibility = Visibility.Collapsed;
        gameLoop.Start();
    }
 
    void Page_LostFocus(object sender, RoutedEventArgs e)
    {
        clickToStart.Visibility = Visibility.Visible;
        gameLoop.Stop();            
    }
 
    void Page_GotFocus(object sender, RoutedEventArgs e)
    {
        clickToStart.Visibility = Visibility.Collapsed;
        gameLoop.Start();
    }
 
    void gameLoop_Update(TimeSpan elapsed)
    {
        // do your game loop processing here
        if (keyHandler.IsKeyPressed(Key.A) || keyHandler.IsKeyPressed(Key.Left))
        {
            ship.RotationAngle -= rotationSpeed * elapsed.TotalSeconds;
        }
        else if (keyHandler.IsKeyPressed(Key.D) || keyHandler.IsKeyPressed(Key.Right))
        {
            ship.RotationAngle += rotationSpeed * elapsed.TotalSeconds;
        }
    }
 
}

 

We're done for now, you should now be able to control the rotation of the ship using the A and D or cursor keys.

Posted by Bill Reiss | 2 comment(s)

Oops found a bug in the last tutorial

The DispatcherTimerGameLoop had a problem where it started the timer immediately when it was created instead of waiting for the start event. The tutorial and the source code have been updated with this fix. You can find the updated source code here:

http://silverlightrocks.com/cs/files/folders/silverlight_2_tutorials_source_code/entry337.aspx

Posted by Bill Reiss | with no comments

Another Silverlight 2 Game Programming Blog

I just came across a new Silverlight game programming blog at http://silverlight.net/blogs/msnow/archive/2008/03/25/introduction.aspx

The author is Mike Snow who is a Microsoft employee on the Web Tools team. I think it's great whenever there's more sources for information out there, and I'm sure we'll be covering some different stuff or in a different way, so I look forward to seeing what he comes up with. Welcome, Mike!

Posted by Bill Reiss | 1 comment(s)

The Start of a Game Helper Class Library

Note: This tutorial has been updated to fix a bug in the DispatcherTimerGameLoop class, where the timer would start upon creation instead of waiting for the Start() method.

Source code: http://silverlightrocks.com/cs/files/folders/silverlight_2_tutorials_source_code/entry337.aspx

So I did this with the old tutorials, and a little differently, this time I'm going to try to keep it a bit cleaner and to follow better namespacing and project layout, etc. One of the goals of this blog is to help you write games faster, and one way to do that is to give you some helper classes that can get you going and you can use in your own games. I have added a class library project to the SpaceRocks solution called BlueRoseGames.Helpers and when it makes sense, I'll add classes there so that you can use them in your own projects.

Anything that I add to that class library or anything else I blog about here you are welcome to use and modify in your own games and redistribute freely. It is all provided "as is", and if you're using it for any other purpose than in your own applications (for example, if you wanted to redistribute it as part of your own library, etc) please contact me for permission.

Ok so now that that's out of the way, the first thing I've added to the Helpers class library is the two game loop implementations (Storyboard and DispatcherTimer) that were covered in the Creating a Game Loop tutorials. They both inherit from an abstract GameLoop class and can be used easily in your own game. First the code...

GameLoop.cs:

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Documents;
   5: using System.Windows.Ink;
   6: using System.Windows.Input;
   7: using System.Windows.Media;
   8: using System.Windows.Media.Animation;
   9: using System.Windows.Shapes;
  10:  
  11: namespace BlueRoseGames.Helpers.Timers
  12: {
  13:     public abstract class GameLoop
  14:     {
  15:         protected DateTime lastTick;
  16:         public delegate void UpdateHandler(TimeSpan elapsed);
  17:         public event UpdateHandler Update;
  18:  
  19:         public void Tick()
  20:         {
  21:             DateTime now = DateTime.Now;
  22:             TimeSpan elapsed = now - lastTick;
  23:             lastTick = now;
  24:             if (Update != null) Update(elapsed);
  25:         }
  26:  
  27:         public virtual void Start()
  28:         {
  29:             lastTick = DateTime.Now;
  30:         }
  31:  
  32:         public virtual void Stop()
  33:         {
  34:         }
  35:     }
  36: }

So what we have are a Start, Stop, and Tick method, and an Update event that provides the elapsed time since the last update. By providing this abstract class, it is easy to swap out the two timers for each other without changing other code.

Now for the Storyboard technique. This class is called StoryboardGameLoop.

StoryboardGameLoop.cs:

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Documents;
   5: using System.Windows.Ink;
   6: using System.Windows.Input;
   7: using System.Windows.Media;
   8: using System.Windows.Media.Animation;
   9: using System.Windows.Shapes;
  10:  
  11: namespace BlueRoseGames.Helpers.Timers
  12: {
  13:     public class StoryboardGameLoop : GameLoop
  14:     {
  15:         bool stopped = true;
  16:         Storyboard gameLoop = new Storyboard();
  17:  
  18:         public StoryboardGameLoop(FrameworkElement parent) : this(parent, 0)
  19:         {
  20:             
  21:         }
  22:  
  23:         public StoryboardGameLoop(FrameworkElement parent, double milliseconds)
  24:         {
  25:             gameLoop.Duration = TimeSpan.FromMilliseconds(milliseconds);
  26:             gameLoop.SetValue(FrameworkElement.NameProperty, "gameloop");
  27:             parent.Resources.Add(gameLoop);
  28:             gameLoop.Completed += new EventHandler(gameLoop_Completed);
  29:         }
  30:  
  31:         public override void Start()
  32:         {
  33:             stopped = false;
  34:             gameLoop.Begin();
  35:             base.Start();
  36:         }
  37:  
  38:         public override void Stop()
  39:         {
  40:             stopped = true;
  41:             base.Stop();
  42:         }
  43:  
  44:         void gameLoop_Completed(object sender, EventArgs e)
  45:         {
  46:             if (stopped) return;
  47:             base.Tick();
  48:             (sender as Storyboard).Begin();
  49:         }
  50:     }
  51: }

The constructor takes a FrameworkElement, since Storyboards need to be added to the Resources collection of a FrameworkElement, and optionally a milliseconds between updates value. This will default to 0 if not specified.

The DispatcherTimerGameLoop is similar.

DispatcherTimerGameLoop.cs:

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Documents;
   5: using System.Windows.Ink;
   6: using System.Windows.Input;
   7: using System.Windows.Media;
   8: using System.Windows.Media.Animation;
   9: using System.Windows.Shapes;
  10: using System.Windows.Threading;
  11:  
  12: namespace BlueRoseGames.Helpers.Timers
  13: {
  14:     public class DispatcherTimerGameLoop : GameLoop
  15:     {
  16:         DispatcherTimer t = new DispatcherTimer();
  17:         public DispatcherTimerGameLoop() : this(0)
  18:         {
  19:         }
  20:  
  21:         public DispatcherTimerGameLoop(double milliseconds)
  22:         {
  23:             t.Interval = TimeSpan.FromMilliseconds(milliseconds);
  24:             t.Tick += new EventHandler(t_Tick);
  25:         }
  26:  
  27:         public override void Start()
  28:         {
  29:             t.Start();
  30:             base.Start();
  31:         }