Building Windows Phone Applications Using Silverlight + XNA

    The other day I saw this post with a list of materials for developing for Windows Phone, and, unfortunately, I did not see any article on developing applications using both Silverlight and XNA at the same time. This great opportunity for developers came with the advent of Mango.
    I wanted to fill in this gap and tell you about the following:
    • Using Silverlight and XNA on the Same Page
    • The simplest gesture handling in XNA
    • XNA Camera Basics

    An example from this article may be familiar to visitors to the first stream of Stas Pavlov’s evening school .
    Let's first see what gives us the use of Silverlight and XNA in one application.

    What does this give the developer?


    First of all, game developers have the following features:
    • Quick UI creation using controls from Silverlight
    • Convenient navigation between pages using the Navigation Service
    • Using WebClient to integrate various social services
    • Creating an interface in Expression Blend

    And it is worth noting that not all pages should combine both Silverlight and XNA. I also want to draw attention to the fact that this feature is useful not only for game developers. For example, XNA has wonderful built-in gesture support and very convenient methods for working with textures. An example of a great non-gaming application using Silverlight and XNA is Holiday Photo .

    Howto: developing the first Silverlight + XNA application


    As an example, I chose the very famous mathematical game Life, which was repeatedly written about on Habré ( 1 , 2 , 3, and many others). In short, the game is a "zero-player game" , which means that the player sets only the initial state. Further, the state of each cell in each generation is calculated based on the states of the eight surrounding cells according to fairly simple rules.
    I want to make a reservation right away that I use the simplest algorithm using 2 arrays, but this is not a post about algorithms and data structures. So the implementation of an optimal and fast algorithm will be one of your homework.

    Step 1. Creating a project and rendering an interface


    When creating a new project, you must select "Windows Phone Silverlight and XNA Application . " A new project will be created with 2 MainPage and GamePage pages .

    MainPage - the usual page of the Silverlight application, on which there is a “Change to game page” button, when clicked on, the GamePage page opens accordingly .
    GamePage is just our XNA page. If you look at the contents of its xaml file, then instead of page layout there will be only one line:



    Thus, when we want to add controls to this page, we will need to replace this comment with our code, let's do this by placing the Start button .



    Let's start our project now, press the “ Change to game page ” button and see only a blue screen, our button is nowhere to be found. But there is nothing to worry about, now we will fix it.

    Open the file GamePage.xaml.cs, which contains all the logic of our XNA application. To display Silverlight elements, we need to create a special UIElementRenderer object:

    UIElementRenderer uiRenderer;


    Now in the drawing cycle we need to render and draw the interface:
    
    private void OnDraw(object sender, GameTimerEventArgs e)
    {
        SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.White);
        uiRenderer.Render();
        spriteBatch.Begin();
        spriteBatch.Draw(uiRenderer.Texture, Vector2.Zero, Color.White);
        spriteBatch.End();
        // TODO: Add your drawing code here
    }


    As you may have noticed, we created the uiRenderer object, but did not initialize it anywhere. There are several points of view on how best to do this. I prefer to catch updates to our interface and check for a renderer, and if it is absent or inconsistent, create it again:

    GamePage.xaml



    GamePage.xaml.cs

    private void Layout_LayoutUpdated(object sender, EventArgs e)
    {
        int width = (int)ActualWidth;
        int height = (int)ActualHeight;
        // Ensure the page size is valid
        if (width <= 0 || height <= 0)
            return;
        // Do we already have a UIElementRenderer of the correct size?
        if (uiRenderer != null &&
            uiRenderer.Texture != null &&
            uiRenderer.Texture.Width == width &&
            uiRenderer.Texture.Height == height)
        {
            return;
        }
        // Before constructing a new UIElementRenderer, be sure to Dispose the old one
        if (uiRenderer != null)
            uiRenderer.Dispose();
        // Create the renderer
        uiRenderer = new UIElementRenderer(this, width, height);
    }


    Now run our project again. As you can see, our button now appears in the right place. Let's now draw the playing field.

    2. XNA output


    Let's agree that our field will be 100x100 cells in size, each cell 30x30 pixels. Total our field will be 3000x3000 pixels.
    Create an Area class:

    public class Area
    {
        Texture2D point;
        Rectangle line;
        public Area(Texture2D point)
        {
            this.point = point;
        }
        public void Draw(SpriteBatch spriteBatch)
        {
            for (int i = 0; i < 100; i++)
            {
                line = new Rectangle(i*30, 0, 1, 3000);
                spriteBatch.Draw(point, line, Color.White);
                line = new Rectangle( 0, i*30, 3000, 1);
                spriteBatch.Draw(point, line, Color.White);
            }
        }
    }


    In XNA there is no way to draw a line, but you can draw a rectangle filled with any color or texture. Here we create 100 horizontal and 100 vertical lines, flooded with a point texture containing a black dot of 1x1 size.

    Now let's create and initialize our field in GamePage.xaml.cs:

    Texture2D point;
    Area area;


    When using XNA and Silverlight together, there is no need to use a separate method for loading content, as it was in a pure XNA application (LoadContent). In general, you can download game content whenever you want, but you must make sure that the application is in XNA rendering mode while loading graphic content (by calling the SetSharingMode method). If you try to load graphic content during Silverlight rendering, this will throw an exception. You can download non-graphic content at any time when you have created ContentManager. I usually load content in the OnNavigatedTo method:

    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
        point = contentManager.Load("point");
        area = new Area(point);
        // Start the timer
        timer.Start();
        base.OnNavigatedTo(e);
    }


    Now draw our field in the OnDraw method:

    private void OnDraw(object sender, GameTimerEventArgs e)
    {
        SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
        uiRenderer.Render();
        spriteBatch.Begin();
        area.Draw(spriteBatch)
        spriteBatch.End();
        spriteBatch.Begin();
        spriteBatch.Draw(uiRenderer.Texture, Vector2.Zero, Color.White);
        spriteBatch.End();
        // TODO: Add your drawing code here
    }


    As you can see, I draw the field in a separate spriteBatch. Why this is so - I will tell a little later.

    Launch our application: now we can admire our field with a button.
    We’ll make small cosmetic changes: add a translucent underlay for the button and make the field white.
    GamePage.xaml:


    GamePage.xaml.cs:

    private void OnDraw(object sender, GameTimerEventArgs e)
    {
            SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.White);
            uiRenderer.Render();
            ...


    3. Work with gestures


    XNA Framework supports 10 different gestures, the story about them deserves a separate article (which I will write if you wish). In our application, we will use 2 types of gestures: Tap (click) and FreeDrag (free movement).
    Let's start by adding points to our field. I don’t want to give the full code for the Dots class here, you can download the example at the bottom of the article and see it for yourself. We will now be interested only in the AddDot method, which is used to add points:

    
    public void AddDot(int x, int y, Vector2 shift)
    {
        DotX = (int)(x / DotSize );
        DotY = (int)(y / DotSize );
        DotsNow[DotX, DotY] = !DotsNow[DotX, DotY];
    }


    In this method, we transfer the coordinates of the touch point, and based on it we calculate the desired cell in the matrix. Everything is simple.

    First, we need to enable gesture support in the OnNavigatedTo () method:

    TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag;
    


    Now you can track them in the OnUpdate loop:

    private void OnUpdate(object sender, GameTimerEventArgs e)
    {
        while (TouchPanel.IsGestureAvailable)
        {
            GestureSample gesture = TouchPanel.ReadGesture();
            if (gesture.GestureType == GestureType.Tap)
            {
                dots.AddDot((int)gesture.Position.X, (int)gesture.Position.Y);
            }
        }
    }


    Do not forget to display our living cells on the screen in the OnDraw () method:

    
    dots.Draw(spriteBatch);


    If you start the application now and try to add a point, it will instantly disappear. The reason for this is the update speed. There must be some delay for calling dots.Update (). In my example, the simplest implementation of this delay is:

    
    i++;
    if (IsGameStarted && Math.IEEERemainder(i, 15) == 0)
    {
        i = 0;
        dots.Update();
    }


    Where IsGameStarted is a flag that changes value when you click on the Start button. The second homework is to implement the delay using the GameTime class.
    Now we have a fully functional game, but we see only a small part of the field. Let's fix this by adding the ability to move around the field.

    4. Work with the camera


    In the simplest approximation, a camera is a matrix with which we project our game world onto a screen in the form of an image. The Matrix class has many relevant methods, for example CreateScale (), CreateTranslation (), CreateRotationX (). Just CreateTranslation () we need. First we find out how much we need to move our game world and create a matrix:

    while (TouchPanel.IsGestureAvailable)
    {
        GestureSample gesture = TouchPanel.ReadGesture();
        if (gesture.GestureType == GestureType.Tap)
        {
            dots.AddDot((int)gesture.Position.X, (int)gesture.Position.Y, totalShift);
        }
        if (gesture.GestureType == GestureType.FreeDrag)
        {
            shift = gesture.Delta;
            totalShift += shift;
        }
    }
    matrix *= Matrix.CreateTranslation(totalShift.X, totalShift.Y, 0);


    Then you need to draw a new projection. To do this, pass our matrix to spriteBatch:

    spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, matrix);
    area.Draw(spriteBatch);
    dots.Draw(spriteBatch);
    spriteBatch.End();


    That is why we needed a second spriteBatch: if we all rendered in one, our button would also move with the field, which we do not need. Homework - see what happens if you place the rendering of the game and interface in one spriteBatch.
    One problem remains - we do not take into account the shift of our camera when adding points. This is solved simply, the totalShift must also be passed to the AddDot () method:

    public void AddDot(int x, int y, Vector2 shift)
    {
        DotX = (int)(Math.Abs(x-shift.X) / DotSize );
        DotY = (int)(Math.Abs(y-shift.Y) / DotSize );
        DotsNow[DotX, DotY] = !DotsNow[DotX, DotY];
    }


    Now points will be added in the places we need. Homework: when you try to add a living cell outside the field, there will still be a wrong reaction: either adding at a mirror point or going beyond the boundaries of the array. Make it impossible to go beyond the boundaries of the field. Cheshire Cat



    Conclusion


    Actually, that’s all. You have a fully functional game Life for Windows Phone and homework, over which you can smash your head for a couple of hours. You can also make various buns, such as a generation counter, random filling, and much more. You can find my final version in the Marketplace called SilverLife .

    Download application sources .

    Have a good New Year holidays!

    Also popular now: