Design for a Dialogue System in a RPG

Started by
8 comments, last by Nicholas Kong 10 years, 3 months ago

I need feedback on my design for the dialogue feature I am working on. If you have any recommendations on your design or how I can structure the code better, I will gladly listen to them. Working on a role playing game has been tough but the codebase grows so much in a week! I would think I would be used to this after finished working on small prototypes of simple games.

This feature right now is for one Soldier NPC that is a talking quest givers for my role playing pc game in Java. Future npcs will inherit the below instance variables from a class called NPC.


public class Soldier extends Sprite implements Collidable,Talkable
{
// This is an enum of 4 possible states: attacking, healing, running and idle
private ActionState state;
private SoldierIdleAnimation soldierIdleAnimation;

// for bounding box collision to trigger the talk button indicator
private Rectangle rectangle;
// This is the question mark image on the top of its head when a quest is not done

private QuestNotFinishedImage questNotFinishedImage;
// This is the exclaimation point mark to indicate you have satisfied the requirements for the quest.
private QuestFinishedImage questFinishedImage;
// This is an enum with 2 possible states: Finished and NotFinished
private QuestState questState;
// This is an indicator that pops up when you are near the soldier which means you are near enough to talk to it
private NPCTalkImage npcTalkImage;
// This gets turned on when the main character is near the soldier to trigger the talk button indicator
private boolean canTalk;
// This is the dialogue bar for the npc. Each npc will get their own dialogue bar.
private NPCDialogueBox npcDialogueBox;
// This gets turned on when the main character presses the T button
private boolean isTPressed;
// This is also an enum with 2 possible states: isRunning or IsNotRunning
private Dialogue dialogue;
 
}
 

 


The second question I have is: does this code look overkill for a colliding with talking npcs algorithm for my main character class


for(int i = 0; i < charactersToAdd.size();i++)
{
          Talkable character = (Talkable) charactersToAdd.get(i);
         /* make sure the rectangle is not from the main character
         *  because you do not want to collide with yourself
         */
         if(character != this)
         {
                 // get rectangle from the characters in the list besides main character
                 Rectangle rectangle = character.getRectangle();
 
                 // if main characters bounding box collides with the npc bounding box
                 if(getRectangle().intersects(rectangle) )
                 {
 
                 // make the talk button indicator appear
                 character.setTalk(true, isTPressed);
                 }
                 else 
                 {
                  // make the talk button indicator disappear 
                 character.setTalk(false,isTPressed);
                 }
                 }
          }
}

The third question is how much should I plan when making an RPG? So far development has been okay. Every feature had its failure before finally getting it to work properly. I was able to get by so far with what I have done with a rough outline of the working implemented feature in my head.

This is the first time I am working on an RPG. Working on this type of game made me remember how much code is required to get something simple to work.

Advertisement

Please don't be too disappointed about my comments, but IMHO your implementation is already on the spiral of death. Three reasons for this:

(1) There is a class Soldier. It should not be necessary to sub-class Soldier, Mage, Thief, Peasant, ChickenOnStreet, ChickenInStable, ChickenInSoap, and so on. From the chosen class names, being intentionally provoking when coming to chickens ;), you can see that it would cause an explosion of classes. What happens if a class Dog will become necessary due to story development later? Will its integration break the game because from the many places where the class' type plays a role one for overseen when adapting the code?

Notice that trying to generalize classes (I make a reference to the mentioned class NPC) will not rescue you! That has been proven by many trials in the past. The typical effect would be shifting functionality into the base classes although just being necessary in a sub-set of derived classes only.

(2) The class Soldier/NPC already inherits 3 classes/interfaces: Sprite, Collidable, Talkable, maybe there is already a bunch hidden in Sprite, like e.g. Moveable and Drawable. It is better to think of Soldier/NPC, not as a concrete "is-a" but an more-or-less abstract collection of "has": It has a Sprite as visual representation, it has a Placement, it has a CollisionVolume, it has (okay: can) be talked to.

(3) The obvious "misconception" in the sense of (2) is the fact that Soldier/NPC contains some variables dealing with the fact whether it runs a quest. Maybe it originates in Talkable, although also that would not be 100% okay. However, Talkable is an interface and hence does not have member variables. So you have to implement those variables (and functionality at all) for each and every class that inherits Talkable. What a mess in an RPG where optimally each and every character, perhaps also environmental life (hey, we are in a game here ;)) should be Talkable. The better solution would be a class Quest and a member variable in Soldier/NPC (or whatever) that points to a concrete instance of Quest if the Soldier/NPC instance actually has a quest. So the Soldier/NPC would be Talkable anyway, may or may not have a Quest, and the Quest (if any) may or may not be solved (which is then stored in a member of Quest, not in a member of Soldier/NPC).

So, with regard to your 1st question: As already drafted, the common solution to the above problems is to use composition over inheritance. The second level is to follow further the data-driven approach. This will become more obvious when dealing with different quests and dialogues: You don't want to sub-class Dialogue for each different talk to may have.

With regard to your 2nd question: I'm stumbled about the name "charactersToAdd" in this context. Where are the characters to be added? They are already all in the scene, aren't they? They are not all to be added to the talk, are they? So, what does "to add" mean?

Regardless of this little confusion, I don't see an overkill. What may happen is that the collision check becomes slow if really many, many characters populate the world. Then it may become meaningful to have a broad phase collision detection prefixed. It may also be meaningful to separate collision detection from dialogue mode activation, namely if the same collision can be used for other reactions, too. In such a case first collecting all detected collisions and then deciding what to do with the is probably more efficient.

Another thing is that you have to control the situation where another character is bumping into your avatar during a talk is already in progress. Either you suppress collision detection with the purpose of talk initialization, or you may use the mechanism described in the following paragraph to expand the round of participating talkers.

I'm not sure whether character.setTalk(…) is the way to go. I think it would be better to have an external instance of Talk where all participating characters are bound to. They may refer back to the Talk instance as long as participating, so they are tagged as talking and the other participants can be found easily. The instance of Talk would further be a suitable place where to track how and how far the talk has progressed (in conjunction with a Dialogue instance).

With regard to your 3rd question: What exactly do you mean? IMHO you cannot plan the story in all details before starting game programming. You should have planned completely what features should be available (like: how are quests structured; that dialogues can take place; that sword fighting can happen; that the day-night cycle is considered; …). To become somewhat independent on the story details, using mechanisms like composition and data-driven is suitable.


Working on this type of game made me remember how much code is required to get something simple to work.

Or maybe our definition of what "simple" means, is incorrect. What gamers call simple is mostly out of pure ignorance. Us programmers know better smile.png

I would second composition on this one. Just be aware of what objects you need and who owns them. If you are just going to expose one of the objects inside another then does it really need to exist inside that object or should it be outside and you just pass it as a parameter to some Update() function. The thing about composition is that it can break encapsulation if you just expose the hosted objects themselves.

http://en.wikipedia.org/wiki/Composition_over_inheritance at the bottom it talks about "drawbacks" to composition


One drawback to using composition in place of inheritance is that all of the methods being provided by the composed classes must be implemented in the derived class, even if they are only forwarding methods. In contrast, inheritance does not require all of a base class's methods to be re-implemented within the derived class. Rather, the derived class need only implement (override) the methods having different behavior than the base class methods. This can require significantly less programming effort if the base class contains many methods providing default behavior and only a few of them need to be overridden within the derived class. This drawback can be avoided by using traits.

The reason for this drawback is because you still want to support encapsulation. By simply exposing the internal object so you can have access to all it's functions you are breaking encapsulation and exposing potential issues down the line. This is why I say make sure the "parent" object really needs to store the child object or can it just be passed to one of the parent object functions and you create and handle it outside of the parent object.

If you must store it, then generally avoid calling new yourself in the parent ctor. Don't let the parent control the lifetime but let the user outside as it's more flexible.

In fact, I am glad I got a big post like this. It definitely thought provoking to me! =] To answer your questions:

charactersToAdd is an ArrayList where my characters are stored in the Game class. I also have a separate Arraylist for mainMenuButtons for example ("NEW GAME", "Continue Game"). Game loops draws all the elements for either one of the Arraylist depending on what "state" my game is in. If the game is in the MAIN MENU state, it draws from the Arraylist that contains mainMenuButtons. If the game is in the OVERWORLD state, it draws from the Arraylist that contains the characters(the main character and the soldier).

I do not understand what do you mean by broad phase collision detection prefixed.

Do you have confusion about my talking collision algorithm?

Dialogue mode activation requires a bounding box collision because it is the only approach I can think of and test for the main character being close enough to talk to the npc.

Ah! A Talk instance! I never thought of that. A Dialogue instance? That is interesting. I will see if I can change it to that. Right now my Dialogue is just an enum with two states: ISRUNNING and ISNOTRUNNING.

What do you mean by data driven mechanism?

In terms of planning, I do also have all the required features all written on paper. The challenge every day was how to implement a feature even given my past experience with game programming and how to better structure the code.

I will try the composition approach and see what difference it makes. Time to refactor the dialogue feature.

Thanks for the information on the drawbacks on composition. A good note to bear in mind about.


I do not understand what do you mean by broad phase collision detection prefixed.

Broad phase collision detection means that in a first phase collision is checked not exactly but fast, and false hits are tolerated. There are several approaches to that, dependent on the circumstances and the type of game. For example in 3D, broad phase collision check may be done using spheres as collision volumes, and only if a collision is detected with them the better fitting (but more costly) collision volumes are used. In both 3D and 2D one can think of axis aligned boxes before rotated boxes are used. Another popular approach is to subdivide the amount of potential colliders, e.g. by using spatial regions.


Do you have confusion about my talking collision algorithm?

No; its just about the name "charactersToAdd".


Dialogue mode activation requires a bounding box collision because it is the only approach I can think of and test for the main character being close enough to talk to the npc.

I have nothing to contradict. I just say that collision can be checked without the background of conversation, i.e. the collision detection sub-system itself need not (and should not) know about conversation at all.


Ah! A Talk instance! I never thought of that. A Dialogue instance? That is interesting. I will see if I can change it to that. Right now my Dialogue is just an enum with two states: ISRUNNING and ISNOTRUNNING.

What I meant by those 2 classes is this: An instance of Dialogue is a game asset like a texture or mesh. It holds what can be said by whom and under what conditions, and what effects take place (i.e. item exchange). An instance of Talk, on the other hand, is a runtime thing. Its existence shows that one or more characters are currently talking (i.e. they do gossip or they process a Dialogue) or have talked about something. It stores who participates, where inside a Dialogue the talk currently is, and similar things.


What do you mean by data driven mechanism?

It means that processing is controlled by the data coming with the game entities, not with the code belonging to classes (uh, well, thereabout). For example, let Item be a game entity that can be utilized by a character. Let us say there is an Item that is defined to be a pillow (the graphical representation shows this). However, the level designer has given the pillow a damage value of 10. So the pillow has become a weapon. Your code is not prepared to know that a pillow can be used as weapon, but this is okay because your code knows that a damage value gather than 0 means that the item can be used as weapon.

Another example is Dialogue. When a Talk is in progress by processing a Dialogue, the processor looks into the Dialogue and finds a value that means "the opposite says the subsequently stored sentence and wait for an answer", or "the opposite gives …" followed by the item kind "gold coins" followed by the amount "10".

Interesting. The examples sure did make things the details more concrete for me!

This is what my DialogueSystem looks like. I wanted to know should each NPC have a dialogue system of their own. I'm not sure how to "when to actually create." Should I create it in the NPC class and just have the dialogue system created when the user presses the button to initiate conversation with the NPC? It sounds trivial but I need feedback from experts.
Should the system wipes itself clean if the user stops talking to the npc? Or does the system hangs around in the game loop all the time updating itself to see if the user presses the button for talking to npcs?

public class DialogueSystem {
 
private TextDialogue dialogue;
private DialogueBox box;
private static DialogueState state;
private DrawTextGraphicsSystem textSystem;
private NPC npc;
private DialogueMark mark;
 
}

I think each npc should have that information that is specific to that entity (the text, color, image, what have you), probably stored (or referenced, or what have you) in a HasDialogue component of some sort. I assume you have some manner of interacting with npcs (press a button or collide with), so why not have an OnInteract method that sends the specifics of the dialogue to the DialogueSystem, which handles all of the displaying and whatnot for you.

Something along the lines of:


OnInteract()
{
     DialogueSystem.Process(this.DialogueInfo);
}

Process(DialogueInfo dialogue)
{
     DisplayDialogueWindow();
     WriteDialogueText(dialogue.Text);
}

Inspiration from my tea:

"Never wish life were easier. Wish that you were better" -Jim Rohn

soundcloud.com/herwrathmustbedragons

I think each npc should have that information that is specific to that entity (the text, color, image, what have you), probably stored (or referenced, or what have you) in a HasDialogue component of some sort. I assume you have some manner of interacting with npcs (press a button or collide with), so why not have an OnInteract method that sends the specifics of the dialogue to the DialogueSystem, which handles all of the displaying and whatnot for you.

Something along the lines of:


OnInteract()
{
     DialogueSystem.Process(this.DialogueInfo);
}

Process(DialogueInfo dialogue)
{
     DisplayDialogueWindow();
     WriteDialogueText(dialogue.Text);
}

That works too. I actually got something similar to the above you speak of. The npc interaction is actually a combination of colliding and a button press. valid collision prompts the button to show up. pressing it triggers the conversation.

Awesome response!

This topic is closed to new replies.

Advertisement