Implenting Randomized Dialogues from Platformer to Adventure Game
Categories: AdPlatLearn how Randomized Dialogues are applied to NPCS
- Lesson: Implenting a Randomized Dialogue System with good UI for adventure Game
Lesson: Implenting a Randomized Dialogue System with good UI for adventure Game
Files Involved:
- DialogueSystem.js (added)
- Current Adventure Game (modified)
Part 1: Dialogue System Module Setup
The system should:
- Hold multiple possible lines.
- Randomize which line gets shown.
- Use HTML + CSS for a custom visual popup
Below is the complete code of DialogueSystem.js
. This is a module you have to import in your main game engine in order to implent on your adventure game.
MAKE SURE IT IS IN THE SAME LEVEL AS YOUR OTHER GAME FILES, NOT IN THE GAME ENGINE FOLDER
THIS BELOW IS A 500 LINE TEXT WALL, KEEP THAT IN MIND THIS IS THE ENTIRE FILE
// DialogueSystem.js - Fixed version that addresses both issues
// 1. Unique instances for each NPC to prevent button conflicts
// 2. Works with Eye of Ender collection
class DialogueSystem {
constructor(options = {}) {
// Default dialogue arrays
this.dialogues = options.dialogues || [
"You've come far, traveler. The skies whisper your name.",
"The End holds secrets only the brave dare uncover.",
"Retrieve the elytra and embrace your destiny!"
];
// Create a unique ID for this dialogue system
this.id = options.id || "dialogue_" + Math.random().toString(36).substr(2, 9);
// Track the last shown dialogue to avoid repetition
this.lastShownIndex = -1;
// Create necessary DOM elements
this.dialogueBox = null;
this.dialogueText = null;
this.closeBtn = null;
// Sound effect option
this.enableSound = options.enableSound !== undefined ? options.enableSound : false;
this.soundUrl = options.soundUrl || "./sounds/dialogue.mp3";
this.sound = this.enableSound ? new Audio(this.soundUrl) : null;
// Create the dialogue box
this.createDialogueBox();
// Keep track of whether the dialogue is currently open
this.isOpen = false;
}
createDialogueBox() {
// Create the main dialogue container with unique ID
this.dialogueBox = document.createElement("div");
this.dialogueBox.id = "custom-dialogue-box-" + this.id;
// Set styles for the dialogue box
Object.assign(this.dialogueBox.style, {
position: "fixed",
bottom: "100px",
left: "50%",
transform: "translateX(-50%)",
padding: "20px",
maxWidth: "80%",
background: "rgba(0, 0, 0, 0.85)",
color: "#00FFFF",
fontFamily: "'Press Start 2P', cursive, monospace",
fontSize: "14px",
textAlign: "center",
border: "2px solid #4a86e8",
borderRadius: "12px",
zIndex: "9999",
boxShadow: "0 0 20px rgba(0, 255, 255, 0.7)",
display: "none"
});
// Create the avatar container for character portraits
const avatarContainer = document.createElement("div");
avatarContainer.id = "dialogue-avatar-" + this.id;
Object.assign(avatarContainer.style, {
width: "50px",
height: "50px",
marginRight: "15px",
backgroundSize: "contain",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
display: "none" // Hidden by default
});
// Create the header with character name
const speakerName = document.createElement("div");
speakerName.id = "dialogue-speaker-" + this.id;
Object.assign(speakerName.style, {
fontWeight: "bold",
color: "#4a86e8",
marginBottom: "10px",
fontSize: "16px"
});
// Create the text content area
this.dialogueText = document.createElement("div");
this.dialogueText.id = "dialogue-text-" + this.id;
Object.assign(this.dialogueText.style, {
marginBottom: "15px",
lineHeight: "1.5"
});
// Create close button
this.closeBtn = document.createElement("button");
this.closeBtn.innerText = "Close";
Object.assign(this.closeBtn.style, {
marginTop: "15px",
padding: "10px 20px",
background: "#4a86e8",
color: "#fff",
border: "none",
borderRadius: "5px",
cursor: "pointer",
fontFamily: "'Press Start 2P', cursive, monospace",
fontSize: "12px"
});
// Add click handler
this.closeBtn.onclick = () => {
this.closeDialogue();
};
// Create content container to hold text and avatar side by side
const contentContainer = document.createElement("div");
contentContainer.style.display = "flex";
contentContainer.style.alignItems = "flex-start";
contentContainer.style.marginBottom = "10px";
contentContainer.appendChild(avatarContainer);
// Create text container for speaker + dialogue
const textContainer = document.createElement("div");
textContainer.style.flexGrow = "1";
textContainer.appendChild(speakerName);
textContainer.appendChild(this.dialogueText);
contentContainer.appendChild(textContainer);
// Assemble the dialogue box
this.dialogueBox.appendChild(contentContainer);
this.dialogueBox.appendChild(this.closeBtn);
// Add to the document
document.body.appendChild(this.dialogueBox);
// Also listen for Escape key to close dialogue
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && this.isOpen) {
this.closeDialogue();
}
});
}
// Show a specific dialogue message
showDialogue(message, speaker = "", avatarSrc = null) {
// Set the content (with unique element IDs)
const speakerElement = document.getElementById("dialogue-speaker-" + this.id);
if (speakerElement) {
speakerElement.textContent = speaker;
speakerElement.style.display = speaker ? "block" : "none";
}
// Set avatar if provided
const avatarElement = document.getElementById("dialogue-avatar-" + this.id);
if (avatarElement) {
if (avatarSrc) {
avatarElement.style.backgroundImage = `url('${avatarSrc}')`;
avatarElement.style.display = "block";
} else {
avatarElement.style.display = "none";
}
}
// Set the dialogue text directly
this.dialogueText.textContent = message;
// Show the dialogue box
this.dialogueBox.style.display = "block";
// Play sound effect if enabled
if (this.sound) {
this.sound.currentTime = 0;
this.sound.play().catch(e => console.log("Sound play error:", e));
}
this.isOpen = true;
// Return the dialogue box element for custom button addition
return this.dialogueBox;
}
// Show a random dialogue from the dialogues array
showRandomDialogue(speaker = "", avatarSrc = null) {
if (this.dialogues.length === 0) return;
// Pick a random index that's different from the last one
let randomIndex;
if (this.dialogues.length > 1) {
do {
randomIndex = Math.floor(Math.random() * this.dialogues.length);
} while (randomIndex === this.lastShownIndex);
} else {
randomIndex = 0; // Only one dialogue available
}
// Store the current index to avoid repetition next time
this.lastShownIndex = randomIndex;
// Show the dialogue
const randomDialogue = this.dialogues[randomIndex];
return this.showDialogue(randomDialogue, speaker, avatarSrc);
}
// Close the dialogue box
closeDialogue() {
if (!this.isOpen) return;
// Hide the dialogue box
this.dialogueBox.style.display = "none";
this.isOpen = false;
// Remove any custom buttons
const buttonContainers = this.dialogueBox.querySelectorAll('div[style*="display: flex"]');
buttonContainers.forEach(container => {
// Skip the main content container
if (container.contains(document.getElementById("dialogue-avatar-" + this.id))) {
return;
}
container.remove();
});
}
// Check if dialogue is currently open
isDialogueOpen() {
return this.isOpen;
}
// Add buttons to the dialogue
addButtons(buttons) {
if (!this.isOpen || !buttons || !Array.isArray(buttons) || buttons.length === 0) return;
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'space-between';
buttonContainer.style.marginTop = '10px';
// Add each button
buttons.forEach(button => {
if (!button || !button.text) return;
const btn = document.createElement('button');
btn.textContent = button.text;
btn.style.padding = '8px 15px';
btn.style.background = button.primary ? '#4a86e8' : '#666';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '5px';
btn.style.cursor = 'pointer';
btn.style.marginRight = '10px';
// Add click handler
btn.onclick = () => {
if (button.action && typeof button.action === 'function') {
button.action();
}
};
buttonContainer.appendChild(btn);
});
// Insert before the close button
if (buttonContainer.children.length > 0) {
this.dialogueBox.insertBefore(buttonContainer, this.closeBtn);
}
}
}
export default DialogueSystem;
<IPython.core.display.Javascript object>
In order for the Randomized Dialogue to be visible and implented on your adventure game you must need to
- Replace the current reaction and interact method of your current npc(s)
- Import this file called
DialogueSystem.js
into your adventure game - Double Check to make sure that
DialogueSystem.js
methods are properly referenced when implenting certain methods into the npc keys pair values
Example of Implementation (Endship is the NPC)
const sprite_data_endship = {
id: 'Endship',
greeting: sprite_greet_endship,
src: sprite_src_endship,
SCALE_FACTOR: 5,
ANIMATION_RATE: 1000000,
pixels: {height: 982, width: 900},
INIT_POSITION: { x: (width / 2), y: (height / 2) },
orientation: {rows: 1, columns: 1 },
down: {row: 0, start: 0, columns: 1 },
hitbox: { widthPercentage: 0.1, heightPercentage: 0.2 },
zIndex: 10, // Same z-index as player
dialogues: [
"The end ship looms before you...",
"The end ship seems to beckon you to loot the treasure within...",
"funny purple spaceship heheheheheh",
// Add more later?
],
reaction: function() {
dialogueSystem.showRandomDialogue(); // Using Dialogue system instead of alert
},
interact: function() {
let quiz = new Quiz();
quiz.initialize();
quiz.openPanel(sprite_data_endship);
}
};
<IPython.core.display.Javascript object>
Hack 1 : Create Your Own Dialogue Set
Create a custom DialogueSystem instance with at least 4 unique lines. Replace the default quotes with your own themed around your game (sci-fi, fantasy, mystery, etc.).
Hack 2: Apply to Multiple NPCs
Modify at least 2 different NPCs in your game to each have their own unique DialogueSystem instance and call showRandomDialogue() when the player interacts with them.
Make sure the dialogue themes match the personality of the character!
Homework:
-
At least two functioning NPCs with randomized dialogues.
-
Your game UI showing the custom dialogue box.
-
Brief reflection: What was the biggest challenge in integrating this system?