Click here to Skip to main content
16,021,765 members
Articles / Mobile Apps / Windows Phone 7

A "little snake" leads us through the most important features of the Windows Phone 7

Rate me:
Please Sign up or sign in to vote.
4.95/5 (165 votes)
29 Jan 2011CPOL60 min read 532.2K   3.4K   205   198
Analyzing the Snake game, we'll study an application for the new Windows Phone 7 platform, focusing on localization, Inversion of Control, navigation, transition effects, triggers, Isolated Storage, audio and we'll also use Blend to create a rounded glowing button, and other things...

Hi,

The idea of a contest which leads to the creation of an article has excited me and since I've got a son, Marco, a 15 years old student, potentially gifted for programming, we've started, everyone on his own, preparing an article about the Windows Phone 7. Already after some days, comparing the two articles, the difference was clear. I've left behind my article and here, instead, I've posted the work of my son Marco, for two reasons I think you can share:

  1. The article appears (evaluate your own) very detailed and clear.
  2. Given the Marco's young age, the opinion of really qualified people can be very important, even for the education of the child

This is the Marco's article:

 

Overview of the whole application

Contents

  • Introduction
    Introduction of the whole article
  • Game basics
    A little introduction about the general working of the game
  • Core
    This section is about the core of the game: from the cells to the levels
    • Cells
      The basic part of the whole game: the cell
    • IoC (Inversion of Control)
      A little tutorial about the Inversion of Control
    • A container for Windows Phone 7
      Here we'll analyze a container made to work on the Windows Phone 7 platform
    • Movement controllers
      Analysis of the movement controllers
    • Goals
      Goals of the game: food eaten, elapsed time and length of the snake
    • Levels
      Analysis of the most complex object of the whole application, focused on the localization and the movement of the snake
    • Enemy snakes
      Enemy snakes of the game; focused on the usage of a path finder algorithm
    • Saving and loading levels
      Little explanation of the structure of the XML file which holds the levels
  • UI and design
    This section is about the design and the UI of the application

Introduction

First of all, I apologize for my bad English, but I'm 15 years old and I'm still studying it at school.

This article talks about the development of an application for the new Windows Phone 7 platform: Snake Mobile. The idea of the game is only an opportunity to demonstrate that in a simple application like the Snake, we find a lot of the basic concepts of the development for the Windows Phone 7 platform.

I'm not a designer, in fact the game isn't visually attractive, but this application hasn't been developed to be deployed to the public, but to put inside a single videogame a lot of the fundamental programming concepts. To achieve this goal, sometimes I've used solutions less convenient than others, to create a scenario in which it was necessary to use a particular programming technique. An example of this is the Inversion of Control: using the IoC in a scenario like the one that will be described later is pure madness, but I've decided to use this design pattern the same way, to make its comprehension simpler and to create a container that works on the Windows Phone 7 platform, since that in bigger applications this may be useful.

Choosing to develop a game, in fact, means creating a complex application in which are combined various elements like graphics, audio, data, etc... This article is quite long and can be boring to read it all, but my goal was to be as complete as possible in the exposure and, above all, I didn't want to take anything for granted, in fact, in some particular situations, I also added some deepening links. I also added to the end of the article a list containing all the independent components of the project that you can take away and add freely in yours.

Finally, I remember you, that this article could be improved further: if you see any mistakes that I haven't noticed or any improvement that I can do, please let me know so that I can update the article and let the other people benefit from the improvements.

Are you ready? Let's start!

Game basics

The basic idea of the game, is that we have a Grid, which will be filled with some cells.
We'll store the instances of the Cell class in a bi-dimensional array; the index is used to retrieve the cell from the position on the grid. Example: a wall located on (2, 5) will be stored in the array using the index [2, 5].

To move the snake, we have a Point object, that represents the direction of the snake, and a Timer which makes the snake move (actually, it updates the CellType property of the cells; we'll see later what it is). The enemy snakes work the same way.

The movement controller raises the Up, Down, Left and Right events, which are handled by the Level object, that changes the direction.

At each tick of the main timer, there is a call to check if the goal is accomplished; if it returns true, the level is completed, otherwise, nothing happens.

However, what I've said before, will be analyzed deeper during the following part of the article.

Go to the top of the article   Go to the top of the article

Game core

In this part we'll talk about the most important things of the application: here is analyzed everything, from the simplest components, like the goals or the cells, to the most complex controls like the Level object. The paragraph of the movement controllers contains a parenthesis about the IoC (Inversion of Control) using Castle.Windsor. It isn't for educational purposes; it's only used to show how it can be useful when you want to make your project open to new solutions and to make your objects reusable and more flexible. However, we'll talk about this later.

Cells

The basic part of the game is the cell: this object has a property, CellType, that represents the content of the cell. In the constructor, we bind it to the Content property, using the CellType2ContentConverter. This converter transforms a value of the CellType enum to a visual object, returning the corresponding image. The most interesting member is the HitTest method: this method returns a value indicating what should happen when a cell hits another cell.

C#
public HitTestResult HitTest(Cell other) {
    //Free
    if (this.CellType == CellType.Free || other.CellType == CellType.Free)
        return HitTestResult.None;
    
    //Food
    else if (this.CellType == CellType.Food || other.CellType == CellType.Food)
        return HitTestResult.Eat;

    //DeathFood
    else if (this.CellType == CellType.DeathFood || other.CellType == CellType.DeathFood)
        return HitTestResult.Death;

    //Wall
    else if (this.CellType == CellType.Wall || other.CellType == CellType.Wall)
        return HitTestResult.Death;

    //Snakes
    else
        return HitTestResult.Cut;
}
Go to the top of the article   Go to the top of the article
  

IoC (Inversion of Control)

Let's open a parenthesis about IoC!

(

Let we see what Wikipedia says:

"Inversion of control (IoC) is an abstract principle describing an aspect of some software architecture designs in which the flow of control of a system is inverted in comparison to procedural programming. In traditional programming the flow is controlled by a central piece of code. Using Inversion of Control, this "central control" as a design principle is left behind."

I think it's better if we translate it in a human language...

Let's start from an example: we have to build an object that is able to extract the content of a tag in a HTML page. Everyone of us would write:

C#
public class TagGetter
{
    public string GetContent(string pageUrl, string tagName)
    {

        //Downloads the page
        string page = new System.Net.WebClient().DownloadString(pageUrl);

        //Gets the tag
        string tagContent = System.Text.RegularExpressions.Regex.Match(page, "<" + tagName + ">[^>]*</" + tagName + ">").Value;
        tagContent = tagContent.Substring(tagName.Length + 2, tagContent.Length - 2 * (tagName.Length + 2) - 1);

        //Returns
        return tagContent;

    }
}

Now analyze what this object does: first, downloads the page using the System.Net.WebClient class, then gets the tag using the Regex, and finally returns the found value. Maybe, you're thinking what's going wrong with this piece of code, and I answer you saying: nothing. Objectively, this class is perfect: downloads and extracts the tag well, and if you try extracting the title tag of the Google homepage, it returns "Google". But, try to see this class from a more general point of view: there are 2 main bugs, that require a different approach to be solved.

  1. This class does too many things! A principle of good design is the SoC (Separation of Concerns). According to this principle, a single object must do only one simple task and do it well. We should split this class into 2 different objects: one to download the page, and one to extract the tag.
  2. What if the desired document is not reachable by the HTTP protocol? We should change the body of the method adding (or replacing) this feature with another, like the FTP. The process of extracting the tag can be improved, but this needs the changing of the method body, too. I know this can appear senseless, but try to imagine a particular situation, in which this approach would lead to a better performance. In fewer words, the TagGetter object has a too deep knowledge of the global mechanism, and that's not good, because this leads to bad application design.

Since I'm going to solve these problems using Castle.Windsor (we'll see later what is), I have to use the same words of its documentation to explain the concepts of component and service:

"A component is a small unit of reusable code. It should implement and expose just one service, and do it well. In practical terms, a component is a class that implements a service (interface). The interface is the contract of the service, which creates an abstraction layer so you can replace the service implementation without effort."

Now that we know what a service and a component are, we can go straight to the solution of the problem.

The TagGetter class does too much, and we have to split it: the two tasks of this object are really generic and can be performed in several ways, so, we have to create a service (interface) that defines the actions that an object can do, without writing the concrete implementation (component). Here are the 2 interfaces, one to download the file, the other to extract the content of a tag.

C#
public interface IPageDownloader {
    string Download(string url);
}

public interface ITagExtrator {
    string Extract(string content, string tagName);
}

Now we should write the concrete implementation of these interfaces.

C#
public class PageDownloader : IPageDownloader {
    public string Download(string url) {
        return new System.Net.WebClient().DownloadString(url);
    }
}

public class TagExtractor : ITagExtrator {
    public string Extract(string content, string tagName) {
        string tagContent = System.Text.RegularExpressions.Regex.Match(content, "<" + tagName + ">[^>]*</" + tagName + ">").Value;
        tagContent = tagContent.Substring(tagName.Length + 2, tagContent.Length - 2 * (tagName.Length + 2) - 1);
        return tagContent;
    }
}

Uhm... here comes a little problem... how do we pass these objects to the main TagGetter class? Simple: we change the constructor of this class to accept 2 parameters of type IPageDownloader e ITagExtractor, than we store them in some variables. Here is the new code:

C#
public class TagGetter
{
    //Stored objects
    private IPageDownloader _pageDownloader;
    private ITagExtrator _tagExtractor;

    public TagGetter(IPageDownloader pageDownloader, ITagExtrator tagExtractor)
    {
        //Stores the objects
        _pageDownloader = pageDownloader;
        _tagExtractor = tagExtractor;
    }

    public string GetContent(string url, string tagName)
    {

        //Downloads the page
        string page = _pageDownloader.Download(url);

        //Gets the tag
        string tagContent = _tagExtractor.Extract(page, tagName);

        //Returns
        return tagContent;

    }
}

As you can notice, the code of the method TagGetter.GetContent() is more clean and simple, and it doesn't care of how to download the page or extract the tag: the implementations of the interfaces will do it! In this way, we can easily change the downloader or the extractor without changing the main TagGetter class! Also, we can easily reuse the single component in another application or we can write specific tests for only one component.

However, this has a disadvantage: to call this method we should write:

C#
string title = new TagGetter(new PageDownloader(), new TagExtractor()).GetContent("http://www.google.it", "title");

This isn't bad, but it's too long and complex for me. Here is where Castle.Windsor comes to rescue us. In a few words, Castle.Windsor is a container that you can configure to contain some objects, and than you can get them later when you need. It's a sort of big box containing some objects; there is an index written outside on which are matched the services and the components. When you need a downloader, you look for a IPageDownloader and you find an instance of the PageDownloader class. I think the example is more explicative than the explanation.

C#
//Creates a new container
WindsorContainer container = new WindsorContainer();

//Registers the downloader
container.Register(Component
                   .For<IPageDownloader>()
                   .ImplementedBy<PageDownloader>());

//Registers the tag extractor
container.Register(Component
                   .For<ITagExtrator>()
                   .ImplementedBy<TagExtractor>());

//Registers the tag getter
container.Register(Component
                   .For<TagGetter>());

//Gets the tag getter
TagGetter getter = container.Resolve<TagGetter>();
            
//Calls the method
string title = getter.GetContent("http://www.google.it", "title");

As you can notice, the first 3 calls to Register() are used to register the interfaces and their concrete implementation (the container can be configured using a XML file, too), but the most interesting is the fourth: the call to Resolve() returns a new instance of the TagGetter class. But if you remember, this class has a constructor with 2 parameters, so what's happened? When you call the Resolve() method, the WindsorContainer checks the parameters of the constructor and, if there is a registered service or component compatible with the type of the parameter, the container automatically creates the right new instances (according to the configuration) and so, the trick is done.

This is only a parenthesis about IoC, and I didn't show even the smallest part of what we can do with the IoC or what Castle.Windsor can do, so I close this paragraph with these references:

) - I haven't forgotten the opening bracket at the beginning of the paragraph!

Go to the top of the article   Go to the top of the article

Castle.Windsor is cool, but...

... but cannot be used on Windows Phone 7 devices. Yes, you read correctly. Castle.Windsor cannot be used on Windows Phone 7 devices.

Uhm... and now? What can we do?

At the beginning I started to look for another version of Castle.Windsor working on Windows Phone 7, but I've found nothing interesting. At this moment I started to worry. Luckily, I've had an idea: I'll make my own container! A container can seem very long and complex, but a class that has only some basic features is very very simple.

In our case, a very simple container would have been enough, but, since I'm crazy, I've written a container with some other functions. Here they are:

  Method name Description
Method ChangeRegistration<T>(Type)

Changes the registration for the type T. If the type T isn't registered, will be made a the registration of the type T

Method Deregister<T>()

Deregisters the type T

Method GetComponentTypeForService<T>() + 1 ovrl.

Returns the type associated with the service T

Method Register(WP7ContainerEntry) + 4 ovrl.

Registers a new entry in the container

Method Resolve<T>() + 1 ovrl.

Resolves the registration for the type T

Another important object to analyze is WP7ContainerEntry:

  Member name Description
Property Component

The type of the component represented by this entry

Property Params

Dictionary<string, object> containing the properties to inject (used to inject parameters in the constructor)

Property Service

The type of the service represented by this entry

Method For<T>() + 1 ovrl.

Static method which creates a new WP7ContainerEntry for the provided type T

Method ImplementedBy<T>() + 1 ovrl.

Sets the Component property

Method Parameters(Dictionary<string, object>)

Sets the Params property

Instead of writing all these useless words, let the code speak: let's see an example of the usage of this classes. Imagine we have something like this:

Example for the WindowsPhone 7 Container

In a few words, we have a class, MyClass, which inherits from MyBaseClass and implements IMyInterface; the constructor of this class needs an UsefulObject as parameter. In a scenario like this, you can use the container in a lot of manners, for instance, this:

C#
//Creates the container
WP7Container container = new WP7Container();

//Registers UsefulObject
container.Register<UsefulObject>();

//Registers MyClass as the implementation of IMyInterface
container.Register<IMyInterface, MyClass>();

//Resolves the object
IMyInterface myObj = container.Resolve<IMyInterface>();

The first thing we have to do is creating a new instance of the container, and then, registering all the types we need. Whenever you want, now you can call the Resolve method, and get an instance of one of the registered types. A nice thing to notice is that we don't need to pass any parameters to the constructor of the class: the container automatically finds the constructor that we can call, passing to it whatever it needs, even by resolving other types. In this case, the container notes that the only available constructor needs an UsefulObject, so, to create a new MyClass object, passes to the constructor a new UsefulObject. If there are no constructors that the container can call, an exception is thrown.

C#
//Creates the container
WP7Container container = new WP7Container();

//Registers the interface and its implementation
container.Register<IMyInterface, MyClass>(new Dictionary<string, object>() { 
    { "PropertyOfTheInterface", "interface" }, 
    { "PropertyOfTheClass", 10 }, 
    { "PropertyOfTheBaseClass", "base" }
});

//Registers the useful object
container.Register<UsefulObject>(new Dictionary<string, object>() { 
    { "UsefulProperty", "injected!" }
});

//Resolves the useful object
UsefulObject usefulObj = container.Resolve<UsefulObject>();

//Resolves IMyInterface
IMyInterface obj = container.Resolve<IMyInterface>();

This time, instead, when we register the types, we specify as parameters the name of the property we want to inject and its value: in this way, when we call Resolve, our object has already some properties set. The parameters can also be used to inject values to the constructor: in this case, we must specify the name of the parameter of the constructor and its value. The call to Resolve<UsefulObject> returns a new UsefulObject, where UsefulProperty is set to "injected!". The call to Resolve<IMyInterface>, instead, creates a new MyClass object and set its properties (they can belong to the base class, or they can be an implementation of an interface; it doesn't matter); during the call to the constructor, is called Resolve<UsefulObject>: in this way, will be passed to the constructor of MyClass a new UsefulObject with the UsefulProperty property set to "injected!". In fact, if you evaluate ((MyClass)obj).UsefulStructFromTheConstructor.UsefulProperty, you see that the property has value "injected!".

What WP7Container does is nothing, if we compare it to Castle.Windsor, but I think it's enough for a very little class like this, isn't it? But now it's time to dig a little deeper in the classes to discover how they work.

Container for Windows Phone 7

WP7ContainerEntry is only a class containing some data, so it doesn't need a lot of explanations; on the contrary, WP7Container can be a little more interesting. This is the basic concept: we have a list of WP7ContainerEntry; we should be able to add and remove entries from that list (using Register and Deregister methods) and resolve a type (Resolve method). The core of the whole class is the method ResolveEntry: it transforms a WP7ContainerEntry to the object that represents, finds the right constructor and injects the parameters.

It can be very difficult to write a method like this, but it you know what reflection is, the trick is done. However, here is the code:

C#
//Choose the constructor to call 
ConstructorInfo ctor = 
    entry.Component.GetConstructors()
    .FirstOrDefault(x => 
        x.GetParameters().All(y => 
            (entry.Params.ContainsKey(y.Name) && y.ParameterType.IsAssignableFrom(entry.Params[y.Name].GetType()))
            || IsThereEntryForType(y.ParameterType)
        )
    );

//Checks if the ctor has been found
if (ctor == null)
    throw new ArgumentException(string.Format("Cannot create type {0}: constructor not found. Try with different parameters.", entry.Component.FullName));

//Calls the ctor
ParameterInfo[] ctorParams = ctor.GetParameters();
object[] pars = new object[ctorParams.Count()];
for (int i = 0; i < pars.Length; i++) {
    ParameterInfo p = ctorParams[i];
    if (entry.Params.ContainsKey(p.Name))
        pars[i] = entry.Params[p.Name];
    else
        pars[i] = this.Resolve(p.ParameterType);
}
object obj = ctor.Invoke(pars);

//Checks if there are some properties to set
foreach (var x in entry.Params) {
    PropertyInfo prop = entry.Component.GetProperty(x.Key);
    if (prop != null) {
        if (!prop.PropertyType.IsAssignableFrom(x.Value.GetType()))
            throw new InvalidCastException(string.Format("Cannot cast from {0} to {1}", x.Value.GetType().FullName, prop.PropertyType.FullName));
        else
            prop.SetValue(obj, x.Value, null);
    }
}

//Return
return obj;

What we want to do is creating a new instance of the component of the entry, so, what we must do is finding a constructor that we can call. To achieve this goal, we reiterate through all the available constructors of the type and we find the first one whose parameters are all available, or because they are inside the parameters dictionary, or because they are registered in the container. Once we've found the constructor, we must find the parameters: we check each param and, if it's in the dictionary, we take the supplied value, otherwise, we resolve the type. Next step is creating the object and storing it in a variable. The last thing to do is injecting the parameters: we reiterate through all the supplied entries of the dictionary and check if we can set the value of the property.

As I've said before, this class has limited features: I advice you to use this one only if you cannot use Castle.Windsor, even if I've tried to maintain the same syntax.

Go to the top of the article   Go to the top of the article

Movement controllers

The movement controllers are the components that have to raise events when the user sends the input to change the direction of the snake. Since there are many ways to change the direction, we have to create an abstraction layer and then its implementations. Also, if we want to add a new controller, it's really simple: what we need is only develop that component.

This is the service for the controllers:

IMovementController interface
C#
public interface IMovementController
{
        
    //Events
    event EventHandler Up;
    event EventHandler Down;
    event EventHandler Left;
    event EventHandler Right;

    //Properties
    bool IsVisual { get; }

    //Methods
    FrameworkElement GetVisual();

}

As you can see, this interface contains 4 events (one for each direction); they are raised when the user sends the corresponding input. The next member (the IsVisual property) returns a value indicating whether the controller needs a visual object to work properly; the GetVisual() method, instead, returns that object.

Now it's time to speak about components (implementations of the IMovementController interface)!
The first controller we'll develop is the ArrowMovementController: it's based on a visual control made up of 4 arrows, one for each direction. Each of them is a different button which raises the corresponding event. I won't describe this component, because it's just a restyling of the classic button, and we'll talk about graphics in the second part of the article. However, it should look like this:

ArrowMovementController thumbnail

Go to the top of the article   Go to the top of the article

Goals

Each level must have a target to achieve; since there are a lot of different types of goal, we have to create the common service and then we can develop the single components. This is the abstraction layer:

IGoal interface
C#
public interface IGoal
{
    bool IsAccomplished(GoalEventArgs e);
    Uri ImageUri { get; }
}

As you can see, the interface is very simple. The main method is IsAccomplished(): the e argument of type GoalEventArgs contains some data (like the eaten food or the length of the snake); the method should check these values and return true, if the goal is accomplished, otherwise false. The ImageUri property returns only the Uri of the corresponding image.

For example, this is the FoodEatenGoal:

C#
public class FoodEatenGoal : IGoal 
{

    private int _Needs;

    public FoodEatenGoal(int needs)
    {
        _Needs = needs;
    }

    public bool IsAccomplished(GoalEventArgs e)
    {
        return e.EatenFood >= _Needs;
    }

    public Uri ImageUri { get { return new Uri("/SnakeMobile.Core;component/Images/Food.png", UriKind.Relative); } }

}

The constructor needs a int parameter, which is the minimum amount of food that the snake must eat before the goal is accomplished; the IsAccomplished() method returns whether the snake has eaten enough food.

The other 2 goals (GrowUpGoal and TimeGoal) work in the same way, and I won't write about them.

Go to the top of the article   Go to the top of the article

Levels

So, we've arrived here, at the most complex part of the whole application...  Are you ready? Let's start!

Level class

Let's start from the target of this class: Level must manage the whole game level and raise some events when something interesting happens. These events are Win and Loose. How their name says, the first is raised when the user wins the game, achieving all the goals of the level; the second one, instead, when the snake is killed by an enemy snake, or when it hits a wall. The third event (FoodAdded) is marked as internal because it must be handled only by some internal classes, and it's used to notify to an enemy snake when a new food is added. The need of this event will be explained later, when we'll talk about the FoodCatcherSnake.

Now it's time to speak about how the Level object works.

Let's start from the constructor. It needs a lot of parameters; each of them is then stored in a private field and used later. Here is a list of them:

  • IMovementController movementController: movement controller to use to move the main snake
  • int speed: speed of the game. Actually it's only the interval of the timers.
  • IEnumerable<IGoal> goal: all the goals the the user has to achieve to complete the level
  • Cell[,] cells: two-dimensional array containing all the cells of the game, already filled with the right content (CellType property already set)
  • IEnumerable<Point> snakeCells: list of the coordinates of the cells of the snake
  • IEnumerable<IEnemySnake> enemySnakes: list of the enemy snakes in the level. We'll talk about this later.

This is the body of the constructor:

C#
//Component initialization
InitializeComponent();

//Sets resources
switch (System.Threading.Thread.CurrentThread.CurrentUICulture.Name.ToLower())
{
    case "it-it":
        this.Resources.MergedDictionaries.Add(new ResourceDictionary() { 
            Source = new Uri("/SnakeMobile.Core;component/Resources/it-IT.xaml", UriKind.Relative) 
        });
        break;
    default:
        this.Resources.MergedDictionaries.Add(new ResourceDictionary() {
            Source = new Uri("/SnakeMobile.Core;component/Resources/en-US.xaml", UriKind.Relative)
        });
        break;
}

//Initializes some fields
_speed = speed;
_gridSize = new Size(cells.GetLength(0), cells.GetLength(1));
_cells = cells;
_goals = goal;
_snakeQueue = new Queue<Cell>();
foreach (Point p in snakeCells)
    _snakeQueue.Enqueue(cells[(int)p.X, (int)p.Y]);
_originalSnakeCells = snakeCells;
_enemySnakes = enemySnakes;

//Backup the cells (for the replay)
_originalCells = new Cell[(int)_gridSize.Width, (int)_gridSize.Height];
CloneAndAssign(cells, _originalCells);
_originalSnakeQueue = new Queue<Cell>();
foreach (Point p in snakeCells)
    _originalSnakeQueue.Enqueue(_originalCells[(int)p.X, (int)p.Y]);

//Movement controller
movementController.Up += MovementController_Up;
movementController.Down += MovementController_Down;
movementController.Left += MovementController_Left;
movementController.Right += MovementController_Right;
if (movementController.IsVisual)
    ContentPresenterMovementController.Content = movementController.GetVisual();

//Snake timer
_snakeTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(speed) };
_snakeTimer.Tick += SnakeTimer_Tick;

//Enemy snake timer
_enemySnakesTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(speed * 2) };
_enemySnakesTimer.Tick += EnemySnakesTimer_Tick;

//Private event handlers
this.Loaded += UserControl_Loaded;
this.SizeChanged += UserControl_SizeChanged;

As you can see, in the constructor we just store the params in some private fields, initialize the timers, add handlers and so on... A part that needs some explanation is the switch statement: the application is developed using the English language, but since I'm Italian, I decided to make the whole game localizable. This means that the app is available in English and Italian, and it automatically switches the language according to the global language of the phone. Obviously, for a French user the game will be in English, because it's the default language.

Making an application localizable seems very boring, but I can grant you that the most annoying part is.. is... nothing!

This is the idea: you must have a source for all the localized strings (or everything else that has to be localized), and when you need a string, you just have to get it from that source. In this case, the source is a ResourceDictionary containing some strings. It should be something like this:

en-US.xaml it-IT.xaml
XML
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">

<!-- General strings -->
<sys:String x:Key="OK">OK</sys:String>
<sys:String x:Key="Retry">Retry</sys:String>

<!-- Goal names -->
<sys:String x:Key="Goal_FoodEaten">Food eaten</sys:String>
<sys:String x:Key="Goal_GrowUp">Length</sys:String>
<sys:String x:Key="Goal_Time">Time</sys:String>

<!-- Overlay text -->
<sys:String x:Key="Overlay_Text_ReportGoal">Goals report</sys:String>
<sys:String x:Key="Overlay_Text_Death">You are dead!</sys:String>
<sys:String x:Key="Overlay_Text_Win">Level completed</sys:String>
<sys:String x:Key="Overlay_ButtonText_Return">Return to menu</sys:String>
    
</ResourceDictionary>
XML
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">

<!-- General strings -->
<sys:String x:Key="OK">OK</sys:String>
<sys:String x:Key="Retry">Riprova</sys:String>
    
<!-- Goal names -->
<sys:String x:Key="Goal_FoodEaten">Cibo mangiato</sys:String>
<sys:String x:Key="Goal_GrowUp">Lunghezza</sys:String>
<sys:String x:Key="Goal_Time">Tempo</sys:String>
    
<!-- Overlay text -->
<sys:String x:Key="Overlay_Text_ReportGoal">Riepilogo obiettivi</sys:String>
<sys:String x:Key="Overlay_Text_Death">Sei morto!</sys:String>
<sys:String x:Key="Overlay_Text_Win">Livello completato</sys:String>
<sys:String x:Key="Overlay_ButtonText_Return">Ritorna al menu</sys:String>
    
</ResourceDictionary>

As you can see, in these little dictionaries there are some pairs of strings, one in English an the other in Italian. Important: same strings in different languages must have the same key. Why this? Because when you need a string, e.g. "Retry", you just call this.Resources["Retry"] and you get the localized string. Coming back to the constructor of the Level object, the switch statement is used to fill the resources of the control with the localized strings, so that a call to this.Resources["Retry"] returns "Riprova", if we are in Italy, otherwise, "Retry", in all the other countries.

Come back to the Level object. Maybe now you're asking where the game come to life. Here is the answer: in the UserControl_Loaded() method (handler of the Loaded event of the base class UserControl). Here... uhm... the words are useless now, let the code speak!

C#
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
            
    //Goals message
    MainOverlay.Text = Resources["Overlay_Text_ReportGoal"] as string;
    MainOverlay.AdditionalContent = _goals.BuildReport(32, 22);
    MainOverlay.BackgroundColor = Colors.Orange;
    MainOverlay.Show(new KeyValuePair<string,>(Resources["OK"] as string, () => {

        //Starts the timer
        _beginTime = DateTime.Now;
        _snakeTimer.Start();
        _enemySnakesTimer.Start();

        //Updates rows and columns of the grid
        for (int i = 0; i < _gridSize.Width; i++)
            MainGrid.ColumnDefinitions.Add(new ColumnDefinition());
        for (int i = 0; i < _gridSize.Height; i++)
            MainGrid.RowDefinitions.Add(new RowDefinition());

        //Adds the cells to the grid
        for (int x = 0; x < _gridSize.Width; x++)
            for (int y = 0; y < _gridSize.Height; y++) {
                Cell cell = _cells[x, y];
                MainGrid.Children.Add(cell);
                Grid.SetColumn(cell, x);
                Grid.SetRow(cell, y);
            }

    }));

}</string,>

But... but... what's MainOverlay? It's an OverlayMessage! SnakeMobile.Core.OverlayMessage is a control that allows you to create a sort of "message layer" containing some text, some buttons and an optional content. The Show() methods needs a KeyValuePair<string, Action>[]; the key of each KeyValuePair is the text of the button, and its value is the delegate to invoke when the button is clicked. I've written this control because I sometimes need to ask something to the user, and each action to do when the user clicks on a button is different case by case, so, using an OverlayMessage, I can avoid writing a lot of useless code to manage different handlers.

Now, it's time for the most interesting part of the application: the control of the snake.

Snake movement

In the first part of the previous picture, I've put the snake in a grid (head is at (2, 1)), and we assume that it would like to eat some food on the left.

The direction of the snake is represented by a Point object (stored in the private field _direction). Its coords are only -1, 0, 1, because they represent the variation of the coords between the head cell and the next cell. An example may clear the ideas. The head is at (2, 1) and the user wants to move the snake on the left. The left direction is the point (-1, 0), because if we sum it to the coords of the head, we obtain the coords of the cell that will be the next head (blue circle in the second part of the picture).

(2, 1) + (-1, 0) = (1, 1)

The snake cells are managed using a Queue<Cell>: in the constructor, we add these cells, so that at the top of the queue there's the tail and at the bottom the head.

Explanation of the snake queue

So, to make the snake move, we have to do a lot of actions:

  1. First, we change the CellType property of the last cell, so that it becomes a normal segment of tail
  2. Then, we call Queue<T>.Dequeue(), removing the top element
  3. We update the top cell, so that it becomes the last tail of the snake
  4. Finally, we can enqueue the new head cell. Obviously, the new head cell is calculated using the concept of the direction explained before.
  5. At this point, the actions to do are finished, and we start again from the first point.

I won't write about the code to implement this, because it's simple, but very very long.

However, the SnakeTimer_Tick method (handler of the Tick event of the timer that makes the snake move) does other 2 things of which we haven't spoken about.

What happens when a snake hits a wall? Or how can it grow up? When should the level finish? We'll discover the answer to all these questions in the next episode! No, no, I was joking, and the answer is here, and it's simple, too.

Between the first and the second step of the procedure explained before, there's a little detail that I must show you: after the calculation of the next head cell, we must see what happens when the head hits the next cell.

C#
//Hit test
bool grown = false;
switch (nextCell.HitTest(head))
{
    //Food eaten
    case HitTestResult.Eat:
        _foodEaten++;
        TxtFoodEaten.Text = _foodEaten.ToString();
        grown = true;
        AddFood();
        break;

    //Death
    case HitTestResult.Death:
        KillSnake();
        return;

    //Cut
    case HitTestResult.Cut:
        if (_snakeQueue.Contains(nextCell))
            foreach (Cell cutted in _snakeQueue.Cut(_snakeQueue.IndexOf(nextCell)))
                cutted.CellType = CellType.Free;
        else
            _enemySnakes.ForEach(x => x.Cut(nextCell));
        break;

    //No actions
    case HitTestResult.None:
        break;
}

//Checks if the snake's grown
if (!grown) _snakeQueue.Dequeue().CellType = CellType.Free;

As you can see, we switch on the result of the hit test between the head cell and the next one. If the result is Eat (one of the cells is Food), we increment the field _foodEaten and set the variable grown to false, because this allows us to skip the step number 2 (dequeuing the last tail). If the result is Death (one of the cells is a Wall or a DeathFood), the snake dies and the game ends. If the result is Cut (a snake cuts another snake), we check if the cut snake is the user's snake, otherwise, we try to cut the enemy snakes (we'll talk about the field _enemySnakes later).

The latest unanswered question is "When the game ends?": it depends on when you achieve all the goals of the level. Each level, to be completed, has its own set of goals to achieve. If you remember, the constructor of the Level object needs a parameter representing the goals of the level; these goals are stored in the field _goals. At the end of the SnakeTimer_Tick method, we check them all if they have been completed. If so, we stop all the timers and show a message to the user to inform him that the level has been finished and he has won.

C#
//Checks the goals
GoalEventArgs gea = new GoalEventArgs(_snakeQueue.Count, _foodEaten, DateTime.Now.Subtract(_beginTime));
if (_goals.All(x => x.IsAccomplished(gea))) {
    //Stops the timer
    _snakeTimer.Stop();
    _enemySnakesTimer.Stop();
    //Message
    MainOverlay.AdditionalContent = null;
    MainOverlay.Text = Resources["Overlay_Text_Win"] as string;
    MainOverlay.BackgroundColor = Colors.Green;
    MainOverlay.Show(new KeyValuePair<string,>(Resources["Overlay_ButtonText_Return"] as string, () => RaiseEvent(this.Win, this, gea)));
}</string,>
Go to the top of the article   Go to the top of the article

Enemy snakes

Actually, there's one last aspect of the game of which we haven't spoken about: the enemy snakes. If you notice, some enemy snakes move randomly, and others point directly to a food cell. To solve the problem, I've created the interface which contains the actions that an enemy snake has to do:

IEnemySnake interface

As you can see, there are only one property and 3 methods: the property is the Level object which contains the enemy snake, and it must be set as soon as it's possible. Reset has the task of resetting all the private fields to restore the initial state of the snake; Cut, instead, must cut the snake in the given cell.

The most interesting thing is the Move method: how the name says, this method must make the snake move, and here the 2 types of snakes (randomly moving one, and food catcher one) are different. The RandomlyMovingSnake is the simplest one, because it moves without any logical scope, and, for this, we won't speak about it. On the contrary, we'll explain the FoodCatcherSnake, because it's more complex than the first one, and we don't like  simple things, right? :P

This snake has to point directly to some food cells. But... but... how to find the path to the food? Searching on Google some path finding algorithms, I've found a lot of implementation of the A* (really impressing, you should see one of them in action!), but they were too much for my needs! Luckily, after some other searches I've found this. The only thing I've modified is the return value of the function FindPath from int[,] to IEnumerable<Point>, nothing else. (I advice you to read the article explaining the MazeSolver, otherwise, you couldn't understand the following)

In the constructor we initialize the MazeSolver by setting the walls, i.e. the cells with the CellType property set to Wall or DeathFood. In the Move method, instead, we use the MazeSolver to find the path to follow:

C#
//Checks if the path must be updated
if (_pathToFollow.Count() == 0 ||
    _cells[(int)_pathToFollow.PeekLast().X, (int)_pathToFollow.PeekLast().Y].CellType != CellType.Food) {
                
    //Updates the list of all the food cells
    List<point> lst = new List<point>();
    for (int x = 0; x < _gridSize.Width; x++)
        for (int y = 0; y < _gridSize.Height; y++)
            if (_cells[x, y].CellType == CellType.Food)
                lst.Add(new Point(x, y));
    _foodCells = lst;

    //Checks if there is some food to eat
    if (_foodCells.Count() == 0) throw new Exception();
                
    //Finds the furthest food
    Point furthestFood = _foodCells.OrderBy<point,>(x => Math.Abs((int)x.X) + Math.Abs((int)x.Y)).First();

    //Makes all the segments of the tail of snake wall
    _snakeQueue.ForEach(x => _mazeSolver[Grid.GetColumn(x), Grid.GetRow(x)] = 1);
    _mazeSolver[Grid.GetColumn(head), Grid.GetRow(head)] = 0;

    //Finds the path to follow
    _pathToFollow = new Queue<point>(_mazeSolver.FindPath(Grid.GetColumn(head), Grid.GetRow(head), (int)furthestFood.X, (int)furthestFood.Y).Skip(1));
                
    //Removes the walls from the tail of the snake
    _snakeQueue.ForEach(x => _mazeSolver[Grid.GetColumn(x), Grid.GetRow(x)] = 0);

    //Checks if a path has been found
    if (_pathToFollow.Count == 0) return;

}</point></point,></point></point>

If a new path is needed, first, we find all the cells containing a food, then, we find the furthest one, and store the path in a Queue<Point>. At each call of the Move method, we peek an element from this queue and move the snake at these coords.

Go to the top of the article   Go to the top of the article

Saving and loading levels

As you can see, in the game there are a pinch of default levels; obviously, each of them isn't a different class: in the Levels folder there are some XML files representing the default levels; at runtime, they are loaded by the method  Parse of the LevelParser class, which returns an instance of the Level class, already initialized.

Before we speak about the parsing method, we need to know how an XML file is. Here is the structure:

Schema of the XML files of the levels

The root element Level has a single attribute Speed, representing the speed of the game (the interval between each tick of the main snake timer). Inside it there are a lot of other different types of nodes:

  • Info: this section contains some information about the level, like the title and the description. The IsResource attribute is a value indicating whether the strings contained in the Title and Description nodes are the key to look in the resource dictionary.
  • Goals: container for the goals of the level. Each goal is represented by a Goal node, and its attributes Type and Param are respectively the type of the snake (RandomlyMovingSnake or FoodCatcherSnake) and the parameter to pass to the constructor.
  • Cells: this node is the representation of the initial state of the game grid. The Width and Height attributes are, as their names say, the size of the grid. The content of this node is a bit particular: there is a series of chars put on different lines; each char is a single cell, and this is the legend:
    • # => Free cell
    • W => Wall cell
    • D => DeathFood cell
    • F => Food cell
    • L => LastTail cell
    • T => Tail cell
    • H => Head cell
    • / => EnemyLastTail cell
    • - => EnemyTail cell
    • * => EnemyHead cell
Go to the top of the article   Go to the top of the article

UI and design

So, we arrived here.

The core part of the game is now completed, and it's time to build the graphical part of the game. Here we'll speak about graphical controls, pages, navigation, and some aspects more relevant to the game, like the main menu or the level selection screen.

Navigation

If you are a "Silverlighter", the concept of the navigation isn't new for you, but a revision is never bad, right?

However, let's start from the beginning.

Imagine you're using your browser: you navigate through links to visit pages. When you're bored of the current page, or you've made a mistake, you can press the "Back" button, and you are magically taken to the previous page. The idea is the same for the Windows Phone 7: you use your apps, but you must be able to go back to the previous page, only by clicking the "Back" button.

Don't be afraid! It's not difficult to do this! The most of the work is done by the system: you have only to implement the forward navigation. When you want to change page, is enough calling the Navigate method of the NavigationService class (you can get an instance of this class by accessing the property with the same name belonging to the current Page instance); the parameter is a Uri pointing to the next page.

C#
public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();
        btnNewGame.Click += BtnNewGame_Click;
    }

    private void BtnNewGame_Click(object sender, RoutedEventArgs e) {
        this.NavigationService.Navigate(new Uri("/MyApp;component/Page2.xaml", UriKind.RelativeOrAbsolute));
    }

}

Finished. You have to do nothing else: the "Back" action is automatically handled by the system. Obviously, you can handle this event and change the behaviour.

C#
public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();
        btnNewGame.Click += BtnNewGame_Click;
        BackKeyPress += new EventHandler<System.ComponentModel.CancelEventArgs>(MainPage_BackKeyPress);
    }

    void MainPage_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
    {
        if (MessageBox.Show("Are you sure you want to go back?", "", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel)
            e.Cancel = true;
    }

    private void BtnNewGame_Click(object sender, RoutedEventArgs e) {
        this.NavigationService.Navigate(new Uri("/MyApp;component/Page2.xaml", UriKind.RelativeOrAbsolute));
    }

}

In this case, the only change I've done is a message box which asks to the user if he really wants to go back; if the answer is "ok", nothing happens and the page changes, otherwise, we cancel the event using e.Cancel = true, and the action is canceled.

Go to the top of the article   Go to the top of the article

Transitions

In the previous chapter we've spoken about navigation, but, if you notice, it's very ugly, because the next page is replaced to the previous one without any effects. This is where the Silverlight for Windows Phone Toolkit comes to rescue us. (download link)

The first thing to do (after downloading the toolkit and adding the references to its assembly Microsoft.Phone.Controls.Toolkit), is changing the frame from PhoneApplicationFrame to TransitionFrame. Open App.xaml.cs and change the following in the InitializePhoneApplication method:

C#
// Do not add any additional code to this method
private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;

    // Create the frame but don't set it as RootVisual yet; this allows the splash
    // screen to remain active until the application is ready to render.
    // ------------------------------
    RootFrame = new Microsoft.Phone.Controls.TransitionFrame();   // new PhoneApplicationFrame();
    // ------------------------------
    RootFrame.Navigated += CompleteInitializePhoneApplication;

    // ...
}

But... what have we done? To answer this question, we must go a bit back:

Frame and page navigation
(Image taken from MSDN)

This is the composition of every Windows Phone 7 application. The topmost container is a PhoneApplicationFrame; this object contains a PhoneApplicationPage, which hosts your content. By calling NevigationService.Navigate, we tell the frame to change its content page, so, if we replace the deafult frame with another able to make some transition effects, the trick is done. And this is exactly what we've done before: the TransitionFrame, in fact, is a special frame which animates the pages during the navigation

However, now it's time to choose the transition effects: luckily, even this is simple. The toolkit provides some very useful attached properties that we can set for each page. NavigationInTransition and NavigationOutTransition are, respectively, the animation to use when the user navigates to the page and when the user exits the page. Both of them have 2 important properties: Backward and Forward. The first one is the animation to use during the backward navigation, and the other in the forward navigation.

Page transitions

When you navigate from a page to another, the first one begins a ForwardOut animation, while the second one, a ForwardIn. During a backward navigation, the first page begins a BackwardOut animation, while the second one, a BackwardIn.

Here is the code to use to apply a transition effect on a page:

XML
<phone:PhoneApplicationPage
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit">

  <!-- Navigation IN -->
  <toolkit:TransitionService.NavigationInTransition>
    <toolkit:NavigationInTransition>
      <toolkit:NavigationInTransition.Backward>
        <!-- BackwardIn -->
        <toolkit:TurnstileTransition Mode="BackwardIn"/>
      </toolkit:NavigationInTransition.Backward>
      <toolkit:NavigationInTransition.Forward>
        <!-- ForwardIn -->
        <toolkit:TurnstileTransition Mode="ForwardIn"/>
      </toolkit:NavigationInTransition.Forward>
    </toolkit:NavigationInTransition>
  </toolkit:TransitionService.NavigationInTransition>
  
  <!-- Navigation OUT -->
  <toolkit:TransitionService.NavigationOutTransition>
    <toolkit:NavigationOutTransition>
      <toolkit:NavigationOutTransition.Backward>
        <!-- BackwardOut -->
        <toolkit:TurnstileTransition Mode="BackwardOut"/>
      </toolkit:NavigationOutTransition.Backward>
      <toolkit:NavigationOutTransition.Forward>
        <!-- ForwardOut -->
        <toolkit:TurnstileTransition Mode="ForwardOut"/>
      </toolkit:NavigationOutTransition.Forward>
    </toolkit:NavigationOutTransition>
  </toolkit:TransitionService.NavigationOutTransition>

  <!-- ... Content ... -->
  
</phone:PhoneApplicationPage>

Obviously, you can choose whatever effect you want, instead of TurnstileTransition, like RollTransition, RotateTransition, SlideTransition or SwivelTransition.

Go to the top of the article   Go to the top of the article

Rounded glowing button

However, it's time to come back to our game. Let's start from the main menu!

Main menu of the game

As you can see, this is a menu made up of 3 items (the others are useless): the first one, Continue, is used to resume the current game; New game erases all the data and starts a new game; Settings, instead, navigates to the settings page.

Each item is a Button that I've templated to change its appearance. I've used Microsoft Expression Blend 4 For Windows Phone to achieve this result (this tool is included in the Windows Phone 7 Developer Tools), and this is how I've done. I state that I'm not a designer, I'm only a programmer which tells his experiences with Blend, so, if you want to tell me how to improve my design skills, any advice would be welcome!

If you cannot read everything in the images, you can click them to enlarge them.

  1. Create a new project, and add a new Button on the main page. Right-click the button and select Edit Template > Create Empty

    Making a rounded glowing button with Blend

  2. In the following window, create a new resource dictionary with name "RoundedGlowingButton.xaml" and set the name of the resource to "RoundedGlowingButton".

    Making a rounded glowing button with Blend

  3. Now, you are editing the template of the button. Add a new ellipse and make it fill all the available area, then remove the stroke and bind its Fill property to the Background property of the templated parent (Template binding > Background).

    Making a rounded glowing button with Blend

  4. Duplicate the ellipse and change its Fill property: set a radial brush from white to transparent.

    Making a rounded glowing button with Blend

  5. Select the Gradient tool and modify the brush to obtain something like this (the light must seem coming from the bottom).

    Making a rounded glowing button with Blend

  6. Duplicate again the first ellipse and set its Fill to white, then change its Opacity to 20%.

    Making a rounded glowing button with Blend

  7. Right-click the last ellipse and convert it to a Path object (Path > Convert to Path).

    Making a rounded glowing button with Blend

  8. Select the Direct Selection tool and change the points of the path to make them like this.

    Making a rounded glowing button with Blend

  9. Now, it's time to show the content of the button: open the other assets, type "content" in the search box and select the ContentPresenter control.

    Making a rounded glowing button with Blend

  10. Add a new ContentPresenter and center it horizontally and vertically, then bind its Content property to the Content property of the templated parent.

    Making a rounded glowing button with Blend

  11. Duplicate another time the first ellipse we've created and place it on the back, then make it a little bigger by setting a scaling of 1.5.

    Making a rounded glowing button with Blend

  12. Change its Fill property to a radial brush from white to transparent, then select the Gradient tool and set the white gradient stop on the border of the button, and the other one a bit farther.

    Making a rounded glowing button with Blend

  13. Now, the most complex part is finished! But what if we add some animations? I think it's not a bad idea! So, select the States tab and set the default transition duration to 0.1 seconds; click on the arrow near "Pressed" and select * > Pressed.

    Making a rounded glowing button with Blend

  14. Now click "Pressed". As you can see the whole drawing area has a red border now: this means that Blend is recording the changes to transform in storyboards, I mean, in this way, every change we make, is changed in storyboard, which is played when the button enters the selected state ("Pressed" in our case). In other words, when we click the button, is played a storyboard which applies the changes we've made using Blend. If you want to see a preview of this storyboard you can activate the button Transition preview and select different states: Blend automatically plays these storyboards and you can see their effects directly in the drawing area. However, now that you've selected the Pressed state, select the InnerGlow ellipse and change the first gradient stop of the fill to #FF3D71D8 (or whatever color you want).

    Making a rounded glowing button with Blend

Go to the top of the article   Go to the top of the article

Triggers

Another interesting thing you can notice in the main menu is the triggers. But, first of all: what is a trigger?

A trigger is an object which calls an action when an event is fired. An example here may clean the ideas: i.e., we want to make a trigger that allows us to navigate to a page when the user clicks on a button (like in the application).

Trigger explanation

So, in order to achieve the result, we have to do some things:

  1. First of all, we have to make us sure that there is a reference to the assembly System.Windows.Interactivity, because everything we are talking about is residing into this assembly
  2. Then we have to make our custom action: let's create a class which inherits from TriggerAction<T>, where T is the type of the object to which this action can be attached, in our case T is Button.
    C#
    public class NavigationTrigger : TriggerAction<Button>
    {
    
        #region Uri property
    
        public static readonly DependencyProperty UriProperty = 
            DependencyProperty.Register("Uri", typeof(Uri), typeof(NavigationTrigger), new PropertyMetadata(null));
        public Uri Uri
        {
            get { return (Uri)GetValue(UriProperty); }
            set { SetValue(UriProperty, value); }
        }
    
        #endregion
            
        protected override void Invoke(object parameter)
        {
            //Navigates
            ((App)Application.Current).RootFrame.Navigate(this.Uri);
        }
    
    }

    As you can notice, there are the dependency property Uri and the overriding method Invoke. The most important part of the class is the method: it's what's called when the event is fired. Here we write the heart of the trigger, so, I've written the code for the navigation to the uri indicated by the Uri property.

  3. Now the code behind is finished; what we must do is, from now on, edit only the XAML code. So, let's open our XAML file and add some new xmlns:
    C#
    <phone:PhoneApplicationPage
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:SnakeMobile">
    
        ...
    
    </phone:PhoneApplicationPage>
  4. At last, we must change the code of our button in this way:
    C#
    <Button Content="Settings">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <local:NavigationTrigger Uri="/SnakeMobile;component/Settings.xaml" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>

    With this code, we register an EventTrigger for our button which, when the event Click is fired, calls our action NavigationTrigger which navigates to /SnakaMobile;component/Settings.xaml

As you've seen, we've created with a few lines of code an unit of code that we can use wherever we want, only by using the XAML code. Creating a trigger only for a navigation action, can seem wasted time, but I've done so because I want to explain you some of the features of Silverlight for Windows Phone, even if they can lead me to write some useless code. Imagine a big business application: the use of the triggers leads you to waste a lot less time.

However, if you want to deepen the concept of the triggers, I advice you this article, which talks about behaviors, too: Silverlight and WPF Behaviours and Triggers - Understanding, Exploring And Developing Interactivity using C#, Visual Studio and Blend

Go to the top of the article   Go to the top of the article

A little math refresher: the polar coordinate system

Before starting to speak about the circular selector, we have to do a little math refresher.

Let's start from a thing everybody knows: the Cartesian plane. This is what Wikipedia says:

"A Cartesian coordinate system specifies each point uniquely in a plane by a pair of numerical coordinates, which are the signed distances from the point to two fixed perpendicular directed lines, measured in the same unit of length. Each reference line is called a coordinate axis or just axis of the system, and the point where they meet is its origin."

Translated into a human language, this means that in our plane we have two perpendicular lines (called axis), and each point of the plane is identified by a pair of numbers indicating, respectively, the projection of the point on the X-axis, and on the Y-axis.

Cartesian plane example

In this example, I've put 4 points on the plane: the text near each of them represents the coordinates of the point. E.g., the blue point is (4, 5) because, horizontally, it's 4 units far from the origin, and, vertically, 5 units.

But you should already know this things, right? Obviously, the answer is yes, so we go straight forward to our new system: the polar coordinates system. As usual, let's see what wikipedia says:

"In mathematics, the polar coordinate system is a two-dimensional coordinate system in which each point on a plane is determined by a distance from a fixed point and an angle from a fixed direction."

This may seem more complex than the Cartesian system, but analyze better the text: we have a fixed point and a fixed direction from which we start measuring the angles. Each point of the plane now has 2 different coordinates type: a number and an angle.

Polar coordinate system

The blue point: the first number (7) is the distance from the fixed point (center of the system) and the second one (60°) is the angle between the point and the fixed direction (horizontal line). For the green point, the previous graph is self-explicative.

When we start speaking about curves on the plane, we can define each point as:

Generic polar point

Here, θ is the angle of the point and r(θ) is a function returning the distance of the point from the origin.

However, why have I told you all these things? Why should I use the polar system instead of the Cartesian one? Sincerely, I don't know if there is an accurate way to choose between them, but I think I can give you an example to make you understand what I think: the circle. Imagine a circle with the center on the origin: these are the two equations.

Cartesian plane:

Equation of the circle in a Cartesian plane

Here, we have an equation of the second degree, where r is the radius of the circle. (Not impossible, but ugly, isn't it?)

Polar system:

Equation of the circle in a polar system

Here, the equation is very simple: for each value of theta, we return the radius of the circle. This means that for each angle, the distance from the fixed point is always the radius of the circle.

The example of the circle is simple, but try to imagine something a bit more complex, e.g. a spiral:

Archimedean spiral   (Image taken from Wikipedia)

I don't know if there is a way to do the same thing in Cartesian coordinates, but I know that this is the polar equation of this Archimedean spiral:

Polar equation of the Archimedean spiral

Simple, isn't it? Changing the parameter a will turn the spiral, while b controls the distance between the arms.

The polar system is cool, but... how can I implement this system in my application?? The .NET Framework understands only Cartesian coordinates!! The problem is quickly solved: we have only to make a conversion function from the polar system to the Cartesian one.

Polar to Cartesian

The reason of these 2 equations is really simple:

Polar to Cartesian   (Image taken from Wikipedia)

As you can see, our point with polar coordinates (r, θ) can be placed in a Cartesian plane; using the trigonometric functions sine and cosine we can calculate the projections of the point on the X-axis and on the Y-axis.

But now, it's time to let the code speak! This is the conversion function:

C#
private Point PolarToCartesian(double r, double theta) { 
    return new Point(r * Math.Cos(theta), r * Math.Sin(theta)); 
}

And now? Nothing. We can finally start talking about our CircularSelector.

Go to the top of the article   Go to the top of the article

Circular selector

Circular selector screen shot

And so, this is a screenshot of our control; basically, it's made up of a certain number of items (circular sectors), and each of them is customizable by setting header, color or visibility. However, this is the punctual list of every interesting property of this control (the properties are dependency properties, so you can bind them):

  Property name Property type Description
Property SweepDirection SweepDirection

One of the values of the System.Windows.Media.SweepDirection enum indicating the sweep direction (clockwise or counterclockwise)

Property LinesBrush Brush

Brush used to draw the border of each item

Property AlwaysDrawLines bool

Gets or sets a value indicating whether the border of each item should be drawed even if it's not visible

Property SelectedItemPushOut double

Gets or sets a value indicating the amount of pixels of which the selected item must go out

Property FontFamily FontFamily

Family of the font used for the headers

Property FontSize double

Size of the font used for the headers

Property FontStyle FontStyle

Style of the font used for the headers

Property FontWeight FontWeight

Weight of the font used for the headers

Property Foreground Brush

Brush used to paint the foreground of the headers

Property ItemsSource CircularSelectorItem[]

Array containing all the elements

Property SelectedItem CircularSelectorItem

Gets or sets the currently selected item

Each element is a CircularSelectorItem object; this object has the following properties (even these are dependency property):

  Property name Property type Description
Property Color Color

Gets or sets the color of the item

Property IsVisible bool

Gets or sets the visibility of the item

Property Header string

Gets or sets the header text of the item

Property Tag object

Gets or sets an object containing some additional informations

Well, now our small overview is finished, we can finally start talking about code! The CircularSelectorItem is boring: it's only a simple class with some properties! So, we'll directly go to the CircularSelector object. Where can we start? Maybe from the class diagram? Yes, I think so!

Circular selector

As you can see, there are a lot of properties and methods: their meaning has already been explained, but now I'll dig deeper in the UpdatePaths method. This method updates all the circular sectors of the control; it's called whenever the control changes its size, its ItemsSource or its SweepDirection. Its code is really complex, so, we'll split it in a lot of single pieces:

  1. First thing to do: clean all the existing objects.
    C#
    ClearPaths();
    The function ClearPaths makes only a call to base.Children.Clear() (remember that CircularSelector inherits from Panel)
  2. We check if there is at least one item in the items source
    C#
    if (this.ItemsSource == null || this.ItemsSource.Count() == 0)
        return;
  3. Now we can calculate and store some useful information, like the measure of the angle of each sector, the available size and the radius of the circle
    C#
    //Calculates the angle of each item
    double angle = Math.PI * 2 / this.ItemsSource.Count();
                
    //Calculates the size of the selector
    double size = Math.Min(this.ActualWidth, this.ActualHeight);
    if (double.IsNaN(size) || size == 0)
        return;
    double radius = size / 2;
  4. Here it's a bit more complex:
    C#
    //Finds the points
    double cumulativeAngle = 0;
    List<Point> pointList = new List<Point>();
    angles = new Dictionary<int, double>();
    for (int i = 0; i < this.ItemsSource.Count(); i++) {
        pointList.Add(PolarToCartesian(radius, cumulativeAngle)
                      .Multiply((this.SweepDirection == SweepDirection.Counterclockwise ? 1 : -1), -1)
                      .Offset(radius, radius));
        angles.Add(i, cumulativeAngle);
        cumulativeAngle += angle;
    }
    Here we find the points of the circumference that we'll use later to draw the arcs. Maybe a drawing may help... In a few words, we are looking for the red points:

    Circular selector - fig 0

    Another strange thing, is the first line inside the for statement: there are 2 extension methods I've created to help the manipulation of the points. Here is the code:
    C#
    public static Point Offset(this Point p, double x, double y) { p.X += x; p.Y += y; return p; }
    public static Point Multiply(this Point p, double x, double y) { p.X *= x; p.Y *= y; return p; }
    The code is really simple and need no explanations. But the interesting thing is because I've done so. Let's start from the origins: in our control the origin is on the center of the container and the Y-axis is directed downwards. But what we really have when using the polar coordinates system is a bit different: first, the origin of the system is the upper left corner of the container and, then, the Y-axis is upwards. So, we have to do some transformations to our points, in particular:

    Circular selector - fig 1

    So, the first image is our initial situation (described above); the second one tells us to invert the Y-axis; the last one, instead, is the offset of the origin. Translated in code, this means that:
    1. We have to convert our polar coordinate in Cartesian one
    2. We have to multiply the value of the Y coordinate of the point by -1
    3. If the sweep direction is clockwise, we need to multiply even the X coordinate by -1
    4. And at last, we have to offset our point to make it center
    Another strange thing, is the second line inside the for statement: here we store our angle in a dictionary using as key the index of the sector. This may seem a bit useless now, but later we'll see that it's not so.
  5. #c
    //Creates the paths
    for (int i = 0; i < this.ItemsSource.Count(); i++ ) {
    
        //Item
        CircularSelectorItem item = this.ItemsSource.ElementAt(i);
    
        //Skips the item if it's not visible
        if (this.AlwaysDrawLines == false && item.IsVisible == false)
            continue;
    
        //Creates a new path and a geometry
        Path path = new Path() { 
            Stroke = this.LinesBrush,
            StrokeThickness = 1,
            Fill = (item.IsVisible ? CreateBackgroundBrush(item.Color, i) : new SolidColorBrush(Colors.Transparent)) 
        };
        PathGeometry geom = new PathGeometry();
        PathFigure fig = new PathFigure();
        geom.Figures.Add(fig);
        path.Data = geom;
    
        //Draws the sector
        fig.StartPoint = new Point(radius, radius);
        Point p1 = pointList[i];
        Point p2 = pointList[(i + 1 == pointList.Count ? 0 : i + 1)];
        fig.Segments.Add(new LineSegment() { Point = p1 });
        fig.Segments.Add(new ArcSegment() { 
            Point = p2, 
            Size = new Size(radius, radius), 
            IsLargeArc = angle > Math.PI, 
            SweepDirection = this.SweepDirection, RotationAngle = 0 
        });
        fig.Segments.Add(new LineSegment() { Point = fig.StartPoint });
        base.Children.Add(path);
        path.Tag = new object[] { item, null };
        if (item.IsVisible) path.MouseLeftButtonUp += Path_MouseLeftButtonUp;
    
        //Creates the content of the item
        if (item.IsVisible) {
            //Creates the textblock
            TextBlock txt = new TextBlock() { 
                Text = item.Header, 
                Foreground = this.Foreground,
                FontFamily = this.FontFamily, 
                FontSize = this.FontSize, 
                FontStyle = this.FontStyle,
                FontWeight = this.FontWeight 
            };
            Point middlePoint = new Point((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
            txt.Margin = new Thickness(middlePoint.X - txt.ActualWidth / 2, middlePoint.Y - txt.ActualHeight / 2, 0, 0);
            base.Children.Add(txt);
            txt.MouseLeftButtonUp += Path_MouseLeftButtonUp;
            //Sets the tag of the path
            ((object[])path.Tag)[1] = txt;
            txt.Tag = path.Tag;
        }
    
    }
    This may be the most complex part of the method. The first lines are simple: we iterate through all the items in the ItemsSource and check if we have to draw the current item. From here on, it's a bit more complex. First, we create a new Path object and set its properties (CreateBackgroundBrush is simple function which transforms a Color object into a Brush); then, we create a new PathGeometry with a PathFigure. The next block draws the circular sector:

    Circular selector - fig 2

    The starting point is the center of the control. From that point, we create a line to the arc start point (we've found every one of them a bit earlier, in the previous code block). Then, we draw an arc from the start point to the end point, and, finally, we can close our path by adding the last line from the arc end point to the starting point. To understand the logic of the ArcSegment object, I advice you this excellent post called "The Mathematics of ArcSegment".

    If the item is visible, we add a handler for its MouseLeftButtonUp event; in the handler we only change the SelectedItem property to the clicked item. Only if the item is visible, we create a new TextBlock to display the header of the sector; then we add the same handler for the MouseLeftButtonUp event.

    If you've noticed, we set the same Tag property of the 2 objects to an array of objects containing the istance of the CircularSelectorItem represented by the sector, and the TextBlock used for the header. You'll discover later because I've done so, when we'll speak about the SelectedItem property.

We've finally finished the UpdatePaths method, and we can immediately start analyzing another focal point of the control: the SelectedItem property.

C#
public CircularSelectorItem SelectedItem
{
    get { ... }
    set { 
        
        //Gets the value
        CircularSelectorItem current = SelectedItem;

        //Cheks if it's changed
        if (value != current && base.Children.Count > 0) {

            //Checks if the ItemsSource contains the new element
            if (ItemsSource == null || ItemsSource.Contains(value) == false)
                throw new ArgumentException("Item not in the ItemsSource");

            //Temp vars
            object[] obj = new object[] { };
            Path path = null;
            int i = 0;

            //Animates the current item
            if (current != null) {
                path = base.Children.OfType<Path>().First(x => ((object[])x.Tag)[0] == current);
                obj = (object[])path.Tag;
                i = this.ItemsSource.IndexOf((CircularSelectorItem)obj[0]);
                AnimatePath(path, false, i);
                if (obj[1] != null) AnimatePath((FrameworkElement)obj[1], false, i);
            }

            //Animates the new value
            path = base.Children.OfType<Path>().First(x => ((object[])x.Tag)[0] == value);
            obj = (object[])path.Tag;
            i = this.ItemsSource.IndexOf((CircularSelectorItem)obj[0]);
            AnimatePath(path, true, i);
            if (obj[1] != null) AnimatePath((FrameworkElement)obj[1], true, i);

            //Stores the value
            SetValue(SelectedItemProperty, value);

            //Raises the event
            OnSelectionChanged(new SelectionChangedEventArgs(
                new List<CircularSelectorItem>(new CircularSelectorItem[] { current }),
                new List<CircularSelectorItem>(new CircularSelectorItem[] { value })
            ));

        }

    }

}

Once we've stored in a variable the current item, we check if the previous value is different from the new one and if there is at least one path already drawn. If so, we check if the ItemsSource contains the new item, and we throw an exception if it's false. After all these tests, we animate the current selected path and the new one. To find the Path object representing a CircularSelectorItem, we look for the Path object with Tag property containing that item. In the end, we store the new value and raise the event.

A nice feature of the CircularSelector are the animations used when the selected item changes. This is done by the AnimatePath method:

C#
private void AnimatePath(FrameworkElement path, bool isSelected, int i) {
            
    //Checks if there are the angles
    if (angles == null || angles.Count == 0)
        return;

    //Creates a storyboard
    Storyboard story = new Storyboard();
            
    //Creates a TranslateTransform
    TranslateTransform transform = new TranslateTransform();
    path.RenderTransform = transform;
            
    //Calculates the end point
    double radius = Math.Min(this.ActualWidth, this.ActualHeight) / 2;
    double middleAngle = angles[i] + Math.PI * 2 / this.ItemsSource.Count() / 2;
    Point endPoint = 
        PolarToCartesian(this.SelectedItemPushOut, middleAngle)
        .Multiply((this.SweepDirection == SweepDirection.Counterclockwise ? 1 : -1), -1)
        .Offset(radius, radius);
    Point difference = endPoint.Offset(-radius, -radius);

    //Creates the animation
    DoubleAnimation animX = new DoubleAnimation() { 
        From = (isSelected ? 0 : difference.X), 
        To = (isSelected ? difference.X : 0), 
        Duration = TimeSpan.FromMilliseconds(200) 
    };
    Storyboard.SetTarget(animX, transform);
    Storyboard.SetTargetProperty(animX, new PropertyPath("X"));
    DoubleAnimation animY = new DoubleAnimation() { 
        From = (isSelected ? 0 : difference.Y), 
        To = (isSelected ? difference.Y : 0), 
        Duration = TimeSpan.FromMilliseconds(200) 
    };
    Storyboard.SetTarget(animY, transform);
    Storyboard.SetTargetProperty(animY, new PropertyPath("Y"));

    //Begins the animation
    story.Children.Add(animX);
    story.Children.Add(animY);
    story.Begin();

}

This method is really simple: first, we create a new TranslateTransform and we add it to the path, then we calculate the end point (destination point) and, at last, we do the difference with the center to calculate the difference along the X and Y axis. After that, we set the animations and begin the storyboard.

Circular selector - fig 3

Go to the top of the article   Go to the top of the article

Isolated storage: files and settings

Even this time, if you are a "Silverlighter", the isolated storage shouldn't be new for you, but a little refresher is never bad, isn't it?

Let's analyze the term: "storage" means that it's a sort of repository, and "isolated" means that is only for your application. So, the isolated storage is a repository where you can put your files and your folders, and it's not accessible by other applications, as you cannot access the storage of the other applications.

The use of the isolated storage is compulsory, because there's no way to store persistent data: Windows Phone 7, in fact, doesn't allow you to write or read outside of your isolated storage. From MSDN:

Isolated storage enables managed applications to create and maintain local storage. The mobile architecture is similar to the Silverlight-based applications on Windows. All I/O operations are restricted to isolated storage and do not have direct access to the underlying operating system file system. Ultimately, this helps to provide security and prevents unauthorized access and data corruption.

Application developers have the ability to store data locally on the phone, again leveraging all the benefits of isolated storage including protecting data from other applications.

In other words, this is the schema representing the isolated storage:

Isolated storage explanation

Every necessary class to manage the isolated storage resides into the System.IO.IsolatedStorage namespace.

The first thing to do is obtaining a reference to the storage scoped to our application; to achieve this goal, we call the static method GetUserStoreForApplication of the class IsolatedStorageFile. The object we obtain is a sort of "file system manager": in fact, with this, we can create or delete file and folders, read and write files. To write data in a file, we must create a new file and a stream: we call the OpenFile method to get a stream that we'll pass to the constructor of the StreamWriter class. Once that's done, we can use the StreamWriter to write data in our file. Translated in code, this means:

C#
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream fileStream = storage.OpenFile("MyFile.txt", System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
using (StreamWriter writer = new StreamWriter(fileStream))
    writer.Write("Hello from isolated storage!");

I advice you to use the using statements, because they allow you to save a lot of time, avoiding a lot of calls to Dispose and Close to clean up the memory and close all the streams.

To read data from a file, instead, the procedure is very similar to the previous one:

C#
string readText = "";

using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream fileStream = storage.OpenFile("MyFile.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read))
using (StreamReader reader = new StreamReader(fileStream))
    readText = reader.ReadToEnd();

The only differences are that this time we use a StreamReader instead of a StreamWriter, and that the file is opened using System.IO.FileAccess.Read instead of System.IO.FileAccess.Write.

The isolated storage can be easily used to store the settings of our application, i.e., in a XML file. We can create a new XML file, add nodes, write values, read them later, etc... This procedure is simple, but a bit long. If you've noticed, in the previous schema explaining the isolated storage, I've divided the settings from all the other things. I've done so because we can treat the settings in a different (and simpler) manner.

Wouldn't be simpler treating the settings like a common dictionary, avoiding I/O actions? Here is where the IsolatedStorageSettings class comes to rescue us. By the static property ApplicationSettings, we obtain the settings scoped to our application; the returned object is a IDictionary<string, object>, already filled with our settings! An example here may clean the ideas:

C#
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;

settings["String"] = "value";
settings["Bool"] = true;
settings["DateTime"] = DateTime.Now;

settings.Save();

This means that, the first time that our application is launched, our settings are empty and we add new keys to our dictionary, like a common dictionary. In the end, we save all the settings by calling the Save method. Remember to call this method, otherwise, nothing will be saved, because these are all in-memory changes! The next time that our code is executed, we call IsolatedStorageSettings.ApplicationSettings and we obtain an IsolatedStorageSettings object already filled with our data! The rest of the code, so, updates the dictionary and saves it again.

Go to the top of the article   Go to the top of the article

Settings

The settings page is particular because is the example of a strange thing. But before, we should talk about the code behind! This is the diagram of the SnakeMobile.Core.Settings.Settings class:

Settings class

Let's take a general look to this class: it inherits from DependencyObject because Acceleration, Sound, SondFx and TotalTimePlayed are dependency property; OnAccelerationChanged, OnAccelerationChanged, OnAccelerationChanged and OnAccelerationChanged are the callback methods for the changing of the previous dependency properties. Load and Save methods allow us to load and save the settings to the isolated storage; RestoreDefaultSettings, instead, restores all the default settings.

An interesting property is Instance, but before of speaking about it, we must go a little back in the time, before this class was created.

I need a class which can hold the settings of my application, so a static class with static properties is perfect, because the settings must be valid for the whole application, not for a single instance of the class. But I must remember that I'm using XAML, so, the binding is compulsory if I want to save code, and I MUST save code, so I need a class with bindable properties. This class, so, must expose some dependency properties (and so it must inherit from DependencyObject), and must be static. In other words, I must only write some static dependency properties inside a static class!

EH?!

What I've just said is impossible! I can't bind to static properties, so I need to bind to instance properties; instance properties means constructor, and constructor CAN'T mean static class! The problem, so, is bigger that what I've thought... How can I bind to a static property inside a static class?

think... think... think... EUREKA!

The static class mustn't be static, but must act as if it was! The settings class mustn't be static and all the properties must be normal instance properties. In this way, it can inherit from DependencyObject and the properties can be dependency properties. Now, I don't make the class static, but, instead, I make it singleton: I make the constructor private, and I create a static read-only property called "Instance", and a static field "_instance". This property must return the "_instance" field, and, if the field is null, sets it to a new instance of the Settings class, and then returns "_instance".

 Translating in code what I've just said, this is the most important part:

C#
public class Settings : DependencyObject
{
        
    //Private constructor
    private Settings() { }

    //Static instance
    private static Settings _instance = null;

    /// <summary>
    /// Instance of the class
    /// </summary>
    public static Settings Instance {
        get {
            if (_instance == null)
                return _instance = new Settings();
            return _instance;
        }
    }


    // ...
    // Other code ...
    // ...


}

But the story isn't finished!

Now, the trick is done! In my Settings page, I set the DataContext to the instance of the class, so that I can use binding inside XAML!

This means that in the Settings.xaml.cs (code behind file of Settings.xaml) I must add only this line of code...

C#
public partial class Settings : PhoneApplicationPage
{
    public Settings()
    {
        //Component initialization
        InitializeComponent();
        //Data context
        this.DataContext = SnakeMobile.Core.Settings.Settings.Instance;
    }
}

... to be able to use two-way binding inside XAML:

XML
<toolkit:ToggleSwitch x:Name="ToggleSound" IsChecked="{Binding Sound, Mode=TwoWay}" />

In that way, without writing any code to handle the events of the controls, we can write a complete settings page, only using bindings.

About the graphical controls, I should tell you something about the Pivot, but I'm not a designer, so, I advice you to read this article of Jeff Wilcox, if you want to know something about this control and its brother PanoramaPanorama and Pivot controls for Windows Phone developers

Go to the top of the article   Go to the top of the article

Build action: Content or Resource?
(explanation about Visual Studio)

Before we start talking about the audio, I need to make me sure you have understood the difference between Content and Resource. If you already know the difference, you can skip this paragraph and go to the next one, about the audio.

This paragraph talks about a little difference, but that gave me some trouble; and I want to make me sure you've understood the difference between them, because I don't want you to waste a lot of time, as I did. But let's start from the beginning:

Screenshot of Visual Studio explaining the Build action property

When you click on an element in the Solution Explorer of Visual Studio, in the properties window you can see the property of the element you've clicked. The one we'll analyze is the Build Action. Its values can be a lot, but we'll focus on Resource and Content, because they are the most common (and most confused).

  • Resource

    When you use this value, the file you've selected (e.g. 0.xml) is included in the resources of the DLL, instead of the XAP package.

    Build Action Resource

    In fact, if you take a look into the XAP package (using WinRAR or any other program to manage archives) you can notice that there is no file with the name of 0.xml:

    Build Action Resource

    But if you decompile the DLL (using the .NET Reflector) you can see that it's in the resources:

    Build Action Resource

  • Content

    Content, instead, is the opposite of Resource: the file (e.g. Music.wav), if Build Action is set to Content, is included inside the XAP package.

    Build Action Content

    Inside the DLL, in fact, there isn't a Music.wav...

    Build Action Content

    ... because it's inside the XAP package:

    Build Action Content

To close this paragraph, I want to give you 2 advices when you use Content:

  1. Notice how the structure of the folders that you have in Visual Studio is recreated inside the XAP package.
  2. This options produces these effects only if you are working on the WP7 project. I mean, if you have 2 projects (like in this application), one that is the Windows Phone application, and the other that is a simple DLL, if you set the Build Action to Content of a file inside the DLL, you get no effect (the file isn't inside the XAP package).
Go to the top of the article   Go to the top of the article

Audio & XNA

One last thing before we start: we must remember that we are programming a Silverlight application, so, to use XNA, we must do some tricks. If you write an XNA application from the ground up, you have to do nothing, but if you want to make XNA and Silverlight work in a single Silverlight application, it's enough a little trick. Here it is: XNA is a big framework, with a lot of objects, and who knows what's behind what we see! But one thing is sure: XNA needs that its internal messages are processed. XNA is able to do this automatically, if the application is written entirely for XNA, but this isn't our case, so, what can we do? Simple: we must write a class that dispatches the messages of the XNA framework! Luckily, exists the method Microsoft.Xna.Framework.FrameworkDispatcher.Update, which dispatches the message for us, so, the only thing we have to do is calling regularly this method.

C#
public class XNAAsyncDispatcher : IApplicationService
{
    private DispatcherTimer frameworkDispatcherTimer;

    public XNAAsyncDispatcher(TimeSpan dispatchInterval)
    {
        this.frameworkDispatcherTimer = new DispatcherTimer();
        this.frameworkDispatcherTimer.Tick += new EventHandler(frameworkDispatcherTimer_Tick);
        this.frameworkDispatcherTimer.Interval = dispatchInterval;
    }

    void IApplicationService.StartService(ApplicationServiceContext context) { this.frameworkDispatcherTimer.Start(); }
    void IApplicationService.StopService() { this.frameworkDispatcherTimer.Stop(); }

    void frameworkDispatcherTimer_Tick(object sender, EventArgs e) { Microsoft.Xna.Framework.FrameworkDispatcher.Update(); }
}

public partial class App : Application
{
    public App()
    {
            
        // ...

        //Enables the XNA Async Dispatcher
        this.ApplicationLifetimeObjects.Add(new XNAAsyncDispatcher(TimeSpan.FromMilliseconds(50)));

        // ...

    }
}

(If you don't know what an application lifetime object is, I advice you to read this short post of Shawn Wildermuth: The Application Class and Application Services in Silverlight 3)

So, this is the code. In the constructor of our dispatcher (XNAAsyncDispatcher) we create a DispatcherTimer which calls the FrameworkDispatcher.Update method. Nothing else, this is the dispatcher class. The last step is adding it to the collection of the lifetime objects, so, in the constructor of the App class, we add a new XNAAsyncDispatcher to the ApplicationLifetimeObjects list.

Well... now that XNA is ready to give us full power, we can start!

What we have to do first, is knowing that all the audio we have can be divided in songs and sound effects: the first ones are a continuous piece of music, while the second ones are a brief sound played in specific moments. For instance, a song is the background music of a game, while a sound effect is the noise played when the snake eats some food.

When we start using the XNA framework to play music, we must be careful to follow the Windows Phone 7 Application Certification Requirements.

6.5.1 Initial Launch Functionality

When the user is already playing music on the phone when the application is launched, the application must not pause, resume, or stop the active music in the phone MediaQueue by calling the Microsoft.Xna.Framework.Media.MediaPlayer class.

If the application plays its own background music or adjusts background music volume, it must ask the user for consent to stop playing/adjust the background music (e.g. message dialog or settings menu).

This requirement does not apply to applications that play sound effects through the Microsoft.Xna.Framework.Audio.SoundEffect class, as sound effects will be mixed with the MediaPlayer. The SoundEffect class should not be used to play background music.

In a few words, you should check that the user isn't already playing music, otherwise, you should ask him for consent to stop his music and play yours. Another important point is that, you should play sound effects like sound effects, because the music cannot be mixed, while the effects can be simultaneously played.

How to check if the user is already playing some music? If Microsoft.Xna.Framework.Media.MediaPlayer.GameHasControl is true, you can play your music, because the user is listening to nothing; if it's false, you should ask him for the consent to stop the background music. However, we'll see later an example of this.

If we have two different classifications of the audio, we'll have two different ways to manage them using XNA: to play songs, we'll use the Song object, and to play sound effects, we'll use SoundEffect. They reside inside the Microsoft.Xna.Framework.Audio and Microsoft.Xna.Framework.Media namespaces, but first of all, we need to add a reference to the main assembly of the XNA framework Microsoft.Xna.Framework.dll:

Adding a reference to Microsoft.Xna.Framework.dll

Another important thing to do is preparing the audio: the audio files must be in WAV format to work with XNA, even if MP3 is now supported by the Song object (not by SoundEffect). Another important thing is that, in Visual Studio, we must set the Build Action property of the audio file to Content.

But now it's time to let the code speak:

C#
//Loads a song
Song song = Song.FromUri("BackgroundMusic", new Uri("Sounds/Music.wav", UriKind.RelativeOrAbsolute));

//Loads a sound effect
SoundEffect soundEffect = SoundEffect.FromStream(TitleContainer.OpenStream("Sounds/Death.wav"));

To create a Song from a file, we call the static method FromUri of the Song object; the first parameter is the name of the song, while the second one is the Uri of the file, relative to the XAP package (remember that we've set Build Action to Content). To load a SoundEffect, instead, we call the static method FromStream of the class SoundEffect; the parameter is the stream containing the audio file. TitleContainer.OpenStream returns a Stream pointing to a file inside the XAP package.

Once we've created our objects, we have to play them, isn't it? To play a SoundEffect, is enough call its Play method; while to play a Song we need a media player, so, we must call MediaPlayer.Play and pass as parameter our Song object.

C#
//Plays the song
MediaPlayer.Play(song);

//Plays the sound effect
soundEffect.Play();

Coming back to our application, the class which is responsible of managing the sounds is SnakeMobile.Core.Sounds:

Sounds class

The 3 properties are the paths of the audio files (relative to the XAP package). These properties are initialized inside the App.xaml.cs file:

C#
public partial class App : Application
{
    public App()
    {
            
        // ...

        //Sounds
        SnakeMobile.Core.Sounds.BackgroundMusicUri = new Uri("Sounds/Music.wav", UriKind.RelativeOrAbsolute);
        SnakeMobile.Core.Sounds.DeathSoundPath = "Sounds/Death.wav";
        SnakeMobile.Core.Sounds.EatSoundPath = "Sounds/Eat.wav";

        // ...

    }
}

The other methods, instead, are divided into 2 categories: PlayBackgroundMusic and StopBackgroundMusic are used, as their name says, to play and stop background music; PlayEffect, instead, plays a single sound effect.

C#
public static void PlayBackgroundMusic() {

    //Checks if sound is enabled
    if (Settings.Settings.Instance.Sound == false)
        return;

    //Plays the song
    if (BackgroundMusicUri != null && MediaPlayer.GameHasControl) {
        MediaPlayer.IsRepeating = true;
        MediaPlayer.Play(Song.FromUri("BackgroundMusic", BackgroundMusicUri));
    }

}

For instance, let's analyze PlayBackgroundMusic (the others are very similar): the first thing to is checking if the user has enabled the music inside the game or has disabled it by the Settings page. Then we check if the user isn't playing any music (MediaPlayer.GameHasControl); at this point, we can play the song and enable the repeating.

Go to the top of the article   Go to the top of the article

Portable components

What do you want from a programming article?

You may want 2 things: you may want to know something theoretical, like a design pattern, a way to solve a problem and some other things like these; or you may want to take some components to import them in your project. For the first one, the solution is reading the article, which explains how I've built this application from the ground up; for the second one, instead, I can give you a list of all the independent component you can take away from this project and use in yours. Remember that you are free to use, modify and improve the code of the whole application, but if you apply a modification to one of these components, please, let me know, so that I can update the project and let other people take advantage of the improvements.

Here is the list:

  • Circular selector
    The control used in the page of the level selection to select the level.

    /SnakeMobile/InternalControls/CircularSelector.cs
    /SnakeMobile/InternalControls/CircularSelectorItem.cs
  • Line CheckBox
    Style for the CheckBoxes used in the playing page to enable or disable an option using a line.

    /SnakeMobile.Core/Resources/GlobalStyle.xaml
  • OverlayMessage
    Control to produce a message with custom buttons.

    /SnakeMobile.Core/InternalControls/OverlayMessage.xaml
    /SnakeMobile.Core/InternalControls/OverlayMessage.xaml.cs
  • Rounded glowing button
    Style for buttons to make a rounded glowing button.

    /SnakeMobile/Resources/RoundedGlowingButton.xaml
  • WP7 Container
    Container for Windows Phone 7 used to apply the Inversion of Control.

    /SnakeMobile.Core/WP7Container.cs

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Other
Italy Italy
Marco Cameriero, born in 1995, student of an High School.

Personal website:
Marco's Room

Linkedin profile:
Marco Cameriero

PS: I owned my father's profile... he surrendered! :P






Comments and Discussions

 
GeneralQuite some article Pin
Sacha Barber2-Jan-11 6:32
Sacha Barber2-Jan-11 6:32 
GeneralRe: Quite some article Pin
69Icaro2-Jan-11 6:37
69Icaro2-Jan-11 6:37 
GeneralRe: Quite some article Pin
Sacha Barber2-Jan-11 21:19
Sacha Barber2-Jan-11 21:19 
GeneralRe: Quite some article Pin
69Icaro2-Jan-11 22:30
69Icaro2-Jan-11 22:30 
GeneralRe: Quite some article Pin
Sacha Barber2-Jan-11 22:52
Sacha Barber2-Jan-11 22:52 
GeneralRe: Quite some article Pin
69Icaro2-Jan-11 23:16
69Icaro2-Jan-11 23:16 
GeneralRe: Quite some article Pin
Sacha Barber3-Jan-11 0:56
Sacha Barber3-Jan-11 0:56 
GeneralRe: Quite some article Pin
Wimmo13-Jan-11 8:48
Wimmo13-Jan-11 8:48 
So in gods name how is it possible that you are a programmer and not surfer!?
I understand why I am, I also grew up near water but far less nicer so surfing was not an option! Laugh | :laugh:
•Life moves pretty fast. If you don't stop and look around once in a while, you could miss it. — Ferris Bueller

GeneralRe: Quite some article Pin
Sacha Barber13-Jan-11 21:34
Sacha Barber13-Jan-11 21:34 
GeneralMy vote of 5 Pin
Marcelo Ricardo de Oliveira2-Jan-11 6:20
Marcelo Ricardo de Oliveira2-Jan-11 6:20 
GeneralRe: My vote of 5 Pin
69Icaro2-Jan-11 6:33
69Icaro2-Jan-11 6:33 
GeneralRe: My vote of 5 Pin
Sacha Barber2-Jan-11 6:38
Sacha Barber2-Jan-11 6:38 
GeneralMy vote of 5 Pin
maq_rohit2-Jan-11 6:19
professionalmaq_rohit2-Jan-11 6:19 
GeneralRe: My vote of 5 Pin
69Icaro2-Jan-11 6:26
69Icaro2-Jan-11 6:26 
GeneralRe: My vote of 5 Pin
69Icaro2-Jan-11 22:46
69Icaro2-Jan-11 22:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.