Contents
Introduction
Making games is a great challenge and fun. Making web games is even more challenging for me. I have always looked for new technologies and game concepts on the web.
The release of Silverlight 1.1 Alpha at MIX'07 conference in April 2007 was a great opportunity to check this new platform and (possibly) push forward a game development
on the web. At that time Microsoft had also released tools for Silverlight development: Visual Studio 2008 codename "Orcas" Beta 1 with Tools for Silverlight.
I have downloaded the tools and spent several weekends working on a Silverlight game prototype to try some concepts on the new platform.
This article is divided into two parts:
- In the first part, the Siliverlight platform is discussed from the game developer's point of view. Also some general concepts suitable for web games are presented.
- In the second part, a SilverStunts game project is described with more technical details. The SilverStunts project is a 2D physics driven game running in Siliverlight
scriptable with Python including in-game level editor.
The first part may be interesting for the general audience interested in Siliverlight, games, and web trends. No previous Siliverlight or programming background is needed.
The second part may be useful for programmers going to implement a game in Siliverlight. The code may be used as a skeleton application for a new game project.
It contains a novel approach to embed IronPython scripting and binding it to game objects. The second part expects C#, HTML, and JavaScript background and the reader
to be familiar with Siliverlight concepts and tools. A good introductory article may be Silverlight 1.1 Fun and Games
or other articles in the Silverlight section of the CodeProject site.
A note just to be clear: Every note about Silverlight is about version 1.1 (recently upgraded to 2.0) which includes promising features for game developers.
Web games
The browser is becoming a solid application platform and many desktop applications tend to move to the web as web technologies are becoming more mature and broadband
connectivity is cheaper. Interest about Web2.0 in the last few years is about desktop application migration and new possibilities rising on the web (like sharing, collaboration,
web services, data clouds, and accessibility). And what about games? Are they ready to move to the web?
I think every technology making the web a more rich platform may cause game development migration. At least some classes of games can benefit from
the new features. Let's see what features Siliverlight offers to game developers ...
Siliverlight for game developers
Siliverlight PROs:
- Strong platform == .NET is a serious framework, enables the language of choice, tons of documentation, and
a huge community which is a source of quality developers.
- Great tools == Visual Studio for programmers and Expression Studio for designers
- WPF/E (XAML) == Accelerated 2D rendering engine for games (vector graphics, images, animations, sounds, fonts, ...)
- Similarity with XNA == This opens a great possibility to build games for Web, Desktop, and XBox sharing codebase and content
- Dynamic Language Runtime (DLR) == Enables programmers to embed scripting into their games (Python, Ruby, JScript, ...)
- HD video streaming == Usable for cut-scenes and movie-like games
- Full-screen support == Great playing experience
Siliverlight CONs:
- No low-level TCP/IP networking == Sockets are essential for creating fast network games (peer-to-peer). Siliverlight provides classic AJAX-style networking over HTTP.
- No framebuffer access (per pixel) == This makes sense in vector graphics engines, but game developers need some way to do post-process effects and image transformations.
I wish some game related (fullscreen) effects will be introduced in the new Siliverlight versions.
- User adoption == At this moment, user adoption is slow because of the product alpha stage. I'm curious how Microsoft will manage to push Siliverlight onto users' machines.
Data driven games
A computer game usually contains:
- Game content - game data like images, sounds, game maps, in-game texts, ...
- Code implementing game logic - game rules, AI, player movement, storyboard, ...
- Code implementing game hosting on a given platform - keyboard and mouse inputs, rendering, playing sounds, reading files, OS interop, ...
The first bullet is obvious and everybody understands it as a piece of data (i.e., zipped folder with images). The second bullet is more tricky. The game logic is code,
but can also be seen as data. For example, if a game logic is implemented as a script and runs dynamically using some scripting engine. A script source is just a text,
and can be downloaded and manipulated like data. So, let's count the scripts into the game data. The third bullet is often called "a game engine". A game engine is code which
is responsible for running game content. In other words, a game engine drives game data (including scripts).
A data driven game is a game with a clear separation of game data and the game engine. This is a very strong concept, but it is usually not very easy to achieve it.
In the next sections, I'm going to present the motivation and how it is related to Siliverlight.
Motivation
When you think a little about it, you may recognize some benefits of data driven design:
- game engine reusability
- game tools reusability
- game data may be crafted with different tools - game tools may be under independent development cycle
- game data is portable more easily - we can rewrite an engine and possibly apply some data transformation (e.g., image size reduction)
- game data can be streamed more easily
But there may be some drawbacks:
- data driven design is harder (more expensive) - it may take more time to finish the first game, infrastructure maintenance costs may be too high
- performance - data driven engine may be slower and may eat more resources
- platform restrictions - for example, it is not viable on very limited platforms
- data driven design pays off in bigger projects (with possible continuation)
In data driven design, game quality is strongly dependent on data quality. And this is why tools are more important than engines in this
"data driven world".
Data driven Siliverlight
Microsoft does not bring Siliverlight for game developers in the first place. Microsoft is offering Siliverlight as a general platform for rich web applications.
Fortunately, users of today's web applications strongly demand bells and whistles from the desktop world. Microsoft is pushed to satisfy these demands in this new platform.
That is good for games; "rich" means a shiny, faster, and more sophisticated platform for games. In effect it means that data driven design becomes more viable
in Siliverlight and that is an opportunity to move on the edge and create stunning things.
I strongly believe it is possible to implement powerful data driven 2D game engines in Siliverlight. Silverlight contains two key technologies:
- WPF/E rendering engine - accelerated 2D vector graphics, data driven (XAML is the data)
- Dynamic Language Runtime (DLR) - scripting engine, data driven (script is the data)
Going this way helps also with tools and portability. Microsoft Expression Studio offers a great foundation
for tools for XAML authoring. Microsoft Visual Studio has been a daily bread for many game developers during
the past years. And what is new, Visual Studio can be turned into a custom game editor using Visual Studio
Extensibility (Shell). These tools are Windows based, but their target platforms may be desktop Windows (WPF + .NET), Web (Siliverlight), or XBox (XNA).
This may open interesting chances to create cross-platform games more easily.
Going for web goodies
The web is strong in accesibility and collaboration. It is not needed to use whole Microsoft's infrastructure to create game data. We can build a game editor
directly in Siliverlight and host it on the web for distributed content creation and customization by the crowd. The web community is creative and may be eager
to participate on the game data "in the cloud". This enables new models for content creation and managing game life cycle.
And this can also enable new models for game marketing and profit generation. Web gaming and entertainment is a significant part of web trends. For example,
a novel approach in this field has Kongregate and I can imagine they will open a Siliverlight section
for game creators and players soon. But this discussion is beyond the scope of this article. Let's see an example implementation of a data driven game in Siliverlight ...
SilverStunts game - A proof of concept
SilverStunts is a simple 2D physics-driven game prototype similar to Elastomania. I was working on it during some weekends
in May 2007. My goal was to learn Siliverlight a bit and evaluate it by creating a simple web game.
I decided to use the 2D Physics engine by Chris Cavanagh and ported it to Siliverlight
back in May. This article isn't about the best physics engine for Siliverlight. I haven't updated it, although Chris ported his engine to Siliverlight
later in May. And as of today (December 2007), I would probably
use the Farseer Physics Engine for a new project. Physics engines are quite competitive these days and we can expect
more to pop when the final Siliverlight 2.0 is released.
Features
- Physics-driven 2D game
- Scrolling
- Levels loaded on demand
- In-game editor
- Live scripting with Python on client-side (using HTML script editor)
- Game objects skinnable with XAML templates
Live demo
You can see a live demo at http://silverstunts.com/cp1/index.html.
The site requires Siliverlight 1.1 (Alpha Refresh) and works best with Firefox (IE eats some
keyboard shortcuts). Please follow the instructions on the page if you encounter any problems.
Get started
Assuming you have Siliverlight 1.1 Alpha Refresh (September) runtime installed on your computer,
to run the game in debug mode, download the source code and unzip the archive somewhere on your local disk (say C:\SilverStunts). The Visual Studio
project (C:\SilverStunts\game\SilverStunts.sln) works for Visual Studio 2008 Professional (or Beta2). It should also work for the Express Edition
of Visual Studio 2008 (not tested). You should also have Microsoft
Silverlight 1.1 Tools Alpha for Visual Studio 2008 installed on your computer.
The project is configured for an ASP.NET Development Server. We need a real web server, because Siliverlight in the latest version
has a restriction for downloading local files using the downloader
object. Before you first run the website in debug mode, please take care to set the Web Site setting (right click to C:\SilverStunts\site in Solution Explorer -> Property Pages).
Note, you can put a breakpoint somewhere (e.g., Page.xaml.cs, in the GameTick
method) and hit F5 for debugging. The page will open in your default web browser.
Project structure
Sources are divided into three projects:
- SilverStunts - game sources and Siliverlight entry point (produces SilverStunts.dll)
- Physics - sources for physics engine (produces Physics.dll)
- Website - web files, website is "linked" to the SilverStunts project, so every build copies Page.xaml into the website root and DLLs into ClientBin
Note: due to Python scripting, I need DLLs to be located in the root of the web site. The problem is that the Visual Studio build system always places them into ClientBin
and I couldn't find out a way to reconfigure it. I ended up with a URL rewriter which is properly configured in the site's Web.config. See comments for more details.
Interesting technical details
Here we will discuss some parts of the project I find interesting.
Embedding IronPython scripting
This is the part I'm quite proud of in this project. Embedding IronPython is not very straightforward in the current version. You have to create a custom Platform Adaptation Layer (PAL)
as described in this article. Back in May, at the time I was investigating possibilities,
there were no resources on the web talking about these issues.
I have found another way. Very tricky, but it works. The idea is to boot the Python subsystem by starting a Python script from the default XAML page. Then inside the Python script,
in the 'Loaded' event, we can expose .NET functionality and load the SilverStunts assembly dynamically. Then create and init the main page the same way it would be called
by the Siliverlight runtime from the XAML page pointing to the C# assembly.
Page.xaml contains two lines to run in 'Python mode':
<x:Code Source="page.py" Type="text/ironpython" />
<Canvas x:Name="loader" Loaded="onLoaded" />
You can look at the Python bootstrapper in page.py:
# this is python code driving our silverlight control
# it acts as a scripting DLR bootstrapper (I was unable
# to intialize python scripting engine from managed code)
# we load managed assembly and route all relevant actions to it
import sys, clr
SilverStunts = clr.LoadAssemblyByName("SilverStunts, Version=1.0.0.0")
####################################################################
# class suitable for output redirect
class Redirect:
def __init__(self, kind):
self.method = SilverStunts.SilverStunts.Page.Current.PrintConsole
self.kind = kind
def write(self, s):
self.method(s, self.kind)
####################################################################
def onLoaded(sender, args):
# bootstrap page
global page
page = SilverStunts.CreateInstance("SilverStunts.Page")
page.Init(sender.Parent)
# redirect standard outputs
sys.stdout = Redirect(SilverStunts.SilverStunts.Page.ConsoleOutputKind.Output)
sys.stderr = Redirect(SilverStunts.SilverStunts.Page.ConsoleOutputKind.Error)
Note: Redirect magic is here to redirect output from the Python scripting engine into my web-based console.
I have implemented a little wrapper around Microsoft.Scripting.Hosting.ScriptEngine
in Shell.cs. You can see there
the specific details for initializing the scripting module and executing Python expressions.
For example, calling the tick event in the level script boils down to:
private delegate bool TickDelegate(int tick, int elapsed);
private TickDelegate tickDelegate;
public void InitLevel(...)
{
...
tickDelegate = shell.Engine.EvaluateAs<TickDelegate>("tick", shell.Module);
...
}
public void Tick(int tick, int elapsed)
{
tickDelegate(tick, elapsed); }
The game loop
In Siliverlight, you don't get an exclusive thread to run your code. The execution thread is borrowed from the browser. That means when you execute a long running job,
the browser may become unresponsive. It is important to return the control to the browser as soon as possible.
Your code is called via some event from the browser or the Siliverlight runtime. The first chance to subscribe to events is the XAML page initialization event.
There you have a chance to subscribe to timers. You can subscribe to an HTML Timer or XAML Storyboard. For a game loop, the XAML Storyboard is the preferred way.
I use a similar approach as described in the A Better Game Loop
article. My implementation is in the Timer
class.
I run GameTick
at speed of 60FPS, but the rendering is done at half the speed.
public void GameTick(TimeSpan timeElapsed)
{
tick++;
game.ProcessInputs(keyboard.keys);
game.Simulate(); level.Tick(tick, (int)timeElapsed.TotalMilliseconds);
if (tick % 2 == 0)
{
level.UpdateVisuals();
game.UpdateScrolling();
renderTick++;
}
HandleContinueMessage();
}
Entities, Visuals, and plain game objects
When implementing the scripting subsystem, I wanted the script to be the first class citizen in the system. There is a problem with the scripting system keeping track of what
is going inside the scripting box and keep the game state updated accordingly. For example: in SilverStunts, you have an interactive console and you can type
and execute margaret = Circle(185, 155, 35)
during a game session. This will create a new entity called margaret
in the Python subsystem.
OK, it is a Python variable, but it has to have some shadow in the game engine and in the renderer to be visible on the screen. The Managed C# game core must be notified that
a new entity was created and get a chance to register it in the system. That is why I want to distinguish between Entities, Visuals, and plain objects.
- Plain Object - is a raw data structure (for example, physics object)
- Visual - is a data structure in C# that acts as a visual representation of Plain Object (it holds
Canvas
, a piece of XAML, and rules to update the Canvas
)
- Entity - is a data structure in C#, but visible in the scripting context; it keeps a reference to the visual and plain object
So, entities are top level objects living in C# and visible from the script. When a new entity is created from a script, we get notified by
the code entering an entity's constructor. So, the possible scenarios are:
- Entity is created from script - constructor is called and the corresponding visual and plain objects are created
- Entity is deleted from script - because of garbage collector, the destructor may be called with a delay; we may call the
Die()
method from
the script for immediate entity destruction
- Entity is created by game - game has full control and knows how to update its state
- Entity is deleted by game - game has full control and knows how to purge the data
You can examine the entities implementation in the Entities subfolder of the SilverStunts project.
And what about Visuals? I have implemented a simple templating system for Visuals. Visual templates are located in Visuals.xml and a template may look like this:
...
<Visual Type="CircleParticle">
<Canvas.RenderTransform>
<TransformGroup>
<TranslateTransform X="{CenterOffset.X}" Y="{CenterOffset.Y}"/>
<TranslateTransform X="{Curr.X}" Y="{Curr.Y}"/>
</TransformGroup>
</Canvas.RenderTransform>
<Ellipse Width="{Diameter}" Height="{Diameter}"
Fill="Blue" RenderTransformOrigin="0.5, 0.5"/>
</Visual>
...
This is a piece of XAML extended by binding properties (see them in curly brackets). These are bindings to Plain Object's properties. This XML is loaded as data on startup
and is parsed. Curly brackets are transformed into a bindings table (see the Bindings
class). When a new entity is created, some template is assigned to it and
a new visual is created based on that template. During gameplay, Plain Object's properties will be animated (for example, the physics engine alters the object's position).
The Visual is able to update the Canvas
using new values from the Plain Object's properties. This is implemented using .NET Reflection and code can be found in Visual.cs:
class Binding
{
int id;
string attribute;
string field;
public Binding(int id, string attribute, string field)
{
this.id = id;
this.attribute = attribute;
this.field = field;
}
public object GetValue(Object source)
{
BindingFlags f = BindingFlags.IgnoreCase |
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string[] names = field.Split('.');
Object value = source;
for (int i = 0; i < names.Length; i++)
{
Type st = value.GetType();
value = st.InvokeMember("get_" + names[i], f |
BindingFlags.InvokeMethod, null, value, new object[] { });
}
return value;
}
public void SetValue(Object target, Object value)
{
BindingFlags f = BindingFlags.IgnoreCase | BindingFlags.Instance |
BindingFlags.NonPublic | BindingFlags.Public;
Type tt = target.GetType();
tt.InvokeMember("set_" + attribute,
f | BindingFlags.InvokeMethod, null, target, new object[] { value });
}
public void Update(Canvas content, Object source)
{
Object target = content.FindName(id.ToString());
Object value = GetValue(source);
SetValue(target, value);
}
}
Scrolling
Scrolling is achieved by the clipping canvas. Clipping canvas hides everything beyond its bounding rectangle. The implementation is in the ClipCanvas
class.
Here is the Game.xaml which defines the layout of the game scroller using ClipCanvas
:
<ss:ClipCanvas x:Name="viewport"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ss="clr-namespace:SilverStunts;assembly=SilverStunts.dll"
Width="1000" Height="600"
Background="#EEEEEE"
>
<Canvas x:Name="gui" >
-->
</Canvas>
-->
<Canvas x:Name="scroller">
<Canvas x:Name="background">
-->
</Canvas>
<Canvas x:Name="world">
-->
</Canvas>
<Canvas x:Name="foreground">
-->
</Canvas>
<Canvas x:Name="workspace">
-->
</Canvas>
</Canvas>
<Canvas Visibility="Collapsed" x:Name="grid"
Opacity="0.1" Width="1000" Height="600">
<Canvas.Background>
-->
<ImageBrush ImageSource="images/grid.png" />
</Canvas.Background>
</Canvas>
</ss:ClipCanvas>
Scrolling is done by applying a translation to the Canvas
named "scroller
". You can see the code in Page.xaml.cs
in the UpdateScrolling
method:
TranslateTransform tt = new TranslateTransform();
tt.X = -(cameraX - 500);
tt.Y = -(cameraY - 400);
scroller.RenderTransform = tt;
Note: We have to apply a negative (inverse) camera transformation, because we are moving with the world instead of the ViewPort.
In-game editor
The in-game editor is pretty neat. You can get into edit mode by pressing Space. You can click on an object and its edit gizmo is displayed (as a gray rectangle).
Gizmos have some handles and you can tweak object properties using handles. Of course, you can do multi-selection and move objects around, copy and paste
them, or delete them. Changes made to the level visually are reflected in the script editor (see Entities tab). Changes from the script editor are reflected on the game.
The editor is implemented in Editor.cs. Gizmos are implemented in Gizmo.xaml.cs. Each gizmo has to implement this interface:
public interface IGizmo
{
void Destroy();
bool HitTest(System.Windows.Input.MouseEventArgs e);
void HandleMouseLeftButtonDown(object sender,
System.Windows.Input.MouseEventArgs e);
void HandleMouseLeftButtonUp(object sender,
System.Windows.Input.MouseEventArgs e);
void HandleMouseMove(object sender, System.Windows.Input.MouseEventArgs e);
void Update();
}
Editor routes mouse events into gizmos via this interface and gizmos are responsible for affecting the object's properties.
Problems encountered
I encountered some problems during implementation. I assume these issues will be fixed in
the final version.
- Keyboard - some browsers eat keystrokes or react to many of them (I was not able to use F-keys, arrow keys, or TAB on IE)
- Mouse capture - when you have an active mouse capture and you leave a browser window, capture is never released (this is a bug in Siliverlight)
- IronPython embedding - embedding IronPython should be more straightforward in the final version
Conclusion
Siliverlight 1.1 is still in alpha so I don't want to do a strict judgment. Making the game and learning Siliverlight was great fun for me. From this experience,
I can say that Siliverlight is a rich runtime platform and definitely suitable for casual games. In this article, I wanted to present unique Siliverlight features
like DRL and WPF that are great helpers when implementing a data driven game engine. I believe in the future of Siliverlight development and fast acceptance by end users.
Simply, I'm a fan of Siliverlight and the engineering team behind.
Feel free to use my code as a startup skeleton for your game project. You can jump in right now.
Credits
History
- 31 December 2007 - First public version.