RPG Style Dialogue Boxes?

Hi all! I’m new here as you can probable tell, but a website dedicated to Java game development? How did I not know sooner?

Anyway, I’m working on a project for class, I’m just trying to make a small “game” akin to the Megaman Battle Network franchise. “Game” because it’s not a full game, just a single room where the player character can interact with some items around the room. There’s no object to it, just a sort of demo, I guess?

So I haven’t actually programmed Java in a while, it was ages ago I learned any Java in high school. I remember enough to be able to make small little simple things, but a full blown game wasn’t ever anything I learned how to do (though I did try to brute force once for my final project back then).

I’ve been following this guide from for the basic framework. I figured my game just needed to be a restriced sized space where the character could move about and certain locations on the screen would turn up a dialogue box with dialogue on screen. That’s all I need to make, really, but the dialogue box is the issue.

I’m not exactly sure where this code should be added (I’m thinking to the Board class? I’m not 100% sure.) But I have graphics for the dialogue box and a vague idea of what the dialogues would be. I don’t think I can totally use the code in the site, since the only text that appears shows on screen and never goes away, where a dialogue box text would only need to appear when it is called by a keyboard action and disappear after all the text has been shown on screen.

Browsing through the website I found a thread that had info on text wrapping inside of a specified set of boundaries, but I’m still not sure how to make the whole thing work. It’s the last bit of coding I need to do to finish all the code for the project, so I just need to get it working and I’ll be pretty much done (I think!).

For what it’s worth, I’ve altered the code so that the “aliens” class is just a class that adds a space (defined by the image added into it) that the character can interract with when enter is pressed and doesn’t move around. Am I barking up the wrong tree? Do I need to start from scratch again? Or is it just a little bit further away? Can anyone give me any idea about where to start next?

Thanks in advance!

I haven’t used awt for a long time (very long!), if you want to stick to this then go for it :slight_smile: but you can also use javafx / opengl (there are quite a few engines around to help you out there) if you want to use some more modern api’s.

Without going through all that code I would structure things as follows :

  • Draw all the game objects first
  • Then draw any 2d items on top of this (dialogs / hud type panels / etc)

When you program your game I would try to isolate things in to different units and to keep them separate when drawing to the screen

This is hard to help without seeing what you have, but I think I understand enough to help. What you have here is how my current project was until I moved to OpenGL. For your classwork, awt and this linked code source should have what you need.

I’m not sure how you are trying to structure this. If I were you, I’d make a messageBox object. Create that and then draw it. You don’t need an object, but is easier imo.

However you do it, you can use:

int textOffsetX = 5;
int textOffsetY = 7; //example offsets

//draw message box background image at given coordinates
g.drawImage(messageBoxImage, messageBoxImageX, messageBoxImageY, this); //I think you can pass null instead of "this"?

//draw text for message box at given coordinates plus an offset
g.drawString("Contents of message box.", messageBoxImageX + textOffsetX, messageBoxImageY + textOffsetY);

If you want the text to be multiple lines, use a line break character. You can do:
[icode]String newline = System.getProperty(“line.separator”);[/icode]
And then do:

g.drawString("Contents of message box" + newline + "And another line now too.", 
messageBoxImageX + textOffsetX, messageBoxImageY + textOffsetY);

Oh, also, what I did was make static strings and stored them in a class. You also don’t have to do that. But if you want many messages, it may get out of hand. But you can have various string variables that hold all the text, then just say g.drawString(messageString1, x, y) for each. Makes it all cleaner and more manageable.

Hello!! Thanks for the responses!!

I realize there’s probably better ways to go about doing this, but I only know just Java and it’ll take too long to learn OpenGL or javaFX imo to just do a little project for class. It doesn’t even have to be a totally sound program since it’s not even really for a programming class.

I was trying to get away with posting my code since it’s probably a bit of a mess haha, but I’ve hit a wall. So here is the “Board” class for my game. I’ve been tweaking it but I’m still hitting problems.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;


public class Board extends JPanel implements ActionListener {

    private Timer timer;
    private GameCharacter craft;
    private final int DELAY = 10;
    private boolean ingame;
    private boolean textDisplay = false;
    private boolean boxDisplay = false;
    private DialogueBox box;
    private ArrayList<InteractObject> candy;
    private ArrayList<String> messages;
    private int invisibleCount = 0;
    private final int ICRAFT_X = 40;
    private final int ICRAFT_Y = 60;
    private final int B_WIDTH = 400;
    private final int B_HEIGHT = 300;
    private final int BOX_X = 40; 
    private final int BOX_Y = 200;
    private final int TEXT_OFFSET_X = 90;
    private final int TEXT_OFFSET_Y = 50;
    private int msgNum = 0;
    
    
    private final int[][] pos = 
    {
        {200,150}, {10,0}, {160,35}, {250,15},
        {330,120}, {325, 210} 
    };
   
    private final String[] msg =
    {
       "Candy gotten.", "Candy obtained.", "Candy recieved.",
       "Candy from a baby.", "Sweet.", "Maybe I should lay off the candy.", " "
    };
    
    

    public Board() 
    {

        initBoard();
        System.out.println("Board has been initialized");
    }
    
    private void initBoard() 
    {
        
        box = new DialogueBox(BOX_X,BOX_Y);
        box.setVisible(false);
        
        addKeyListener(new TAdapter());
        setFocusable(true);
        ingame = true;
        
        setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));

        craft = new GameCharacter(ICRAFT_X, ICRAFT_Y);
        
        initObjects();
        
        timer = new Timer(DELAY, this);
        timer.start();        
    }
    
    public void initObjects() 
    {
        candy = new ArrayList<>();

        for (int[] p : pos) 
        {
            candy.add(new InteractObject(p[0], p[1]));
        }
        
        messages = new ArrayList<>();
      
        for(int i=0; i<msg.length; i++)
        {
            messages.add(msg[i]);
        }
        
    }


    @Override
    public void paintComponent(Graphics g) 
    {
        super.paintComponent(g);

        
        if (ingame) {

            drawObjects(g);

        } 
        else {

            drawGameOver(g);
        }

        Toolkit.getDefaultToolkit().sync();
    }
    
     private void drawObjects(Graphics g) 
     {

        for (InteractObject a : candy) {
            if (a.isVisible()) {
                g.drawImage(a.getImage(), a.getX(), a.getY(), this);
            }
        }
        
        if (craft.isVisible()) {
            g.drawImage(craft.getImage(), craft.getX(), craft.getY(),
                    this);
        }
        
        if (box.isVisible())
        {
            g.drawImage(box.getImage(), box.getX(), box.getY(), this);
            
             Font small = new Font("Microsoft Jheng Hei UI", Font.BOLD, 14);
             FontMetrics fm = getFontMetrics(small);

             g.setColor(Color.black);
             g.setFont(small);
             g.drawString(messages.get(msgNum-1), box.getX() + TEXT_OFFSET_X, box.getY() + TEXT_OFFSET_Y);
        }
        
    }
    
     private void drawGameOver(Graphics g) 
     {

        String msg = "Thanks for playing!";
        Font small = new Font("Microsoft Jheng Hei UI", Font.PLAIN, 14);
        FontMetrics fm = getFontMetrics(small);

        g.setColor(Color.black);
        g.setFont(small);
        for(int i=0; i< msg.length(); i++)
        {
        String s = ""; 
        s += msg.charAt(i);
        g.drawString(msg, (B_WIDTH - fm.stringWidth(msg)) / 2,
                B_HEIGHT / 2);
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) 
    {
    
        inGame();

        updateCandy();
        updateCraft();
        updateDialog();

        checkCollisions();

        repaint();
    }
    
    private void inGame() 
    {
        
        if (!ingame) 
        {
            System.out.println("Not in game");
            timer.stop();
        }
    }

    private void updateCraft() 
    {

        if (craft.isVisible()) 
        {
            craft.move();
        }
    }
    
     private void updateCandy() 
     {
        if (candy.isEmpty())
        {
            ingame = false;
            return;
        }
        
        for (int i = 0; i < candy.size(); i++) 
        {
            InteractObject a = candy.get(i);
            if (!a.isVisible()) 
            {
              //msgNum = i;
               candy.remove(i);
               messages.remove(i);
            }
        }
     }
     
     private void updateDialog()
     {
         box.setVisible(boxDisplay);  
         if(!craft.isEnter())
         {
            boxDisplay = false;
         }
     }
     
        
        public void checkCollisions() 
        {

        Rectangle r3 = craft.getBounds();
        

        for (InteractObject lolly : candy) 
        {
            Rectangle r2 = lolly.getBounds();

            if (r3.intersects(r2) && craft.isEnter()) 
            {                
                boxDisplay = true;            
                lolly.setVisible(false);
                msgNum = candy.indexOf(lolly);
            }
        }
        
        }

    
    private class TAdapter extends KeyAdapter 
    {

        @Override
        public void keyReleased(KeyEvent e) 
        {
            craft.keyReleased(e);
            //box.keyPressed(e);
        }

        @Override
        public void keyPressed(KeyEvent e) 
        {
            craft.keyPressed(e);
        }
    }
}

The above code, when it works, opens up a message box when an object of the InteractObject class registers a collision and an enter press with the craft object. The box remains open as long as the player only hits enter and nothing else (not ideal, but have no clue how to keep the box open only until enter is hit again).

Trouble is, most times I get an index out of bounds error when some of the objects register a collission, and no text box popup and I’m not sure why? The message ArrayList is populated at the same time the candy ArrayList is, and they’re updated at the same time. So why is it giving me errors that it can’t find the index?

Also, does anyone have any idea how to keep the message box open until enter is hit again? I feel like I need a while loop somewhere, but I have no idea where.

Why are you doing from scratch? Java has plenty of text components that support wrapping.

Here one way of doing it without reinventing anything:

Lets assume that Object for storing Dialogue trees. Lets call it DialogueTree.
Lets also assume you have JComponent for showing and navigating through them. Lets call it DialogueComponent.

~At startup

  1. Add DialogueComponent to your GUI. Hide it. Also make sure that its accessible from anywhere you need it.
    You also probably want to make it resize and its font resize with your screen resolution.

~Dialogue start

  1. Disable player input and stop game from updating (optional)
  2. Show the DialogueComponent. Parse the DialogueTree. Request focus for the DialogueComponent.

~Dialogue ends

  1. Hide DialogueComponent
  2. Enable player input and start game updating. Return focus to game.