Irrlicht Tutorial
From PX Documentation
Contents |
Overview
The enclosed is a code sample from the Collisions tutorial for the Irrlicht Open Source cross-platform 3D Engine which we have updated to function as a multiplayer online game. It has been included to give an example of how to integrate NetDog into an existing game. We have highlighted the code changes necessary to add NetDog to this tutorial.
In this tutorial we demonstrate the following key steps for setting up NetDog in a game environment:
- Include NetDog header files
- Initialize constants such as server IP/port
- Define event types
- Install event callback function
- Initialize and set up network connections
- Get and set owner IDs for client
- Execute NetDog event loop
Usage
To exit the client, press either the 'Q' or ESCAPE keys.
Tutorial
#include <irrlicht.h> #include <iostream>
#include "ND.h"
using namespace irr;
#ifdef _MSC_VER #pragma comment(lib, "Irrlicht.lib") #endif
int serverPort = 56882;
static const int kMaxPlayers = 1024; scene::ISceneManager* smgr; video::SMaterial fmaterial; scene::IAnimatedMesh* faerie = NULL; scene::ITriangleSelector* selector = 0; scene::IAnimatedMeshSceneNode* players[kMaxPlayers];
enum { kMovePlayerEvent = 1 };
typedef struct {
core::vector3df pos;
} MovePlayerEvent;
scene::IAnimatedMeshSceneNode* createFaerieNode(){
scene::IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode(faerie);
if (node == NULL){
printf("Attempt to add new player failed (could not load graphic)!\n");
return(0);
}
node->setPosition(core::vector3df(-70,0,-90));
node->setMD2Animation(scene::EMAT_RUN);
node->getMaterial(0) = fmaterial;
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, node, core::vector3df(30,50,30),
core::vector3df(0,-3,0),
core::vector3df(0,0,0));
node->addAnimator(anim);
anim->drop();
return node;
}
int PlayerCallback(NDEventData evtData)
{
scene::IAnimatedMeshSceneNode* node = 0;
int pNum = NDEventDataGetOriginID(evtData);
if (pNum >= kMaxPlayers) return(0);
if (players[pNum] == NULL)
{
players[pNum] = createFaerieNode();
}
if (players[pNum] != NULL){
players[pNum]->setPosition(((MovePlayerEvent *)usrEvt)->pos); } return(0); }
int main()
{
// let user select driver type
video::E_DRIVER_TYPE driverType;
printf("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");
char i;
std::cin >> i;
switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 0;
}
// create device
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<s32>(640, 480), 16, false);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
smgr = device->getSceneManager();
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* q3node = 0;
if (q3levelmesh)
q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));
/*
So far so good, we've loaded the quake 3 level like in tutorial 2. Now, here
comes something different: We create a triangle selector. A triangle selector
is a class which can fetch the triangles from scene nodes for doing different
things with them, for example collision detection. There are different triangle
selectors, and all can be created with the ISceneManager. In this example,
we create an OctTreeTriangleSelector, which optimizes the triangle output a l
little bit by reducing it like an octree. This is very useful for huge meshes
like quake 3 levels.
Afte we created the triangle selector, we attach it to the q3node. This is not
necessary, but in this way, we do not need to care for the selector, for example
dropping it after we do not need it anymore.
*/
if (q3node)
{
q3node->setPosition(core::vector3df(-1350,-130,-1400));
selector = smgr->createOctTreeTriangleSelector(q3levelmesh->getMesh(0), q3node, 128);
q3node->setTriangleSelector(selector);
}
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f, -1, 0, 0, true);
camera->setPosition(core::vector3df(-100,50,-150));
if (selector)
{
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(30,50,30),
core::vector3df(0,-3,0),
core::vector3df(0,20,0));
camera->addAnimator(anim);
anim->drop();
}
/*
Because collision detection is no big deal in irrlicht, I'll describe how to
do two different types of picking in the next section. But before this,
I'll prepare the scene a little. I need three animated characters which we
could pick later, a dynamic light for lighting them,
a billboard for drawing where we found an intersection, and, yes, I need to
get rid of this mouse cursor. :)
*/
// disable mouse cursor
device->getCursorControl()->setVisible(false);
// add billboard
scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
bill->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialFlag(video::EMF_ZBUFFER, false);
bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));
// add animated faeries.
video::SMaterial material;
fmaterial.setTexture(0, driver->getTexture("../../media/faerie2.bmp"));
fmaterial.Lighting = true;
scene::IAnimatedMeshSceneNode* node = 0;
faerie = smgr->getMesh("../../media/faerie.md2");
// ND: Start with just one player
for (int i=0; i<kMaxPlayers; i++)
players[i] = NULL;
if (faerie)
{
players[0] = createFaerieNode();
node = createFaerieNode();
node->setVisible(false); // ND: hide our own graphic, to provide first-person view
}
NDInstallEventType(kMovePlayerEvent, &PlayerCallback, NULL, "move");
int conn = NDCreateClientConn(serverIP, serverPort, NDConnFlags::assignOwnerIDs | NDConnFlags::useKeepAlives);
//Connection error handling
if (conn < 0){ printf("Error: Could not establish connection to server\n"); exit(1); }
6b. Wait for the server to start the session (it should start automatically). Be sure to call the NetDog Event Loop to send/receive network events during this time. Also sleep periodically.
// ND: wait for connection to complete
while (!NDConnIsStarted(conn)){
NDEventLoop();
NDSleepMS(10); // 10ms
}
7. Check to see if the server assigned us an ownerID
if (playerNum >= kMaxPlayers){
printf("playerNum[%d] exceeds kMaxPlayers[%d]! Please restart the server.\n", playerNum,
kMaxPlayers);
exit(0);
} else if (playerNum < 0){
printf("Error: server returned garbage data\n", playerNum);
exit(0);
}
players[playerNum] = node;
material.setTexture(0, 0);
material.Lighting = false;
// Add a light
smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
video::SColorf(1.0f,1.0f,1.0f,1.0f),
600.0f);
/*
For not making it to complicated, I'm doing picking inside the drawing loop.
We take two pointers for storing the current and the last selected scene node and
start the loop.
*/
scene::ISceneNode* selectedSceneNode = 0;
scene::ISceneNode* lastSelectedSceneNode = 0;
int lastFPS = -1;
while(device->run())
{
NDEventLoop();
if (device->isWindowActive())
{
driver->beginScene(true, true, 0);
smgr->drawAll();
/*
After we've drawn the whole scene whit smgr->drawAll(), we'll do the first
picking: We want to know which triangle of the world we are looking at. In addition,
we want the exact point of the quake 3 level we are looking at.
For this, we create a 3d line starting at the position of the camera and going
through the lookAt-target of it. Then we ask the collision manager if this line
collides with a triangle of the world stored in the triangle selector. If yes,
we draw the 3d triangle and set the position of the billboard to the intersection
point.
*/
core::line3d<f32> line;
line.start = camera->getPosition();
line.end = line.start + (camera->getTarget() - line.start).normalize() * 1000.0f;
core::vector3df intersection;
core::triangle3df tri;
if (smgr->getSceneCollisionManager()->getCollisionPoint(
line, selector, intersection, tri))
{
bill->setPosition(intersection);
driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(material);
driver->draw3DTriangle(tri, video::SColor(0,255,0,0));
}
core::vector3df charPos = line.start + core::vector3df(0,-20,0);
if ((playerNum >= 0) && (charPos != players[playerNum]->getPosition()))
{
players[playerNum]->setPosition(charPos);
NDSendEvent(&charPos, kMovePlayerEvent, sizeof(charPos));
/*
Another type of picking supported by the Irrlicht Engine is scene node picking
based on bouding boxes. Every scene node has got a bounding box, and because of
that, it's very fast for example to get the scene node which the camera looks
at. Again, we ask the collision manager for this, and if we've got a scene node,
we highlight it by disabling Lighting in its material, if it is not the
billboard or the quake 3 level.
*/
selectedSceneNode = smgr->getSceneCollisionManager()->getSceneNodeFromCameraBB(camera);
if (lastSelectedSceneNode)
lastSelectedSceneNode->setMaterialFlag(video::EMF_LIGHTING, true);
if (selectedSceneNode == q3node || selectedSceneNode == bill)
selectedSceneNode = 0;
if (selectedSceneNode)
selectedSceneNode->setMaterialFlag(video::EMF_LIGHTING, false);
lastSelectedSceneNode = selectedSceneNode;
/*
That's it, we just have to finish drawing.
*/
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Collision detection example - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
NDSleepMS(1); // 1ms
}
device->drop();
return 0; }
