How to implement figures as independently operating objects?

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
tchilrri
Posts: 8
Joined: Mon Jun 28, 2004 6:26 pm
Location: Germany

How to implement figures as independently operating objects?

Post by tchilrri »

The following approach implements independently operating game figures, each one in its own thread. I used the collision example for it.
My question is: is that approach basically right or how should it be done?
Note: I used the Qt library for some things. Sorry for not posting an URL, I don't have an FTP area or something similar.

Code: Select all

#include <irrlicht.h>

#include <qthread.h>
#include <qdatetime.h>
#include <qptrlist.h>

using namespace irr;

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

scene::ISceneManager* smgr;
video::IVideoDriver* driver;
IrrlichtDevice *device;
video::SMaterial material;
scene::ITriangleSelector* selector;

scene::IAnimatedMeshSceneNode* CreateFaerie()
{
	// add animated faerie.
	material.Texture1 = driver->getTexture("../../media/faerie2.bmp");
	material.Lighting = true;

	scene::IAnimatedMeshSceneNode* node = 0;
	scene::IAnimatedMesh* faerie = smgr->getMesh("../../media/faerie.md2");

	if (faerie) {
		node = smgr->addAnimatedMeshSceneNode(faerie);
		node->setPosition(core::vector3df(-70,0,-90));
		node->setMD2Animation(scene::EMAT_RUN);
		node->getMaterial(0) = material;
	}

    material.Texture1 = 0;
	material.Lighting = false;

    return node;
}

void AnimateObj(scene::ISceneNode* pNode, bool bFixedRadius = false)
{
    core::aabbox3df box = pNode->getBoundingBox();
    core::vector3df radius = bFixedRadius ? core::vector3df(30,50,30) : box.MaxEdge - box.getCenter();

	scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
		selector, pNode,
        radius, // how big the object is (radius of an ellipsoid)
		core::vector3df(0,-100,0), // direction and speed of gravity (0,0,0 to disable gravity)
        100.0f, // acceleration value when falling down
		core::vector3df(0,0,0)); // translation from the center of the ellipsoid
	pNode->addAnimator(anim);
	anim->drop();
}

class FaerieControl : public QThread
{
public:
    FaerieControl() : QThread()
        ,m_bRunning(true), m_direction(0), m_bShouldRenderMove(false)
        ,m_id(0), m_jump(0),m_stepWidth(4)
    {
        static int id = 0;
        m_id = id++;
        static int x = -150;
        x += 40;
        m_pFaerie = CreateFaerie();
        m_pFaerie->setPosition(core::vector3df(x,50,0));
        m_oldPos.Z = 99;
        m_pFaerie->setRotation(core::vector3df(0,90,0));
        AnimateObj(m_pFaerie);
    }
    
    void doExit()
    {
        m_bRunning = false;
    }

    void move()
    {
        // really set to queued position
        m_pFaerie->setPosition(m_newPos);
        // reset queue flag
        m_bShouldRenderMove = false;
    }

    bool shouldMove()
    {
        return m_bShouldRenderMove;
    }

protected:
    virtual void run()
    {
        msleep(500 + 17*(m_id+10)); // ensure different start times

        // as long as the program isn't shutdown
        while (m_bRunning) {
            // wait 100 milliseconds until the next action of the faerie
            msleep(100);
            // if the queued new position is not already rendered, don't do further steps
            if (m_bShouldRenderMove) {
                continue;
            }

            bool bChangeDirection = false;
            core::vector3df pos = m_pFaerie->getPosition();
            // was she able to walk a step, or has her position not changed?
            bool bStoppedSomehow = isWalkingStopped(pos);
            if (bStoppedSomehow && !m_jump) {
                // decide to try to jump over the obstacle that stopped her
                // (that helps at stairs)
                m_jump = 3;
            }
            else if (bStoppedSomehow) {
                // if she already tried to jump but that didn't help, decide to change the direction
                // (that helps at walls)
                m_jump = 0;
                bChangeDirection = true;
            }
            // sometimes randomly decide to change the direction
            int timeMSec = QTime::currentTime().msec();
            if ((timeMSec % 10) == 0) {
                bChangeDirection = true;
            }
            // maybe turn to another direction
            if (bChangeDirection) {
                decideNewDirection();
                turnToNewDirection();
            }
            // compute the new position where we'll be after the step
            // and queue it for rendering
            m_newPos = pos + computeNextStep();
            m_bShouldRenderMove = true;

            // store her old position for further checking if the queued step will be successful
            m_oldPos = pos;
        }
    }

    bool isWalkingStopped(core::vector3df pos)
    {
        bool bStopped;
        switch (m_direction) {
        case 0: bStopped = pos.Z >= m_oldPos.Z; break;
        case 1: bStopped = pos.X >= m_oldPos.X; break;
        case 2: bStopped = pos.Z <= m_oldPos.Z; break;
        case 3: bStopped = pos.X <= m_oldPos.X; break;
        }
        return bStopped;
    }

    void decideNewDirection()
    {
        static unsigned int i = 0;
        int oldDirection = m_direction;
        do {
            m_direction = i % 4;
            i++;
        }
        while (oldDirection == m_direction);
    }

    void turnToNewDirection()
    {
        switch (m_direction) {
        case 0: m_pFaerie->setRotation(core::vector3df(0,90,0)); break;
        case 1: m_pFaerie->setRotation(core::vector3df(0,180,0)); break;
        case 2: m_pFaerie->setRotation(core::vector3df(0,270,0)); break;
        case 3: m_pFaerie->setRotation(core::vector3df(0,0,0)); break;
        }
    }

    core::vector3df computeNextStep()
    {
        core::vector3df move;
        switch (m_direction) {
        case 0: move = core::vector3df(0,m_jump,-m_stepWidth); break;
        case 1: move = core::vector3df(-m_stepWidth,m_jump,0); break;
        case 2: move = core::vector3df(0,m_jump,m_stepWidth); break;
        case 3: move = core::vector3df(m_stepWidth,m_jump,0); break;
        }
        return move;
    }

    scene::IAnimatedMeshSceneNode*  m_pFaerie;
    bool                            m_bRunning;
    core::vector3df                 m_oldPos;
    core::vector3df                 m_newPos;
    int                             m_direction;
    bool                            m_bShouldRenderMove;
    int                             m_id;
    int                             m_jump;
    int                             m_stepWidth;
};

int main(int /*argc*/, char** /*argv*/)
{
    device = createDevice(video::EDT_DIRECTX9, core::dimension2d<s32>(640, 480), 16, false);
	driver = device->getVideoDriver();
	smgr   = device->getSceneManager();
	device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");

	scene::IAnimatedMesh* q3levelmesh   = smgr->getMesh("20kdm2.bsp");          if (!q3levelmesh) return 1;
    scene::IMesh*         pMesh         = q3levelmesh->getMesh(0);
	scene::ISceneNode*    q3node        = smgr->addOctTreeSceneNode(pMesh);     if (!q3node) return 1;
	q3node->setPosition(core::vector3df(-1370,-130,-1400));

    selector  = smgr->createOctTreeTriangleSelector(pMesh, q3node, 128);
	q3node->setTriangleSelector(selector);
	selector->drop();

	scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f);
	camera->setPosition(core::vector3df(0,50,0));
    camera->setRotation(core::vector3df(0,180,0));
    AnimateObj(camera, true);

    // create a number of faeries, each one an own thread doing its action every 100 milliseconds
    QPtrList<FaerieControl> faeries;
    for (int i = 0; i < 9; i++) {
        FaerieControl* pFaerie = new FaerieControl;                             if (!pFaerie) return 1;
        faeries.append(pFaerie);
        pFaerie->start();
    }

	smgr->addLightSceneNode(0, core::vector3df(-60,100,400), video::SColorf(1.0f,1.0f,1.0f,1.0f), 600.0f); // Add a light
	device->getCursorControl()->setVisible(false); // disable mouse cursor

    // infinite rendering loop
	while (device->run()) {
        // check all faeries if they've queued a new step.
        // if so, do the walk
        QPtrListIterator<FaerieControl> it(faeries);
        while (*it) {
            FaerieControl* pFaerie = *it;
            if (pFaerie->shouldMove()) {
                pFaerie->move();
            }
            ++it;
        }
		driver->beginScene(true, true, 0);
		smgr->drawAll();
		driver->endScene();
	}

    // exits all faerie threads
    QPtrListIterator<FaerieControl> it(faeries);
    while (*it) {
        FaerieControl* pFaerie = *it;
        pFaerie->doExit();
        ++it;
    }

	device->drop();
	return 0;
}
:?: :?: :?:
Thulsa Doom
Posts: 63
Joined: Thu Aug 05, 2004 9:40 am
Location: Germany

Post by Thulsa Doom »

Well nice work,
I played around with the idea of a neuronal network KI using threads.
In principle it's easy to create just anouther thread, but you should consider the memory overhead. On the other hand threads can be independent of framerate and one one should make use of this advantage.
But I strongly recommed to read :
In conlusion one should limit the number of threads to the needs:
- sound
- network
(- userinput)
(- KI for all NPC's)

In my opinion, four threads per application are sufficient.
Guest

Post by Guest »

How do you synchronize the action of the game figures with the rendering loop?
Do you have only one independent thread for all KI at once?
Thulsa Doom
Posts: 63
Joined: Thu Aug 05, 2004 9:40 am
Location: Germany

Post by Thulsa Doom »

Sure,
the rendering loop is only the output.
The 'problem' it has a fixed timeslice given by framerate.
KI calculation are on variing timescales. I prefer using a boolean variable to ask if there are new updates available.
What is your approach?
Guest

Post by Guest »

Thulsa Doom wrote: What is your approach?
I just have the one shown in the code above (I'm tchilrri). But I tend to think your approach is better to safe the overhead many threads have. Though it means I need an AI master-control thread calling the 'run()'-method for each self-controlled game figure registered to the AI controller. Means I think I'm going to change my code to your approach. Thanks :-)
But it's good to hear you also had the idea of just letting the rendering thread know there's something to update, and therefore the AI thread is requested for action from the rendering thread at a certain (right) time.
Thulsa Doom
Posts: 63
Joined: Thu Aug 05, 2004 9:40 am
Location: Germany

Post by Thulsa Doom »

sorry tchilrri, :roll:
I thougth there was another one, nevermind.
BTW KI is very interesting topic.
Start a thread at the Forums Open Discussion!
skajake
Posts: 9
Joined: Sun Sep 12, 2004 6:26 pm

Post by skajake »

Code: Select all

  QPtrList<FaerieControl> faeries;
    for (int i = 0; i < 9; i++) {
        FaerieControl* pFaerie = new FaerieControl;                             if (!pFaerie) return 1;
        faeries.append(pFaerie);
        [color=red]pFaerie->start();[/color]
    } 
did you mean:

pFaerie->run();?

There is no start function in the class.
skajake
Posts: 9
Joined: Sun Sep 12, 2004 6:26 pm

Post by skajake »

Also, it says this will spawn new threads, however when i implement it, and call pFaerie->run() it just simply takes over. The while loop in run() begins and my main comes to a halt, it doesnt spawn a new thread that lets my main continue.

Am I misunderstanding how this works?
Shai-Tan
Posts: 15
Joined: Fri Sep 10, 2004 8:37 pm

Post by Shai-Tan »

Code: Select all

 #include <qthread.h> 
#include <qdatetime.h> 
#include <qptrlist.h>
What are these headers? Sorry, I'm a newb. I want to see how it runs but it wont let me compile, I dont have qThread.h
Flatline
Posts: 49
Joined: Fri Sep 03, 2004 7:46 am
Location: England

Post by Flatline »

Note: I used the Qt library for some things
You can't compile it unless you have the Qt library installed.
skajake
Posts: 9
Joined: Sun Sep 12, 2004 6:26 pm

Post by skajake »

Ok, it is starting to make sense now. The class inherits the thread class, allowing it to spawn with the start() function.

What thread class / header file should I use in Vis Studio.net if I do not have the QT library installed?
jikbar
Posts: 62
Joined: Wed Aug 25, 2004 4:48 pm
Location: Canada
Contact:

Post by jikbar »

i forget the name but theres a simple thread function in process.h (comes with standard c++) but it seems kinda buggy when i use it. CreateThread works fine though.

one thing to note, i was looking through process viewer while debugging my game, and irrlicht uses 11 threads :shock: [/i]
Guest

Post by Guest »

I decided after hours of pai ing. I removed the loop in the run function, and I call that in my render function.
tchilrri
Posts: 8
Joined: Mon Jun 28, 2004 6:26 pm
Location: Germany

Post by tchilrri »

QThread is a class of the Qt library (for free on Linux) which does things in QThread::run() and can be started with QThread::start(). The MFC library of Win32 has CThread which basically does the same. You might also use the appropriate Win32 API-function but it's not comfortable to fulfil all the parameters in the right way.
Anyway, in the meanwhile my "AI" test programl has become cooler. :-) I just noticed it's quite slow with many fairies. Do you know if my approach in the source is efficient? Second, the fairies don't get it to go upstairs. Can you help me?
Now the code currently is:

#include <irrlicht.h>

#include <qthread.h>
#include <qptrlist.h>
#include <qdatetime.h>
#include <qmutex.h>

#include <stdlib.h>

using namespace irr;

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

scene::ISceneManager* smgr;
video::IVideoDriver* driver;
IrrlichtDevice *device;
video::SMaterial material;
scene::ITriangleSelector* selector;
bool s_bDebug = false;
bool s_bRun = true;
QMutex smgrAccess;

int random()
{
return QTime::currentTime().msec() + rand();
}

scene::ISceneNodeAnimatorCollisionResponse* AnimateObj(scene::ISceneNode* pNode, bool bFixedRadius = false)
{
core::aabbox3df box = pNode->getBoundingBox();
core::vector3df radius = bFixedRadius ? core::vector3df(30,50,30) : box.MaxEdge - box.getCenter();

scene::ISceneNodeAnimatorCollisionResponse* anim = smgr->createCollisionResponseAnimator(
selector, pNode,
radius, // how big the object is (radius of an ellipsoid)
core::vector3df(0,-100,0), // direction and speed of gravity (0,0,0 to disable gravity)
100.0f, // acceleration value when falling down
core::vector3df(0,0,0)); // translation from the center of the ellipsoid
pNode->addAnimator(anim);
anim->drop();
return anim;
}

class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(SEvent event)
{
if (event.EventType == irr::EET_KEY_INPUT_EVENT) { //&& !event.KeyInput.PressedDown)
switch (event.KeyInput.Key) {
case KEY_SPACE:
s_bDebug = true;
return true;
}
}
return false;
}
};

class Fairy
{
public:
Fairy()
: m_direction(90), m_bShouldRenderMove(false)
,m_id(0), m_jump(0),m_stepWidth(6)
{
static int id = 0;
m_id = id++;
m_pFairyNode = createFairy();

static int x = -150;
x += 5;
m_oldPos = core::vector3df(x,50,5);
m_pFairyNode->setPosition(core::vector3df(x,50,0));
m_pFairyNode->setRotation(core::vector3df(0,m_direction,0));
m_pCollRespAnim = AnimateObj(m_pFairyNode);
}

void move()
{
// really set to queued position
m_pFairyNode->setPosition(m_newPos);
}

void signalFinishedMove()
{
// reset queue flag
m_bShouldRenderMove = false;
}

bool shouldMove()
{
return m_bShouldRenderMove;
}

bool isDying()
{
if (m_pFairyNode->getPosition().Y < -1000) {
// falling in the depth
return true;
}
return false;
}

void live()
{
// if the queued new position is not already rendered, don't do further steps
if (m_bShouldRenderMove) {
return;
}

if (s_bDebug) {
s_bDebug = false;
}

core::vector3df pos = m_pFairyNode->getPosition();
core::vector3df move = computeNextStep();
bool bCanWalk = !moodForDirectionChange();
bool bDecidedNewDirection = false;
do {
if (!bCanWalk) {
decideNewDirection();
bDecidedNewDirection = true;
move = computeNextStep();
}
bCanWalk = canWalk(pos, move);
}
while (!bCanWalk);

if (bDecidedNewDirection) {
turnToNewDirection();
}

// compute the new position where we'll be after the step
// and queue it for rendering
m_newPos = pos + computeNextStep();
m_bShouldRenderMove = true;

// store her old position for further checking if the queued step will be successful
m_oldPos = pos;
}

protected:
scene::IAnimatedMeshSceneNode* createFairy()
{
// add animated fairy.
material.Texture1 = driver->getTexture("../../media/faerie2.bmp");
material.Lighting = true;

scene::IAnimatedMeshSceneNode* node = 0;
scene::IAnimatedMesh* pFairy = smgr->getMesh("../../media/faerie.md2");

if (pFairy) {
node = smgr->addAnimatedMeshSceneNode(pFairy);
node->setPosition(core::vector3df(-70,0,-90));
node->setMD2Animation(scene::EMAT_STAND); // EMAT_RUN
node->getMaterial(0) = material;
}

material.Texture1 = 0;
material.Lighting = false;

return node;
}

bool moodForDirectionChange()
{
int r = random();
if ((r % 40) == 0) {
return true;
}
else {
return false;
}
}

bool canWalk(const core::vector3df& pos, const core::vector3df& move)
{
core::triangle3df triangle;
core::vector3df resultPos;
bool f = false;
smgrAccess.lock();
resultPos = smgr->getSceneCollisionManager()->getCollisionResultPosition(
m_pCollRespAnim->getWorld(), pos,
m_pCollRespAnim->getEllipsoidRadius(), move, triangle, f);
smgrAccess.unlock();
if (resultPos.X-(pos.X+move.X) < 0.1 &&
resultPos.Y-(pos.Y+move.Y) < 0.1 &&
resultPos.Z-(pos.Z+move.Z) < 0.1)
{
return true;
}
return false;
}

void decideNewDirection()
{
int oldDirection = m_direction;
int diff;
do {
m_direction = random() % 360;
diff = abs(oldDirection - m_direction);
}
while (diff < 60 || diff > 300);
}

void turnToNewDirection()
{
m_pFairyNode->setRotation(core::vector3df(0, m_direction, 0));
}

core::vector3df computeNextStep()
{
// 0° +X, 0
// 90° 0, -Z
// 180° -X, 0
// 270° 0, +Z
core::vector3df move = core::vector3df(m_stepWidth * cos(m_direction), m_jump, m_stepWidth * -sin(m_direction));
return move;
}

scene::IAnimatedMeshSceneNode* m_pFairyNode;
scene::ISceneNodeAnimatorCollisionResponse* m_pCollRespAnim;
core::vector3df m_oldPos;
core::vector3df m_newPos;
int m_direction;
bool m_bShouldRenderMove;
int m_id;
int m_jump;
int m_stepWidth;
};

class AiControl : public QThread
{
public:
AiControl() : QThread()
,m_bRunning(true)
{
// create a number of fairies, each one doing its action every 100 milliseconds
for (int i = 0; i < 3; i++) {
Fairy* pFairy = new Fairy;
m_fairies.append(pFairy);
}
}

virtual ~AiControl()
{
// destroys all fairies
QPtrListIterator<Fairy> it(m_fairies);
while (*it) {
delete *it;
++it;
}
}

void doExit()
{
m_bRunning = false;
}

bool isSomethingToDo()
{
QPtrListIterator<Fairy> it(m_fairies);
while (*it) {
Fairy* pFairy = *it;
if (pFairy->shouldMove()) {
return true;
}
++it;
}
return false;
}

void doAI()
{
// check all fairies if they've queued a new step.
// if so, do the walk
QPtrListIterator<Fairy> it(m_fairies);
while (*it) {
Fairy* pFairy = *it;
if (pFairy->shouldMove()) {
pFairy->move();
}
++it;
}
}

void signalFinishedRendering()
{
QPtrListIterator<Fairy> it(m_fairies);
while (*it) {
Fairy* pFairy = *it;
if (pFairy->shouldMove()) {
pFairy->signalFinishedMove();
}
++it;
}
}

protected:
virtual void run()
{
while (m_bRunning) {
// wait some milliseconds until the next action of the fairy
msleep(50);
// from time to time let the fairies do something intelligent
QPtrListIterator<Fairy> it(m_fairies);
while (*it) {
Fairy* pFairy = *it;
if (pFairy->isDying()) {
QPtrListIterator<Fairy> it2 = it;
++it;
m_fairies.remove(it2);
delete pFairy;
if (!m_fairies.count()) {
s_bRun = false;
}
}
else {
pFairy->live();
++it;
}
}
}
}

protected:
QPtrList<Fairy> m_fairies;
bool m_bRunning;
};

int main()
{
MyEventReceiver receiver;
device = createDevice(video::EDT_DIRECTX9, core::dimension2d<s32>(640, 480), 16, false, false, &receiver);
driver = device->getVideoDriver();
smgr = device->getSceneManager();
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");

scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp"); if (!q3levelmesh) return 1;
scene::IMesh* pMesh = q3levelmesh->getMesh(0);
scene::ISceneNode* q3node = smgr->addOctTreeSceneNode(pMesh); if (!q3node) return 1;
q3node->setPosition(core::vector3df(-1370,-130,-1400));

selector = smgr->createOctTreeTriangleSelector(pMesh, q3node, 128);
q3node->setTriangleSelector(selector);
selector->drop();

scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f);
camera->setPosition(core::vector3df(0,50,0));
camera->setRotation(core::vector3df(0,270,0));
AnimateObj(camera, true);

AiControl aiControl;
aiControl.start();

smgr->addLightSceneNode(0, core::vector3df(-60,100,400), video::SColorf(1.0f,1.0f,1.0f,1.0f), 600.0f); // Add a light
device->getCursorControl()->setVisible(false); // disable mouse cursor

// infinite rendering loop
while (s_bRun && device->run()) {
bool bDoAI = false;
if (aiControl.isSomethingToDo()) {
bDoAI = true;
aiControl.doAI();
}
driver->beginScene(true, true, 0);
smgrAccess.lock();
smgr->drawAll();
smgrAccess.unlock();
driver->endScene();
if (bDoAI) {
aiControl.signalFinishedRendering();
}
}

device->drop();
return 0;
}

:)
bal
Posts: 829
Joined: Fri Jun 18, 2004 5:19 pm
Location: Geluwe, Belgium

Post by bal »

And always put your code between [.code] and [/.code] (without dots).
General Tools List
General FAQ
System: AMD Barton 2600+, 512MB, 9600XT 256MB, WinXP + FC3
Post Reply