Irrlicht Tutorial

From PX Documentation

Jump to: navigation, search

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:

  1. Include NetDog header files
  2. Initialize constants such as server IP/port
  3. Define event types
  4. Install event callback function
  5. Initialize and set up network connections
  6. Get and set owner IDs for client
  7. Execute NetDog event loop

Usage

To exit the client, press either the 'Q' or ESCAPE keys.

Tutorial

   #include <irrlicht.h>
   #include <iostream>
1. Include NetDog header files
#include "ND.h"
   using namespace irr;
   #ifdef _MSC_VER
   #pragma comment(lib, "Irrlicht.lib")
   #endif
2. Initialize constants such as server IP/port

int serverPort = 56882;

char* serverIP = "127.0.0.1";
   static const int kMaxPlayers = 1024;
   scene::ISceneManager* smgr;
   video::SMaterial fmaterial;
   scene::IAnimatedMesh* faerie = NULL;
   scene::ITriangleSelector* selector = 0;
   scene::IAnimatedMeshSceneNode* players[kMaxPlayers];
3. Define event types
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;
    }
4. Set up callback function for updating player state information
int PlayerCallback(NDEventData evtData)
   {
	scene::IAnimatedMeshSceneNode* node = 0;
5a. Determine the unique (server-assigned) ownerID of the client which sent this event
int pNum = NDEventDataGetOriginID(evtData);
	if (pNum >= kMaxPlayers)
	   return(0);
	if (players[pNum] == NULL)
	{	
		players[pNum] = createFaerieNode();
	}
5b. Retrieve the event itself, in this case a "move" event

if (players[pNum] != NULL){

void* usrEvt = NDEventDataGetUserEvent(evtData);
		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));
//selector->drop(); // we need selector later, so don't drop it
		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));

Initialize array to store our players and create our own (faerie) player
	// 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
	}
	
6a. Initialize network connections by installing an event type for player movement, then creating a connection with flags for automatically assigned owner IDs and connection keep alives

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

int playerNum = NDConnGetLocalOwnerID(conn);
	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()) {
8. Call the NetDog Event Loop at least as often as you call the main application/graphics loop
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));
		}

See if our character moved, and if so, send a move event to the server

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;
		}
	}

Make this sleep a little...
NDSleepMS(1); // 1ms
	}
	device->drop();
	return 0;
   }

Other Tutorials

  1. Irrlicht Server Tutorial
  2. Object Driven Model Tutorial