Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SilverStunts - A data driven game in Silverlight

0.00/5 (No votes)
31 Dec 2007 1  
The article discusses the concepts of data driven web games. An example game 'SilverStunts' is presented and described in technical details.

SilverStunts powered by Siliverlight

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;
// initialization 
public void InitLevel(...)
{
    ...
    // wire dynamically loaded python script with tickDelegate
    tickDelegate = shell.Engine.EvaluateAs<TickDelegate>("tick", shell.Module);
    ...
}

// tick event
public void Tick(int tick, int elapsed)
{
    tickDelegate(tick, elapsed); // call into dynamically loaded python script
}

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)
{
    // this game loop should run at 60FPS
    tick++;

    game.ProcessInputs(keyboard.keys);
    game.Simulate(); // here is simulated physics
    level.Tick(tick, (int)timeElapsed.TotalMilliseconds);
    // here is executed level script

    // throttle rendering to half speed (30FPS is OK for visuals)
    if (tick % 2 == 0)
    {
        level.UpdateVisuals();
        game.UpdateScrolling();
        renderTick++;
    }

    // this is ugly, but who cares ...
    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:

// binding between plain object (source) and visual's content (target)
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" >
        <!-- here will be inserted game UI elements -->
    </Canvas>
    
    <!-- scrolling canvas for game and editor -->
    <Canvas x:Name="scroller">

        <Canvas x:Name="background">
            <!-- here will be inserted background xaml -->
        </Canvas>

        <Canvas x:Name="world">
            <!-- here will be inserted game entities -->
        </Canvas>

        <Canvas x:Name="foreground">
            <!-- here will be inserted foreground xaml -->
        </Canvas>

        <Canvas x:Name="workspace">
            <!-- here will be inserted editor UI elements (gizmos, etc) -->
        </Canvas>

    </Canvas>

    <Canvas Visibility="Collapsed" x:Name="grid" 
            Opacity="0.1" Width="1000" Height="600">
        <Canvas.Background>
            <!-- Siliverlight 1.1ALPHA does not support TileMode property 
                 on ImageBrush => need to improvise using image -->
            <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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here