Message #449
Sujet: Petit moteur "tilemap" en C# avec Irrlicht.
| Type | Date | Auteur | Contenu |
|---|---|---|---|
| Dernière modification | 02-11-2006 17:32:59 | Agar |
Ce tutorial est un peu gros (500 Ko), avec des fichiers de texture et des modèles. Vous pouvez le télécharger ici: http://irrlichtfr.free.fr/Tutoriaux/tiletutorial.zip
ATTENTION: Irrlicht.DLL et Irrlicht.Net.DLL ne sont PAS présents dans l'archive. Vous devrez les copier dans le répertoire du programme pour qu'il se lance.
Voici le code source de l'unique fichier .CS Notez bien que vous aurez besoin des fichiers présents dans l'archive tiletutorial.zip pour le faire tourner.
using System; using System.Collections.Generic; using System.Windows.Forms; using Irrlicht; using Irrlicht.Core; using Irrlicht.Scene; using Irrlicht.Video; /* * * TUTORIAL - Moteur "tile engine" très très basique avec Irrlicht. * Ecrit par Ambroise Garel (agar@cafedefaune.com) pour www.irrlicht.fr * * Le but de ce tutorial est de démonter au lecteur qu'il est extrêmement simple de réaliser un petit * moteur utlisant des "tiles" avec Irrlicht. * * Cette version est très basique, mais donne les outils principaux (repérage de l'endroit sur lequel le joueur * clique afin de déplacer son perso, etc...) * * Ce moteur peut servir de base à la réalisation d'un Diablo-like, d'un "Zelda", d'un jeu de stratégie, etc... * * Merci de bien vouloir me tenir au courant si vous avez de ce tutorial autre chose qu'un usage privé * (upload sur votre site, traduction, etc...). Pas pour vous crier dessus, juste pour être au courant. Merci! * * NB: Je sais que tous ces objets statiques ne sont pas beaux du tout, mais c'est un tutorial, pas un vrai * programme. J'ai cherché à faire au plus lisible et au plus simple. * * */ namespace TileTutorial { public class TileEngine { // La taille de la carte const int MAPSIZE_X = 32; const int MAPSIZE_Y = 32; // La taille de la fenêtre d'affichage const int WINDOWSIZE_X = 800; const int WINDOWSIZE_Y = 600; // Le device Irrlicht et ses petits. public static IrrlichtDevice dev; public static ISceneManager scm; public static IVideoDriver drv; // Le receveur d'évènement, une classe nommée MyEventReceiver (voir plus bas) public static MyEventReciever evnt; // La camera public static ICameraSceneNode cam; // Les tiles public static ISceneNode[,] FloorTile = new ISceneNode[MAPSIZE_X, MAPSIZE_Y]; // Le joueur public static IAnimatedMeshSceneNode Player; // La position où le joueur doit aller. public static Vector3D playertargetpos; // Une source de lumière public static ILightSceneNode light; // Tant que Running est "true", le programme tourne. public static bool Running; // Fonction Main() // Tout commence ici. [STAThread] static void Main() { // On demande à l'utilisateur s'il souhaite utiliser OpenGL ou DirectX DialogResult DR; DR = MessageBox.Show("Souhaitez-vous utiliser OpenGL ? Si vous répondez non, Direct3D 9.0c sera utilisé.", "OpenGL ou non ?", MessageBoxButtons.YesNo, MessageBoxIcon.Question); DriverType DT; if (DR == DialogResult.Yes) DT = DriverType.OPENGL; else DT = DriverType.DIRECT3D9; // On demande à l'utilisateur s'il souhaite utiliser du normal mapping bool usenormals = false; DR = MessageBox.Show("Souhaitez-vous utiliser des normal maps ?", "Normal maps ou vieilles textures à l'ancienne ?", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (DR == DialogResult.Yes) usenormals = true; // Création du device Irrlicht dev = new IrrlichtDevice(DT, new Dimension2D(WINDOWSIZE_X, WINDOWSIZE_Y), 32, false, false, false, false, new IntPtr()); scm = dev.SceneManager; drv = dev.VideoDriver; dev.WindowCaption = "Pressez échap pour quitter"; // Création du receveur d'évènements evnt = new MyEventReciever(); dev.EventReceiver = evnt; // On charge le mesh utilisé pour le sol. IMesh TileMesh = scm.GetMesh(@"..\\..\\media\\floor.x").GetMesh(0); // Il faut créer les tangeantes pour utiliser le normal mapping. if (usenormals) TileMesh = scm.MeshManipulator.CreateMeshWithTangents(TileMesh); // Et maintenant la petite texture qui va avec. ITexture TileTex = drv.GetTexture(@"..\\..\\media\\floor.jpg"); // Et la normal map. ITexture TileTexNormal = drv.GetTexture(@"..\\..\\media\\floor_normal.jpg"); // Création du monde... // 1- D'abord, on ajoute les "tiles" du sol for (int i = 0; i < MAPSIZE_X; i++) for (int j = 0; j < MAPSIZE_Y; j++) { // Très important : les nodes des tiles ont une ID de 1! Cela servira lors de la détection // des clics. FloorTile[i, j] = scm.AddMeshSceneNode(TileMesh, null, 1); // FloorTile[i, j].SetMaterialFlag(MaterialFlag.LIGHTING, false); FloorTile[i, j].SetMaterialTexture(0, TileTex); FloorTile[i, j].Position = new Vector3D(i * 5, 0, j * 5); if (usenormals) { FloorTile[i, j].SetMaterialTexture(1, TileTexNormal); FloorTile[i, j].SetMaterialType(MaterialType.NORMAL_MAP_SOLID); } } // Et voilà votre incarnation : syndney.md2 Player = scm.AddAnimatedMeshSceneNode(scm.GetMesh(@"..\\..\\media\\sydney.md2"), null, 0); Player.SetMaterialTexture(0, drv.GetTexture(@"..\\..\\media\\sydney.bmp")); Player.Position = new Vector3D(2.5f, 2.5f, 2.5f); // Syndey est un peu grosse, alors on va la réduire. Player.Scale = new Vector3D(0.15f, 0.15f, 0.15f); Player.SetMD2Animation(MD2AnimationType.STAND); // Pour le moment, le joueur ne va nulle part, donc sa position cible sera sa position actuelle. playertargetpos = Player.Position; // On ajoute la caméra... cam = scm.AddCameraSceneNode(null, new Vector3D(0, 25, 25), new Vector3D(0, 0, 0), 0); // Et une petite source de lumière, histoire qu'on y voit quelque chose. light = scm.AddLightSceneNode(null, new Vector3D(25, 25, 25), new Colorf(1.0f, 0.95f, 0.9f), 100, 0); // C'est parti! Tant que running est "true", la boucle principale tourne. Running = true; // Et voici donc cette boucle de dessin principale. DrawLoop(); // On est sorti de la boucle principale car la fenêtre n'est plus active. // Il ne reste plus... // 1- Qu'à détruire le device dev.CloseDevice(); // 2- Et à faire un peu le ménage dans la mémoire. GC.Collect(); } static void DrawLoop() { // Boucle tant que la fenêtre est active. while (Running) { // On dessine toutes ces petites choses. drv.BeginScene(true, true, new Color(0, 0, 0, 0)); scm.DrawAll(); drv.EndScene(); // On laisse un peu respirer l'application. Application.DoEvents(); // Si le joueur ne se trouve pas à sa position cible, il faut qu'il bouge... if ( (playertargetpos.X != Player.Position.X) || (playertargetpos.Z != Player.Position.Z) ) { float speedx = 0.0f; float speedz = 0.0f; // On vérifie l'axe X if (Player.Position.X < playertargetpos.X) speedx = 0.1f; else if (Player.Position.X > playertargetpos.X) speedx = -0.1f; // On vérifie l'axe Z if (Player.Position.Z < playertargetpos.Z) speedz = 0.1f; else if (Player.Position.Z > playertargetpos.Z) speedz = -0.1f; // On fait bouger tout ça... Player.Position = new Vector3D(Player.Position.X + speedx, 2.5f, Player.Position.Z + speedz); //Pour éviter un effet de "sautillement", si le joueur est suffisemment près de sa position //cible, on l'y place directement. if (Math.Abs(Player.Position.X - playertargetpos.X) < 0.15f) Player.Position = new Vector3D(playertargetpos.X, 2.5f, Player.Position.Z); if (Math.Abs(Player.Position.Z - playertargetpos.Z) < 0.15f) Player.Position = new Vector3D(Player.Position.X, 2.5f, playertargetpos.Z); } // La caméra suit le perso cam.Position = new Vector3D(Player.Position.X + 10, 15, Player.Position.Z + 10); cam.Target = Player.Position; // La lumière suit le joueur, puisqu'il a un "ring of light +1" // Je ne vous l'avait pas dit? Maintenant, c'est fait. light.Position = new Vector3D(Player.Position.X +10, 5, Player.Position.Z +10); } } } public class MyEventReciever : IEventReceiver { // Lecture des évènements Irrlicht (en l'occurence, les clics de souris) public bool OnEvent(Event e) { if (e.Key == KeyCode.KEY_ESCAPE) TileEngine.Running = false; // Si c'est pas la souris, poubelle. Il n'y a que la souris qui nous intéresse. if (e.Type != EventType.MouseInput) return false; // Un clic gauche... Ah, ah! Ca veut dire que le joueur veut bouger. if (e.MouseInputType == MouseInputEvent.PressedDownLeft) { // On doit trouver sur quel tile le joueur a cliqué. // Ca se fait avec le SceneCollisionManager. // Notez que le paramètre "idbitmask" permet de ne prendre en compte que certains nodes. // (Ici, ceux avec l'ID 1, donc les tiles. Cliquer sur Sydney ne fait donc rien). ISceneNode ClickedNode = TileEngine.scm.SceneCollisionManager.GetSceneNodeFromScreenCoordinatesBB(e.MousePos, 1, true); // Si le joueur n'a pas cliqué sur une tile, on oublie tout ça... if (ClickedNode == null) return false; // Sinon, le joueur doit maintenant aller sur cette tile. // On ajoute 2,5 aux coordonnées X et Z pour que le joueur aille au centre de la tile, pas // dans un coin. TileEngine.playertargetpos = new Vector3D(ClickedNode.Position.X + 2.5f, 0, ClickedNode.Position.Z + 2.5f); } return false; } } } /* * * Merci d'avoir lu mon tutorial. * Si vous avez des questions ou des suggestions, vous pouvez m'écrire à l'adresse agar@cafedefaune.com. * * QUELQUES SUGGESTIONS POUR ALLER PLUS LOIN: * ========================================== * * 1- Déjà, il serait bien que le perso soit animé et se tourne dans la direction où il se déplace. Si le premier * point est très simple à régler, le deuxième nécessite de sortir un peu de trigonométrie. * * 2- La carte est petite par défaut (32x32), donc ça tourne pas trop mal. Maintenant, avec une carte plus grande, * les tiles trop nombreuses risquent de noyer Irrlicht (et la carte vidéo) sous une avalanche de "nodes". Il * faudra donc écrire une petite fonction qui détermine si telle ou telle tile est visible à un moment précis et * affiche dynamiquement uniquement les tiles nécessaires. * * 3- En utilisant les "id" des nodes, pourquoi ne pas ajouter des ennemis? Ensuite, quand on clique, le programme * devra vérifier si l'ID du node cliqué est 1 (le sol) ou 2, par exemple. Si c'est 2, alors lancer la séquence * d'attaque... Avec cette technique, il est très très rapide de créer un "petit diablo". * * 4- Ajouter des murs n'est pas très compliqué non plus. Il suffit d'utiliser le modèle "floor.x" et de le faire * pivoter de 90 degrés. * * 5- Qui dit mur dit obstacle. Et qui dit obstacle dit pathfinding. Ca peut être intéressant à ajouter, mais * c'est un peu plus de travail. * * 6- Et allez, si vous avez du sommeil à perdre... Vous nous écrirez bien un petit générateur aléatoire de * donjons, non ? * */ |
| Création du message | 02-11-2006 16:07:18 | Agar |
Ce tutorial est un peu gros (500 Ko), avec des fichiers de texture et des modèles. Vous pouvez le télécharger ici: http://irrlichtfr.free.fr/Tutoriaux/tiletutorial.zip
ATTENTION: Irrlicht.DLL et Irrlicht.Net.DLL ne sont PAS présents dans l'archive. Vous devrez les copier dans le répertoire du programme pour qu'il se lance.
Voici le code source de l'unique fichier .CS Notez bien que vous aurez besoin des fichiers présents dans l'archive tiletutorial.zip pour le faire tourner.
using System; using System.Collections.Generic; using System.Windows.Forms; using Irrlicht; using Irrlicht.Core; using Irrlicht.Scene; using Irrlicht.Video; /* * * TUTORIAL - Moteur "tile engine" très très basique avec Irrlicht. * Ecrit par Ambroise Garel (agar@cafedefaune.com) pour www.irrlicht.fr * * Le but de ce tutorial est de démonter au lecteur qu'il est extrêmement simple de réaliser un petit * moteur utlisant des "tiles" avec Irrlicht. * * Cette version est très basique, mais donne les outils principaux (repérage de l'endroit sur lequel le joueur * clique afin de déplacer son perso, etc...) * * Ce moteur peut servir de base à la réalisation d'un Diablo-like, d'un "Zelda", d'un jeu de stratégie, etc... * * Merci de bien vouloir me tenir au courant si vous avez de ce tutorial autre chose qu'un usage privé * (upload sur votre site, traduction, etc...). Pas pour vous crier dessus, juste pour être au courant. Merci! * * NB: Je sais que tous ces objets statiques ne sont pas beaux du tout, mais c'est un tutorial, pas un vrai * programme. J'ai cherché à faire au plus lisible et au plus simple. * * */ namespace TileTutorial { public class TileEngine { // La taille de la carte const int MAPSIZE_X = 32; const int MAPSIZE_Y = 32; // La taille de la fenêtre d'affichage const int WINDOWSIZE_X = 800; const int WINDOWSIZE_Y = 600; // Le device Irrlicht et ses petits. public static IrrlichtDevice dev; public static ISceneManager scm; public static IVideoDriver drv; // Le receveur d'évènement, une classe nommée MyEventReceiver (voir plus bas) public static MyEventReciever evnt; // La camera public static ICameraSceneNode cam; // Les tiles public static ISceneNode[,] FloorTile = new ISceneNode[MAPSIZE_X, MAPSIZE_Y]; // Le joueur public static IAnimatedMeshSceneNode Player; // La position où le joueur doit aller. public static Vector3D playertargetpos; // Une source de lumière public static ILightSceneNode light; // Tant que Running est "true", le programme tourne. public static bool Running; // Fonction Main() // Tout commence ici. [STAThread] static void Main() { // On demande à l'utilisateur s'il souhaite utiliser OpenGL ou DirectX DialogResult DR; DR = MessageBox.Show("Souhaitez-vous utiliser OpenGL ? Si vous répondez non, Direct3D 9.0c sera utilisé.", "OpenGL ou non ?", MessageBoxButtons.YesNo, MessageBoxIcon.Question); DriverType DT; if (DR == DialogResult.Yes) DT = DriverType.OPENGL; else DT = DriverType.DIRECT3D9; // On demande à l'utilisateur s'il souhaite utiliser du normal mapping bool usenormals = false; DR = MessageBox.Show("Souhaitez-vous utiliser des normal maps ?", "Normal maps ou vieilles textures à l'ancienne ?", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (DR == DialogResult.Yes) usenormals = true; // Création du device Irrlicht dev = new IrrlichtDevice(DT, new Dimension2D(WINDOWSIZE_X, WINDOWSIZE_Y), 32, false, false, false, false, new IntPtr()); scm = dev.SceneManager; drv = dev.VideoDriver; dev.WindowCaption = "Pressez échap pour quitter"; // Création du receveur d'évènements evnt = new MyEventReciever(); dev.EventReceiver = evnt; // On charge le mesh utilisé pour le sol. IMesh TileMesh = scm.GetMesh(@"..\\..\\media\\floor.x").GetMesh(0); // Il faut créer les tangeantes pour utiliser le normal mapping. if (usenormals) TileMesh = scm.MeshManipulator.CreateMeshWithTangents(TileMesh); // Et maintenant la petite texture qui va avec. ITexture TileTex = drv.GetTexture(@"..\\..\\media\\floor.jpg"); // Et la normal map. ITexture TileTexNormal = drv.GetTexture(@"..\\..\\media\\floor_normal.jpg"); // Création du monde... // 1- D'abord, on ajoute les "tiles" du sol for (int i = 0; i < MAPSIZE_X; i++) for (int j = 0; j < MAPSIZE_Y; j++) { // Très important : les nodes des tiles ont une ID de 1! Cela servira lors de la détection // des clics. FloorTile[i, j] = scm.AddMeshSceneNode(TileMesh, null, 1); // FloorTile[i, j].SetMaterialFlag(MaterialFlag.LIGHTING, false); FloorTile[i, j].SetMaterialTexture(0, TileTex); FloorTile[i, j].Position = new Vector3D(i * 5, 0, j * 5); if (usenormals) { FloorTile[i, j].SetMaterialTexture(1, TileTexNormal); FloorTile[i, j].SetMaterialType(MaterialType.NORMAL_MAP_SOLID); } } // Et voilà votre incarnation : syndney.md2 Player = scm.AddAnimatedMeshSceneNode(scm.GetMesh(@"..\\..\\media\\sydney.md2"), null, 0); Player.SetMaterialTexture(0, drv.GetTexture(@"..\\..\\media\\sydney.bmp")); Player.Position = new Vector3D(2.5f, 2.5f, 2.5f); // Syndey est un peu grosse, alors on va la réduire. Player.Scale = new Vector3D(0.15f, 0.15f, 0.15f); Player.SetMD2Animation(MD2AnimationType.STAND); // Pour le moment, le joueur ne va nulle part, donc sa position cible sera sa position actuelle. playertargetpos = Player.Position; // On ajoute la caméra... cam = scm.AddCameraSceneNode(null, new Vector3D(0, 25, 25), new Vector3D(0, 0, 0), 0); // Et une petite source de lumière, histoire qu'on y voit quelque chose. light = scm.AddLightSceneNode(null, new Vector3D(25, 25, 25), new Colorf(1.0f, 0.95f, 0.9f), 100, 0); // C'est parti! Tant que running est "true", la boucle principale tourne. Running = true; // Et voici donc cette boucle de dessin principale. DrawLoop(); // On est sorti de la boucle principale car la fenêtre n'est plus active. // Il ne reste plus... // 1- Qu'à détruire le device dev.CloseDevice(); // 2- Et à faire un peu le ménage dans la mémoire. GC.Collect(); } static void DrawLoop() { // Boucle tant que la fenêtre est active. while (Running) { // On dessine toutes ces petites choses. drv.BeginScene(true, true, new Color(0, 0, 0, 0)); scm.DrawAll(); drv.EndScene(); // On laisse un peu respirer l'application. Application.DoEvents(); // Si le joueur ne se trouve pas à sa position cible, il faut qu'il bouge... if ( (playertargetpos.X != Player.Position.X) || (playertargetpos.Z != Player.Position.Z) ) { float speedx = 0.0f; float speedz = 0.0f; // On vérifie l'axe X if (Player.Position.X < playertargetpos.X) speedx = 0.1f; else if (Player.Position.X > playertargetpos.X) speedx = -0.1f; // On vérifie l'axe Z if (Player.Position.Z < playertargetpos.Z) speedz = 0.1f; else if (Player.Position.Z > playertargetpos.Z) speedz = -0.1f; // On fait bouger tout ça... Player.Position = new Vector3D(Player.Position.X + speedx, 2.5f, Player.Position.Z + speedz); //Pour éviter un effet de "sautillement", si le joueur est suffisemment près de sa position //cible, on l'y place directement. if (Math.Abs(Player.Position.X - playertargetpos.X) < 0.15f) Player.Position = new Vector3D(playertargetpos.X, 2.5f, Player.Position.Z); if (Math.Abs(Player.Position.Z - playertargetpos.Z) < 0.15f) Player.Position = new Vector3D(Player.Position.X, 2.5f, playertargetpos.Z); } // La caméra suit le perso cam.Position = new Vector3D(Player.Position.X + 10, 15, Player.Position.Z + 10); cam.Target = Player.Position; // La lumière suit le joueur, puisqu'il a un "ring of light +1" // Je ne vous l'avait pas dit? Maintenant, c'est fait. light.Position = new Vector3D(Player.Position.X +10, 5, Player.Position.Z +10); } } } public class MyEventReciever : IEventReceiver { // Lecture des évènements Irrlicht (en l'occurence, les clics de souris) public bool OnEvent(Event e) { if (e.Key == KeyCode.KEY_ESCAPE) TileEngine.Running = false; // Si c'est pas la souris, poubelle. Il n'y a que la souris qui nous intéresse. if (e.Type != EventType.MouseInput) return false; // Un clic gauche... Ah, ah! Ca veut dire que le joueur veut bouger. if (e.MouseInputType == MouseInputEvent.PressedDownLeft) { // On doit trouver sur quel tile le joueur a cliqué. // Ca se fait avec le SceneCollisionManager. // Notez que le paramètre "idbitmask" permet de ne prendre en compte que certains nodes. // (Ici, ceux avec l'ID 1, donc les tiles. Cliquer sur Sydney ne fait donc rien). ISceneNode ClickedNode = TileEngine.scm.SceneCollisionManager.GetSceneNodeFromScreenCoordinatesBB(e.MousePos, 1, true); // Si le joueur n'a pas cliqué sur une tile, on oublie tout ça... if (ClickedNode == null) return false; // Sinon, le joueur doit maintenant aller sur cette tile. // On ajoute 2,5 aux coordonnées X et Z pour que le joueur aille au centre de la tile, pas // dans un coin. TileEngine.playertargetpos = new Vector3D(ClickedNode.Position.X + 2.5f, 0, ClickedNode.Position.Z + 2.5f); } return false; } } } /* * * Merci d'avoir lu mon tutorial. * Si vous avez des questions ou des suggestions, vous pouvez m'écrire à l'adresse agar@cafedefaune.com. * * QUELQUES SUGGESTIONS POUR ALLER PLUS LOIN: * ========================================== * * 1- Déjà, il serait bien que le perso soit animé et se tourne dans la direction où il se déplace. Si le premier * point est très simple à régler, le deuxième nécessite de sortir un peu de trigonométrie. * * 2- La carte est petite par défaut (32x32), donc ça tourne pas trop mal. Maintenant, avec une carte plus grande, * les tiles trop nombreuses risquent de noyer Irrlicht (et la carte vidéo) sous une avalanche de "nodes". Il * faudra donc écrire une petite fonction qui détermine si telle ou telle tile est visible à un moment précis et * affiche dynamiquement uniquement les tiles nécessaires. * * 3- En utilisant les "id" des nodes, pourquoi ne pas ajouter des ennemis? Ensuite, quand on clique, le programme * devra vérifier si l'ID du node cliqué est 1 (le sol) ou 2, par exemple. Si c'est 2, alors lancer la séquence * d'attaque... Avec cette technique, il est très très rapide de créer un "petit diablo". * * 4- Ajouter des murs n'est pas très compliqué non plus. Il suffit d'utiliser le modèle "floor.x" et de le faire * pivoter de 90 degrés. * * 5- Qui dit mur dit obstacle. Et qui dit obstacle dit pathfinding. Ca peut être intéressant à ajouter, mais * c'est un peu plus de travail. * * 6- Et allez, si vous avez du sommeil à perdre... Vous nous écrirez bien un petit générateur aléatoire de * donjons, non ? * */ |
| Options | Liens officiels | Caractéristiques | Statistiques | Communauté |
|---|---|---|---|---|
|
Préférences cookies Corrections |
![]() ![]() ![]() ![]() |
Propulsé par Django xhtml 1.0 css 2.1 |
884 membres 1440 sujets 11337 messages |
Dernier membre inscrit: Saidov17 195 invités en ligne membre en ligne: - RSS Feed |