TECHNOLOGIES USED: Silverlight 2 Beta 1, Expression Blend 2.5 Beta, Expression Design 2 Beta, Visual Studio 2008
I decided to enter the RIA Run contest, mostly to learn Silverlight 2 while building casual games. Since I was already learning how to make a "shoot'em up" type of game, I kept going with it - but added more interesting characters. Upon reading the contest description and rules, I realized that this competition was perhaps more about showcasing the new features in Silverlight 2 than about the gameplay. It makes sense - this is a Microsoft sponsored event and they always want to promote their new technologies. For this version of my game, I made a strategic decision to focus more on the technology than on the gameplay.
The game I made is called Solar Shooter: A Gas Guzzler's Nightmare. Part of my score in the competition will be based on how many people play the game on the devx.com website. As soon as I get my link, I will post it right here. In the meanwhile, you can play the game on my new SL42.com games website.
UPDATE: Solar Shooter was selected as a finalist! From May 16 to June 5, 2008, support my entry into the contest by playing Solar Shooter at devx.com.
Here's a brief description of the game:
Frustrated by the high gas prices at the pump? Blow off some steam playing “Solar Shooter: A Gas Guzzler’s Nightmare.” In this shoot’em up arcade game, it’s you vs. the gas guzzlers. Use your solar powered, electricity shooting hover car to face off against the single occupant commuter, the angry guy in the SUV, and even the dreaded gas station pump. Gotta zap them before they soak you.
The entry form asked me to briefly describe "How does this game use Silverlight 2.0 Beta features? What quirks, tips, or gotchas did you uncover?" They were looking for a short description... but I had way more to say about Silverlight 2 features than could fit into 60 words. Below is my complete list.
Silverlight 2 Beta 1 Features Used in Solar Shooter
Managed code. The application was built entirely within the Silverlight framework. No HTML or Javascript needed. BCL and CLR all the way!!! Three cheers for events, delegates, strongly typed objects, object inheritance and composition.
Layout Management. The game makes extensive use of the built-in layout management controls (i.e., Grid, StackPanel, Canvas) in addition to the built-in layout management capabilities.
- RotateTransforms are used for rotating the game characters (also known as "sprites")
- TranslateTransforms are used to add a shadow to text
- A ScaleTransform is used to resize the game when it's parent browser window resizes
- A Border was used to create rounded corners on for the in-game score
- ZIndex, Opacity and Visibility are used to manage the dynamic display
- Margin is typically used to position objects within a grid cell. To remember what the 4 numbers mean when setting margins in XAML (i.e. Margin="4,12,30,2") - think of the acronym "LTRB," which sounds like "Letter B," and is short for Left,Top,Right,Bottom.
Data Binding. The game uses databinding whenever possible to simplify connecting the UI to several data sources.
- Two-Way data binding is used to manage the audio effects. The MediaElement playing the sounds is bound to a toggle button that can be used to mute them. Since there is no way to automatically databind the two properties directly, I made a bindable CLR class (implements INotifyPropertyChanged) that acts as a binding surrogate to connect them.
- One-Way data binding is used for the score and level text in TextBlocks. In both cases, I wrote Converters to change the data content into text to show on screen. For example, one converter modifies the score to include the thousands separator.
- One-Way data binding is also used to display the ObservableCollection of high scores in a DataGrid. A Factory Pattern is used to allow for multiple scoreboards (i.e., local, server, etc.)
Control template and skinning. This game uses both Styles and ControlTemplates.
- Styles are used to format the text on the instructions page.
- ControlTemplates are used to make the custom round buttons for Mute, closing the instructions / high score screens, on the pause screen and on the game over screen. They were also used to make irergular shaped buttons on the main screen. The button on that screen are shaped like a car, lightning bolt, and an explosion.
- ControlTemplates are also used to provide animated effects for events like mouseover or pressed.
- Making templates from Expression Design was harder than I would have liked... but much easier than trying to hand-code the vector graphics. Some of the gotchas I encountered while exporting the graphics follow. Inside a ContentTemplate, you cannot have two controls with the same x:Name... but Design likes to export XAML with the x:Name specified. I found it easiest to export the entire graphics for the different states of my controls... leave them in the <Canvas> objects as exported by Design... and toggle the opacity of those Canvas objects in the event storyboards. Probably not the best performance - fairly fast to build!
Core Form Controls. Several controls are used in making this game.
- TextBlocks are used for the current score, level transitions, game over screen, and instructions screen.
- Buttons are used for most user interaction: show screens, start a new game, etc.
- A WatermarkedTextbox is used to get the user's name for display on the high scores list.
- A ToggleButton is used to allow the user to mute the sound.
Build-In Layout Management Controls. The game makes extensive use of Canvas controls (included in Silverlight 1.0) as well as the newly introduced Grid and StackPanel controls.
- The Canvas is the lightest weight control and I used it whenever I could. It's worth pointing out that it is also the only layout control with a ZIndex attached property.
- The Grid makes some layout tasks extremely simple and is used as the main "LayoutRoot" container. The Rows and Columns are used for placing the items in the top bar (score + lives), side bar (volume control), and bottom bar (links).
- The StackPanel is used whenever items could just flow down or across the screen... for example the "number of lives" indicator.
Common Functionality Controls. The game used other controls where it made sense.
- MediaElement is used to play the background music and also could be used for character sounds.
- Hyperlink buttons are used to provide the game player with access to information from another website.
Data Manipulation Controls. Games typically don't use as much data as business applications, so there isn't much need for controls that handle data. However, the "high scores" listing does provide one place to show off some data manipulation.
- A DataGrid is used to display data from multiple objects (score and user objects). Through databinding, the underlying data source could be edited by adding a new score at the end of a game or deleting a score by pressing the DELETE key.
Collections. Collections are used for keeping track of game objects and databinding the saved scores.
- Managing game objects (sprites) was done by adding and deleting them from a List<SpriteBase> collection.
- One trick to get around was dynamically re-sorting an ObservableCollection when adding a new item. I created a new subclass to handle this functionality.
I/O & Local Storage. Scores are saved to the computer in a persistent location. In other words, they are available across browser sessions.
- Saving scores to the computer uses a Silverlight concept called Isolated Storage. This is functionality a programmer can use to access a sandboxed location on the user's hard drive. Data can be saved to the disk and retrieved from the disk using I/O operations. To imlpement this functionality, I use IsolatedStorageFile, IsolatedStorageFileStream, StreamReader, and StreamWriter objects.
- Local storage is also used to save user settings between sessions. Specifically, the ApplicationSettings object is used to save the player's name. It was tricky to store a CLR object in both a bindable Resource (defined in XAML) and in the ApplicationSettings. Basically, I had to use the Decorator Pattern to consolidate the functionality. Also, only serializable objects can be put in the ApplicationSettings.
Generics. Generics are a useful tool in making comlpicated coding tasks simpler.
- Generics are used to help in managing the collections of game characters (sprites). For example, the collection that kept track of the enemies on the screen is defined as a List<SpriteBase>... but contains several types of objects: EnemyOilBarrel, EnemyGasTanker, and EnemyGasCan.
- Generics are used with databinding to a collection.
Threading. The new DispatcherTimer class was THE fundamental concept behind making this game work.
- In Silverlight 1.0, developers would use a Storyboard object to manage the game loop (the core component of the game engine that makes sure game actions happen smoothly.
- This game uses the Silverlight 2 DispatcherTimer class to run the game loop. DispatcherTimer is used to run a Timer object on the UI thread. The Interval is set to TimeSpan.FromMilliseconds(0) for automatically smooth animations. The Tick event is used to manage critical actions, like making random enemies, making hero and enemy ammo, moving heroes, enemies, and ammo, collision detection, handling user input (keystrokes) and also initiating game events like changing levels.
- A wrapper class is used with the DispatcherTimer to convert game action to elapsed time, making the gameplay independent of CPU speed.
Globalization. This game incorporates globalization by creating a framework for globalization and showing culturally specific number formatting on the current score and in the high scores table.
- The game integrates with ASP.NET for initial globalization support. ASP.NET is used to retrieve the culture settings from the browser and pass them to the Silverlight plug-in's Init property. The Silverlight project grabs the setting in App.xaml and stores the browser-based culture setting in an application Resource. It also sets the UserControl's Language DependencyProperty.
- This framework can be expanded by incorporating RESX resource files to globalize the text displayed on screen. All text was displayed in bindable TextBlock controls to aid in this effort. No text was stored inside a graphic (in Expression Design, select Export Text as TextBlock).
- This framework can also be extended to allow the user to override the initial culture setting by selecting another language from a Configuration or Main Menu screen.
- Note that the DataGrid showing a date inside a TextBlock automatically formatted the date to comply with cultural sensitivity. I did not have to add code to explicitly format the dates. I just had to set the current culture in the UserControl's Language DependencyProperty.
LINQ to XML. The game uses XML to store the scores in the Isolated Storage.
- To simplify the serialization and deserialization of data stored to the Isolated Storage, LINQ to XML is used. I found the XDocument object model easier to work with than XMLWriters for serialization. Pulling out the data was even easier with a XDocument.Parse() method to extract the XML tree and then a LINQ query to convert it to CLR objects.
OOP. This game makes use of some object oriented concepts.
- Interfaces were used to support the creation of different scoreboards. For example, one scoreboard could use scores saved to "my computer" and a different scoreboard could use scores saved on a public server.
- Inheritance is used on the UserControls to consolidate the common base functionality for sprites. Since they are visual elements, putting them in UserControl containers made sense. This approach was tricky to implement and worked, but meant losing designer support in Expression Blend for any subclassed UserControl.
- A Decorator Pattern is used to consolidate an object defined in the UserControl.Resources markup with an object saved in the IsolatedStorage with ApplicationSettings.
Keyboard Input. The game action interacts with the user via keyboard input.
- The System.Windows.Input.Key enum was added in Silverlight 2 and it is nice!
- Since keyboard input is used, the application cannot be run in true "FullScreen" mode. Silverlight does not allow keyboard input while in FullScreen. A workaround for this is using 100% of the available browser window and letting the user maximize the browser.
Mouse Input. The game uses mouse input mostly for administrative tasks.
- Mouse events are used to capture button clicks.
- Mouse actions are automatically captured by the HyperlinkButtons.
Attached Properties, Dependency Properties, etc.
- Attached Properties are commonly used in conjunction with layout management support. They are seen in properties like Canvas.Top.
- Dependency Properties are used in data binding situations, using framework methods like GetValue and SetValue.
XAP Packaging. This game was deployed as a XAP compressed file.
- The XAP package was analyzed for size in KB. This was an important task because a smaller download size makes the applications more accessible to users with slower Internet connections. Here are some tips for reducing the XAP size:
- Use Expression Design tricks to compress the XAML graphics.
- Remove unneeded project references, like Syndication or XMLSerialization.
- Leave media files outside the XAP by marking them with a Build Action of None and Always Copy to the output directory. Manually copy them to the deployment server. This will increase the load time for your application because media files are typically not needed when the application first loads.
Audio Support. The game uses audio files to play background and special effects music.
- The audio files are converted to compressed WMA format using Expression Encoder 2 Beta.
- The files are deployed to the server outside the XAP.
- The media is played using a MediaElement file. It is bound to other controls/objects, looped for continuous playback, and played from code in response to an event.
- The audio files must be compressed in a compatible format. Expression Encoder is a good tool for the job. I got media errors when trying to play MP3 files compressed in Audacity.
Number Formatting. The number formatting classes of the framework are used to display the current score in a culture-specific manner.
C# Automatic Properties. For simple properties, my custom classes use the { get; set; } syntax.
C# Implicitly Typed Variables. For the LINQ to XML code, the var keyword is used to create imlicitly typed variables.
Error Handling. To assist in development and error reporting, I use global error handlers.
- A global error handler is used in the App.xaml class: Application_UnhandledException
- The plugin also provides an interface to pass errors to a JavaScript function.
Backgrounds: No DrawingBrush. A simple method to add a fancy background is to put vector graphics in a resource that is added as a DrawingBrush. Since Silverlight does not include this object, a workaround was to put the graphics in a container (UserControl, Canvas, etc.) and place that container behind everything else using the ZIndex property.
2D Animations. Aaah, one of the cornerstones of a Silverlight application.
- Storyboard objects are used to animate the level transitions, the blinking car crash, the explosions, and much more. The Storyboards in this came are defined with DoubleAnimationUsingKeyFrame and SplineDoubleKeyFrame objects.
- Game characters (sprites) are manually animated by setting their Canvas.Top and Canvas.Left attached properties to move them around the screen (game surface).
2D Vector Graphics. To take advantage of application resizing, all graphics in this game are vector graphics created in Expression Design.
- Rectangle, Ellipse, and Path objects are used to draw the graphics. Path mini language was frequently used
- LinearGradientBrush, RadialGradientBrush, GradientStop, Fill, Stroke are used to modify the appearance of the shape objects.
Text. Text is rendered through a variety of controls.
- TextBlocks show read-only text, TextBoxes interact with the user, DataGrid and Buttons display other text for user interaction. Common formatting properties are used, like font size, alignment, font family, and font weight.
Garbage Collection.
- The C# Using statement is used when working with Isolated Storage. The Using statement improves garbage collection, automatically making calls to the Dispose method.
End To End Experience with Expression Studio
Visual Studio 2008 is a good tool for writing code and also for writing XAML markup. However, it is not a strong tool for creating the dynamic layout, animations, fancy graphics, and multimedia necessary in a good game. For those tasks, Expression Studio makes a good complimentary toolset. Below are brief descriptions of my experiences using these products while making Solar Shooter.
Expression Blend 2.5 Beta. This tool was used alongside Visual Studio to develop the application.
- It was better than Visual Studio when performing layout or animation tasks. It provides a rich controls palette and the properties panel was a good place to discover what I could do to a control. For editing raw XAML and C# code, Visual Studio was better (it includes intellisense for XAML!!).
- Be cautious when using both tools simultaneously! It is easy to overwrite edits in one program with the other.
- Blend provides an interactive designer surface, which provides a much nicer design experience than the current read-only designer in Visual Studio.
- The Blend designer tends to throw more errors about the underlying CLR classes than the Visual Studio designer or even the runtime. For example, Blend expects Resources to either subclass from DependencyObject OR use the x:Key declaration.
- Blend has an awesome feature that translates layout controls. For example, you can convert a Canvas containing vector graphics absolutely positioned with Top and Left coordinates into a Grid with positioning done by Margin.
Expression Design 2 Beta. All graphics used in this game are vector graphics created in Expression Design.
- Expression Design is a much better tool than Expression Blend for creating and editing graphics. It is much more powerful and provides a more efficient design experience.
- Since Expression Design 2 Beta was built to comply with Silverlight 1, importing the graphics as Silverlight XAML was not a completely smooth process. The XAML exported by Design needed to be manually edited to comply with Silverlight 2.
- To optimize the export size from Expression Design, do not name objects, do not place objects in XAML layout containers, and leave text as Editable TextBlocks. Also, when building the objects, try to use the simplest shapes possible, minimize the number of nodes on paths, and avoid fancy Strokes. Using bitmaps (in "live effects", Fill, Stroke, etc.) might lead to unpredictable behavior when exporting to XAML vector based graphics.
- To facilitate globalization, all text must be editable by code. Ideally, no text would be embedded in bitmap graphics or converted to paths in vector graphics.
- One gotcha I discovered was that your vector graphics may extend outside of the slice and Design tries to solve this by putting a clipping mask on the parent canvas.
Expression Encoder 2 Beta. The audio files were converted to WMA format and compressed with Expression Encoder.
- I was impressed with Encoder's interface. I was able to easily compress the audio files using WMA settings 16kbps bitrate, 22kHz sample rate, 16 bits per sample, and Mono channels. The compression rivaled MP3 compression from other programs... and worked seamlessly with Silverlight.
More Silverlight 2 Features
As if I didn't already use enough features, here are more that I would have implemented if I had more time:
- Use a Checkbox control to filter the scoreboard to only show my scores.
- Making the DataGrid (scoreboard) be re-sortable.
- Use two DatePicker controls to filter the scoreboard to only show scores within a certain date range.
- Use a Slider control to adjust the difficulty of the game.
- Use a ListBox control to allow the user to change their language. (A DropDown control would be better, but that control was not included in Silverlight 2 Beta 1.)
- Use the FileOpen dialog to upload an avatar (picture) from the user. Show that avatar on the scoreboard.
- Use Cross Domain Network Access (sockets) to allow people to create game groups with private scoreboards that update each other.
- Use HTTP Networking and WCF Services to provide a global public scoreboard that is hosted on the server.
- Use JSON for passing data across networks, since it is lighter weight than XML.
- Use Asynchronous Programming with sockets, networking, and perhaps with downloading media.
- Use Cryptography to cache and encrypt user information.
- Extend the usage of Globalization to also globalize text (it currently just "localizes" number and date formatting).
- Use HTML DOM / JavaScript Interop for... wait a minute! This is a managed-code application! Screw the interop!
Conclusion
Whew! I put a lot into this game. Looking back on this process, I can see that I did learn a lot about getting the most out of Silverlight 2. It is a very powerful framework that met most of my needs.
What's next? I'm going to learn and incorporate more features into this game. I also plan on enhancing the gameplay with boss characters, special weapons, more sound effects, and more storyline. The updated version will be posted to the SL42.com website.