Contents
This article shows how to embed a XNA-based game into a WinForms control with ease. Also, it explains how to integrate an XNA GS project into VS2008 (this IDE is not currently supported by XNA GS), and in turn, be able to use WPF with your XNA-based creation.
Any game developer knows that having a level-editing tool to construct the game's world is nowadays "a must". Depending on the project, it helps you build everything up faster and easier than if you had to design the level with the Notepad, by using something like, say:
00001110 00010000
00200100 02000000
0*000000***000 0
...........................
00030020000900010
What is more, most of us dream of making a level editor that fits our project's needs. Just let us face it: one thing is using a third-party tool, but creating our own custom-made level editor tool is a whole different story. Specially, if you plan to use XNA GS to create that tool.
When the XNA Framework was first released, I said "This is what I was waiting for!". When the second version of XNA GS was released some days ago, I added: "This is getting better and better!". As a C# (advanced) programmer, I love using XNA GS for the development of my games, prototypes, and "proof-of-concept". It is fun and -most of the times- simple.
Unfortunately, XNA GS seems not to be that straightforward if you want to create your own level editor, given that at first sight, there is no easy way to embed a XNA Game project into a WinForms control.
This issue was planned to be solved for the release of v2, but due to time issues, it was postponed by the XNA team for a later update. Please guys, understand all the effort the XNA team put to release XNA GS 2 -with all the new functionality- before Christmas. Come on! In fact, they deserve kudos and a break!
OK, but where does this situation leave us? Simple, we have to find a way to deal with it on our own.
If you do a Google search, or read this thread on the creator's forums, you will find different ways to get to the same goal, which, most of them implies re-implementing the graphics device, hiding the Game.Run()
functionality, and so on.
What if you want to take advantage of that functionality and still embed your XNA-based project into a WinForms control? What if you do not want to re-implement anything because you are lazy like me or you do not just feel like it?
To make things worse, what if you want to use VS2008 to handle your XNA-based project? You know that both XNA GS versions, either v1 (the refresh), or the just-released v2, do not yet support 2008 editions of this great IDE. Read this thread for more information.
To sum up, is there a simple way of embedding a XNA-based project into a WinForms control and at the same time of using VS2008? Well, let us find out.
This article is for Windows only (either XP or Vista).
Why? Because, in order to compile your games for the Xbox 360, you will only need any edition of VS2005 (as said in the previous section, VS2008 is not yet supported). Plus, I do not believe you can use WinForms controls in the 360. Otherwise, there would be no point in writing this article at all ;)
Also, if you are thinking of creating a level-editing tool, take into account that it is not the purpose of this article to explain how to create a level editor; therefore, and in particular, it will not show you how to dynamically load custom content at runtime, say, by using MSBuild. There are plenty of articles and a project that will teach you how to achieve that.
In fact, the main objective of this article is to demonstrate how to use VS2008 to create and manage XNA-based projects.
In order to compile the projects, you will need to install:
- VS C# Express 2005 (or any other VS2005 edition)
- VS C# Express 2008 (or any other VS2008 edition)
- The latest DirectX SDK
- The latest drivers of your graphics card
- The latest updates available for all the above (to get most of them, you can use "Windows Update"), and of course
- XNA GS 2.0 (just released: cheers for that!)
In the following sections, I will try to keep everything just "plain and simple" to assure an easy reading and understanding of the concepts and the example code.
Attached to this article, you will find two zip files containing the source code of each section. You are free to use and modify both, following "The Code Project Open License" (CPOL).
To keep the file size small, all that the XNA-based project does is to show the current date and time on the screen by using SpriteFonts. I assume you have the required knowledge of the XNA framework, so I am sure that after reading this article, you will extend the examples and templates as desired to meet your needs and dreams.
By the way, this is the first article I write for "The Code Project", so I appreciate your comments and suggestions ... just be nice, though ;)
Although this is not the main purpose of the article, I have found what I deem as an elegant and simple way of embedding an XNA-based game into a WinForms control.
Please do not misunderstand me. I still believe that when you need to go beyond what XNA GS offers right now on this regard, handling and controlling (a) how the Graphics device should be created and (b) how the main loop should behave, is the right way to go. No discussion about that.
However, and as I said a couple of sections above, if you are lazy like me, you could then be interested in the alternative I will soon present. Thus, if you do, then read on, but if you do not, then just skip this section.
First things first:
- Create a Windows Game project,
- Add a
Form
control (the IDE will automatically set a reference to System.Windows.Forms
), - Finally, add the WinForms control where you want to render, say a
Panel
, and - Implement a property that returns the handle associated to the control you will use for rendering.
Following the criteria of adding a Panel
control, the code of your partial class should be something similar to what is shown next:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsGame1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public IntPtr PanelHandle
{
get
{
return this.panel1.IsHandleCreated ?
this.panel1.Handle : IntPtr.Zero;
}
}
}
}
Now, let us go to main game file, by default named "Game1.cs", to modify the code as required.
Here, we will do a few things:
- Hide the game's window when it is first shown.
- Show our newly created
Form
control, instead. - Copy everything we draw to our
Panel
's canvas. - Exit the game when our
Panel
is destroyed.
In order to do everything but the third task, we need to modify the Initialize
method a little bit:
...
...
using SysWinForms = System.Windows.Forms;
...
...
namespace WindowsGame1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
...
...
Form1 myForm;
...
...
protected override void Initialize()
{
base.Initialize();
SysWinForms.Form gameWindowForm = (SysWinForms.Form)SysWinForms.Form.
FromHandle(this.Window.Handle);
gameWindowForm.Shown += new EventHandler(gameWindowForm_Shown);
this.myForm = new Form1();
myForm.HandleDestroyed += new EventHandler(myForm_HandleDestroyed);
myForm.Show();
}
...
...
}
}
Also, we need to implement how we will handle the two above-mentioned events:
namespace WindowsGame1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
...
...
void myForm_HandleDestroyed(object sender, EventArgs e)
{
this.Exit();
}
void gameWindowForm_Shown(object sender, EventArgs e)
{
((SysWinForms.Form)sender).Hide();
}
...
...
}
}
It is time to do the third task: to show all the things we draw in our Panel
control. In order to do that, we just need to add one simple line at the end of the Draw
method:
...
...
{
public class Game1 : Microsoft.Xna.Framework.Game
{
...
...
protected override void Draw(GameTime gameTime)
{
...
...
base.Draw(gameTime);
this.GraphicsDevice.Present(this.myForm.PanelHandle);
}
}
}
At this point, nothing else is needed. You should be able to execute your project and see that this implementation simply works just fine. By the way, now you know -in case you did not discover it earlier- why we needed to get the Panel
's handle in the first place.
In the attached zip file, you will find the complete source code with a couple of additions: we draw the current date and time on screen by using a simple SpriteFont
, and we also add a PropertyGrid
control -as the picture shows at the top of this article- just for fun.
One final comment about this implementation: you could probably experience an overhead because even though you are hiding the "main" game's window, you may be still drawing to it. Honestly, I did not test if that is the case because as I am using the technique for creating my own level editor, I just simply do not care that much about performance issues right now. Plus, as you can notice, I am lazy ;)
One of the "myths" behind the XNA framework is that you cannot create a project in VS2008. As it will be shown in this section, that assumption is wrong. Or at least, not completely true.
Despite the fact that VS2008 is not yet officially supported, you can find workarounds to create and manage your XNA projects in that IDE -edit, compile, run, and debug- since the XNA framework assemblies can be manually set as references, as we usually do with our external components, third-party components and all of the required .NET Framework assemblies to compile our code.
What you will lose by taking this path are mainly two crucial things: first, you will not be able to deploy your games to the Xbox 360, and second, using the content pipeline is out of the question.
The first restriction is not important to us, since we are targeting our code for the Windows platform only. But, what about the second restriction? Since this is a temporary workaround, here is where things get a bit more complicated in practice -even though the solution is simple in concept.
Unless we use MSBuild to compile the assets, we will need both VS2005 and VS2008. What we will do is separate the content from the rest of the code, and manage the former in VS2005 and the latter in VS2008. With this trick, you will be able to compile the content when needed, with VS2005, and then allow your VS2008 game project to use the files generated by the content pipeline in the form of binary output.
Take due note that there is no reason to manually copy the output from folder to folder in order to compile and execute your "whole" project, just create both related projects -that is, the VS2005 and VS2008 ones- and let them share the same Debug and Release folders. To change the output path, open your project's Properties, prompt for the "Build" tab, and make the changes in the proper field.
Do not forget to target your builds for the "x86" platform in VS2008, or you will get an error.
Let us summarize the steps we have just followed:
- Create a "
WindowsGame
" project in VS2008, - Set a reference to the
Microsoft.Xna.Framework
assembly, - Set a reference to the
Microsoft.Xna.Framework.Game
assembly, - Target any compilation for "x86" platform only,
- Create a "
WindowsGameLibrary
" project in VS2005, - Add all the assets you will use within the "
Content
" project, - Let both projects share the same Debug and Release folders,
- Compile the VS2005 project to generate the contents' binary files,
- Compile and run the VS2008 project/solution, and
- Enjoy the show.
You will find a complete sample with source code in the attached zip file.
An interesting thing about creating your game projects in VS2008 is that you can use all the goodies provided by the .NET Framework 3.5; that is, anonymous types, lambdas, LINQ to SQL, LINQ to Objects, and so on. But I will let you play around with these as a homework.
In the next couple of sections, things turn out to be more interesting. Believe me!
The previous section was all about integrating XNA into a VS2008 WinForms control.
You may probably know this already: the Windows Presentation Foundation allows us to construct UI elements for our applications by using a new declarative XML-based language: XAML (which stands for "eXtensible Application Markup Language").
Now, is it possible to embed an XNA game into a Windows Presentation Foundation's control? What is more, can we use XAML?
Simple answer for both questions: Yes!
Let us start by setting up a new solution, and then:
- Create a "WPF Application" project in VS2008,
- Set a reference to the
Microsoft.Xna.Framework
assembly, - Set a reference to the
Microsoft.Xna.Framework.Game
assembly, - Target any compilation for "x86" platform only,
- Create a "
WindowsGameLibrary
project in VS2005, - Add all the assets you will use within the "
Content
" project, - Configure IIS accordingly, and
- Let both projects share the same Debug and Release folders (as you did in the previous section).
Now, here is where differences appear in comparison with the example of the previous section.
First of all, in order to embed a WinForms control into a WPF window, you must use WindowsFormsHost
, which will serve as a host of the former. Open "Window1
" in Design view, and edit the XAML code as follows:
<Window x:Class="WindowsGame1_WPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wfc="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
Title="XNA & Beyond: The Path To VS2008 (Example 3)"
Height="587" Width="484" Background="SteelBlue">
<Grid Name="myGrid">
<Button Height="23" Margin="104,0,99,11" Name="button1"
VerticalAlignment="Bottom" Click="button1_Click">Press Me!</Button>
<WindowsFormsHost Margin="20,20,20,45" Name="windowsFormsHost1">
<wfc:Panel x:Name="myXnaControl" BackColor="Black" />
</WindowsFormsHost>
</Grid>
</Window>
As you can see in the code above, we are directly creating the WinForms Panel
within the XAML code, and we are naming it "myXnaControl
".
It is now my turn to modify the code behind this window so that it looks quite like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WindowsGame1_WPF
{
public partial class Window1 : Window
{
Game1 game;
public Window1()
{
InitializeComponent();
this.game = new Game1(this.myXnaControl.Handle);
this.Closing += new System.ComponentModel.CancelEventHandler(Window1_Closing);
}
void Window1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if(this.game != null)
{
this.game.Exit();
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.Background = Brushes.Black;
this.button1.IsEnabled = false;
this.game.Run();
}
}
}
What is new here? First, when the window is closed, we exit the game (if you remember the previous examples that were handled from within the game class itself). Second, when we construct the game, we are passing the Panel
's handle as a parameter. Third, the game will not run until we press a button (you can modify this behavior so that the game runs when you want).
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using SysWinForms = System.Windows.Forms;
namespace WindowsGame1_WPF
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont Font1;
Vector2 FontPos;
IntPtr myXnaControlHandle;
public Game1(IntPtr myXnaControlHandle)
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
this.myXnaControlHandle = myXnaControlHandle;
}
protected override void Initialize()
{
base.Initialize();
SysWinForms.Form gameWindowForm = (SysWinForms.Form)
SysWinForms.Form.FromHandle(this.Window.Handle);
gameWindowForm.Shown += new EventHandler(gameWindowForm_Shown);
}
void gameWindowForm_Shown(object sender, EventArgs e)
{
((SysWinForms.Form)sender).Hide();
}
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);
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
string output = DateTime.Now.ToString();
Vector2 FontOrigin = Font1.MeasureString(output) / 2;
spriteBatch.DrawString(Font1, output, FontPos, Color.LightGreen,
0, FontOrigin, 1.0f, SpriteEffects.None, 0.5f);
spriteBatch.End();
base.Draw(gameTime);
this.GraphicsDevice.Present(this.myXnaControlHandle);
}
}
}
The main changes in the above code are:
- We are not paying attention to the
HandleDestroyed
event anymore, and - We are initializing the class with a parameter of type
IntPtr
.
Guess the reason for what is stated in the last bullet? To avoid "interop" issues. Let me remind you that in all the previous examples, we used to get the handle from the respective Panel
's property when rendering. We could have implemented all those examples the same way, by passing the handle to the game's constructor, but I wanted to show the difference.
Anything left to do? Just compile both projects to see something like the picture below:
And the following must happen when you press the button located at the bottom of the window:
Piece of cake, right? Now you can take full advantage of this new technology and enjoy its benefits.
This is something I will investigate in the future.
The reason: maybe there is a way to embed and run an XNA-based application into a webpage by using the Silverlight technology. It would be really nice to see our games loaded and played within a browser.
However, thinking loud about it, I believe that some restrictions shall apply:
- The client must have installed the latest DirectX drivers (so any OS based solely on OpenGL technology cannot run the game), and as a corollary,
- Given that any solution eventually found is for Windows only, an implementation for the Xbox 360 should not be considered a priori -I am afraid.
Perhaps, my second article should focus on this investigation once Silverlight 2.0 is finally released; as far as I know, that version will allow us to consume WinForms controls.
In the meantime, there is another workaround in this regard, which I hereunder present for learning purposes, only.
A strong word of warning before you read on: you should never give any assembly (and or a website) full-trust privileges because you can open your system to face security risks since your machine could get remotely owned by third-parties. If you do, because you think you know what you are doing or for any other reason, you are granting that trust/privileges at your own risk.
When I was trying to figure out a way to run a XNA-based game within a browser, I remembered that using the <Object>
HTML tag and the proper settings, we can embed a .NET assembly into a webpage. Like, say:
<Object id="myControl" name="myControl"
classid="myWinControl.dll#myNameSpace.myWinControl"
width="400" height="300" VIEWASTEXT></Object>
Although the above-mentioned method does work, some conditions shall be met to avoid problems and disappointment:
- It may work with Internet Explorer, only,
- You cannot sign your assembly with a strong name,
- Every time you modify and rebuild the object, you should manually clear the GAC (or manually locate and delete the DLL file first cached),
- You should grant your assembly full-trust privileges,
- If you are building an ASP.NET site, you should not deploy your DLL to the bin folder -since the latter is for private access only,
- You should add your website (localhost or remote one) to the trust-zone list, and
- You should pray and wait for all the planets in our Solar System to get perfectly aligned.
This whole "mess" is not what we want, isn't it? So, I looked into a second approach which, in turn, drove me to this great article: "Hosting a .NET ActiveX Control in Visual FoxPro".
Making the assembly COM-visible opens the door for great possibilities in this field since we are exposing our .NET assemblies as ActiveX controls. Plus, it works "with less trouble" than the previous alternative. On the other hand, and as said before, it could also open the door to security risks, so be careful when you mess around with the security policies of your machine or any clients' machines. I repeat, this example is for learning purposes only.
OK, let us see some code, shall we? By the way, please bear in mind that this is a "proof-of-concept", with the sole purpose of showing that the idea can be achieved in practice. Therefore, the following implementation is simple in design, and thus, it lacks certain desirable features and controls.
First, create a WinForms project with Visual Studio (or VC Express edition), name it "XnaGame
", and then add the Game1
file we used in the previous examples. Also, create a UserControl
, and name it "XnaPanel
". We will not be needing any Form
control created, by default, so just delete it.
Open the properties of the project, and register the assembly for COM interop; also, make the assembly "COM
" visible by modifying the AssemblyInfo.cs file (under the Properties folder):
...
[assembly: ComVisible(true)]
...
Having done so, the code of our XnaPanel
class should be the following:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
using System.IO;
namespace XnaGame
{
[Guid("2CD2873E-2A50-4ac9-98CA-B13ACFCC6DFA")]
[ProgId("XnaGame.XnaPanel")]
[ComVisible(true)]
public partial class XnaPanel : UserControl
{
static string localPath;
public XnaPanel()
{
InitializeComponent();
this.HandleCreated += new EventHandler(XnaPanel_HandleCreated);
}
void XnaPanel_HandleCreated(object sender, EventArgs e)
{
localPath =
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
localPath += @"\Temp\XnaGame\Content";
while(!Directory.Exists(localPath))
{
}
Thread gameThread = new Thread(RunGame);
gameThread.Start();
}
void RunGame()
{
new Game1(this.Handle, localPath).Run();
}
}
}
What we are doing here is nothing different from what we had seen before, with the exception of:
- The loop inside the
XnaPanel_HandleCreated
method, - The fact that the game will run on its own thread (as suggested by DonCroco), and
- The COM-related attributes (you must provide this class with a
GUID
).
As you can see from the code above, we will wait until the Content folder is created locally.
Why? Well, how can we access the Content folder in the server? So far, and please correct me if I am wrong, we cannot. Given that the COM object is executed on the client-side and that the Content Pipeline does not currently allow us to set a folder on the Internet as our game's Content folder, we must create the Content folder and copy all the assets, locally.
Who creates it? And when? We shall see in a couple of paragraphs below.
In order to give the final touches to this project, let us change the constructor of our game so that it takes into account the path to the local Content folder and notifies the content pipeline properly:
...
public Game1(IntPtr myPanelHandle, string localPath)
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = localPath;
this.myPanelHandle = myPanelHandle;
}
...
We are now in conditions of building the project. When doing so for the first time, VS will register the assembly for COM interop by including the following entries to the Windows Registry:
[HKEY_CLASSES_ROOT\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}]
@="XnaGame.XnaPanel"
[HKEY_CLASSES_ROOT\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\Implemented Categories]
[HKEY_CLASSES_ROOT\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\Implemented Categories\
{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]
[HKEY_CLASSES_ROOT\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="XnaGame.XnaPanel"
"Assembly"="XnaGame, Version=1.0.0.0, Culture=neutral, PublicKeyToken=99c20c94ca29957b"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="<ThePathToTheFolderWhereYourAssemblyIsLocated>/XnaGame.dll"
[HKEY_CLASSES_ROOT\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\InprocServer32\1.0.0.0]
"Class"="XnaGame.XnaPanel"
"Assembly"="XnaGame, Version=1.0.0.0, Culture=neutral, PublicKeyToken=99c20c94ca29957b"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="<ThePathToTheFolderWhereYourAssemblyIsLocated>/XnaGame.dll"
[HKEY_CLASSES_ROOT\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\ProgId]
@="XnaGame.XnaPanel"
[HKEY_CLASSES_ROOT\XnaGame.XnaPanel]
@="XnaGame.XnaPanel"
[HKEY_CLASSES_ROOT\XnaGame.XnaPanel\CLSID]
@="{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}]
@="XnaGame.XnaPanel"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\
{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\Implemented Categories]
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\
{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\Implemented Categories\
{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\
{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="XnaGame.XnaPanel"
"Assembly"="XnaGame, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=99c20c94ca29957b"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="<ThePathToTheFolderWhereYourAssemblyIsLocated>/XnaGame.dll"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\
{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\InprocServer32\1.0.0.0]
"Class"="XnaGame.XnaPanel"
"Assembly"="XnaGame, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=99c20c94ca29957b"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="<ThePathToTheFolderWhereYourAssemblyIsLocated>/XnaGame.dll"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}\ProgId]
@="XnaGame.XnaPanel"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\XnaGame.XnaPanel]
@="XnaGame.XnaPanel"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\XnaGame.XnaPanel\CLSID]
@="{2CD2873E-2A50-4AC9-98CA-B13ACFCC6DFA}"
<ThePathToTheFolderWhereYourAssemblyIsLocated>
is not the actual output, just a dummy placeholder for example purposes. Instead, if you check your Registry, that fake path should be substituted with the real path to the folder where the DLL is located on your machine (or remotely, in case you uploaded it to your website on the Internet).
Also, this is "automagically" done by VS, but when you deploy your project to the Internet, you must provide a way to register the COM object, as explained by the article "Hosting a .NET ActiveX Control in Visual FoxPro" (please refer to the sample implementation of the RegisterClass
/UnregisterClass
static
methods in that article).
Second project: Create a new ASP.NET web project (with VS or VWD Express), name it "XnaOnWebSite
", and add the Content folder with the font file to that project (as we had seen in the previous sections).
First, we modify the code inline as follows:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="XnaOnWebSite._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Xna-Based Game On A WebPage</title>
</head>
<body bgcolor="Black">
<form id="form1" runat="server" style="margin-top: 50px;">
<div style="text-align: center;">
<object id="myXnaGameControl" name="myXnaGameControl"
classid="clsid:2CD2873E-2A50-4ac9-98CA-B13ACFCC6DFA"
width="640" height="480" VIEWASTEXT>
</object>
</div>
</form>
</body>
</html>
Please notice that instead of using classid="XnaGame.dll#XnaGame.XnaPanel"
, we are using the class GUI
we provided to the COM-visible class.
Then, we modify the code-behind:
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.IO;
namespace XnaOnWebSite
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string serverPath = Server.MapPath(@"\Content");
DirectoryInfo serverFolder = new DirectoryInfo(serverPath);
if (!serverFolder.Exists)
{
throw new DirectoryNotFoundException("The Content" +
" folder is not present on the server.");
}
string localPath = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData);
localPath += @"\Temp\XnaGame\Content";
if (Directory.Exists(localPath))
{
DirectoryInfo localFolder = new DirectoryInfo(localPath);
localFolder.Delete(true);
}
Directory.CreateDirectory(localPath);
FileInfo serverFile = new FileInfo(serverPath + @"\myFont.xnb");
if (!serverFile.Exists)
{
throw new FileNotFoundException("The file 'myFont.xnb' is" +
" not present in the server's Content folder.");
}
serverFile.CopyTo(localPath + @"\myFont.xnb", true);
}
}
}
I guess the code above is self-explanatory, but basically, what we are doing is copying the content of the server's Content file to a local Content file. For the example, there is no need to use recursive operations since we know there is only one file to copy.
OK. When you build and execute the project, your browser will open the new "local" website (which you must have added to the trusted-zone list).
You will notice that the assembly is still cached on your local machine (somewhere on you AppData folder; look for a folder named "dl2" or "dl3") and that the DLL is accompanied by one ini file, by default named "_AssemblyInfo_.ini", which includes a content similar to this:
X n a G a m e , 1 . 0 . 0 . 0 , , 9 9 c 2 0 c 9 4 c a 2 9 9 5 7 b
<ThePathToTheFolderWhereYourAssemblyIsLocated> / X n a G a m e . d l l
One interesting note: You can modify the Registry entries created by VS when you first build the COM-visible assembly; so, let us say that you are hosting your XnaGame.dll file in a folder on the URI "http://yoursite.com/MyBins"; you can modify the Registry, changing the "old" path with the new one, and the next time you check for that file, you will get the following:
X n a G a m e , 1 . 0 . 0 . 0 , , 9 9 c 2 0 c 9 4 c a 2 9 9 5 7 b
h t t p : / / y o u r s i t e . c o m / M y B i n s / X n a G a m e . d l l
Meaning? Every time you open the website, the file will be downloaded and cached from that remote location.
At last! The nice part: if everything went OK, you should see something like the screenshot below:
If something went wrong, do not forget that in order to run the example code, you must:
- Sign the assembly with a strong name,
- Register the COM object, and
- Add your website to the Trusted-Zone list.
Depending on your browser's configuration, the COM object could get blocked -because it cannot verify the provider since we have not attached any certificate to our assembly- but if you did the above-mentioned tasks, you will do just fine.
As said before, this example is just a simple base. A start point. An interesting idea based on this would be creating a "XNA Web Player", where the player assembly remains separated from the game itself.
A way to implement this is by creating an installer that saves the COM-visible assembly on the client's "Program Files" directory; and this assembly (a) includes, say, a sort of plug-in system and (b) when executed by the browser, it:
- Checks whether the Content folder exists, locally,
- Checks whether new files should be copied to that folder,
- Copies any new version of the assets,
- Downloads the game to memory or to a "temp" folder,
- Executes the game, and obviously
- Serves as a host for the game within the browser.
In other words, something similar to the usual "web players", or to those offered by game-related frameworks where you install the player and the DLL containing the game-engine DLLs.
Guess what? Our basic DLLs are already present on the clients' machines when they installed XNA GS 2 distributable assemblies in the first place. The only ones to handle would be third-party DLLs your game consumes.
Then, it would be just a matter of inserting the following code somewhere in the webpage:
<Object id="myControl" name="myControl"
classid="clsid:2CD2873E-2A50-4ac9-98CA-B13ACFCC6DFA"
width="400" height="300" VIEWASTEXT>
<param name="Game" value="/myServerFolder/myGame.dll">
<param name="Content" value="/myServerFolder/Content/">
...
</Object>
I cannot imagine how handy something like this would come for showing off our demos, prototypes, and so on. And, I guess you share this feeling ...
As demonstrated in this article, with "some little" effort, we can use XNA GS 2.0 and VS2008 side by side without problems to create and manage our Windows projects.
The workaround presented is simple, and allows us to take advantage of all the new features included in .NET Framework 3.5.
It is also demonstrated how to embed a XNA-based game into a webpage by using the <Object>
tag and COM interop with .NET assemblies. The example provided in this regard is just a "kick off" for future investigation.
I hope you have found this article useful - as well as enjoyed reading it as much as I have writing it.
As we wait for an official solution from the XNA team, here is a list of interesting ideas for you to play with:
- Use all the "tricks" explained with your own XNA-based projects (I have been able to test all my example codes with the XNA team's "Net Rumble" starter kit, so all I can say is that the code works). Get the most of the new features available in .NET 3.5 in your XNA-based projects; that is, to use anonymous types, LINQ, lambdas, and so on,
- Use the code for rendering one viewport per WinForms control - no level editor is a good level editor if it cannot provide different views of the game world at the same time: perspective view, front view, top view, left view, etc.
- And, of course, do not forget to implement a nice grid object.
This is the second version of the article.
Changes since first version:
- Added content showing how to embed an XNA-based game into a webpage
- Updated example code to XNA GS 3.1 and .NET Framework 3.5 with a great fix (thanks Don Croco!)