XNA dans une Image WPF
Le problème de mélanger ces deux technologies que sont WPF et XNA existe depuis leurs débuts, chacune étant fort performante dans son domaines mais dans certains cas, nous aimerions bien pouvoir mixer les deux.
Au hasard, un éditeur d’environnement pour jeux vidéo
.
Il existe plusieurs solution plus ou moins viables, on peut citer par exemple le classique WindowsFormHost qui marche mais implique certaines limitations comme l’impossibilité de poser des contrôles WPF par-dessus le rendu XNA.
Valentin Billotte propose quant à lui une méthode différente et qui pour l’avoir vu tournée marche plutôt bien mais certains ont quelques réticences pour l’utiliser en raison de son principe de superposition des rendu
Par le biais de mon travail de recherche sur le produit Artlantis.com, une idée a germée dans mon esprit mais est restée bien longtemps tranquille par manque de temps.
Il y a peu, je suis tombé sur un forum ou quelqu’un évoquait cette même idée mais n’avait pas vraiment fait de proof-of-concept donc je me suis dis que c’était le bon moment !
Une fois expliqué en détail le process, un de mes collègues sur le projet de fin d’étude que je lead (PrismEngine), Alexandre Bossard nous a mis rapidement en place ce proto pour démontrer que oui cette solution est viable !
La solution a été un peu retouché car est maintenant notre méthode de rendu au sein de notre éditeur mais voici la marche à suivre si vous souhaitez essayer cette méthode qui pour le moment ne nous a pas déçu.
A noter que cette solution est viable pour un éditeur mais je ne promets rien pour un jeu
, d’une part utilisation de WPF donc aucune porta sur Xbox360/Zune/WP7 mais surtout malgré un FPS tout à fait correct, la transformation reste couteuse.
Son avantage est de garder toute la puissance de WPF, avec une modification minimum de votre class Game coté XNA.
La seule chose « contraignante » est la partie input qu’il faut légèrement surcouché car on ne travail plus directement sur le rendu XNA mais une simple Image WPF.
Oui, oui, une simple Image WPF donc aucuns soucis pour y ajouter divers contrôle, effet graphique et autre choses marrantes
.
Voici la marche à suivre pour faire le premier proto, une bonne réorganisation est utiles pour rendre les choses plus élégantes mais allons au plus simple pour comprendre l’idée!
Dans l’ordre nous aurons besoin de :
1. Un projet XNA pour PC que l’on compilera sous forme de library et non exécutable (pour pouvoir l’embarquer au sein de l’appli WPF
2. Une appli WPF dans laquelle on ajoute une référence vers Microsoft.Xna.Framework.Game
Préparons notre class Game maintenant :
On y ajoute quelques « helpers » qui vont nous servir a faire le gros du travail
RenderTarget2D target; // Nous allons dessiner notre scene dedans public WriteableBitmap bitmap = new WriteableBitmap(800, 460, 96, 96, System.Windows.Media.PixelFormats.Bgra32, null); // l'image que nous donnerons a WPF pour l'éditeur Color[] colors = new Color[800 * 460]; // le tableau pour remplir notre image private System.Windows.Threading.DispatcherTimer timer; // un timer, nous allons nous passer du cycle XNA classique donc il faut simuler nos ticks
Au sein de votre Constructor, nous allons ajouter 3 choses:
// on fix une hauteur et largeur en rapport avec notre image, bien sur ensuite ce morceau se devra de gérer les changements de résolution au runtime this.graphics.PreferredBackBufferHeight = 460; this.graphics.PreferredBackBufferWidth = 800; this.graphics.ApplyChanges(); //on appel différentes méthodes de la classe Game manuellement, nous ne passerons pas par le cycle XNA donc c'est a nous de le faire this.Initialize(); this.LoadContent(); //on lance le timer qui va nous permettre de gérer nos Ticks this.timer = new System.Windows.Threading.DispatcherTimer(); this.timer.Interval = new System.TimeSpan(5000); this.timer.Tick += new EventHandler(this.EngineTick); this.timer.Start();
dans le LoadContent, nous allons initialiser notre RenderTarget:
this.target = new RenderTarget2D(graphics.GraphicsDevice, 800, 460, false, SurfaceFormat.Bgra4444, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents);
Et viens la seule partie qui implique vraiment quelques changements dans votre code, la méthode Draw.
Au lieu de le faire de manière classique, nous allons rendre l’image dans notre texture puis ensuite copier son contenu au sein de notre WriteableBitmap qui sera l’image que nous donnerons a l’interface WPF.
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.SetRenderTarget(target); // on indique que l'on veut rendre dans notre RenderTarget
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime); // on rend notre scene
graphics.GraphicsDevice.SetRenderTarget(null);
target.GetData<Microsoft.Xna.Framework.Color>(colors) // on recupere les pixels de notre scene
// Bridge wpf/xna
// Nous allons ecrire dans notre WriteableBitmap et lui assigner les pixels de notre scene
bitmap.Lock();
unsafe
{
int bb = (int)bitmap.BackBuffer;
foreach (Microsoft.Xna.Framework.Color color in colors)
{
*((int*)bb) = color.B | (color.G << 8 ) | (color.R << 16) | (color.A << 24);
bb += sizeof(IntPtr);
}
}
bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, 800, 460));
bitmap.Unlock();
}
On remarque que l’utilisation d’un peu de code unsafe est obligatoire, la doc msdn est d’accord avec nous sur ce point, pensez donc bien a activé le code unsafe dans les propriétés de votre projet.
Dans notre Windows WPF, il nous suffit maintenant d’instancier notre classe Game mais au lieu de faire appel à la méthode Run qui va d’elle-même créer une fenêtre de rendu et faire appel aux méthodes Init, load,…, chose que nous ne voulons pas, nous allons laisser faire notre Constructor légèrement modifié.
Voici le code-behind de ma fenetre de test :
public partial class MainWindow : Window
{
XNAPart.Game1 game;
public MainWindow()
{
InitializeComponent();
this.Init();
}
private void Init()
{
this.game = new XNAPart.Game1();
// le fichier XAML de notre Windows comporte un controle de type Image qui porte le nom "Preview", on lui assign l'image de notre jeu
this.Preview.Source = this.game.bitmap;
}
}
Pas trop compliqué pour un résultat vraiment sympa et agréable à utiliser
.
Petite preview avec le rendu XNA dans l’image du haut, un petit bouton par-dessus et un coup de visualBrush à partir de notre Image, chose impossible par exemple avec la méthode du WindowFormHost.


twittercontrol