Snail Run For Windows Phone






4.97/5 (23 votes)
Learn how to integrate Silverlight and XNA for Windows Phone in the same application
Table of Contents
- Introduction
- System Requirements
- Silverlight and XNA: Becoming Friends
- Overhauling MainPage.xaml
- GamePage: Putting Silverlight and XNA Into Work
- Being Chased By Squids
- Creating Level Map with Mappy Win32
- XNA Content Pipeline Extension for Mappy Files
- Final Considerations
- History
Introduction
So far, game development with Windows Phone has been a lot of fun. In this article I'm going to present the "Snail Run!" game, which is a traditional maze, Pac-Man kind of game. Besides of the fun, the points of interest here are truly serious: the combined Silverlight and XNA development in the same solution, the use of the "Mappy" map creation tool, the importer/processor of maps inside the Visual Studio game project, and how to implement the path finding algorithm, most precisely the "A* search algorithm" to give life to the game
System Requirements
To use the Snail Run for Windows Phone provided with this article, you must download and install the following 100% free development tool directly from Microsoft:
Whether you’re familiar with, or new to, Silverlight and XNA Game Studio programming, Visual Studio 2010 Express for Windows Phone provides everything you need to get started building Windows Phone apps.
The Windows Phone Software Development Kit (SDK) 7.1 provides you with all of the tools that you need to develop applications and games for both Windows Phone 7.0 and Windows Phone 7.5 devices.
Silverlight and XNA: Becoming Friends
Windows Phone is well known for providing two distinct framework for developers: Silverlight and XNA. While Silverlight is suitable and enough for most of the application needs, due to the inherent page navigation, flexibility of XAML markup language, built-in transformations, storyboard and animations, advanced databinding, vectorial rendering, panorama and pivot applications, just to name a few, while on the other hand the update/draw loops, visual and audio effects and the fast sprite rendering engine of XNA Framework usually makes it into the developers' choice when it comes to game development.
If you already developed Windows Phone games using XNA framework alone, you probably faced and missed the lack of page navigation, the XAML and binding and other benefits of Silverlight development. Some trivial application requirements, such as rendering a simple listbox, may require a lot of effort in XNA. Fortunately, this architecture dilemma is finally over once you decide to create a new Visual Studio project, choosing a project template called Windows Phone Silverlight and XNA Application.
This solution template for a project named "MyGame" creates a new solution with three projects:
- MyGame, which is a Silverlight project but also that contains references for the XNA framework. This is where Silverlight and XNA are rendered together.
- MyGameLib, which is an XNA-only project. You can use this project to reuse existing XNA code or separate XNA code from your Silverlight code.
- MyGameLibContent: the content pipeline project where you can find the assets for the solution.
When you run the application, you'll see that it looks pretty much like a regular Silverlight application. This is
The only noticeable exception is the central button, that obviously navigates to the game page:
<!--Create a single button to navigate to the second page which is rendered with the XNA Framework-->
<Button Height="100" Content="Change to game page" Click="Button_Click" />
// Simple button Click event handler to take us to the second page
private void Button_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/GamePage.xaml", UriKind.Relative));
}
As for the game page: the "GamePage.xaml" makes it also sound like a regular Silverlight page, but that's not the case.
<phone:PhoneApplicationPage
x:Class="MyGame.GamePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="480"
shell:SystemTray.IsVisible="False">
<!--No XAML content is required as the page is rendered entirely with the XNA Framework-->
</phone:PhoneApplicationPage>
Now let's take a loot at the code behind class for
First, you'll notice that some XNA elements are present in this class:
public partial class GamePage : PhoneApplicationPage
{
ContentManager contentManager;
GameTimer timer;
SpriteBatch spriteBatch;
public GamePage()
{
InitializeComponent();
// Get the content manager from the application
contentManager = (Application.Current as App).Content;
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
timer.Update += OnUpdate;
timer.Draw += OnDraw;
}
Then you have the
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
// TODO: use this.content to load your game content here
// Start the timer
timer.Start();
base.OnNavigatedTo(e);
}
Whenever you navigate away from the
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// Stop the timer
timer.Stop();
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
base.OnNavigatedFrom(e);
}
If you are already a XNA developer, you'll notice that the
private void OnUpdate(object sender, GameTimerEventArgs e)
{
// TODO: Add your update logic here
}
Along with the
private void OnDraw(object sender, GameTimerEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
}
As you can see from the above code, the default Silverlight and XNA template is nice enough to save you some time and effort, but unfortunately it leaves some important parts unexplained, despite the comments spread all over the code. In the next sections of the article we'll try to deal with these gaps.
Overhauling MainPage.xaml
The first make-up is done in the
The interesting point here is the making of the bubble animation. The
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ctr:SplashScreen x:Name="splashScreen" VerticalAlignment="Center" Grid.ColumnSpan="3" Grid.RowSpan="3"/>
<Image Source="MainPage.png" MouseLeftButtonUp="Image_MouseLeftButtonUp"></Image>
</Grid>
The
private void CreateBubbles()
{
var linearBubbleBrush = new LinearGradientBrush()
{ StartPoint = new Point(1, 0), EndPoint = new Point(0, 1) };
linearBubbleBrush.GradientStops.Add(
NewGradient.Stop(Color.FromArgb(0xFF, 0x00, 0x20, 0x40), 0.0));
linearBubbleBrush.GradientStops.Add(
NewGradient.Stop(Color.FromArgb(0x00, 0xFF, 0xFF, 0xFF), 1.0));
var radialBubbleBrush = new RadialGradientBrush() {
Center = new Point(0.25, 0.75), RadiusX = .3, RadiusY = .2, GradientOrigin = new Point(0.35, 0.75) };
radialBubbleBrush.GradientStops.Add(
NewGradient.Stop(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), 0.0));
radialBubbleBrush.GradientStops.Add(
NewGradient.Stop(Color.FromArgb(0x00, 0xFF, 0xFF, 0xFF), 1.0));
for (var i = 0; i < 10; i++)
{
var diameter = 10 + (i % 4) * 10;
var ms = 2000 + i % 7 * 500;
var ellBubble = new Ellipse()
{
Width = diameter,
Height = diameter,
Stroke = linearBubbleBrush,
Fill = radialBubbleBrush,
StrokeThickness = 3
};
ellBubble.SetValue(Canvas.LeftProperty,
i * (40.0 + 40.0 - diameter / 2));
ellBubble.SetValue(Canvas.TopProperty, 0.0 + 40.0 - diameter / 2);
cnvBubbles.Children.Add(ellBubble);
var leftAnimation = new DoubleAnimation()
{
From = 40.0 * i,
To = 40.0 * i,
Duration = TimeSpan.FromMilliseconds(ms)
};
var topAnimation = new DoubleAnimation()
{
From = 400,
To = 0,
Duration = TimeSpan.FromMilliseconds(ms)
};
var opacityAnimation = new DoubleAnimation()
{
From = 1.0,
To = 0.0,
Duration = TimeSpan.FromMilliseconds(ms)
};
Storyboard.SetTarget(leftAnimation, ellBubble);
Storyboard.SetTargetProperty(leftAnimation, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTarget(topAnimation, ellBubble);
Storyboard.SetTargetProperty(topAnimation, new PropertyPath("(Canvas.Top)"));
Storyboard.SetTarget(opacityAnimation, ellBubble);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity"));
leftAnimation.EasingFunction = new BackEase()
{ Amplitude = 0.5, EasingMode = EasingMode.EaseOut };
topAnimation.EasingFunction = new BackEase()
{ Amplitude = 0.5, EasingMode = EasingMode.EaseOut };
var sb = new Storyboard();
sb.Children.Add(leftAnimation);
sb.Children.Add(topAnimation);
sb.Children.Add(opacityAnimation);
sb.RepeatBehavior = RepeatBehavior.Forever;
bubbles.Add(ellBubble);
storyBoards.Add(sb);
sb.Begin();
}
}
GamePage: Putting Silverlight and XNA Into Work
This section explains how to get Silverlight and XNA to work together. I took me some time to figure it out how to do it.
First, you have to add content to the project. The only types of content we have are sprites and level maps. Sprite is a common concept that doesn't require further explanation. We have graphic contents for the Snail, the Pearl, Squid and Starfish. These are the characters of our game. Level maps are maps created by a third party, open source tool called MapWin that are then incorporated to our game content.
Now, let's take a look at code behind class located at GamePage.xaml.cs. The noticeable elements inside this class scope are:
- The already known
ContentManager ,GameTimer andSpriteBatch instances. - The Camera2d instance. As the name implicates, it is used to scroll/zoom while we run across the game maze.
- The UIElementRenderer is the most important part: it will allow us to render Silverlight content into a texture, thus we can put them along with XNA-generated graphics.
- GameSettings is for predefined game settings, such as speed, screen resolution and so on.
- The ScoreManager manages, well... the score.
- The Level class contains information regarding the levels of the game.
.
.
.
public partial class GamePage : PhoneApplicationPage
{
ContentManager contentManager;
GameTimer timer;
SpriteBatch spriteBatch;
Camera2d camera;
// For rendering the XAML onto a texture
UIElementRenderer elementRenderer;
GameSettings settings = new GameSettings();
ScoreManager scoreManager = new ScoreManager();
GameStateMachine stateMachine = new GameStateMachine();
List<Level> levels = new List<Level>();
int CurrentLevelNumber = 1;
double minUpdateTimeSpanMs = 20;
double accumulatedUpdateTimeSpanMs = 0;
double minChaseTimeSpanMs = 500;
double accumulatedChaseTimeSpanMs = 0;
Texture2D seaTexture;
public GamePage()
.
.
.
Now we move on to the class constructor. Here we are preparing our game for first use, so many things are set up in the constructor. For example, the contentManager
is first instantiated, and then it is ready to be used. The timer is an instance of XNA framework's
public GamePage()
{
InitializeComponent();
// Get the content manager from the application
contentManager = (Application.Current as App).Content;
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(166667);
timer.Update += OnUpdate;
timer.Draw += OnDraw;
camera = new Camera2d(2, 0, settings.ScreenHeight, settings.ScreenWidth, settings.CameraCenter);
// Use the LayoutUpdate event to know when the page layout
// has completed so that we can create the UIElementRenderer.
LayoutUpdated += new EventHandler(GamePage_LayoutUpdated);
TouchPanel.EnabledGestures = GestureType.Flick | GestureType.Hold | GestureType.Tap;
this.DataContext = scoreManager;
LoadLevels();
}
Then it's time to load and initialize our content. This is done through the page's
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
// Start the timer
timer.Start();
foreach (var level in levels)
{
level.Initialize();
}
seaTexture = contentManager.Load<Texture2D>(string.Format("{0}/Sea", settings.SpritesRoot));
base.OnNavigatedTo(e);
scoreManager.Level = CurrentLevelNumber;
stateMachine.GameStateChanged += new GameStateMachine.GameStateChangedHandler(stateMachine_GameStateChanged);
stateMachine.ChangeState(GameState.PreparingToStartLevel);
}
The
private void OnUpdate(object sender, GameTimerEventArgs e)
{
UpdateLevel(e);
ChaseSnail(e);
HandleInput();
UpdateCamera();
}
private void UpdateLevel(GameTimerEventArgs e)
{
accumulatedUpdateTimeSpanMs += e.ElapsedTime.TotalMilliseconds;
if (accumulatedUpdateTimeSpanMs > minUpdateTimeSpanMs)
{
accumulatedUpdateTimeSpanMs = 0;
CurrentLevel.Update(e.ElapsedTime);
}
}
Then comes the searching algorithm which is used by squids to pursue the snail. This is done by calling the
private void ChaseSnail(GameTimerEventArgs e)
{
if (stateMachine.CurrentSate == GameState.Playing)
{
accumulatedChaseTimeSpanMs += e.ElapsedTime.TotalMilliseconds;
if (accumulatedChaseTimeSpanMs > minChaseTimeSpanMs)
{
accumulatedChaseTimeSpanMs = 0;
CurrentLevel.Squids.ForEach(x =>
{
if (x.ChaseMovementQueue.Count == 0)
{
x.ChaseSnail();
}
});
}
}
}
Then there is the validation of game input. The snail is moved up, down, left and right every time the user performs a Flick gesture. When the user taps the screen, the snail just halts.
private void HandleInput()
{
//Check if touch Gesture is available
if (TouchPanel.IsGestureAvailable)
{
// Read the gesture so that you can handle the gesture type
GestureSample gesture = TouchPanel.ReadGesture();
switch (gesture.GestureType)
{
case GestureType.Flick:
var x = gesture.Delta.X;
var y = gesture.Delta.Y;
CurrentLevel.Flick(x, y);
break;
case GestureType.Tap:
CurrentLevel.Snail.EnqueueMove(Gesture.Tap);
break;
default:
//do something
break;
}
}
}
Finally, there is the camera update. Normally the camera tries to follow the squid movements. But when the squid reaches the edges of the maze, the camera stops moving. So, there is a restricte area for the camera moves.
private void UpdateCamera()
{
var newCameraPos = CurrentLevel.Snail.Position * settings.TileWidth;
if (newCameraPos.X < camera.ViewportWidth / 4)
{
newCameraPos.X = camera.ViewportWidth / 4;
}
else if (newCameraPos.X > CurrentLevel.MapWidth * settings.TileWidth -
camera.ViewportWidth / (camera.Zoom * 2) + settings.MapTopLeftCorner.X)
{
newCameraPos.X = CurrentLevel.MapWidth * settings.TileWidth -
camera.ViewportWidth / (camera.Zoom * 2) + settings.MapTopLeftCorner.X;
}
if (newCameraPos.Y < camera.ViewportHeight / 4)
{
newCameraPos.Y = camera.ViewportHeight / 4;
}
else if (newCameraPos.Y > CurrentLevel.MapHeight * settings.TileWidth -
camera.ViewportHeight / (camera.Zoom * 2) + settings.MapTopLeftCorner.Y)
{
newCameraPos.Y = CurrentLevel.MapHeight * settings.TileWidth -
camera.ViewportHeight / (camera.Zoom * 2) + settings.MapTopLeftCorner.Y;
}
camera.Pos = newCameraPos;
}
Then we have the
private void OnDraw(object sender, GameTimerEventArgs e)
{
switch (stateMachine.CurrentSate)
{
case GameState.PreparingToStartLevel:
case GameState.GameOver:
case GameState.LevelCompleted:
case GameState.LevelFailed:
case GameState.Paused:
DrawCentralMessage(e);
break;
case GameState.Playing:
DrawPlaying(e);
break;
}
}
When the game is in a state other than
private void DrawCentralMessage(GameTimerEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.Black);
// Render the Silverlight controls using the UIElementRenderer.
elementRenderer.Render();
this.spriteBatch.Begin();
// Using the texture from the UIElementRenderer,
// draw the Silverlight controls to the screen.
spriteBatch.Draw(elementRenderer.Texture, Vector2.Zero, Color.White);
this.spriteBatch.End();
}
The
private void DrawPlaying(GameTimerEventArgs e)
{
DrawBackground();
DrawMaze(e);
DrawAnimals(e);
DrawSilverlight();
}
First, we draw the background, that is, the sea texture:
private void DrawBackground()
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.Black);
this.spriteBatch.Begin();
// Using the texture from the UIElementRenderer,
// draw the Silverlight controls to the screen.
spriteBatch.Draw(seaTexture, Vector2.Zero,
new xna.Rectangle(0, 0, settings.ScreenWidth, settings.ScreenHeight),
Color.White);
this.spriteBatch.End();
}
Then we draw the maze on top of it:
private void DrawMaze(GameTimerEventArgs e)
{
this.spriteBatch.Begin(SpriteSortMode.BackToFront,
BlendState.AlphaBlend, null, null, null, null,
camera.Transformation(spriteBatch.GraphicsDevice));
this.spriteBatch.Draw((float)e.ElapsedTime.TotalSeconds,
CurrentLevel.Map2D, settings.MapTopLeftCorner, Color.White);
this.spriteBatch.End();
}
And then the animals are rendered:
private void DrawAnimals(GameTimerEventArgs e)
{
this.spriteBatch.Begin(SpriteSortMode.BackToFront,
BlendState.AlphaBlend, null, null, null, null,
camera.Transformation(spriteBatch.GraphicsDevice));
CurrentLevel.Snail.Draw(e.ElapsedTime, spriteBatch,
settings.MapTopLeftCorner, 0);
foreach (var pearl in CurrentLevel.Pearls)
{
pearl.Draw(e.ElapsedTime, spriteBatch, settings.MapTopLeftCorner, 1);
}
foreach (var starFish in CurrentLevel.StarFishes)
{
starFish.Draw(e.ElapsedTime, spriteBatch, settings.MapTopLeftCorner, 1);
}
foreach (var squid in CurrentLevel.Squids)
{
squid.Draw(e.ElapsedTime, spriteBatch, settings.MapTopLeftCorner, 1);
}
this.spriteBatch.End();
}
Finally, we draw the Silverlight texture on top of everything. This silverlight part is just the score information that appears at the top of the screen during the game play.
private void DrawSilverlight()
{
// Render the Silverlight controls using the UIElementRenderer.
elementRenderer.Render();
this.spriteBatch.Begin();
// Using the texture from the UIElementRenderer,
// draw the Silverlight controls to the screen.
spriteBatch.Draw(elementRenderer.Texture, Vector2.Zero, Color.White);
this.spriteBatch.End();
}
You can read useful further information at MSDN article entitled "How to: Combine Silverlight and the XNA Framework in a Windows Phone Application".
Being Chased By Squids
This game uses the classic path finding algorithm called "A*" (pronounced "A Star"). According to Wikipedia:
As A* traverses the graph, it follows a path of the lowest known cost, keeping a sorted priority queue of alternate path segments along the way. If, at any point, a segment of the path being traversed has a higher cost than another encountered path segment, it abandons the higher-cost path segment and traverses the lower-cost path segment instead. This process continues until the goal is reached.

Illustration of A* search for finding path from a start node to a goal node in a robot motion planning problem. The empty circles represent the nodes in the open set, i.e., those that remain to be explored, and the filled ones are in the closed set. Color on each closed node indicates the distance from the start: the greener, the further. One can firstly see the A* moving in a straight line in the direction of the goal, then when hitting the obstacle, it explores alternative routes through the nodes from the open set. (Source: Wikipedia)
The algorithm mentioned above is just perfect solution for our problem. Fortunately, I borrowed one of the wonderful Sacha Barber's contribution to the Code Project (dealing with finding the best path between any two stations of the London Underground) and made myself a version of it. I only had to change the concepts: Sacha's article deals with a person trying to find the optimal path between the current station and the, respecting the geographical connections between those stations. On the other side, in Snail Quest the person is represented by the squid. The desired station is the cell where the snail is found, the stations are the empty cells inside the maze, and instead of connection between stations, we now have connections between empty cells, which are the "corridors" inside the maze.
public List<MovementType> DoSearch(Vector2 squidPosition, Vector2 snailPosition)
{
pathsSolutionsFound = new List<List<Vector2>>();
pathsAgenda = new List<List<Vector2>>();
List<Vector2> pathStart = new List<Vector2>();
pathStart.Add(squidPosition);
pathsAgenda.Add(pathStart);
while (pathsAgenda.Count() > 0 && pathsAgenda.Count() < 100)
{
List<Vector2> currPath = pathsAgenda[0];
pathsAgenda.RemoveAt(0);
if (currPath.Count(
x => x.Equals(snailPosition)) > 0)
{
pathsSolutionsFound.Add(currPath);
break;
}
else
{
Vector2 currPosition = currPath.Last();
List<Vector2> successorPositions =
GetSuccessorsForPosition(currPosition);
foreach (var successorPosition in successorPositions)
{
if (!currPath.Contains(successorPosition) &&
pathsSolutionsFound.Count(x => x.Contains(successorPosition)) == 0)
{
List<Vector2> newPath = new List<Vector2>();
foreach (var station in currPath)
newPath.Add(station);
newPath.Add(successorPosition);
pathsAgenda.Add(newPath);
}
}
}
}
//Finally, get the best Path, this should be the 1st one found due
//to the heuristic evaluation performed by the search
if (pathsSolutionsFound.Count() > 0)
{
var solutionPath = pathsSolutionsFound[0];
var movementList = new List<MovementType>();
var Vector2 = solutionPath[0];
for (var i = 1; i < solutionPath.Count(); i++)
{
var movement = MovementType.None;
if (solutionPath[i].X > Vector2.X)
movement = MovementType.Right;
if (solutionPath[i].X < Vector2.X)
movement = MovementType.Left;
if (solutionPath[i].Y > Vector2.Y)
movement = MovementType.Bottom;
if (solutionPath[i].Y < Vector2.Y)
movement = MovementType.Top;
movementList.Add(movement);
Vector2 = solutionPath[i];
}
return movementList;
}
return null;
}
Creating Level Map with Mappy Win32
As I discovered at the beginning of the development of this game, creating scenarios with repetitive blocks is really a boring task. Fortunately, there is a very interesting map tool for game developers: Mappy Win32. It allows the development of large game scenarios with a small set of common graphical blocks. All the old school games (think Mario Bros, Pac-Man, Sonic, etc.) were made by using a limited number of blocks (or "tiles" as they are so often called).
You can download Mappy Tool freely at this website.
Let's see how to create a level scene with Mappy: First, open the File menu, and choose New Map.
Then we configure our map to work with 25 x 15 tiles, having each tile 32 x 32 pixels:
The next important step is import the Tile Strip, which is the set of 32 x 32 blocks containing the limited tiles used by our map:
Finally, we draw our map freely, using the blocks imported previously.
The last step is to save the file. We save it with the Level1.FMP name in the \SnailRun\SnailRun\SnailRunLibContent\Maps\ folder. Notice that the file is now part of the content of our game:
But wait, this .FMP extension is proprietary to the Mappy application. How can the XNA import/process this kind of file? The answer is, it can't. It does know nothing about .FMP extensions. This is why we have to do it by ourselves. Fortunately, again, some smart people already did the hard job for us.
XNA Content Pipeline Extension for Mappy Files
Although XNA framework can handle various kinds of files, unfortunately there is a limited number of them it can read and process. This is why, if some new kind of file is involved, we need to implement a new content pipeline extension by ourselves. Fortunately, I found the XNA Content Pipeline Extension to Mappy Maps(.FMP) at Codeplex, developed by Brazilian game developer Luciano José for the Windows and XBox platforms.
"Project Description
This XNA Library to Mappy Maps helps XNA Developers to integrate the Tile Map maked up in the Mappy Tool with your XNA Project(Windows and Xbox 360).
This library allows you just drag and drop your archive(.FMP) at the Content Project in the XNA Project(Windows and Xbox 360).
Create your tile map with the Mappy Tool(http://www.tilemap.co.uk/mappy.php) for integrating with this XNA Library.
See my article(in portuguese) about this library at SharpGames(the Brazilian XNA Community):
http://www.sharpgames.net/Artigos/Artigo/tabid/58/selectmoduleid/376/ArticleID/1585/reftab/54/Default.aspx
Visit the Brazilian XNA Community: http://www.sharpgames.net/"
It works by reading the .FMP file and reading the underlying structures/objets inside it. This way we can effectively "read" the map, and use the data to find routes, test collisions with obstacles, and so on.
Unfortunately, like I said, XNA Content Pipeline Extension for Mappy is targeted only to Windows and XBox platforms. But with some time I was able to port it to Windows Phone platform and got it to work. I then compiled it in release mode and included as a reference in the project.
After you add Level1.FMP to the Content project, you still have to configure the content processor properties:
In the end, everything is working and everybody is happy:
Final Considerations
That's it! I hope you liked both game and the concepts presented here. And if have questions, complaints, ideas, then please leave your comments below.
History
- 2012-02-29: Initial version.