Keyboard Input
Source code for this tutorial: http://silverlightrocks.com/Community/files/folders/slg101_tutorials/entry17.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.
Introducing SLG101Utilities
As I progress through these tutorials, there will be classes and code that are not specific to this particular game, and to make it easier to take advantage of them in your own games, I have added a SLG101Utilities project to the main game project. Eventually I'll make this available separately but for now since it will change a lot, it will be packaged with the SpaceRocks solution.
Keyboard Overview
Since Silverlight is a cross platform framework, keyboard handling is a bit different than what you may be used to. The KeyDown and KeyUp events pass a KeyboardEventArgs object which contains a Key and a PlatformKey field. The Key field contains the common keys to both Windows and the Mac and the values for each key are listed here:
http://msdn2.microsoft.com/en-us/library/bb232871.aspx
Since you typically would want your browser-based game to work on as many platforms as possible without custom code, you'll probably stick to using the Key field and ignore the PlatformKey field.
NOTE: The following will probably change before 1.1 goes to release.
There currently isn't a Key enum defined in Silverlight 1.1 Alpha, so I have defined my own in SLG101Utilities, using the same values specified in the URL above. I have defined this key enum in Key.cs.
Trapping Keyboard Events
Keyboard handling in Silverlight 1.1 Alpha consists of handling the KeyDown and KeyUp events on the root canvas. This is the canvas that is associated with the Page class in SpaceRocks (since we didn't rename the PAge class after the Orcas new project wizard generated it for us). Adding event listeners to other Canvas objects will cause errors.
To make things easier from a game development standpoint, it's typically easier to poll whether a key is pressed during the game loop than to handle the key events and set states appropriately. So in SLG101Utilities, I have added a KeyHandler class which makes this simple. Here is the source code for the KeyHandler class:
public class KeyHandler
{
bool[] isPressed = new bool[255];
Canvas targetCanvas = null;
public void ClearKeyPresses()
{
for (int i = 0; i < 255; i++)
{
isPressed[i] = false;
}
}
public void Attach(Canvas target)
{
ClearKeyPresses();
targetCanvas = target;
target.KeyDown += new KeyboardEventHandler(target_KeyDown);
target.KeyUp += new KeyboardEventHandler(target_KeyUp);
target.LostFocus += new EventHandler(target_LostFocus);
}
public void Detach(Canvas target)
{
target.KeyDown -= new KeyboardEventHandler(target_KeyDown);
target.KeyUp -= new KeyboardEventHandler(target_KeyUp);
target.LostFocus -= new EventHandler(target_LostFocus);
ClearKeyPresses();
}
void target_KeyDown(object sender, KeyboardEventArgs e)
{
isPressed[e.Key] = true;
}
void target_KeyUp(object sender, KeyboardEventArgs e)
{
isPressed[e.Key] = false;
}
void target_LostFocus(object sender, EventArgs e)
{
ClearKeyPresses();
}
public bool IsKeyPressed(Key k)
{
int v = (int)k;
if (v < 0 || v > 82) return false;
return isPressed[v];
}
}
Basically how it works is that you call the Attach() method of the KeyHandler object passing in your root canvas object. This Attach method wires up the events for your root canvas so that they are handled by the KeyHandler object. 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 namespace for the SLG101Utilities library is SilverlightGames101.Utilities. So first at the top of the Page.xaml.cs file, let's add a using statement for easier access to the KeyHandler class:
using SilverlightGames101.Utilities;
Now declare a KeyHandler field in the Page class
KeyHandler keyHandler = new KeyHandler();
In Page_Loaded, we need to Attach to the keyHandler, like this:
keyHandler.Attach(this);
and now all that's left is to check which keys are pressed in the game loop. For this game, we'll use the standard left, right, up keys that a lot of games use, where W=Thrust, A=Left, D=Right, and Space=Fire. For now, we'll just handle the Left and Right keys. So remove the test code from the last tutorial from the gameLoop_Completed method (the ship.RotationAngle++ statement) and replace it with the following:
if (keyHandler.IsKeyPressed(Key.A))
{
ship.RotationAngle -= rotationSpeed;
}
if (keyHandler.IsKeyPressed(Key.D))
{
ship.RotationAngle += rotationSpeed;
}
If the A key is pressed, we'll subtract rotationSpeed from the rotation angle, and if D is pressed, we'll add the rotationSpeed. Define rotationSpeed as a field in the Page class, as follows:
float rotationSpeed = 3;
You can adjust this value to change the speed of rotation, but it seems to be about right.
We're done for now, you should now be able to control the rotation of the ship using the A and D keys.
Drawing a Sprite
Source code for this completed tutorial: http://silverlightrocks.com/Community/files/folders/slg101_tutorials/entry10.aspx
In game programming, 2D elements that are drawn to the screen are commonly called sprites. Sprites are the building block upon which most 2D games are based, and so it makes sense that we cover it first. Unless you're doing a completely vector-based game, you'll have to deal with sprites at some point during your game development.
For this game, we need a Ship sprite. So add a new item to the SpaceRocks project of type "Silverlight User Control" and name it "Ship". Then replace the contents of Ship.xaml with the following:
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="25"
Height="40"
>
<Path Data="M0,38 L12,0,24,38,18,32,7,32z" Stroke="#FFFFFFFF" StrokeThickness="2"/>
</Canvas>
The Path element allows you to draw vector graphics using a concise format. It allows for lines and curves (bezier and arcs), and can optionally be filled. You can specify a brush for the stroke and the fill. More information on the format of the Data attribute here:
http://msdn2.microsoft.com/en-us/library/ms752293.aspx
In this example, the M command moves the current drawing position to (0,38) then the L command specifies a polyline to (12,0),(24,38),(18,32),(7,32), and then the "z" connects the polyline back to the original point of (0,38).
The Stroke attribute consists of 4 bytes of data, the first being the alpha channel for transparency and the other 3 bytes being red, green, and blue values respectively. In this case, we will be drawing fully opaque with a white solid brush. The StrokeThickness attribute specifies that the lines will have a thickness of 2 pixels.
So now to add the ship sprite to the game surface. There are two main ways you can put a child object in a canvas. You can either declare it in the XAML for the parent object, or you can create it in code and add it to the game surface. Creating the objects in code and dynamically adding them to the game surface is very useful when your game needs to add and remove sprites from the game surface on the fly, or if you don't know how many objects of a certain type you are going to have ahead of time. In a future tutorial we will create some objects in code dynamically. In this case, since this game will need one and only one ship, we can add it to the XAML.
Change the page.xaml file to look like this:
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SpaceRocks="clr-namespace:SpaceRocks;assembly=ClientBin/SpaceRocks.dll"
x:Name="parentCanvas"
Loaded="Page_Loaded"
x:Class="SpaceRocks.Page;assembly=ClientBin/SpaceRocks.dll"
Width="640"
Height="480"
Background="Black"
>
<SpaceRocks:Ship x:Name="ship" Width="30" Height="40" Canvas.Left="305" Canvas.Top="220"/>
</Canvas>
Now if you go ahead an run the program, you should get a good idea of what game we're creating here.
Notice the Canvas.Left and Canvas.Top attributes. This is how absolute positioning is done in XAML. These attributes will become important as we start animating the ship. We'll start getting into that in the next tutorial.
Welcome to Silverlight Games 101
On April 30, 2007, Microsoft announced Silverlight 1.1 which I feel will change web development forever. Silverlight 1.1 allows developers to create rich cross-browser and cross-platform web applications using managed code such as C#.
This blog will focus on topics related to developing Silverlight games in managed code. Since I am most comfortable with C#, all sample code will be in C# format, but it could be written in any managed language including VB.Net and even IronPython.
Even though we will focus on game development here, many of the concepts will be useful to non-gaming applications.
Please be aware that Silverlight 1.1 is currently an Alpha release meaning interfaces, class definitions, and pretty much anything else can change before the official release. I will do my best to make sure that the code samples here remain up to datw with the latest version of Silverlight.
Bill Reiss