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 ?
*
*/
merci beaucoup de ta contribution
Je regarderai ça, ça m'a l'air bien fait
C'est hébergé
Hors ligne
izguit :
C'est hébergé
Merci beaucoup. J'ai modifié mon message en conséquence.
Dernière modification par Agar (02-11-2006 18:33:33)
Hors ligne
Merci beaucoup pour ta contribution c'est vraiment sympa !
Hors ligne
Merci pour ce tuto bien sympa ! De plus, j'ai l'impression qu'il y a une vague ces derniers temps d'amateurs du genre ; qui reprennent nos bons vieux jeux d'antan...
Hors ligne
J'ai essayé de compiler le tuto sous Mono (linux), et, une flopée d'erreurs apparait, parmi lesquelles certaines me semblent assez bizarres...
Enfin bref, j'ai "corrigé" (essayé de corriger serait plus juste) le code, et voilà ce que ça donne (étant donné que j'ai fait à moitié des copier-collers, et que j'ai repris une bonne partie afin de me familiariser, tous les commentaires ne sont pas là...)
Si ça peut intéresser quelqu'un...
using System; using System.Collections.Generic; using System.Windows.Forms; using IrrlichtNETCP; using IrrlichtNETCP.Inheritable; 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 device; public static SceneManager smgr; public static VideoDriver driver; // La camera public static CameraSceneNode camera; // Les tiles public static SceneNode[,] FloorTile = new SceneNode[Mapsize_X, Mapsize_Y]; // Le joueur public static AnimatedMeshSceneNode Player; // La position où le joueur doit aller. public static vector3d playertargetpos; // Une source de lumière public static LightSceneNode light; public static bool Running; static double pi =3.1415926535; static void Main() { // Création du device Irrlicht device = new IrrlichtDevice(DriverType.OpenGL, new dimension2d(640, 480), 32, false, true, true, true); device.FileSystem.WorkingDirectory = "./media"; device.OnEvent += new OnEventDelegate(device_OnEvent); smgr = device.SceneManager; driver = device.VideoDriver; //Initialisation du gestionnaire d'évènement // On charge le mesh utilisé pour le sol. Mesh TileMesh = smgr.GetMesh("floor.x").GetMesh(0); TileMesh = smgr.MeshManipulator.CreateMeshWithTangents(TileMesh); Texture TileTex = driver.GetTexture("floor.jpg"); // Et la normal map. Texture TileTexNormal = driver.GetTexture("floor_normal.jpg"); 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] = smgr.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); FloorTile[i, j].SetMaterialTexture(1, TileTexNormal); FloorTile[i, j].SetMaterialType(MaterialType.NormalMapSolid); } Player = smgr.AddAnimatedMeshSceneNode(smgr.GetMesh("sydney.md2")); Player.SetMaterialTexture(0, driver.GetTexture("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.SetFrameLoop(0,159); playertargetpos = Player.Position; // On ajoute la caméra... camera = smgr.AddCameraSceneNode(null); camera.Position=new vector3d(0, 0, 0); camera.Target=new vector3d(0, 25, 25); // Et une petite source de lumière, histoire qu'on y voit quelque chose. light = smgr.AddLightSceneNode(null, new vector3d(25, 25, 25), new Colorf(1.0f,1.0f, 0.95f, 0.9f), 100, 0); // Et voici donc cette boucle de dessin principale. Running=true; // DrawLoop(); int lastFPS = -1; while (device.Run()) { if (device.WindowActive) { device.VideoDriver.BeginScene(true, true, new Color(255, 100, 101, 140)); device.SceneManager.DrawAll(); driver.EndScene(); 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 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); // La caméra suit le perso camera.Position = new vector3d(Player.Position.X + 10, 15, Player.Position.Z + 10); camera.Target = Player.Position; int fps = device.VideoDriver.FPS; } } // 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 device.Dispose(); // 2- Et à faire un peu le ménage dans la mémoire. GC.Collect(); } static bool device_OnEvent(Event e) { if (e.KeyCode == KeyCode.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.MouseInputEvent) return false; // Un clic gauche... Ah, ah! Ca veut dire que le joueur veut bouger. if (e.MouseInputEvent == MouseInputEvent.LMousePressedDown) { // 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). SceneNode ClickedNode = TileEngine.smgr.CollisionManager.GetSceneNodeFromScreenCoordinates(e.MousePosition, 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; } } }
PS : vu la date du dernier message, il se pourrait fortement que ces erreurs soient dues à un (voir plusieurs) changements de syntaxe dans irrlicht.
PPS : En fait, il s'agit d'une convertion vers Irrlicht.Net CP ^^
Dernière modification par Krankmann (10-09-2008 22:16:50)
Hors ligne
Pages: 1