Dr. Erhard Henkes   07.09.2008

Grafik- und Spieleprogrammierung mit der Irrlicht Engine

Es gibt umfangreiche Literatur zum Thema "Spieleprogrammierung". Die Frage ist nur, wie starte ich als Einsteiger optimal durch?

Tipps im Sinne von "Lerne Vektoren- und Matrizenrechnung, Mechanik, die Programmiersprache C++ und beschäftige dich erst mal ein Jahr mit der Konsole" sind nicht ermunternd und führen die meisten geradlinig zum Misserfolg.

Man sollte C++ und die Ideen der OOP mehr als grundlegend kennen. Man benötigt wirklich solide Grundlagen in C++, vor allem was den Umgang mit Klassen angeht, denn bei der Entwicklung eigener Programme muss man schlicht und einfach in der Lage sein, neue Klassen zu entwerfen und diese robust zum Laufen zu bringen. Wer hier Nachholbedarf hat, dem kann ich z.B. das locker geschriebene aber dennoch inhaltsvolle C++-OOP-Buch von Marcus Bäckmann empfehlen.

Auf keinen Fall sollte man grundlegend mit DirectX oder OpenGL - gar mit dem Ziel des Entwurfs einer eigenen Engine - starten.
Wer ein Spiel programmieren will, benötigt eine brauchbare Engine, dies ist wahr. Aber deshalb muss man diese heutzutage nicht gleich selbst schreiben.
Verwenden Sie einfach die von vielen Benutzern als einsteigerfreundlich beschriebene und im Design vorbildlich aufgebaute Irrlicht Engine.
Für mich ist ein weiterer wichtiger Grund für die Wahl dieser Engine die hervorragende Community rund um Irrlicht. Nicht verschweigen darf man, dass jede Engine ihre Grenzen hat. Das ist der Punkt, wo man umsteigt oder gar eine eigene schreibt. Diese Grenzen muss man aber erst einmal in der Praxis austesten.

Kenntnisse?

Programmiersprachen/Toolchain

Compiler/Linker/Debugger sollte man sicher beherrschen. Als Toolchain verwenden wir eine Profi-Werkzeugkiste, nämlich die IDE MSVC++ 2008 Express.

Mathematik/Physik

Hier benötigt man zumindest einfache Kenntnisse, z.B. bezüglich Geometrie/Trigonometrie, Matrizen und Vektoren.
Bei der Bewegung von 3D-Objekten geht es um Skalierung (Größe verändern), Translation (geradeaus bewegen) und Rotation (drehen).
Einfache Kenntnisse der Mechanik schaden nicht, da wir uns mit bewegten Objekten in Kräftefeldern (z.B. Gravitation) beschäftigen.
Irrlicht arbeitet gut mit Physik Engines zusammen. Das hat aber zunächst noch Zeit.

DirectX/OpenGL

Verwendet man eine Grafik Engine, kann man diese Basics als "Innereien" betrachten, denen man sich später zuwenden kann.
Die Kenntnis der Grundtechniken un der Begriffe der 3D-Programmierung sind jedoch von großem Vorteil, da man weiß, was man überhaupt suchen soll.


Wir beginnen

Programmiersprache C/C++

Datentypen:
http://www.cpp-tutor.de/cpp/le02/le02_01.htm#ganzzahl

Kontrollstrukturen:
http://www.cpp-tutor.de/cpp/le05/le05_01.htm
http://www.cpp-tutor.de/cpp/le05/le05_02.htm
http://www.cpp-tutor.de/cpp/le05/le05_03.htm
http://www.cpp-tutor.de/cpp/le05/le05_04.htm
http://www.cpp-tutor.de/cpp/le05/le05_05.htm

Arrays und Zeiger:
http://www.henkessoft.de/C++/C/arrays_pointer.htm
http://www.cpp-tutor.de/cpp/le03/le03_05.htm#definition
http://www.cpp-tutor.de/cpp/le06/le06_01.htm

File-Handling:
http://www.c-plusplus.de/forum/viewtopic-var-t-is-39469.html
http://home.fhtw-berlin.de/~junghans/cref/FUNCTIONS/fopen.html

Klassen:
http://www.henkessoft.de/C++/C++/kapselung.htm
http://ittk.falb.at/pt/unterlagen/vcppk/klassen.html
http://www.cpp-tutor.de/cpp/le09/le09_01.htm#klasse
http://www.cpp-tutor.de/cpp/le09/le09_01.htm#oop_begriffe
http://tutorial.schornboeck.net/oop2.htm
http://de.geocities.com/throni3/



Werkzeuge vorbereiten

Das wesentliche Werkzeug ist die IDE. Wir werden hier unter MS Windows das kostenlose MS Visual C++ 2008 Express verwenden.
Zusätzlich benötigt man das DirectX SDK und die Irrlicht Tools (irrlicht, irrklang, irredit, usw.). Das Einbinden in die IDE erfolgt über das Menü
Extras - Optionen - VC++-Verzeichnissse. Hier ergänzt man die Pfade:



Pfade für die include-Files (header)




Pfade für die lib-Files (libraries)


Dies erfolgt auch bei anderen IDEs in analoger Weise.

Nun kann man im Editor des MSVC++ den entsprechenden Quelltext eingeben und durch den Compiler/Linker in Maschinensprache übersetzen lassen.

Halt! Ein ganz wichtiger Punkt vorab: Irgendwann gibt man sein Produkt stolz an einen anderen Rechner zum Testen weiter. Dann hagelt es i.d.R. eine üble Fehlermeldung, und das Programm startet nicht. Das liegt an der fehlenden DLL für die C++-Runtime-Bibliothek. Damit dies von Anfang an in Ordnung geht, bitte immer statisch linken. Nachfolgend findet sich eine hervorragende Erklärung, wie man dies beim MSVC++ 2008 praktisch durchführt:

http://www.kalmbach-software.de/screencasts/VC2008EE-StaticLinkCRT/


Aktuelle Irrlicht Engine

Für die, die immer up-to-date sein wollen, gibt es die nightly builds. Man kann sich von dort die neuesten Files downloaden (ca. 20 MB). Allerdings muss man dann die Sources selbst zu den Bibliotheken irrlicht.lib und irrlicht.dll (MS Windows) kompilieren und linken. Die entsprechenden Projektdateien sind bereits mitgeliefert, so dass dies zumindest unter MSVC++ keine große Aufgabe darstellt, sondern lediglich wenige Minuten in Anspruch nimmt.

Stand 30.08.2008: Nightly Build Download (SVN rev: 1520 )

Irrlicht - erste Experimente

Zunächst wollen wir unser System aufsetzen. Hierbei wollen wir auch sofort XML zum Setzen der Parameter verwenden, denn dies wird durch IrrXML unterstützt.
Downloaden und installieren Sie sich sofort IrrXML (z.Z. Version 1.2) und geben Sie dem MSVC++ bei den Include-Verzeichnissen zusätzlich das Verzeichnis für die Header bekannt. Bei mir ist dies z.B.   D:\Irrlicht Engine\irrxml-1.2\irrxml-1.2\src

Dies reicht in diesem Fall. Eine Bibliothek muss nicht bekannt gegeben werden.
Alternativ können Sie gerne eine andere XML-Bibliothek verwenden, wenn Sie diese bevorzugen, z.B. TinyXML.

Nun erstellen wir ein separates XML-File mit folgendem Inhalt:

<?xml version="1.0"?>
<config>
<!--This is a config file for the Irrlicht Engine.-->
<settings width = "400" height = "300" fullscreen = "0" vsync = "0">
</settings>
</config>

Ich verwende für die Bearbeitung von XML-Files bevorzugt das Programm XML Marker.
Man kann selbstverständlich auch MS Notepad oder einen anderen Editor verwenden.



Diese XML-Datei speichern wir unter dem Namen "settings.xml" ab.
Wir legen es im gleichen Verzeichnis ab, in dem sich unser exe-File nach dem Kompilieren befinden wird.

#include "stdafx.h"
#include <iostream>
#include <limits>
#include <irrlicht.h>

using namespace irr;   using namespace core; using namespace scene;
using namespace video; using namespace io;   using namespace gui;
using namespace std;

#pragma comment(lib, "Irrlicht.lib")

void wait() // prohibits "unexpected shutdown" of console
{
 std::cin.clear();
 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
 std::cin.get();
}

void drawScene(IVideoDriver* driver, ISceneManager* smgr, IGUIEnvironment* guienv)
{
  driver->beginScene(true, true, SColor(0,200,200,200));
  smgr->drawAll();
  guienv->drawAll();
  driver->endScene();
}

s32 main()
{
    s32 screenWidth, screenHeight, fullScreen, vSync;   
   
    IrrXMLReader* xml = createIrrXMLReader("settings.xml");

    while(xml && xml->read())
    {   
      switch(xml->getNodeType())

      {   
        case EXN_ELEMENT:

        {   
          if (!strcmp("settings", xml->getNodeName()))

          {   
            screenWidth  = xml->getAttributeValueAsInt("width");
            screenHeight = xml->getAttributeValueAsInt("height");
            fullScreen   = xml->getAttributeValueAsInt("fullscreen");
            vSync        = xml->getAttributeValueAsInt("vsync");
            //...
          }
        }
        break;
      }
    }
    delete xml;   

    IrrlichtDevice* device = createDevice(
        EDT_DIRECT3D9,                               // Type of the device.               
        dimension2d<s32>(screenWidth,screenHeight),  // Size of the window or the video mode in fullscreen mode.         
        16,                                          // Bits per pixel in fullscreen mode. Ignored if windowed mode.      
        fullScreen,                                  // fullscreen
        false,                                       // stencilbuffer
        vSync,                                       // vsync
        0);                                          // IEventReceiver*
   
    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    ICursorControl*  cursor = device->getCursorControl();
    ILogger*         logger = device->getLogger();
   
    u32 scene = 0;
   
    while(device->run())
    {
      if(device->isWindowActive())
      {       
            stringw strc = "Scene: ";
            strc += (++scene);
            device->setWindowCaption(strc.c_str());
           
            drawScene(driver, smgr, guienv);
           
            stringc logstr = "Scene: ";
            logstr += (scene);
            logstr += " FPS: ";
            logstr += driver->getFPS();
            logger->log(logstr.c_str());   
            if( scene >= 10000 ) break;
      }
      else
        device->yield();
  }
    device->drop();
    cout << "GAME OVER - Press ENTER (twice), please. Thank you for playing this game." << endl;
    wait();
}


Falls es zu dieser Meldung kommt:



bitte die irrlicht.dll direkt zum exe-File setzen, damit der Spaß starten kann. Wenn Sie nightly builds verwenden, müssen Sie die Sources selbst kompilieren und die hierbei frisch entstandene irrlicht.dll verwenden. Es gibt also nicht die irrlicht.dll, sondern ganz viele!

Die Daten werden aus den Attributen des Knotens "settings" übernommen. Das Wechselspiel zwischen der späteren exe-Datei und Parametern aus XML-Dateien ist für die Spielentwicklung wichtig. Ansonsten muss man bei jedem Ausprobieren einer neuen Einstellung frisch compilieren, was zuviel Zeit kostet. Außerdem können so Programmierer und Tester getrennt agieren. Setzen Sie daher von Anfang an diese Technik ein.

Mit createDevice(...) wird die Grafikausgabe auf die gewünschten Parameter eingestellt. Wir verwenden ein Window mit 400*300 Pixel und verzichten zunächst auf fullscreen. AlsRückgabewert dieser Funktion erhalten wir einen Pointer auf unser Objekt "IrrlichtDevice", das wir device nennen.
Damit erzeugen wir weitere Objekte und verwenden deren Zeiger:

    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    ICursorControl*  cursor = device->getCursorControl();
    ILogger*         logger = device->getLogger();

Die eigentliche Schleife, die jedes Spiel benötigt, und der Test, ob das Fenster aktiv ist, ist in folgender while-Schleife verborgen:

while(device->run())
{
  if(device->isWindowActive())
  {
   
driver->beginScene(true, true, SColor(0,255,255,0));
    smgr->drawAll();
    guienv->drawAll();
    driver->endScene();
  }
  else
    device->yield();
}
device->drop();

Dies ist der Kern der Steuerung unseres Spielablaufs. Damit Sie bereits etwas sehen, habe ich eine Ausgabe im Titel (Caption) des Fensters und gleichzeitig via Logger erzeugt, der seinen Output in die Konsole lenkt.



device->setWindowCaption(...);

ILogger* logger = device->getLogger();
logger->log(...);

Der Logger ist z.B. interessant, wenn man im Vollbildmodus arbeitet. Geben Sie am Anfang soviel wie möglich aus, damit Sie die Kontrolle über den Ablauf behalten.
Das Fenster ist "aktiv", wenn Sie mit der Maus darauf klicken. Testen Sie den Unterschied zwischen aktiv und inaktiv aus. Ich habe einen Scene-Zähler eingefügt, damit wir nach 10000 Runden die Schleife mit break verlassen.

Man kann mit Irrlicht die Möglichkeiten der GUI ebenfalls leicht nutzen.
Wir probieren dies am Beispiel eines einfachen statischen Textes aus, den man aber dennoch via setText(...) verändern kann:

//...

ILogger* logger = device->getLogger();

   
IGUIStaticText* text = guienv->addStaticText(L"", rect<int>(10,10,200,22), true);

u32 scene = 0;
   
while(device->run())
{
  if(device->isWindowActive())
  {       
    stringw strc = "Scene: ";
    strc += (++scene);
    device->setWindowCaption(strc.c_str());
           
    text->setText(strc.c_str());

    drawScene(driver, smgr, guienv);
//...



Nun wird es aber höchste Zeit, dass wir ein "Hello World" ausgeben. Das muss einfach bei jedem neuen Tool sein.
Die zentrale Stelle, wenn man etwas hinzufügen möchte ist unser "SceneManager", den wir mit

ISceneManager* smgr = device->getSceneManager();

erstellt haben. Geben Sie einfach smgr->add oder smgr->create ein und schauen Sie sich die angebotenen Funktionen an, mit denen man eine Menge erstellen kann. Hierbei findet man auch eine Kamera und Text. Also frisch ans Werk:

  IGUIStaticText* text =
  guienv->addStaticText(L"",    rect<int>(10,10,200,22), true);

  ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS(); // First Person Shooter Camera

  ITextSceneNode* nodeText = smgr->addTextSceneNode(guienv->getFont("bigfont.png"),L"hello, world");
  nodeText->setTextColor(SColor(255,255,0,0));
  nodeText->setPosition(vector3df(0,0,0));

  u32 scene = 0;
   
  while(device->run())


Wir müssen allerdings einen vernünftigen großen Font anbieten, damit das Ganze auch eine optische Freude bereitet. Kopieren Sie die Datei bigfont.png aus dem Unterverzeichnis ...\Irrlicht Engine\irrlicht...\media direkt in unser exe-Verzeichnis. "bigfont.png" sieht als Bild wie folgt aus:



Als Ergebnis erhält man anschließend folgende Textausgabe, die man sich mit der beweglichen Kamera (Pfeiltasten, Maus) anschauen kann:



Die Methodik ist sehr systematisch. Der "SceneManager" erschafft (create...) oder fügt etwas hinzu (add...). Hierbei lassen wir uns einen Pointer auf das Objekt in eine Zeigervariable zurück liefern, mit der wir das Objekt anschließend manipulieren können. Den Scene-Zähler sollten Sie nun etwas höher stellen, damit Sie sich in Ruhe mit der Kamera (am besten mit der Maus) bewegen können.

PONG und TETRIS Clones

Es ist ein absolutes Muss neben "Hallo Welt" auch die Klassiker PONG und TETRIS nachzubilden, um an einem kompletten Spielablauf experimentieren zu können.
Diese Einstiege sind ideale Plattformen um selbst AI, Sound, Texturen, OOP, ... hinzu zu fügen.
Hier finden Sie Beispiele:

PONG: http://www.c-plusplus.de/forum/viewtopic-var-t-is-222054.html
TETRIS: http://www.c-plusplus.de/forum/viewtopic-var-t-is-221747.html

IrrSpiel - STEP1

Wir werden uns step-by-step ein kleines Spiel entwickeln, um die Möglichkeiten von Irrlicht und Irrklang kennen zu lernen.Wir geben folgenden Sourcecode ein.

// IrrSpiel.cpp from www.HenkesSoft.de
// STEP 1
//
// Irrlicht types cf. irrTypes.h, e.g.
// s32: __int32, u32: unsigned __int32,
// f32: float,   f64: double

#include "stdafx.h"    // MS VisualC++
#include <iostream>    // cout, cin, endl
#include <limits>      // wait()
#include <irrlicht.h>  // grafic engine (free)
#include <irrklang.h>  // sound system (free for private use)

// irrlicht namespace
using namespace irr; using namespace core; using namespace scene;
using namespace video; using namespace io; using namespace gui; using namespace std;

// linker commands
#pragma comment(lib, "irrlicht.lib")
#pragma comment(lib, "irrklang.lib")

void wait() // prohibits "unexpected shutdown" of console
{
 std::cin.clear();
 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
 std::cin.get();
}

void drawScene(IVideoDriver* driver, ISceneManager* smgr, IGUIEnvironment* guienv)
{
  driver->beginScene(true, true, SColor(0,200,200,200));
  smgr->drawAll();
  guienv->drawAll();
  driver->endScene();
}

// entry point of program
s32 main()
{
    // start Irrlicht
    IrrlichtDevice* device = createDevice(
    EDT_DIRECT3D9, dimension2d<s32>(800,600),32,0,0,0,0);
    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    device->getCursorControl()->setVisible(false); // no mouse cursor
   
    u32 scene = 0;
    u32 fps   = 0;

    /********************* "device->run()" with FPS and Info ********************/
    while(device->run())
    {
      if(device->isWindowActive())
      {        
        // draw scene

        drawScene(driver, smgr, guienv);
        ++scene;

        // deliver information in window caption
        fps = driver->getFPS();
        stringw str = L"HenkesSoft \"MUHAHA Code1\"  FPS: ";
        str += fps;
        str += " Scene: ";
        str += scene;
        device->setWindowCaption(str.c_str());
       
        if( scene >= 10000 )           
          break;
      }//if
    }//while
 
    device->drop(); // delete object
    cout << "GAME OVER - Press ENTER (twice), please. Thank you for playing this game." << endl;
    wait(); // windows console specialty ;-)
}//main

IrrSpiel - STEP1

Nach dem hoffentlich auch bei Ihnen erfolgreichen Compilieren/Linken und dem Starten der exe-Datei empfängt uns folgende Meldung:



Ohne die passende binäre Irrlicht.dll geht es in MS Windows nicht. Sie finden diese in den Downloads der Irrlicht Engine.
Fügen Sie die dll direkt bei der exe hinzu.



Nun wird dies mit dem Start des exe-Files klappen, wenn Sie die passende DLL hinzu gefügt haben.
Wir finden nun zwei Fenster, nämlich zum einen die Konsole (früher "DOS-Box"),
die wir selbst mittels main() generiert haben



und zum anderen unser "Window", das die Irrlicht Engine für uns erzeugt hat.



Im Titel (caption) des Fensters haben wir zwei Informationen eingeblendet:
1) FPS bedeutet Frames Per Second und beschreibt die Anzahl Bilder, die in einer Sekunde angezeigt werden. Werte > 60 sind hier angenehm.
2) Scene zeigt, wie oft die Funktion "drawScene" durchlaufen wird.

Wollen wir das "Irrlicht Logo" links neben dem Titel einblenden, so müssen wir noch die Icon-Datei "irrlicht.ico" in dieses Verzeichnis kopieren.

        

Wir haben den Ablauf so programmiert, dass nach 10000 "drawScene(...)" das Programm zur Konsole zurück kehrt.
Die Funktion wait() sorgt dafür, dass hier kein abrupter Abschluss erfolgt.



Nun schauen wir uns die Details an:

IrrlichtDevice* device = createDevice(
    EDT_DIRECT3D9, dimension2d<s32>(800,600),32,0,0,0,0);


Diese Anweisung erzeugt die "device", ein Zeiger auf unser zentrales Objekt zur Darstellung der 3D-Grafik.
In unserem Beispiel wurde der "Renderer" Direct3D 9.0 verwendet, wie man in der Konsole neben anderen Informationen mittels ausgegeben sieht.

Eine hervorragende Informationsquelle sind die Header-Dateien der Irrlicht Engine.
Nutzen Sie diese, indem Sie mit Rechtsklick auf createDevice "Gehe zu Deklaration" wählen.
Dies führt zu folgender Information:

    //! Creates an Irrlicht device. The Irrlicht device is the root object for using the engine.
    /** If you need more parameters to be passed to the creation of the Irrlicht Engine device, use the createDeviceEx() function.
    \param deviceType: Type of the device. This can currently be video::EDT_NULL, video::EDT_SOFTWARE, video::EDT_BURNINGSVIDEO, 
    video::EDT_DIRECT3D8, video::EDT_DIRECT3D9 and video::EDT_OPENGL.

    \param windowSize: Size of the window or the video mode in fullscreen mode.
    \param bits: Bits per pixel in fullscreen mode. Ignored if windowed mode.
    \param fullscreen: Should be set to true if the device should run in fullscreen. Otherwise the device runs in windowed mode.
    \param stencilbuffer: Specifies if the stencil buffer should be enabled.
    Set this to true, if you want the engine be able to draw stencil buffer shadows.
    Note that not all devices are able to use the stencil buffer. If they don't no shadows will be drawn.

    \param vsync: Specifies vertical syncronisation: If set to true, the driver will wait for the vertical retrace period, otherwise not.
    \param receiver: A user created event receiver.
    \param sdk_version_do_not_use: Don't use or change this parameter. Always set it to IRRLICHT_SDK_VERSION, which is done by default.
    This is needed for sdk version checks.

    \return Returns pointer to the created IrrlichtDevice or null if the device could not be created.
    */
    IRRLICHT_API IrrlichtDevice* IRRCALLCONV createDevice(
        video::E_DRIVER_TYPE deviceType = video::EDT_SOFTWARE,
        // parantheses are necessary for some compilers
        const core::dimension2d<s32>& windowSize = (core::dimension2d<s32>(640,480)),
        u32 bits = 16,
        bool fullscreen = false,
        bool stencilbuffer = false,
        bool vsync = false,
        IEventReceiver* receiver = 0,
        const c8* sdk_version_do_not_use = IRRLICHT_SDK_VERSION);

Wir haben hier folgendes gewählt:

IrrlichtDevice* device = createDevice(
    EDT_DIRECT3D9,             // Direct3D
    dimension2d<s32>(800,600), // Windows mit 800 * 600 Pixel
    32,                        // Bits per Pixel (gilt nur bei Fullscreen Mode) 
    0,                         // Windowed Mode (kein Fullscreen)
    0,                         //
kein Stencil Buffer
    0,                         // kein vsync
    0);                        // kein event receiver

Als Returnwert erhalten wir einen Zeiger auf das gerade erzeugte "Device"-Objekt.
Folgende Funktionen stehen mit "device->" zur Verfügung:



IVideoDriver*    driver = device->getVideoDriver();

ISceneManager*   smgr   = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();

Damit erhalten wir Video-Driver, Scene-Manager, GUI-Environement und vieles mehr.

device->getCursorControl()->setVisible(false);

ist eine weitere Funktion, die den Mauszeiger unsichtbar macht.

Irrlicht under the hood:
Deklarationen dieser Funktionen findet man inklusive kurzer Beschreibungen in der Datei “IrrlichtDevice.h”.
Das „WinAPI“-Innenleben mit WndProc(…) und CreateWindow(…) findet man in der Datei „CIrrDeviceWin32.cpp“.


while(device->run())
{
  if(device->isWindowActive())
{        
    // draw scene

    // further actions
  }
}


ist ein typisches Vorgehen, um Darstellungen auf den Bildschirm zu bringen.
Im Window-Modus kann man die Titelzeile (caption) ebenfalls zur Ausgabe von Informationen nutzen.

fps = driver->getFPS();
stringw str = L"HenkesSoft \"MUHAHA Code1\"  FPS: ";
str += fps;
str += " Scene: ";
str += scene;
device->setWindowCaption(str.c_str());
driver->getFPS() besorgt die Frames per Second. Diese Information ist sehr wichtig, um den Bewegungsfluss einheitlich steuern zu können.
Eine Fixierung der FPS erfolgt z.B. durch Setzen des Parameters vsync in der Funktion createDevice(...) auf true. Probieren Sie es aus!



IrrSpiel - STEP2

Bevor wir uns dem nächsten Schritt zuwenden, sollte man das Koordinatensystem kennen, das allgemein üblich verwendet wird: Es gibt drei senkrecht aufeinander stehende Achsen, die als X,Y,Z bezeichnet werden. Der Punkt (0,0,0) trennt hierbei jeweils den positiven und den negativen Achsenabschnitt.  Jeder Punkt im Raum ist durch eine absolute Position (X,Y,Z) gekennzeichnet.  Damit können wir Objekte gezielt dreidimensional platzieren oder bewegen bzw. deren aktuelle Position abfragen.

Fügen Sie bitte folgenden Text ein:

s32 main()
{
    // start Irrlicht
    IrrlichtDevice* device = createDevice(
    EDT_DIRECT3D9, dimension2d<s32>(800,600),32,0,0,0,0);
    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    device->getCursorControl()->setVisible(false); // no mouse cursor
   
   // "Skybox"  six times "black sky with some stars" (512*512 pixel)
   ISceneNode* SkyBox = smgr->addSkyBoxSceneNode(
     driver->getTexture("../skybox/top.jpg"    ),
     driver->getTexture("../skybox/bottom.jpg" ),
     driver->getTexture("../skybox/left.jpg"   ),
     driver->getTexture("../skybox/right.jpg"  ),
     driver->getTexture("../skybox/front.jpg"  ),  
     driver->getTexture("../skybox/back.jpg"   ));
    
 // Camera
     ICameraSceneNode* cam =
       smgr->addCameraSceneNode(0, vector3df(0,1000,0), vector3df(0,0,0));

   
    u32 scene = 0;
    u32 fps   = 0;

IrrSpiel - STEP2

Um die Bilddateien laden zu können, müssen Sie neben dem Release-Verzeichnis ein Verzeichnis skybox eröffnen und für die sechs Seiten eines imaginären Würfels jeweils ein Bild mit 512 * 512 Pixel erstellen. Diesen imaginären Kubus nennt man "Skybox".

Sie können sich hier die entsprechenden Ressourcen downloaden oder einfach selbst etwas entwerfen:
http://www.henkessoft.de/Sonstiges/IrrSpiel.zip

Das sieht doch schon richtig gut aus:



In diesem "Weltall" haben wir eine fixe Kamera installiert. Diese sitzt auf dem Punkt (0,1000,0)und schaut auf den Punkt(0,0,0).
Ohne Kamera sehen wir nichts. Vielleicht haben Sie Lust sich unsere (oder Ihre) Skybox genauer anzusehen. Dazu muss man die Kamera in den "Egoshooter"-Modus verwandeln:

ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();

Das FPS bedeutet hier übrigens First-Person-Shooter.

Nun platzieren wir ein Objekt, unser Raumschiff (zur Sicherheit noch einmal der ganze Code):

// IrrSpiel.cpp from www.HenkesSoft.de
// STEP 2
//
// Irrlicht types cf. irrTypes.h, e.g.
// s32: __int32, u32: unsigned __int32,
// f32: float,   f64: double

#include "stdafx.h"    // MS VisualC++
#include <ctime>       // clock, time
#include <cstdlib>     // srand, rand
#include <iostream>    // cout, cin, endl
#include <limits>      // wait()
#include <irrlicht.h>  // grafic engine (free)
#include <irrklang.h>  // sound system (free for private use)

// irrlicht namespace
using namespace irr; using namespace core; using namespace scene;
using namespace video; using namespace io; using namespace gui; using namespace std;

// linker commands
#pragma comment(lib, "irrlicht.lib")
#pragma comment(lib, "irrklang.lib")

void wait() // prohibits "unexpected shutdown" of console
{
 std::cin.clear();
 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
 std::cin.get();
}

void drawScene(IVideoDriver* driver, ISceneManager* smgr, IGUIEnvironment* guienv)
{
  driver->beginScene(true, true, SColor(0,200,200,200));
  smgr->drawAll();
  guienv->drawAll();
  driver->endScene();
}

// entry point of program
s32 main()
{
    // start Irrlicht
    IrrlichtDevice* device = createDevice(
    EDT_DIRECT3D9, dimension2d<s32>(800,600),32,0,0,0,0);
    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    device->getCursorControl()->setVisible(false); // no mouse cursor
   
  // "Skybox"  six times "black sky with some stars" (512*512 pixel)
  ISceneNode* SkyBox = smgr->addSkyBoxSceneNode(
   
driver->getTexture("../skybox/top.jpg"    ),
    driver->getTexture("../skybox/bottom.jpg" ),
    driver->getTexture("../skybox/left.jpg"   ),
    driver->getTexture("../skybox/right.jpg"  ),
    driver->getTexture("../skybox/front.jpg"  ),  
    driver->getTexture("../skybox/back.jpg"   ));
     
  ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();

  // SpaceShip //http://www.turbosquid.com/FullPreview/Index.cfm/ID/261430 (free)
  const vector3df STARTPOSSPACESHIP = vector3df(-200,0,0);
 
  // Texture
  SMaterial matSpaceShip;
  matSpaceShip.setTexture(0, driver->getTexture("../media/orange.jpg"));
  matSpaceShip.Lighting = false;
 
  // Mesh
  IAnimatedMeshSceneNode* nodeSpaceShip;
  IAnimatedMesh* meshSpaceShip = smgr->getMesh("../media/spaceship.3ds");
  if (meshSpaceShip)
  {
    nodeSpaceShip = smgr->addAnimatedMeshSceneNode(meshSpaceShip);
    nodeSpaceShip->setPosition(STARTPOSSPACESHIP);
    nodeSpaceShip->getMaterial(0) = matSpaceShip;
    nodeSpaceShip->setMaterialFlag(EMF_LIGHTING, false);
  }

  u32 scene = 0;
  u32 fps   = 0;

  /********************* "device->run()" with FPS and Info ********************/
  while(device->run())
  {
      if(device->isWindowActive())
      {  
        // draw scene
        drawScene(driver, smgr, guienv);
        ++scene;

        // deliver information in window caption
        fps = driver->getFPS();
        stringw str = L"HenkesSoft \"MUHAHA Code1\"  FPS: ";
        str += fps;
        str += " Scene: ";
        str += scene;
        device->setWindowCaption(str.c_str());
       
        if( scene >= 10000 )
        /**/;
//damit wir mehr als 10000 "scenes" haben.
      }
  }//while
 
  device->drop(); // delete object
  cout << "GAME OVER - Press ENTER (twice), please. Thank you for playing this game." << endl;
  wait(); // windows console specialty ;-)
}//main


Das Raumschiff sitzt am Punkt (-200,0,0). Durch die FPS (first person shooter) - Kamera können Sie sich das Raumschiff genau betrachten.
Die Skybox ist jeweils unendlich weit entfernt.




Nachdem Sie sich den Nachthimmel ausreichend ("10000 scenes" aushebeln! Siehe oben) angeschaut haben, stellen Sie bitte wieder die fixierte Kamera auf.


IrrSpiel - STEP3

Nun zum gesamten Spielablauf:

// IrrSpiel.cpp from www.HenkesSoft.de
// First 3D-Game "Muhaha Code1"
//
// Irrlicht types cf. irrTypes.h, e.g.
// s32: __int32, u32: unsigned __int32,
// f32: float,   f64: double

#include "stdafx.h"                // MS VisualC++
#include <ctime>                   // clock, time
#include <cstdlib>                 // srand, rand
#include <iostream>                // cout, cin, endl
#include <limits>                  // wait()
#include <irrlicht.h>              // grafic engine (free)
#include <irrklang.h>              // sound system (free for private use)
//#include "../media/impact.h"     // sound impact.wav

// irrlicht namespace
using namespace irr; using namespace core; using namespace scene;
using namespace video; using namespace io; using namespace gui; using namespace std;

// linker commands
#pragma comment(lib, "irrlicht.lib")
#pragma comment(lib, "irrklang.lib")

void wait() // prohibits "unexpected shutdown" of console
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}

void drawScene(IVideoDriver* driver, ISceneManager* smgr, IGUIEnvironment* guienv)
{
  driver->beginScene(true, true, SColor(0,200,200,200));
  smgr->drawAll();
  guienv->drawAll();
  driver->endScene();
}

// Event Handler
class MyEventReceiver : public IEventReceiver
{
  public:
    virtual bool OnEvent(const SEvent& event)
    {
      if (event.EventType == EET_KEY_INPUT_EVENT)
        KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
      return false;
    }
    virtual bool IsKeyDown(EKEY_CODE keyCode) const
{ return KeyIsDown[keyCode]; }
    MyEventReceiver(){for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i) KeyIsDown[i] = false;}
  private:
    bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

struct MyBullet
{
  IAnimatedMeshSceneNode* nodeBullet;
  bool hasHit;   
  bool isValid;
};

// Simple collision functions
bool isCollisionA(ISceneNode* one, ISceneNode* two, int size)
{
  return( one->getAbsolutePosition().getDistanceFrom(two->getAbsolutePosition()) < size );
}

bool isCollisionB(ISceneNode* one, ISceneNode* two)
{
  return ( one->getTransformedBoundingBox().intersectsWithBox( two->getTransformedBoundingBox() ) );
}

// entry point of program
s32 main()
{
    // init randomizer
    srand(unsigned(time(0))); // generate randomized numbers with rand()

    // start Irrlicht grafic engine
    MyEventReceiver receiver;
    IrrlichtDevice* device = createDevice(
    EDT_DIRECT3D9, dimension2d<s32>(800,600),32,0,0,0,&receiver);
    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    device->getCursorControl()->setVisible(false); // no mouse cursor

    // start Irrklang sound engine and load sound waves
    irrklang::ISoundEngine* sndEngine = irrklang::createIrrKlangDevice();
    irrklang::ISoundSource* backgroundSound =  sndEngine->addSoundSourceFromFile("../media/IrrlichtTheme.ogg",irrklang::ESM_NO_STREAMING,true);
 
    irrklang::ISoundSource* shootSound  = sndEngine->addSoundSourceFromFile("../media/impact.wav",irrklang::ESM_NO_STREAMING,true);
    irrklang::ISoundSource* muhahaSound  =      sndEngine->addSoundSourceFromFile("../media/muhaha.wav",irrklang::ESM_NO_STREAMING,true);
   
 

    // "Skybox"  six times "black sky with some stars" (512*512 pixel)
    ISceneNode* SkyBox = smgr->addSkyBoxSceneNode (
      driver->getTexture("../skybox/top.jpg"    ),
        driver->getTexture("../skybox/bottom.jpg" ),
        driver->getTexture("../skybox/front.jpg"  ),  
        driver->getTexture("../skybox/back.jpg"   ),  
        driver->getTexture("../skybox/right.jpg"  ),
        driver->getTexture("../skybox/left.jpg"   ));
   
    // Intro - Background Music - Camera
    IGUIImage* img =
    guienv->addImage( driver->getTexture("../media/uglyface_starterImage.jpg"), position2d<int>(30,30));

    irrklang::ISound* snd = sndEngine->play2D(backgroundSound, true, false, true);
 
    if(snd){ snd->setVolume(0.2f); snd->drop(); }

    ICameraSceneNode* cam = smgr->addCameraSceneNode(0, vector3df(0,1000,0), vector3df(0,0,0));


    // SpaceShip //http://www.turbosquid.com/FullPreview/Index.cfm/ID/261430 (free)
    const vector3df STARTPOSSPACESHIP = vector3df(-200,0,0);
    const vector3df RESETPOSSPACESHIP = vector3df(-600,0,0);
    const s32 MAXSHOOTS_PER_SEC       = 10;
    f32 velocitySpaceship             = 2250.0;
    // Texture
    SMaterial matSpaceShip;
    matSpaceShip.setTexture(0, driver->getTexture("../media/orange.jpg"));
    matSpaceShip.Lighting = false;
    // Mesh
    IAnimatedMeshSceneNode* nodeSpaceShip;
    IAnimatedMesh* meshSpaceShip = smgr->getMesh("../media/spaceship.3ds");
    if (meshSpaceShip)
    {
       nodeSpaceShip = smgr->addAnimatedMeshSceneNode(meshSpaceShip);
       nodeSpaceShip->setPosition(STARTPOSSPACESHIP);
       nodeSpaceShip->getMaterial(0) = matSpaceShip;
       nodeSpaceShip->setMaterialFlag(EMF_LIGHTING, false);
    }

    // Enemies
    const vector3df STARTPOSENEMY = vector3df( 700,0, 800);
    const vector3df ENDPOSENEMY   = vector3df(-600,0,-800);
    // Texture
    SMaterial matEnemy;
    matEnemy.setTexture(0, driver->getTexture("../media/orange.jpg"));// 512*512 pixel (orange color)
    matEnemy.Lighting = false;
    // Mesh
    IAnimatedMeshSceneNode* nodeEnemy;
    IAnimatedMesh* meshEnemy = smgr->getMesh("../media/vm21.3ds"); // really nice ;-)
        //http://www.turbosquid.com/FullPreview/Index.cfm/ID/222232 (free)
    if (meshEnemy)
    {     
         nodeEnemy = smgr->addAnimatedMeshSceneNode(meshEnemy);
         nodeEnemy->setPosition(STARTPOSENEMY);
         nodeEnemy->getMaterial(0) = matEnemy;
         nodeEnemy->setMaterialFlag(EMF_LIGHTING, false);
         nodeEnemy->setScale(vector3df(20,20,20));   // x-,y-,z-scaling factors
         nodeEnemy->setRotation(vector3df(0,90,30)); // rotate by ... degrees (x,y,z)
    }
   
    ISceneNodeAnimator* animFS = smgr->createFlyStraightAnimator(STARTPOSENEMY, ENDPOSENEMY, 2000, true);
    if(animFS)
    {
        nodeEnemy->addAnimator(animFS);  // move enemy
        animFS->drop();   
    }
   
    // Bullet
    const s32 MAXSHOOTS = 2000;      
    MyBullet bullets[MAXSHOOTS];
    for(int i=0; i<MAXSHOOTS; ++i)
    {
        bullets[i].hasHit     = false;
        bullets[i].nodeBullet = 0;
        bullets[i].isValid    = false;
    }

    // Game data
    u32 t        =    0;   // time
    s32 life     =   10;   // life of SpaceShip
    s32 echelon  =    1;   // attack waves
    s32 shoots   =    0;   // shoots of SpaceShip
    s32 hits     =    0;   // enemies hit
    s32 fps      = 1500;   // first value before exact measurement
    s32 level    =    0;   // start image
   
   
    /********************* "device->run()" with FPS and Info ********************/
    while(device->run())
    {
        if( (life <= 0) || (shoots > MAXSHOOTS) ) break;
        if(device->isWindowActive())
        {           
            f32 delta = velocitySpaceship/fps; // target 1.50

            while( device->run() && !level ) // device->run() necessary due to receiver!
            {
                if( receiver.IsKeyDown(KEY_RETURN) )
                {
                    level = 1; // first game level
                    img->setVisible( false );
                }
                drawScene( driver, smgr, guienv );
            }
           
            // SpaceShip absolute Position
            vector3df v = nodeSpaceShip->getAbsolutePosition();   
           
            // keys W A S D pressed?
            if (receiver.IsKeyDown(KEY_UP))   {v.X += delta; }               
            if (receiver.IsKeyDown(KEY_DOWN)) {v.X -= delta; }
            if (receiver.IsKeyDown(KEY_LEFT)) {v.Z += delta; }
            if (receiver.IsKeyDown(KEY_RIGHT)){v.Z -= delta; }
           
            // keys B M T pressed?
            if (receiver.IsKeyDown(KEY_KEY_T)){velocitySpaceship = 5000.0f; }
            if (receiver.IsKeyDown(KEY_KEY_M)){velocitySpaceship = 2250.0f; }
            if (receiver.IsKeyDown(KEY_KEY_B)){velocitySpaceship = 1500.0f; }
           
            // check collision between bullets and enemies
            for(int i=0; i<MAXSHOOTS; ++i)
            {               
                if(bullets[i].nodeBullet && bullets[i].isValid && nodeEnemy)
                {
                    if((bullets[i].nodeBullet->getAbsolutePosition().X) > 700) // upper border of window
                    {
                        ISceneNodeAnimator* animDel = smgr->createDeleteAnimator(1);
                        if(animDel)
                        {
                            bullets[i].nodeBullet->addAnimator(animDel); // delete bullet
                            bullets[i].isValid = false;                       
                            animDel->drop();   
                        }
                    }

                    if(isCollisionB(bullets[i].nodeBullet,nodeEnemy) && (!bullets[i].hasHit))
                    {
                        bullets[i].hasHit = true;
                        bullets[i].nodeBullet->setVisible(false);
                        ISceneNodeAnimator* animDel = smgr->createDeleteAnimator(1);
                        if(animDel)
                        {
                            bullets[i].nodeBullet->addAnimator(animDel); // delete bullet
                            bullets[i].isValid = false;                       
                            animDel->drop();   
                        }
                        ++hits;
                        // Irrklang not free for comercial purpose!
                        snd = sndEngine->play3D(muhahaSound, v, false, false, true);
                        // http://download.dual-players.eu/cs/cstrike/sound/dualplayers/ (free?)
                        if(snd){ snd->setMinDistance(1000.0f); snd->setVolume(1.0f); snd->drop(); }


                        // fireball
                        IParticleSystemSceneNode* fireball;
                        fireball = smgr->addParticleSystemSceneNode(false);
                        fireball->setPosition( nodeEnemy->getAbsolutePosition() );
                        fireball->setScale(vector3df(20.0f,20.0f,20.0f));
                        fireball->setParticleSize(dimension2d<f32>(20.0f, 10.0f));
                        IParticleEmitter* em = fireball->createBoxEmitter(
                            aabbox3d<f32>(-70,0,-70,70,10,70),
                            vector3df(0.0f,0.6f,0.0f), 80, 100,
                            SColor(0,255,0,0),SColor(0,255,255,255), 800,2000);
                        fireball->setEmitter(em);
                        em->drop();

                        IParticleAffector* paf = fireball->createFadeOutParticleAffector();
                        fireball->addAffector(paf);
                        paf->drop();

                        fireball->setMaterialFlag(video::EMF_LIGHTING, false);
                        fireball->setMaterialTexture(0, driver->getTexture("../media/fireball.bmp"));
                        fireball->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);
                    }
                }
            }           

            // check collision between spaceship and enemies
            if(isCollisionB(nodeSpaceShip,nodeEnemy))
            {
                v = RESETPOSSPACESHIP;
                --life;
            }

            // SPACE pressed? SHOOT!
            if (receiver.IsKeyDown(KEY_SPACE))
            {
                if (shoots > MAXSHOOTS)           
                    break;

                static clock_t last_t;
                if( clock() > (last_t + CLOCKS_PER_SEC / MAXSHOOTS_PER_SEC) )
                {
                    // Shoot sound
                    irrklang::ISound* snd = sndEngine->play3D(shootSound, v, false, false, true);
                   
                    if (snd)
                    {
                        snd->setMinDistance(1000.0f);
                        snd->setVolume(1.0f);   
                        snd->drop();
                    }
                    last_t = clock();               
               
                    // bullet
                    IAnimatedMeshSceneNode* bullet = 0;
                    IAnimatedMesh* meshBullet = smgr->getMesh("../media/sphere.3ds");
                    if (meshBullet)
                    {
                         bullet = smgr->addAnimatedMeshSceneNode(meshBullet);
                         bullet->setPosition(v);
                         bullet->getMaterial(0) = matSpaceShip;
                         bullet->setMaterialFlag(EMF_LIGHTING, false);
                    }
                    bullet->setScale(vector3df( 1.0f, 0.3f, 0.3f ));
                    ISceneNodeAnimator* animFS = smgr->createFlyStraightAnimator(
                                                 v,vector3df(1000,v.Y,v.Z), 500, false);
                    if(animFS)
                    {
                        bullet->addAnimator(animFS);  // move bullet
                        animFS->drop();   
                    }

                    bullets[shoots].nodeBullet = bullet;
                    bullets[shoots].isValid = true;
                    ++shoots;
                }//if
            }//if
                       
            // borders for moving SpaceShip  
            if(v.X>  100)v.X=  100;
            if(v.X< -600)v.X= -600;
            if(v.Z>  900)v.Z=  900;
            if(v.Z< -900)v.Z= -900;
           
            // define position
            nodeSpaceShip->setPosition(v);

            // draw scene
            drawScene(driver, smgr, guienv);

            // deliver information in window caption
            fps = driver->getFPS();
            stringw str = L"HenkesSoft \"MUHAHA Code1\"  FPS: ";
            str += fps;
            str += L"  Wave: ";
            str += echelon;
            str += L"  Shoots: ";
            str += shoots;
            str += L"  Hits: ";
            str += hits;
            str += L"  Life: ";
            str += life;
            device->setWindowCaption(str.c_str());
            if( (life <= 0) || (shoots > MAXSHOOTS) ) break;
        }//if
    }//while
 
    device->drop();    // delete grafic engine
    sndEngine->drop(); // delete sound engine
    cout << "GAME OVER - Press ENTER (twice), please. Thank you for playing this game." << endl;
    wait(); // windows console specialty ;-)
}//main




Das Spiel umfasst bereits einige Objekte: Skybox, Raumschiff, Gegner, Bullets und Partikel.

Ich gehe hier nur auf einige Besonderheiten ein:
Die Schussfrequenz soll beschränkt werden. Dafür gibt es ein einfaches Standardverfahren:

static clock_t last_t;
if( clock() > ( last_t + CLOCKS_PER_SEC / MAXSHOOTS_PER_SEC) )
{
  // action
 last_t = clock();
}       


Man definiert eine Variable last_t als static, damit der Wert nicht verloren geht. Die Zeitspanne, in der nichts passieren soll, definiert man durch CLOCKS_PER_SEC / MAXSHOOTS_PER_SEC 

Das ist ein sehr einfaches Verfahren. Die Uhrzeit erhält man mittels clock() aus der Bibliothek ctime.

Wichtig ist, dass man zum Abschluss last_t wieder auf die aktuelle Zeit setzt. Die if-Schleife wird erst wieder betreten, wenn die zeitliche Bedingung erfüllt wird.
Damit kann man Vorgänge in der Frequenz steuern.

Klänge bietet die Bibliothek irrklang, die für private Zwecke frei ist. Der Umgang damit ist relativ unkompliziert.



3D-Viewer

Zur Betrachtung von Models (zum Import empfehlen sich vor allem x-, irr- und irrmesh-Files) benötigt man einen einfachen 3D-Viewer. Hier ist ein Code als Basis für eigene Entwicklungen:

// 3DViewer.cpp from www.HenkesSoft.de
//
// Irrlicht types cf. irrTypes.h, e.g.
// s32: __int32, u32: unsigned __int32,
// f32: float,   f64: double

#include "stdafx.h"    // MS VisualC++
#include <iostream>    // cout, cin, endl
#include <limits>      // wait()
#include <irrlicht.h>  // grafic engine (free)

// irrlicht namespace
using namespace irr; using namespace core; using namespace scene;
using namespace video; using namespace io; using namespace gui;

// C++ namespace
using namespace std;

// linker commands
#pragma comment(lib, "irrlicht.lib")
#pragma comment(lib, "irrklang.lib")

//void wait(){ cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cin.get(); }

// Global Objects and variables
IrrlichtDevice* device  = 0;
IGUIEnvironment* guienv = 0;

void drawScene(IVideoDriver* driver, ISceneManager* smgr,    IGUIEnvironment* guienv){
  driver->beginScene(true, true, SColor(0,255,255,255)); smgr->drawAll(); guienv->drawAll();    driver->endScene(); }

void setCaption( IrrlichtDevice* device, IVideoDriver* driver, ICameraSceneNode* nodeCam)
{
    vector3df camPos = nodeCam->getAbsolutePosition();   
    stringw str = L"HenkesSoft \"3D-Viewer 0.91\" [";
    str += driver->getName();
    str += "]  FPS: "; str += driver->getFPS();
    str += "    X: ";       str += s32(camPos.X);
    str += " Y: ";      str += s32(camPos.Y);
    str += " Z: ";      str += s32(camPos.Z);   
    device->setWindowCaption(str.c_str());
}

// Event Handler
class MyEventReceiver : public IEventReceiver
{
    public:
    virtual bool OnEvent(const SEvent& event)
        {
        if (event.EventType == EET_KEY_INPUT_EVENT)
            KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
                if (event.EventType == EET_MOUSE_INPUT_EVENT)
                      MouseEvent = FetchMouseInput(event);
                if (event.EventType == EET_GUI_EVENT)
                {
                    s32 id = event.GUIEvent.Caller->getID();
                    IGUIEnvironment* guienv = device->getGUIEnvironment();
                    switch(event.GUIEvent.EventType)
                    {
                        case EGET_BUTTON_CLICKED:
                                return true;
                        break;
                      default:
                        break;
                    }
                }
                return false;
        }
       
        SEvent::SMouseInput GetMouseInput(){return MouseEvent;}
        virtual bool IsKeyDown(EKEY_CODE keyCode) const {return KeyIsDown[keyCode];}
        MyEventReceiver(){for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i) KeyIsDown[i] = false;}
 
  private:
    bool KeyIsDown[KEY_KEY_CODES_COUNT];
        SEvent::SMouseInput FetchMouseInput(const SEvent& event){return event.MouseInput;}
    SEvent::SMouseInput MouseEvent;
};

s32 main()
{
    // ************ Setup Irrlicht ***************
   
    // let user select driver type
    E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

    cout << "Please select the driver you want for this example:\n"\
        " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
        " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
        " (f) NullDevice\n (otherKey) exit\n\n" << endl;

    char i;
    std::cin >> i;

    switch(i)
    {
        case 'a': driverType = EDT_DIRECT3D9;       break;
        case 'b': driverType = EDT_DIRECT3D8;       break;
        case 'c': driverType = EDT_OPENGL;          break;
        case 'd': driverType = EDT_SOFTWARE;        break;
        case 'e': driverType = EDT_BURNINGSVIDEO;   break;
        case 'f': driverType = EDT_NULL;            break;
        default: return 0;
    }   
   
    MyEventReceiver receiver;
    device = createDevice(driverType, dimension2d<s32>(1024,768),32,0,0,0,&receiver);
    IVideoDriver*    driver = device->getVideoDriver();
    ISceneManager*   smgr   = device->getSceneManager();
    guienv = device->getGUIEnvironment();
    device->getCursorControl()->setVisible(false); // mouse cursor
     
    // GUI
    IGUISkin* skin = guienv->getSkin();
    //IGUIFont* font = guienv->getFont("../media/fonthaettenschweiler.bmp");
    //if (font)    skin->setFont(font);
    //skin->setFont(guienv->getBuiltInFont(), EGDF_TOOLTIP);

    // Camera
    ICameraSceneNode* nodeCam = smgr->addCameraSceneNodeMaya();
    vector3df camPos = vector3df(-500, -2000, 0);    
    vector3df camTar = vector3df(-500, -2000, 0);    
    nodeCam->setPosition(camPos);
    nodeCam->setTarget(camTar);
   
    // general light
    ILightSceneNode* light0 =
        smgr->addLightSceneNode(0, vector3df(-1000,    0,    0), SColorf(1.0f, 1.0f, 1.0f, 1.0f), 10000.0f);
    ILightSceneNode* light1 =  
        smgr->addLightSceneNode(0, vector3df( 1000,    0,    0), SColorf(1.0f, 1.0f, 1.0f, 1.0f), 10000.0f);
    ILightSceneNode* light2 =
        smgr->addLightSceneNode(0, vector3df(    0,    0,-1000), SColorf(1.0f, 1.0f, 1.0f, 1.0f), 10000.0f);
    ILightSceneNode* light3 =
        smgr->addLightSceneNode(0, vector3df(    0,    0, 1000), SColorf(1.0f, 1.0f, 1.0f, 1.0f), 10000.0f);
    ILightSceneNode* light4 =
        smgr->addLightSceneNode(0, vector3df(    0, 1000,    0), SColorf(1.0f, 1.0f, 1.0f, 1.0f), 10000.0f);
    ILightSceneNode* light5 =
        smgr->addLightSceneNode(0, vector3df(    0,-1000,    0), SColorf(1.0f, 1.0f, 1.0f, 1.0f), 10000.0f);
   
   
    // add animated character
    IAnimatedMesh* mesh = smgr->getMesh("test.x");
    IAnimatedMeshSceneNode* nodeCharacter = smgr->addAnimatedMeshSceneNode(mesh);
    nodeCharacter->setPosition( vector3df(0,-800,0) );
    nodeCharacter->setAnimationSpeed(15);
    nodeCharacter->addShadowVolumeSceneNode();    // add shadow
    smgr->setShadowColor(SColor(150,0,0,0));
    nodeCharacter->setScale(vector3df(1,1,1));    // scale
    nodeCharacter->setRotation(vector3df(0,0,0)); // rotation
    nodeCharacter->setMaterialFlag(EMF_NORMALIZE_NORMALS, true); // normalize for correct lighting
 
    // general preparations
    u32 scene = 0;
    u32 fps   = 0;
   
   
    /********************* "device->run()" with FPS and Info ********************/
    while(device->run())
    {
        if(device->isWindowActive())
        {  
            // key input
            if (receiver.IsKeyDown(KEY_KEY_1)){/*...*/}       
           
            // mouse input
            SEvent::SMouseInput emi = receiver.GetMouseInput();

            // LEFTMOUSE
            // if (emi.Event == EMIE_LMOUSE_PRESSED_DOWN) {}
   
            // draw scene
            drawScene(driver, smgr, guienv);

            // deliver information in window caption
            setCaption(device, driver, nodeCam);
        }
    }//while
 
    device->drop(); // delete object
    //cout << "Press ENTER (twice), please. Thank you for using this program." << endl;
    //wait(); // windows console specialty ;-)
}//main



Man kann damit einiges selbst entwicklen, z.B. ein Bild eines kleinen Spiels: