Creating game dialogue

Hi

I am in the process of developing my first game. It is an adventure platformer comprised of various missions that lead you to the overall objective (growing a flower). I am currently trying to develop a system to implement dialogue, however, I have not found many resources online specific to P5. Trying to follow tutorials in Java and C# is rather confusing as I do not have much experience with those languages and they also include functions not available to P5.

I guess my question is where do I even start? How can I effectively store strings of dialogue, call on them, and cycle through a conversation? I have attached a quick sketch for a potential dialogue method that does work but I don’t know if there is a way it could be more concise. It also does not take into consideration your character’s dialogue. I would love to know your thoughts on how expandable/efficient it seems.

class NPC{
constructor(x,y,l,w){
this.pos = createVector(x,y);
this.size = createVector(l,w);
// this.dialogue = [“Oh hey there”,“keep going”, “bye”,“mission 2 bitch”,“lol”];
this.convoPart = 0;
}

display(){
	fill(200, 88, 188);
	ellipse(this.pos.x, this.pos.y, this.size.x, this.size.y);
}
chat(mission){
	if(mission == 0){
		fill(200);
		if(this.convoPart == 0){
			textSize(25)
			text(this.dialogue[0], this.pos.x, this.pos.y - 50);
		}
		else if(this.convoPart == 1){
			textSize(25)
			text(this.dialogue[1], this.pos.x, this.pos.y - 50);
		}
		else if(this.convoPart == 2){
			textSize(25)
			text(this.dialogue[2], this.pos.x, this.pos.y - 50);
		}
	}
	
	if(mission == 1){
		if(this.convoPart == 0){
			textSize(25)
			text(this.dialogue[3], this.pos.x, this.pos.y - 50);
		}
                    if(this.convoPart == 1){
			textSize(25)
			text(this.dialogue[4], this.pos.x, this.pos.y - 50);
		}
	}
}

}

function mousePressed(){
npc.convoPart++;
if(npc.convoPart > 2){
npc.convoPart = 0;
}
}

I would be grateful for any resources/advice/thoughts.

-fuc

I think if-else structures like this will lead you to loads of problems. Adding new parts of dialogue means that you need to put new code to the right part in structure. It will be difficult to manage and change.

If you think the structure of dialogue and tasks (or text based games) on a bit more abstract level there are

  • goals (related to tasks)
  • items needed to fulfill goals
  • possibly places and certain situation. These can be modelled as states.

Dialogue is in some cases tied to certain places or situations. So dialogue can be dependent on a state.Fulfilling a goal can be modelled as change of a state https://en.wikipedia.org/wiki/State_(computer_science). State can be as simple as set of variables.

For dialog I would make a data structure that has state requirements with the actual text. State requirements means conditions that has to be in place to open this dialog item. For example [“shop”, “50€”, “Here you go Sir, ten pumpkin seeds”]. That has to be also tied to change in variables to indicate possession of ten pumpkin seeds. So perhaps data structure should be like [“shop”, “50€”, “Here you go Sir, ten pumpkin seeds”, 10, “pumpkin seed”]. This way you can separate dialogs and other data from the program structure.

If you search with keywords “javascript text adventure engine” you’ll find articles and other useful resources to help you.

2 Likes

Hey and welcome to the foum!

Great to have you here!

Interesting idea.

When it is a complex role play where the player meets different npc:

Often the npc says something and the player can choose between 3 or 4 possible answers (for example with the mouse).

Depending on the answer, the npc answers something else.

Then again the player would have 3 possible answers to choose from and so forth.

This is a tree of sentences.

What to do

In this scenario when you are into OOP make a class Dialogue.

class Dialogue {

}

Make an ArrayList listOfDiaolgues of the class Dialogue.

ArrayList<Dialogue> listOfDiaolgues = new ArrayList();

In the class you have a 2D String array arrStrings.

Each line holds what npc says OR what the player can choose from. It looks like this :

  • “0”, “Dialog name: Player meets Pirate Captain”, “npc:Captain”, “Hello stranger”, “1”,"","", “”, “”
  • “1”, “Dialog name: Player meets Pirate Captain”, “player”, “Can I work on your ship”, “2”, “Never mind”, “3”, “You got a nice beard, Captain”, “4”
  • “2”, “Dialog name: Player meets Pirate Captain”, “npc:Captain”, “Have you ever seen a decent ship from the inside, boy?”, “5”, “”,"","",""
  • “3”, “Dialog name: Player meets Pirate Captain”, “npc:Captain”, “BYE THEN”,"","","",""

As you can see the lines always hold the same amount of Strings.

The line numbers are used to navigate this tree.

  • We start with line number 0. We display “Hello stranger”.
  • after 0 it goes to 1. Here the player has to choose his answer.
  • When player chooses “Can I work on your ship” we go to line 2,
  • when you choose “Never mind”, to line 3, etc.
  • In these lines, different answers of the Captain can be found.

You need a line number pointer variable (int) to keep track of this. lineNumber.
This is the index for arrStrings: arrStrings[lineNumber]

That’s the text and can be stored on your hard drive.

Now you need a program to navigate the tree and read out the texts and the 2 / 3… parts… see below.

Warm regards,

Chrisir

1 Like

here is a similar example which loads a text file that I will post too. See below.

The example works with labels instead of line numbers. I personally would prefer line numbers. But here you go.

When you would have a lot of Dialogues, I would recommend a assistant program that would assist you in making the dialogue and would write the text file. The assistant would handle the line number automatically.

I haven’t done this.

It could look like this:

// text adventure program

// Version 0.1: 
// now with a command in the line to have different backgrounds 
// now with labels instead of line numbers
// now with file "textFile.txt" to load instead of having those data in the sketch 
//       This must be in data folder and must hold the lines.

/*
E.g.: 
 
 LabelX1#Would you like \nto help me or not?#Heck, why not#Leave me alone!#What's in it for me?#I am not sure#ThatIsNice#LabelX3#LabelX6#LabelX8#command0
 ThatIsNice#That is nice!#Whatever you say#LabelX8#command2
 LabelX3#That is sad. Do you want to tell me your name?#Of course#No way!#Test 1#Test 2#LabelAskForName#LabelX5#LabelX8#LabelX8#command1
 LabelAskForName#What's your name#?#!#LabelX8#LabelX8#command2
 LabelX5#You don't tell your name# Correct #LabelX8#command1
 LabelX6#A lot of money. #OK#LabelX7#command1
 LabelX7#How are you#LabelSeven#command1
 LabelSeven#!!!     HOOray !!!!!! \n  YOU Won !!!!!  #LabelX1#command2
 LabelX8#Game over\nPress mouse to play again. #LabelX1#command2
 
 */


// the data:
// each room / situation / question in the text adventure program
// is represented by one line in the array "fileText". 

// Each line holds: 
//     * A description of a room, 
//     * one or more button texts.
//     * one or more line numbers to go next within fileText. 
//     * a command pointer to be able to make different backgrounds etc. 

// Those things are called components of a line.
// The components of a line are separated by #.

// There are 5 different types of lines:
// Those with 0 button (= text for the room + line to jump to) 
// Those with 1 button (= text for the room + text for button + line to jump to) 
// Those with 2 buttons (= text for the room + 2 texts for button + 2 lines to jump to)
// Those with 3 buttons (= text for the room + 3 texts for button + 3 lines to jump to)
// Those with 4 buttons (= text for the room + 4 texts for button + 4 lines to jump to)

// As you can see, the number of components vary from 3 to 10.  

// The text and the buttons are displayed in parseText(). 

// Depending on where you click, a different button is clicked in mousePressed().

// Depending on this button the associated line number from the components becomes the current line number "pointer", 
// we start again using this new line number (representing a new room / question / set of buttons). 

// To make new rooms / situations / questions in the text adventure program, just append lines to 
// fileText. You might also want to store them in a textfile (use loadStrings). 

// When you append lines, make sure to use # to seprate components.
// The numbers of button texts must match the number of associated line numbers (they are pairs). 
// Make sure that each line number associated with a button points to a line number that exists. 

// use this or loadStrings for all lines: 
String[] fileText;

// where are we in this text file ? current line number: 
int pointer = 0; // 0 = very first line 

// when we look at one line with its components  
String[] thisLine; // this holds the components 

// some constants: // indexes of thisLine
final int undefined  = -1;
final int labelIndex =  0;
final int question   =  1;  

Button[] buttons = new Button[4]; 

HashMap<String, Integer> hm = new HashMap(); 

// --------------------------------------------------

void setup() {

  // setup() runs only once 

  size(940, 680);

  rectMode(CORNER);
  textAlign(LEFT);
  strokeWeight(1.5);
  background(#778C85);

  // load the fle from hard drive
  fileText = loadStrings ("textFile.txt");
  // trim all lines; here we could sort out comment lines if we had those  
  for (int i=0; i<fileText.length; i++) {
    fileText[i]=trim(fileText[i]);
    if (fileText[i].equals(""))
      println("One line is empty");
  }// for 

  // error check over all lines, preparing HashMap    -----------------------------------------
  boolean errorDetected=false;
  for (int i=0; i<fileText.length; i++) {

    // we use split to get the components of a line
    thisLine = split(fileText[i], "#"); 

    println (i + ": "+fileText[i]+ "\t -------- has length " + thisLine.length);

    // error check 
    if (thisLine.length!=4&&thisLine.length!=5&&thisLine.length!=7&&thisLine.length!=9&&thisLine.length!=11) {
      // error
      println ("+++++++++++++++++ not right number components in length"+fileText[i]+"      ++++++++++++++++++++++++++++++");
      errorDetected=true;
    }

    // test if the label already occured  
    int dummy = -1; 
    if (hm.containsKey(thisLine[labelIndex])) {
      //if (dummy!=-1)
      println ("HashMap Error, duplicate label ###########################################  "+thisLine[labelIndex]);
      errorDetected=true;
      exit();
    }

    // store label in HashMap and associate with line number
    hm.put(thisLine[labelIndex], i);
  }//for 

  // evaluate error check over all lines (optional) -----------------------------------------
  if (errorDetected) {
    println ("+++++++++++++++++");
    exit();
  }

  thisLine=null;
  println ("End of setup() -----------------");
}

void draw() {
  // draw() runs on and on 

  background(#778C85);

  // analyse current line 
  parseText();
}

// -----------------------------------------------------------

void parseText() {

  // called by draw() 

  fill(255);  // WHITE 

  // we use split to get the components of a line
  thisLine = split(fileText[pointer], "#"); 

  int lastComponent=thisLine.length-1; 
  callASpecialFunction(thisLine[lastComponent]); 

  // display number / this is more for testing / in the upper left corner (optional)
  textSize(14); // small text size
  text(pointer, 22, 22); 

  // build buttons 
  buildButtons(); 

  // show main text // same for all kinds of lines
  // (no matter how many components)
  textSize(22); // big text size
  text (thisLine[question], 222, 222);
  textSize(14); // small text size

  // display buttons 
  for (Button b : buttons) {
    b.display();
  } 
  //
}

void callASpecialFunction(String command) {

  String cmdNumber = trim ( command.replace("command", ""));
  int cmd = int( cmdNumber );

  switch(cmd) {

  case 0:
    background(255, 2, 234); // pink 
    break; 

  case 1:
    background(255, 2, 2); // red 
    break;

  case 2:
    background(2, 2, 255); // blue 
    break;

  default:
    println ("++++++++++++++++++++++++++++++++++++++++   ");
    println ("Error 220 ++++++++++++++++++++++++++++++++++++++++   \n"
      +fileText[pointer]);
    println ("++++++++++++++++++++++++++++++++++++++++   ");
    exit(); 
    break;
  }
}

// ---------------------------------------------------------

void mousePressed () {

  // called automatically  

  if (pointer==undefined) {
    // restart 
    pointer = 0;
    return;
  } // if 

  // ----

  // branch, go to line associated button
  for (Button b : buttons) {
    if (b.mouseOver()) {
      //go to next line
      b.setPointer();
    }//if
  }//for
  //
} //func

// -------------------------------------------------------------------------

void buildButtons() {

  // This is the only function that is a little complicate.  
  // Here we bring the current line into buttons and their pointers.  

  // constants for buttons
  float wButton=220, hButton=23;

  // reset all buttons 
  for (int i=0; i<4; i++) {
    buttons[i]=new Button(2, 2, wButton, hButton, "", "");
    buttons[i].exist=false;
  }

  // depending on the numbers of components in the line 
  switch (thisLine.length) {

  case 4:
    // no button / we make ONE big button
    // those are for thisLine with a length of 2 components
    final int threeComponentsPointer = 2;  
    buttons[0]=new Button(-10, -10, width+10, height+10, "", thisLine[threeComponentsPointer]);
    break; 

  case 5:
    // 1 + 2 components (text, one button, one pointer) + command 
    // build button
    // those are for thisLine with a length of 2 or 3 components 
    final int fourComponentsText1 = 2; 
    final int fourComponentsButtonPointer = 3;
    buttons[0]=new Button(222+66, 222+55, wButton, hButton, thisLine[fourComponentsText1], thisLine[fourComponentsButtonPointer]);
    break; 

  case 7: 
    // 1+ 2x2 components (text, 2 button-pointer-pairs)
    // yes / no buttons
    final int sixComponentsText1     = 2;
    final int sixComponentsText2     = 3; 
    final int sixComponents1Pointer  = 4; 
    final int sixComponents2Pointer  = 5; 
    buttons[0]=new Button(222-120, 222+55, wButton, hButton, thisLine[sixComponentsText1], thisLine[sixComponents1Pointer]);
    buttons[1]=new Button(222+120, 222+55, wButton, hButton, thisLine[sixComponentsText2], thisLine[sixComponents2Pointer]);  
    break; 

  case 9:
    // 1 + 3x2 components (text, 3 button-pointer-pairs)
    // yes / no buttons
    float x=222-100; 
    // those are (additionally) for thisLine with a length of 5 components

    // those are for thisLine with a length of 7+1 components
    final int eightComponentsText1      = 2;
    final int eightComponentsText2      = 3; 
    final int eightComponentsTextThird  = 4;
    final int eightComponentsPointer1   = 5;
    final int eightComponentsPointer2   = 6;
    final int eightComponentsPointer3   = 7; 
    buttons[0]=new Button(222-100, 222+55, wButton, hButton, thisLine[eightComponentsText1], thisLine[eightComponentsPointer1]);
    buttons[1]=new Button(x+=100, 222+55, wButton, hButton, thisLine[eightComponentsText2], thisLine[eightComponentsPointer2]);
    buttons[2]=new Button(x+=100, 222+55, wButton, hButton, thisLine[eightComponentsTextThird], thisLine[eightComponentsPointer3]);    
    break; 

  case 11:
    // 1 + 4x2 components (text, 4 button-pointer-pairs)
    // those are for thisLine with a length of 9+1 components
    // final int tenComponentsQuestion   = 0;
    final int tenComponentsText1      = 2;
    final int tenComponentsText2      = 3;
    final int tenComponentsText3      = 4;
    final int tenComponentsText4      = 5;
    final int tenComponentsPointer1   = 6;
    final int tenComponentsPointer2   = 7;
    final int tenComponentsPointer3   = 8; 
    final int tenComponentsPointer4   = 9;
    int index  = tenComponentsText1; 
    int index2 = tenComponentsPointer1;
    for (int i=0; i<4; i++) {
      buttons[i]=new Button(222, 222+100*(i+1), wButton, hButton, thisLine[index], thisLine[index2]);
      index++;
      index2++;
    }        
    break; 

  default:
    println ("++++++++++++++++++++++++++++++++++++++++   ");
    println ("Error 359 ++++++++++++++++++++++++++++++++++++++++   \n"
      +fileText[pointer]);
    println ("++++++++++++++++++++++++++++++++++++++++   ");
    exit(); 
    break;
  }//switch
}//func 

// ================================================================

class Button {

  // a standard button class for mouse clickable areas on the screen

  float x, y, // pos 
    w, h;                // size 
  String textButton;     // text to display
  boolean exist=false;   // does the button exist? 
  String pointerButton;  // the line number we jump to 

  Button(float x_, float y_, 
    float w_, float h_, 
    String textButton_, 
    String pointerButton_) {

    x=x_;
    y=y_;
    w=w_;
    h=h_;

    textButton=textButton_;
    pointerButton=pointerButton_;

    exist = true;
  }//constr

  void display() {
    if (!exist)
      return;

    // rect 
    noFill(); 
    stroke(255); //White 
    rect(x, y, w, h);
    //text
    fill(0); 
    text(textButton, x+3, y+h/2+5);
  }

  boolean mouseOver() {
    if (!exist)
      return false;
    return mouseX>x && mouseY>y&&
      mouseX<x+w && mouseY<y+h;
  }

  void setPointer() {
    if (!exist)
      return;
    pointer=int(getLineNumberFromLabel(pointerButton));
  }

  int getLineNumberFromLabel(String label) { 
    return hm.get( label );
  }
  //
}//class
//

content of the text file:

LabelX1#Would you like \nto help me or not?#Heck, why not#Leave me alone!#What's in it for me?#I am not sure#ThatIsNice#LabelX3#LabelX6#LabelX8#command0
ThatIsNice#That is nice!#Whatever you say#LabelX8#command2
LabelX3#That is sad. Do you want to tell me your name?#Of course#No way!#Test 1#Test 2#LabelAskForName#LabelX5#LabelX8#LabelX8#command1
LabelAskForName#What's your name#?#!#LabelX8#LabelX8#command2
LabelX5#You don't tell your name# Correct #LabelX8#command1
LabelX6#A lot of money. #OK#LabelX7#command1
LabelX7#How are you#LabelSeven#command1
LabelSeven#!!!     HOOray !!!!!! \n  YOU Won !!!!!  #LabelX1#command2
LabelX8#Game over\nPress mouse to play again. #LabelX1#command2