NPC Dev?

Started by
5 comments, last by reoxthen 15 years, 9 months ago
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
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 otherfrom->Rotate()->SetDirectionTo( to->Position() );to->Rotate()->SetDirectionTo( from->Position() );//Make the bubblefrom->SpeechBubble( what );}friend void Give( InteractiveObject* from, InteractiveObject* to, std::string what){//Make the speaker and the listener face each otherfrom->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 bubblefrom->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 upif( 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. :)
Holy crap, you can read!
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 pointertypedef 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);}
Thanks guys, now i have an idea on how to code this :D

-DM
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.
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.
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.

This topic is closed to new replies.

Advertisement