Jump to content
  • Advertisement
Sign in to follow this  
dudman

NPC Dev?

This topic is 3715 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey :) I'm working on a simple little rpg, and i can't seem to figure out how to do the npc system at all. I Want to have functions like Say(who, what), TakeGold, GiveGold, etc. So, i tried to make a simple function, like, Chat() that calls those in order to make a fun chat system. The problem is, when i do that they all fire off at once, causing big chaos. I Also cant have the functions wait until the button is pressed, because then none of the rendering, gui, or other logic would work, which would be epic fail. My first system was ugly, an generally a wreck. What i did was have an int ResponseNum, and did something like this: // in the loop: switch(ResponseNum) { case 1: Say("bob", "Hey"); break; case 2: Say("you", "Hey, bob"); break; // etc }; if(ButtonIsPressed) { ResponseNum++; } so that was pretty dirty, and i didnt really want to do that. Also, if i wanted to have forks and such(choose an option), that would epic fail. My next try was simple, have a CMessage class that holds a who,what and when Say() is called, add one of those to a list of them. Then, when the button is pressed, pop one off of the list and draw it, but this also left no room for forks. So, if anyone has experience with npcs or has an answer to my problem, that'd be greatly appriciated :) -DudMan

Share this post


Link to post
Share on other sites
Advertisement
Eh... I'm not entirely sure how your wanting this set up, but here's something I would do:


class InteractiveObject
{
virtual draw() = 0;
virtual update() = 0;
InventoryComponent* m_pInvComp;
...
..
.

friend void Say( const InteractiveObject* from, const InteractiveObject* to, std::string what)
{

//Make the speaker and the listener face each other
from->Rotate()->SetDirectionTo( to->Position() );
to->Rotate()->SetDirectionTo( from->Position() );

//Make the bubble
from->SpeechBubble( what );

}

friend void Give( InteractiveObject* from, InteractiveObject* to, std::string what)
{

//Make the speaker and the listener face each other
from->Rotate()->SetDirectionTo( to->Position() );
to->Rotate()->SetDirectionTo( from->Position() );

switch ( what )
{
case "money":
to->SetInventoryList()->Money()->Add( from->GetInventoryList()->Money() );

std::string output;
format_string_with_arg( &output, "*You are given %i gold*", from->GetInventoryList()->Money() );

from->SetInventoryList()->Money->Subtract( from->GetInventoryList()->Money() );
break;
case "answer to life":
std::string output = "42";
break;
}

//Make the bubble
from->SpeechBubble( output );

}

class Player : public InteractiveObject
{

...
..
.
};

class NPC : public InteractiveObject
{

...
..
.
};

class CapitalCity
{
private:

Player* pPlayer;
NPC* pBob;
NPC* pChuck;
World* pWorld;

public:

void render( void )
{
pPlayer->draw();
pBob->draw();
pChuck->draw();
pWorld->render();
}

void logic()
{

//See if the distance is enough for the speech bubble to pop up
if( pPlayer->GetRadius().SpeechBoundary() <= pBob->GetRadius().SpeechBoundary() )
{

if( pBob->GetInventoryList()->Money() != 0 )
{
Say( pBob, pPlayer, "Here's some gold!");
Give( pBob, pPlayer, "money");
/*From, To, What. Accesses the Inventory component
"money" in pBob and transfers it to pPlayer and also puts a
bubble displaying the amount given*/

}

//Bob's money is now 0, so it'll never reach here again
}

//Next NPC. There's a better way to go through the NPCs, but this wasn't your question. :)
if( pPlayer->GetRadius().SpeechBoundary() <= pChuck->GetRadius().SpeechBoundary() )
{
Say( pBob, pPlayer, "Hey, ol' Chuck is mute! He's not much for conversation. " );
Say( pChuck, pPlayer, " ... *shrugs* " );
}

}

...
..

CapitalCity()
{
pPlayer = CreatePlayer( x(20.0f), y(0.0f). z(-53.0f) );
pBob = CreateNPC( x(40.0f), y(0.0f), z(-49.5f);
pChuck = CreateNPC( x(44.0f), y(0.0f), z(-43.5f);
pWorld = CreateWorld( "cap_city_mapdat.3ds" );

pBob->SetInventoryList()->Money()->Add( 100 );
pBob->SetInventoryList()->Weapons()->Add( "long_sword" );

...
..
.

}

~CapitalCity()
{

delete pBob;
delete pChuck;
delete pPlayer;
delete pWorld;

....
..
.
}

};

GameRoom = new CapitalCity();






A round about way, both NPC and Player need to derive from the same
base with similar components: inventory, the speech bubble, position, rotation, and etc so they can get data form one another.

So when the Say() function is called, both people
( the player and the NPC ) rotate to face each other and calls the speech bubble to show what they're saying.

The Give() function works about the same way the Say() function works,
but instead of only saying, it can give items from the NPC's inventory.

I hope it makes sense and helps. :)

Share this post


Link to post
Share on other sites
Yeah, you shouldn't stop mid-function and wait for a button press (that's not to say you CAN'T, I did it back before I knew what I was doing). You always want to have a main loop that you get back out to every frame.

For a text box, you'll want to create it, and then call an update function for it every frame. The update function could check for a button press and close the text box, or if no press then just return and let the next thing update, and check again next frame.

One way to do sequences of text boxes is by chaining events together. If you don't have any sort of scripting system, an "event" can just be a normal C/C++ function.

So what you can do is give the text box a function pointer to call when it closes. Say you have an event function called by the NPC when you talk to him. That function can open a text box, and pass in a pointer to a second event function. When the text box closes, it calls the function pointer. That event can open a new text box and pass in a third event function, and so on.

You can also do branching dialog that way. For a simple yes/no, just pass in 2 function pointers, one to call if the user selects yes, and another to call if the user selects no.

Coding with such a system might look something like this:
// Syntax for a function pointer
typedef void (*EventFunctionPointer)();
void Say(const char *name, const char *dialog, EventFunctionPointer onClose);


void Event1()
{
Say("George", "Howdy!", Event2);
}

void Event2()
{
Say("George", "Have some money.", Event3);
}

void Event3()
{
AddGold(100);
}

Share this post


Link to post
Share on other sites
still, I'd prefer a chat enum and a switch-type handler instead of using function pointers since function pointers have a tendency to cause higher call overheads.

Share this post


Link to post
Share on other sites
Quote:
Original post by reoxthen
still, I'd prefer a chat enum and a switch-type handler instead of using function pointers since function pointers have a tendency to cause higher call overheads.


That is true. This means, than instead of theoretical 870 million calls per second you will get only 690 million.

The two NPCs chatting will therefore be able to go through their dialog in 2.90 nanoseconds instead of 2.90. Note that there is no difference, since the overhead of function call compared to actual logic is a fraction of a percent at most.

As a comparison, user will make 1 choice every 5 seconds.

Switch has its uses, but is incredibly cumbersome in logic. Scripts are generally the most configurable part, and should be flexible. Changing a small dialog option would be a matter of editing a config file, not doing a full recompile of the project.

This is moot point where designer is also the programmer, but anywhere else, that's considered a show-stopper.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus

That is true. This means, than instead of theoretical 870 million calls per second you will get only 690 million.

The two NPCs chatting will therefore be able to go through their dialog in 2.90 nanoseconds instead of 2.90.


well, maybe you're right for this example but in the point he decides to put a game world, culling, collision detection, other basic game physics, load/unload of resources, user i/o handling and many more stuff that should be calculated in around 60-70Hz in his game, that theoretical loss may become something serious for such a simple part of his game.

that's why still, I'd prefer an enum-switch style handling.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!