Click here to Skip to main content
Click here to Skip to main content

Snail Run For Windows Phone

, 8 Mar 2012
Rate this:
Please Sign up or sign in to vote.
Learn how to integrate Silverlight and XNA for Windows Phone in the same application

Table of Contents

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:

  • Visual Studio 2010 Express for Windows Phone
    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.

  • Windows Phone SDK 7.1
    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 MainPage.xaml page:

    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. GamePage is where Silverlight and XNA are really mixed together. For now you'll see that the XAML for this page looks empty, so later on we'll have to do a little work on it:

    <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 GamePage page:

    First, you'll notice that some XNA elements are present in this class: ContentManager, GameTimer and SpriteBatch:

        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 OnNavigatedTo class, where the SpriteBatch is instantiated, the GameTimer is started and the XNA rendering is turned on. Also, this is the place where you load the content (sprites, sounds, and so on) for the game.

            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 GamePage, the XNA rendering is disabled and the timer is stopped:

            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 OnUpdate method is the replacement for the familiar XNA's Update method. This is where you run logic such as updating the world, checking for collisions, gathering input, and playing audio.

            private void OnUpdate(object sender, GameTimerEventArgs e)
            {
                // TODO: Add your update logic here
            }
    

    Along with the OnUpdate method, the OnDraw is also a counterpart for the familiar XNA Update method. As you can see, there's not much here, but this is where all the rendering of the game will happen:

            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 MainPage page. As seen earlier, these page is an ordinary Silverlight page. It is the entry point for our application, so it would be interesting to put here the links for user options, such as settings, leaderboards, about page and the mos obvious, game start. I didn't changed the functionality at all, just created a foreground image and applied some animations to the bubbles that appear on the background, to give the game a submarine atmosphere.

    The interesting point here is the making of the bubble animation. The MainPage itself has an instance of the SplashScreen control, which in turn is responsible for the animations:

            <!--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 SplashScreen code behind class creates exactly 10 Ellipse elements, each one of which is subjected to an independent animation. Each animation cause the corresponding bubble to move rapidly from the bottom of the Canvas to the top of it. Besides, an EasingFunction is applied to the animations, so that the movement looks more natural. The whole animation process is beyond the scope of this article, but here is the code, in case you want to investigate how it works:

            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 and SpriteBatch 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 Timer class. This class allows the control of the two important events, Update and Draw. Here we notice how the events are bound to the methods OnUpdate and OnDraw. Then we have the camera instance, which is intended to show just one part of the maze each time. The TouchPanel.EnabledGestures property is set to allow the gestures: Flick, Hold and Tap. The page's DataContext is set to the scoreManager instance (this is later used for Silverlight data binding purposes). And finally we load the game levels.

            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 OnNavigatedTo event function:

            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 OnUpdate method is started by the update of the current game level. Notice that the CurrentLevel.Update method is not called all times, but only when the elapsed time reaches a preconfigured amount of time. This is done to avoid unnecessary processing.

            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 ChaseSnail on each squid object. The code below shows that this is done only when the game is in the state Playing, otherwise there would be no reason for calling the function.

        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 OnDraw method. Depending on the game state, it has to draw differently:

            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 Playing, it must show messages. These messages are defined in Silverlight XAML, and right here is the way to render Silverlight content alongside XNA: the elementRenderer.Render(); renders the Silverlight page (that is, constructs all visual elements) and then the spriteBatch.Draw(elementRenderer.Texture, Vector2.Zero, Color.White); tells the application to render the Silverlight content in the game's sprite batch.

        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 DrawPlaying code is divided in smaller functions for the sake of simplicity:

            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.

    License

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

    Share

    About the Author

    Marcelo Ricardo de Oliveira
    Software Developer
    Brazil Brazil
    Marcelo Ricardo de Oliveira is a senior software developer who lives with his lovely wife Luciana and his little buddy and stepson Kauê in Guarulhos, Brazil, is co-founder of the Brazilian TV Guide TV Map and currently works for ILang Educação.
     
    He is often working with serious, enterprise projects, although in spare time he's trying to write fun Code Project articles involving WPF, Silverlight, XNA, HTML5 canvas, Windows Phone app development, game development and music.
     
    Published Windows Phone apps:
     
     
    Awards:
     
    CodeProject MVP 2012
    CodeProject MVP 2011
     
    Best Web Dev article of March 2013
    Best Web Dev article of August 2012
    Best Web Dev article of May 2012
    Best Mobile article of January 2012
    Best Mobile article of December 2011
    Best Mobile article of October 2011
    Best Web Dev article of September 2011
    Best Web Dev article of August 2011
    HTML5 / CSS3 Competition - Second Prize
    Best ASP.NET article of June 2011
    Best ASP.NET article of May 2011
    Best ASP.NET article of April 2011
    Best C# article of November 2010
    Best overall article of November 2010
    Best C# article of October 2010
    Best C# article of September 2010
    Best overall article of September 2010
    Best overall article of February 2010
    Best C# article of November 2009

    Comments and Discussions

     
    GeneralMy vote of 5 PinmemberAvelino Ferreira6-Jun-14 13:19 
    GeneralRe: My vote of 5 PinmvpMarcelo Ricardo de Oliveira6-Jun-14 15:48 
    GeneralMy vote of 5 Pinmemberfahad121-Jan-13 20:05 
    GeneralRe: My vote of 5 PinmvpMarcelo Ricardo de Oliveira6-Jun-14 15:45 
    GeneralMy vote of 5 PinmvpKanasz Robert21-Sep-12 0:44 
    GeneralRe: My vote of 5 PinmvpMarcelo Ricardo de Oliveira21-Sep-12 3:52 
    QuestionDLL FILE MISSING Pinmemberrevathi151018-Jul-12 22:54 
    QuestionGreat article PinmemberPatrick Kalkman8-Apr-12 9:39 
    AnswerRe: Great article PinmvpMarcelo Ricardo de Oliveira9-Apr-12 4:50 
    GeneralMissed this one but its another PinmvpSacha Barber18-Mar-12 22:50 
    GeneralRe: Missed this one but its another PinmvpMarcelo Ricardo de Oliveira19-Mar-12 6:38 
    GeneralMais um excelente artigo de XNA e Windows Phone Pinmemberrodrigo.ratan15-Mar-12 14:11 
    GeneralRe: Mais um excelente artigo de XNA e Windows Phone PinmvpMarcelo Ricardo de Oliveira15-Mar-12 15:33 
    GeneralMy vote of 5 PinmemberSlacker0071-Mar-12 6:17 
    GeneralRe: My vote of 5 PinmvpMarcelo Ricardo de Oliveira1-Mar-12 14:22 
    GeneralMy vote of 5 Pinmembernick_journals1-Mar-12 2:38 
    GeneralRe: My vote of 5 PinmvpMarcelo Ricardo de Oliveira1-Mar-12 14:21 
    GeneralMy vote of 5 PinmemberFlorian Rappl29-Feb-12 20:59 
    GeneralRe: My vote of 5 PinmvpMarcelo Ricardo de Oliveira1-Mar-12 14:21 
    QuestionVote of 5 PinmemberGanesanSenthilvel29-Feb-12 17:10 
    AnswerRe: Vote of 5 PinmvpMarcelo Ricardo de Oliveira1-Mar-12 14:19 

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

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

    | Advertise | Privacy | Mobile
    Web03 | 2.8.140821.2 | Last Updated 8 Mar 2012
    Article Copyright 2012 by Marcelo Ricardo de Oliveira
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid