ARTICLE

Using Silverlight in XNA - Part III: Silverlight Menus

Posted by Ibrahim Ersoy Articles | XNA with C# September 29, 2010
In our previous article (Using Silverlight in XNA: Part III), I showed you how you can use these technologies in a single solution. In this article we will create a Menu UI and use it in our XNA Project.
Reader Level:
 

In our previous article (Using Silverlight in XNA: Part III), I showed you how you can use these technologies in a single solution.

In this article we will create a Menu UI and use it in our XNA Project. I'm planning to prove that Menu UIs can be developed faster in Silverlight (Blend tool) than using codes in XNA. And by using Microsoft Expression Blend, you can change the UI anytime you want. So adding a Silverlight support in your XNA Projects will take heavy burdens from your shoulders and help you finish your work much more faster than usual.

In addition, I have seen lots of people saying we can't use Silverlight in XNA (with great confidence) and we should look for another solution that works in XNA. I totally disagree, well not a real integration but its absurd to say its impossible to do it. I'm going to prove these people are wrong! You can use Silverlight as a Menu or HUD in XNA!

Allright then lets start with designing our UI in Blend 4.I actually built one:

1.gif
 
It has 4 image and textbox controls.

And this images change when mouse hover:

2.gif  (Normal: "kutu.png")

3.gif  (Mouse-over: "kutu_over.png")

Here's XAML code of our MainPage:

<UserControl
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          x:Class="SLMenu.MainPage"
          Width="640" Height="480">
          <Grid x:Name="LayoutRoot" Background="White">
                   <Image Name="img1" HorizontalAlignment="Left" Height="32" Margin="16,48,0,0" Source="images/kutu.png" Stretch="Fill" VerticalAlignment="Top" Width="156" Cursor="Hand" MouseEnter="Image_MouseEnter" MouseLeave="Image_MouseLeave" ImageFailed="img1_ImageFailed" MouseLeftButtonDown="img1_MouseLeftButtonDown" />
                   <Image Name="img2" HorizontalAlignment="Left" Height="32" Margin="16,96,0,0" Source="images/kutu.png" Stretch="Fill" VerticalAlignment="Top" Width="156" Cursor="Hand" MouseEnter="img2_MouseEnter" MouseLeave="img2_MouseLeave" MouseLeftButtonDown="img2_MouseLeftButtonDown" />
                   <Image Name="img3" Margin="16,144,0,0" Source="images/kutu.png" Stretch="Fill" HorizontalAlignment="Left" Height="32" VerticalAlignment="Top" Width="156" Cursor="Hand" MouseEnter="img3_MouseEnter" MouseLeave="img3_MouseLeave" MouseLeftButtonDown="img3_MouseLeftButtonDown" />
                   <Image Name="img4" HorizontalAlignment="Left" Height="32" Margin="16,192,0,0" Source="images/kutu.png" Stretch="Fill" VerticalAlignment="Top" Width="156" Cursor="Hand" MouseEnter="img4_MouseEnter" MouseLeave="img4_MouseLeave" MouseLeftButtonDown="img4_MouseLeftButtonDown" />
                   <TextBlock Name="txt1" HorizontalAlignment="Left" Margin="55,53,0,0" TextWrapping="Wrap" Text="New Game" VerticalAlignment="Top" Foreground="White" Width="78" FontSize="13.333" Cursor="Hand" MouseEnter="txt1_MouseEnter" MouseLeave="txt1_MouseLeave" MouseLeftButtonDown="txt1_MouseLeftButtonDown" />
                   <TextBlock Name="txt2" HorizontalAlignment="Left" Margin="65,102,0,0" TextWrapping="Wrap" Text="Options" VerticalAlignment="Top" Foreground="White" Width="78" FontSize="13.333" Cursor="Hand" MouseEnter="txt2_MouseEnter" MouseLeave="txt2_MouseLeave" MouseLeftButtonDown="txt2_MouseLeftButtonDown" />
                   <TextBlock Name="txt3" HorizontalAlignment="Left" Margin="68,151,0,0" TextWrapping="Wrap" Text="Credits" VerticalAlignment="Top" Foreground="White" Width="78" FontSize="13.333" Cursor="Hand" MouseEnter="txt3_MouseEnter" MouseLeave="txt3_MouseLeave" MouseLeftButtonDown="txt3_MouseLeftButtonDown" />
                   <TextBlock Name="txt4" HorizontalAlignment="Left" Margin="76,199,0,0" TextWrapping="Wrap" Text="Exit" VerticalAlignment="Top" Foreground="White" Width="35" FontSize="13.333" Cursor="Hand" MouseEnter="txt4_MouseEnter" MouseLeave="txt4_MouseLeave" MouseLeftButtonDown="txt4_MouseLeftButtonDown" />
          </Grid>
</UserControl>

And here's the Codebehind:

private void Image_MouseEnter(object sender, MouseEventArgs e)
{
   img1.Source= new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void Image_MouseLeave(object sender, MouseEventArgs e)
{
    img1.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void txt1_MouseEnter(object sender, MouseEventArgs e)
{
    img1.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void txt1_MouseLeave(object sender, MouseEventArgs e)
{
    img1.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void img2_MouseEnter(object sender, MouseEventArgs e)
{
    img2.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void img2_MouseLeave(object sender, MouseEventArgs e)
{
    img2.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void txt2_MouseEnter(object sender, MouseEventArgs e)
{
    img2.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void txt2_MouseLeave(object sender, MouseEventArgs e)
{
    img2.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void img3_MouseEnter(object sender, MouseEventArgs e)
{
    img3.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void img3_MouseLeave(object sender, MouseEventArgs e)
{
    img3.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void txt3_MouseEnter(object sender, MouseEventArgs e)
{
    img3.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void txt3_MouseLeave(object sender, MouseEventArgs e)
{
    img3.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void img4_MouseEnter(object sender, MouseEventArgs e)
{
    img4.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void img4_MouseLeave(object sender, MouseEventArgs e)
{
    img4.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void txt4_MouseEnter(object sender, MouseEventArgs e)
{
    img4.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu_over.png", UriKind.Relative));
}
private void txt4_MouseLeave(object sender, MouseEventArgs e)
{
    img4.Source = new BitmapImage(new Uri("/SLMenu;component/images/kutu.png", UriKind.Relative));
}
private void img1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("New_Game");
}
private void img2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("Options");
}
private void img3_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("Credits");
}
private void img4_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("Exit");
}
private void txt1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("New_Game");
}
private void txt2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("Options");
}
private void txt3_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("Credits");
}
private void txt4_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Clipboard.SetText("Exit");
}

The process is so simple: When the mouse is over the box it changes the image source and when left returns it to normal. When mouse clicked the box, it sets a ClipBoard.

Its clear up to now. Before writing more codes I wish to clear up something. If we are to use Silverlight UI in our XNA Project we must ensure that there wont be a feel-of WebBrowser.

What I have tried to say is you've to remove Context Menu of Silverlight when Right-Clicked on WebBrowser and set the background of both of them the same. For example in this example we have been designing a White background UI, so we will have to set GraphicsDevice.Clear as the same color as Silverlight Background which is White.

Now take it further!

What is Silverlight Context Menu?

It's the menu that displays when we right click on a Silverlight-based web page, as seen below:

4.gif
 
Removing Context Menu is easy. All you have to do is following these steps shown below:
  1. Find your output file which you're running it on a Webbrowser.([Project]TestPage.html or [Project]TestPage.aspx)
  2. Find this section:

    <
    div id="silverlightControlHost">
    <object .....>
    .....
    .....
    </object>

  3. Add this parameter:

    <
    param name="Windowless" value="true" />

  4. Inside MainPage.xaml.cs add:

    using
    System.Windows.Browser;

  5. Add a new Class in your Silverlight Project named ContextMenuInterceptor and make it look like:

    public
    class ContextMenuInterceptor
    {
        public ContextMenuInterceptor() 
        {
            HtmlPage.Document.AttachEvent("oncontextmenu", this.OnContextMenu);
        }
        private void OnContextMenu(object sender, HtmlEventArgs e)
        {
            e.PreventDefault();
        }
    }

  6. In you MainPage call it:

    ContextMenuInterceptor
    context = new ContextMenuInterceptor();

  7. Run the Project. And you will see no ContextMenu when you right-click.
I would like to thank Diptimaya Patra for this nice tip.

After running go to the output folder and copy the .xap and .html(or .aspx) files to a destination which you can access easier.

Now that we have finished our Silverlight Menu, lets take a dive in our XNA Project.

This XNA Application is a demonstration. There is no game inside! Just writes some SpriteFont to the screen according to the value sent from Silverlight UI.
Create a new XNA 4.0 Windows Game Project. And inside Bin-> Debug folder copy the files (.xap and .html/.aspx) which we have earlier took output.
Add reference to System.Drawing and System.Windows.Forms and add them in your namespace section:

using System.Windows.Forms;
using System.Drawing;

Add a new SpriteFont file in your Content Folder and name it MyFont which looks like:

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">
    <FontName>Kootenay</FontName>
    <Size>20</Size>
    <Spacing>0</Spacing>
    <UseKerning>true</UseKerning>
    <Style>Regular</Style>
    <CharacterRegions>
      <CharacterRegion>
        <Start>&#32;</Start>
        <End>&#126;</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>

Add these variables anywhere:

WebBrowser browser = new WebBrowser();
SpriteFont Font1;
Vector2 FontPos;

In your Game1 constructor add:

this.IsMouseVisible = true;
Clipboard.Clear();

And in your UnloadContent function add:

Clipboard.Clear();

Why did we Clear ClipBoard?

Its because we don't want any previous or after values to be able to be used in our XNA Project. You don't need to use Ctrl+C and Ctrl+V combination so it fits to our purpose why we are using ClipBoard in our XNA Project.

Lets go ahead!

Update your Initialize Function as seen below:

protected override void Initialize()
{
  base.Initialize();
  browser.Width = graphics.PreferredBackBufferWidth / 2;
  browser.Height = graphics.PreferredBackBufferHeight;
  browser.AllowWebBrowserDrop = true;
  browser.ScriptErrorsSuppressed = true;
  browser.Navigate(Application.StartupPath + "/TestPage.html");
  Control.FromHandle(Window.Handle).Controls.Add(browser);           
}

You can change "TestPage.html" according to your output file from Silverlight.

We are setting the WebBrowser object to be displayed Left side of the XNA Project.

Update your LoadContent as seen below:

protected override void LoadContent()
{
  spriteBatch = new SpriteBatch(GraphicsDevice);
  Font1 = Content.Load<SpriteFont>("MyFont");
  FontPos = new Vector2(graphics.GraphicsDevice.Viewport.Width / 2, graphics.GraphicsDevice.Viewport.Height / 2);
}

MyFont is the asset we're using to access our previously added SpriteFont.

Create a function to easily WriteText using SpriteFont:

public void DrawText(string val)
{
  spriteBatch.Begin();
  string output = val;
  Vector2 FontOrigin = Font1.MeasureString(output);
  spriteBatch.DrawString(Font1, output, FontPos, Microsoft.Xna.Framework.Color.Black,
  0, FontOrigin, 1.0f, SpriteEffects.None, 0.3f);
  spriteBatch.End();
}

And finally Update your Draw Function as seen below:

protected override void Draw(GameTime gameTime)
{
  GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White);           
  KeyboardState stat = Keyboard.GetState();
  if (stat.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape))
  {
    Clipboard.Clear();
  }
  if (Clipboard.GetText() == "New_Game")
  {
    DrawText("New Game Clicked");
  }
  else if (Clipboard.GetText() == "Options")
  {
    DrawText("Options Clicked");
  }
  else if (Clipboard.GetText() == "Credits")
  {
    DrawText("Credits Clicked");
  }
  else if (Clipboard.GetText() == "Exit")
  {
    this.Exit();
  }
  if (Clipboard.ContainsText())
  {
    browser.Visible = false;
  }
  else
  {
    browser.Visible = true;
  }
  base.Draw(gameTime);
}

I will explain it step-by-step right now. Before that I believe it is time to tell you how the system works.

How System Works?

By clicking to a Menu Item in Silverlight application we are assigning some ClipBoard variables which are "New_Game","Options","Credits" and "Exit".

When we assign them we can get it's values from XNA using Clipboard.GetText()

If we want it to work like a real Game Menu, we have to provide "turning-back" to our game menu later. So we're adding a code like that when user presses Escape button.

KeyboardState stat = Keyboard.GetState();
  if (stat.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape))
  {
    Clipboard.Clear();
  }

We're clearing the ClipBoard because when we first saw menu there was no ClipBoard variable by the help of using ClipBoard.Clear() in Game1 Constructor.

If the ClipBoard has values then our WebBrowser will dissappear, and if not it will show again.

Using these codes we're querying specific ClipBoard values:

if (Clipboard.GetText() == "New_Game")
{
  DrawText("New Game Clicked");
}
else if (Clipboard.GetText() == "Options")
{
  DrawText("Options Clicked");
}
else if (Clipboard.GetText() == "Credits")
{
  DrawText("Credits Clicked");
}
else if (Clipboard.GetText() == "Exit")
{
  this.Exit();
}

And we draw our text using DrawText function we've created earlier.

If ClipBoard has a value named "Exit" we will let our XNA Project exit game and all the ClipBoard along with it will be cleared as we set it in UnLoadContent() function.

And finally:

if (Clipboard.ContainsText())
{
  browser.Visible = false;
}
else
{
   browser.Visible = true;
}

If ClipBoard has any values stored inside, we're telling the browser to dissappear, and if not browser will be seen again.

Before running this application make sure add [STAThread()] in Program.cs as WebBrowser works in a different Thread.

Program.cs:

using System;
namespace XNAPart
{
#if WINDOWS || XBOX
    static class Program
    {
        [STAThread()]
        static void Main(string[] args)
        {
            using (Game1 game = new Game1())
            {
                game.Run();
            }
        }
    }
#endif
}

Run the Project and experience the Silverlight UI working in XNA Game Project!

You can extend it to your own needs, and I would be glad to hear from you about your comments, ideas and critics.

Here are the screenshots from application...

When first run it will look like that:
 
5.gif

When we mouseover its background-image will change.

6.gif 

When we click "New Game" it will display a SpriteFont written "New Game Clicked"

7.gif 

Same as when we click Options,

8.gif 

And Credits...
 
9.gif
 
When we click on Exit, it will close the Application...

Silverlight and XNA integration is not it has to be in this article. But this is the way we can integrate these two technologies.

Hope this article have helped you understand why Silverlight is so important in Game Development especially while using it in XNA. You can use Silverlight for UI in your XNA Games as proven in this article...

Have a nice day!

COMMENT USING