Programmer en C++/Cli avec MonoGame

MonoGame est un Framework écrit en C# pour la plateforme .Net et Mono et permet de créer des jeux vidéo ou des applications multimédia sur plusieurs plateformes. L’avantage avec la machine virtuelle .Net est qu’elle permet d’utiliser plusieurs langages de programmation, ainsi vous pouvez avoir un projet en C# qui appel un projet en Visual Basic .Net qui lui même est appelé par un projet en C++/CLI voir en F#. A l’origine XNA et MonoGame ont été créés pour le langage C# (XNA en Visual Basic a été officiellement supporté à partir de Windows Phone 7.5) car c’est un langage cool et qu’il permet de garder le meilleur des 2 mondes entre C++ et des langages comme Java (oui il y a des choses très bien en Java aussi), cependant ce n’est pas strictement obligatoire, vous pouvez utiliser une dll .Net avec d’autres langages supportés par la CLR (Common Language Runtime) et là tout de suite nous allons voir comment utiliser MonoGame en C++/CLI.

Pour résumer le C++/CLI est une version du C++ qui permet d’accéder aux objets du Framework .Net tout en conservant la syntaxe et les spécifications natives du langage (accès mémoire, bas niveau, etc…) c’est donc particulièrement intéressent car vous pouvez avoir un projet qui utilise des objets gérés par le Framework .Net (on dit qu’ils sont « managed ») et des objets de la STL ou issus d’autres bibliothèques natives. Alors certains me dirons que c’est un peu faire les choses à l’envers mais j’ai trouvé ça assez fun alors je vous propose comment créer votre projet pas à pas. J’utiliserais MonoGame pour OpenGL dans ce tutoriel (vous trouverez tous les fichiers à la fin de l’article) mais vous pouvez utiliser la version DirectX sans problème. La première étape pour vous consiste donc à récupérer les dll de MonoGame et de les mettre de côté.

Création du projet

MG.cpp.1

La première étape consiste à créer un nouveau projet C++ de type CLR vide. Lorsque vous validez un dossier avec le projet est créé. Il faut maintenant copier les fichiers dll de MonoGame dans votre dossier de projet, pour ma part j’ai créé un dossier ThirdParty où ils sont placés. Ce n’est pas obligatoire si vous avez un dossier qui contient déjà ces dll, que vous ne voulez pas les recopier et que vous savez gérer ça vous même, alors vous serez quoi faire à la prochaine étape 😉

MG.cpp.2

Il faut maintenant ajouter les références à MonoGame et ses dépendances pour que nous puissions les utiliser. Un clic droit sur le projet et choisissez références dans le menu contextuel, de là vous pouvez ajouter de nouvelles références, ici on ajoute le contenu du dossier ThirdParty à savoir MonoGame.Framework.dll et ses dépendances.

MG.cpp.4

Il y a une étape supplémentaire par rapport à un projet .Net classique car il faut indiquer au compilateur de Visual Studio où chercher les références supplémentaires. Alors si vous avez MonoGame d’installé dans le Gac ça ne pose pas de problèmes, mais si vous utilisez une version classique (ce que nous faisons actuellement) il faut donner le chemin du dossier qui contient ces références. Pour configurer ça il faut aller dans les propriétés du projet (clic droit/Propriétés) et aller dans l’onglet C/C++ -> Général -> Répertoires #using supplémentaires et ajouter le chemin de votre dossier (ici ThirdParty).

MG.cpp.3

Nous sommes maintenant prêt à mettre du code et à faire un peu de MonoGame en C++ 😀 On va commencer par créer une classe Game1 comme c’est le cas dans le template de base de MonoGame et XNA, ensuite nous créerons un fichier Program.cpp qui contiendra le point d’entré permettant de lancer l’application.

MG.cpp.5

 Le code de base

Sans plus attendre voilà le contenu du fichier Program.cpp

#include <cstdlib>
#include "Game1.h"

int main(int argc, char *argv[])
{
	Game1 game;
	game.Run();

	return EXIT_SUCCESS;
}

Bon avouez que ce n’était pas violent 😛 on a notre classe Game1 qui est instanciée sur la pile et qui lance la méthode Run permettant au programme de ce lancer. Passons maintenant à des choses plus intéressentes, je veux bien sur parler de la classe Game1.

Game1.h

#pragma once

ref class Game1 :
public Microsoft::Xna::Framework::Game
{
private:
	Microsoft::Xna::Framework::GraphicsDeviceManager ^graphics;
	Microsoft::Xna::Framework::Graphics::SpriteBatch ^spriteBatch;

protected:
	// Initialisation de la logique
	virtual void Initialize() new = Microsoft::Xna::Framework::Game::Initialize;

	// Chargement des ressources
	virtual void LoadContent() new = Microsoft::Xna::Framework::Game::LoadContent;

	// Libération des ressources
	virtual void UnloadContent() new = Microsoft::Xna::Framework::Game::UnloadContent;

	// Mise à jour de la logique
	virtual void Update(Microsoft::Xna::Framework::GameTime ^gameTime) new = Microsoft::Xna::Framework::Game::Update;

	// Affichage à l'écran
	virtual void Draw(Microsoft::Xna::Framework::GameTime ^gameTime) new = Microsoft::Xna::Framework::Game::Draw;

public:
	Game1(void);
	virtual ~Game1(void);
};

La première choses qui peut choquer dans ce code est la surcharge des méthodes virtuelles car elle est vraiment lourde à écrire. On aurait pu ajouter l’instruction using namespace Microsoft::Xna::Framework au début du fichier et avoir ainsi une écriture moins lourde mais ce n’est pas recommandé d’utiliser des noms de type non complets. Alors je vous l’accorde c’est assez chiant à écrire au début mais c’est le prix à payer 😉

On remarque au passage la syntaxe du C++/CLI qui est celle du C++ mais avec des ajouts en plus. Par exemple l’objet SpriteBatch est dit « managed » car il est géré par la machine virtuelle .Net. Pour créer des instances de ces objets on utilise le symbole carret ^ qui indique à l’instar d’une étoile pour un pointeur, que cette variable est un handle vers un objet .Net. Sinon on retrouve notre structure globale à savoir un GraphicsDeviceManager, un SpriteBatch et nos méthodes bien connues. Si il y a des mots qui vous font peur ce n’est pas grave, prenez le temps de lire la documentation sur ces termes et tout ira mieux après.

Game1.cpp

#include "Game1.h"

using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
using namespace Microsoft::Xna::Framework::Input;

Game1::Game1(void) : Game()
{
	graphics = gcnew GraphicsDeviceManager(this);
	graphics->PreferredBackBufferWidth = 800;
	graphics->PreferredBackBufferHeight = 600;
	Window->Title = "MonoGame C++/Cli";
	Content->RootDirectory = "Content";
}

Game1::~Game1(void)
{
}

void Game1::Initialize()
{
	Game::Initialize();
}

void Game1::LoadContent()
{
	Game::LoadContent();
	spriteBatch = gcnew SpriteBatch(GraphicsDevice);
}

void Game1::UnloadContent()
{
	Game::UnloadContent();
}

void Game1::Update(GameTime ^gameTime)
{
	Game::Update(gameTime);
}

void Game1::Draw(GameTime ^gameTime)
{
	GraphicsDevice->Clear(Color::CornflowerBlue);
	Game::Draw(gameTime);
}

Bon là nous avons un peu plus de monde mais c’est normal car nous sommes dans le fichier d’implémentation de la classe ! Vous noterez tout de suite les appels aux méthodes parentes via Game::Method(param) ainsi que les paramètres des méthodes Update et Draw qui sont des handles vers un objet .Net de type GameTime. Sinon c’est assez classique et c’est du C++ objet comme on a l’habitude d’en voir. Vous notez qu’en début de fichier j’ai utilisé des directives using namespace, ça permet d’alléger le code en lecture.

La suite ! Charger des images et des fonts

Le C++/CLI est une version spécifique du C++ à apprendre si elle vous intéresse car il y a des choses à savoir sur l’utilisation des objets, leurs types, etc… Voici à titre d’exemple comme créer et utiliser un objet de type Texture2D et SpriteFont. N’oubliez pas qu’il faut des fichiers XNB dans un dossier Content et que ce dossier doit être à côté de l’exécutable. Dans la section private de déclaration de la classe Game1 ajoutez les 2 lignes suivantes :

Microsoft::Xna::Framework::Graphics::SpriteFont ^spriteFont;
Microsoft::Xna::Framework::Graphics::Texture2D ^texture;

Maintenant passons au fichier d’implémentation pour créer les instances et utiliser les objets.

  • La méthode LoadContent où nous chargeons les ressources
void SpriteGame::LoadContent()
{
	Game::LoadContent();
	spriteBatch = gcnew SpriteBatch(GraphicsDevice);
	spriteFont = Content->Load<SpriteFont^>("monSpriteFont");
	texture = Content->Load<Texture2D^>("maTexture");
}

Vous noterez que les nouvelles instances d’objet managés se font avec le mot clé gcnew, si vous avez des instances d’objet natif à faire vous devez utiliser new. Le dernier point concerne les type que l’on passe à la méthode Load<TypeName>, il faut ajouté un carret ^ à la fin du type car ce sont des références managées.

  • Et maintenant la méthode Draw pour afficher tout ça à l’écran
void SpriteGame::Draw(GameTime ^gameTime)
{
	GraphicsDevice->Clear(Color::CornflowerBlue);
	spriteBatch->Begin();
	spriteBatch->DrawString(spriteFont, "MonoGame with C++/Cli", Vector2(150, 30), Color::White, 0.0f, Vector2::Zero, Vector2::One, SpriteEffects::None, 1.0f);

	spriteBatch->Draw(texture, Rectangle(50, 100, texture->Width, texture->Height), Color::White);
	spriteBatch->End();
	Game::Draw(gameTime);
}

Bon et bien voilà je pense que vous avez tout ce qu’il faut pour vous lancer dans la réalisation d’un projet avec MonoGame en C++ ! Sachez que le C++/CLI est aussi utilisable sur Linux et Mac via Mono et que Microsoft tente de le rendre plus populaire en incitant les développeurs de jeux vidéo à l’utiliser. Je vous recommande de lire la FAQ sur developpez.com concernant ce langage ainsi que ce tutoriel d’introduction si vous débutez avec ce nouveau langage. Vous pouvez retrouver les sources dans un projet fonctionnel sur ma page github dédiée à ce blog.