Hey guys, newcomer to PlayCanvas here! I’m trying to code out a branching dialogue option (yes / no) but it gives me an error “cannot set properties of null (setting 'enabled)”. Any help would be appreciated!
var Dialogue = pc.createScript('dialogue');
// define story content
var story = [
{ m: "Welcome to Manis Town!" },
{ m: "The land is sweet and the people are friendly." },
{ question: "Ready to explore? ", answers: [
{ m: "yes", next: "take_yes" },
{ m: "no", next: "take_no" },
] },
{ label: "take_yes", m: "Good to hear! Now go look around, explorer!", next: "take_end" },
{ label: "take_no", m: "That's unfortunate. You're kinda stuck here though, so go look around!", next: "take_end" },
{ label: "take_end", m: "See ya!" }
];
var current_line = 0;
var current_step = story[0];
var answerYes;
var answerNo;
// initialize code called once per entity
Dialogue.prototype.initialize = function() {
this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
this.app.keyboard.on(pc.EVENT_KEYUP, this.onKeyUp, this);
this.text = this.app.root.findByName("DialogueBox");
this.textcontent = this.entity.findByName("Textcontent");
this.answerYes = this.entity.findByPath('UI/DialogueBox/AnswerYes');
this.answerNo = this.entity.findByPath('UI/DialogueBox/AnswerNo');
}
// update code called every frame
Dialogue.prototype.update = function(dt) {
this.talkvariable = this.app.root.findByName('MayorLulu').script.mayorLulu.returnState();
// console.log('talkvariable is set to '+talkvariable);
if (talkvariable){
this.text.enabled = true;
// while the dialogue isn't finished, step through the dialogue
while (current_line < story.length){
// track the step where we're at in the story
var current_step = story[current_line];
// if the player presses enter advance in the story
if (this.app.keyboard.wasPressed(pc.KEY_ENTER)){
// console.log(current_step);
// execute if the step contains a message (m)
if (undefined !== current_step.m){
this.textcontent.element.text = current_step.m;
console.log(answerYes);
console.log(answerNo);
this.answerYes.enabled = false;
this.answerNo.enabled = false;
if (undefined !== current_step.next) {
current_line = story.map(e => e.label).indexOf(current_step.next);
} else {
current_line = current_line + 1;
}
}
// execute if the step contains a question (q)
else if (undefined !== current_step.question){
this.textcontent.element.text = current_step.question;
console.log('Were displaying a question here');
this.answerYes.enabled = true;
this.answerNo.enabled = true;
}
}
// register yes answer if a question is displayed
else if ((this.app.keyboard.wasPressed(pc.KEY_Y)) && (undefined !== current_step.question)){
console.log('key press was registered!');
current_line = story.map(e => e.label).indexOf('take_yes');
}
// register no answer if a question is displayed
else if ((this.app.keyboard.wasPressed(pc.KEY_N)) && (undefined !== current_step.question)){
console.log('key press was registered!');
current_line = story.map(e => e.label).indexOf('take_no');
}
break;
}
}
};
// swap method called for script hot-reloading
// inherit your script state here
// Dialogue.prototype.swap = function(old) { };
// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/
In your initialize() function, make sure the findByName() and findByPath() calls are actually finding the intended entities. One or more of them are not being found and are ending up as null values which causes the error in your screenshot.
A quick and dirty way to see which variables are null is to console log each of them at the bottom of the initialize() function.
Thanks for the suggestion! However, I’ve tried multiple methods to find the entity (using different 'FindBy…" methods) and still no luck. The console.log keeps returning “undefined” This is the hierarchy in my editor:
At the top of your Dialogue script, just below the pc.createScript line, add an attribute for each of the four entities. For example, textcontent would be done like so:
Repeat this for the others. Save the changes and then go back to the editor and click the Parse button on the Dialogue script in the DialogueBox’s script component. The script will then show your 4 attributes you just added. From there, it’ll be a simple matter of linking each entity attribute with the correct entity from your scene hierarchy.
Here’s my code after adding what you suggested (in case I wrote it wrongly):
var Dialogue = pc.createScript('dialogue');
Dialogue.attributes.add('text', { type: 'entity' });
Dialogue.attributes.add('textcontent', { type: 'entity' });
Dialogue.attributes.add('answerYes', { type: 'entity' });
Dialogue.attributes.add('answerNo', { type: 'entity' });
// define story content
var story = [
{ m: "Welcome to Manis Town!" },
{ m: "The land is sweet and the people are friendly." },
{ question: "Ready to explore? ", answers: [
{ m: "yes", next: "take_yes" },
{ m: "no", next: "take_no" },
] },
{ label: "take_yes", m: "Good to hear! Now go look around, explorer!", next: "take_end" },
{ label: "take_no", m: "That's unfortunate. You're kinda stuck here though, so go look around!", next: "take_end" },
{ label: "take_end", m: "See ya!" }
];
var current_line = 0;
var current_step = story[0];
var answerYes;
var answerNo;
// initialize code called once per entity
Dialogue.prototype.initialize = function() {
this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
this.app.keyboard.on(pc.EVENT_KEYUP, this.onKeyUp, this);
this.text = this.app.root.findByName("DialogueBox");
this.textcontent = this.entity.findByName("Textcontent");
this.answerYes = this.entity.findByPath('UI/DialogueBox/AnswerYes');
this.answerNo = this.entity.findByPath('UI/DialogueBox/AnswerNo');
}
// update code called every frame
Dialogue.prototype.update = function(dt) {
this.talkvariable = this.app.root.findByName('MayorLulu').script.mayorLulu.returnState();
// console.log('talkvariable is set to '+talkvariable);
if (talkvariable){
this.text.enabled = true;
// while the dialogue isn't finished, step through the dialogue
while (current_line < story.length){
// track the step where we're at in the story
var current_step = story[current_line];
// if the player presses enter advance in the story
if (this.app.keyboard.wasPressed(pc.KEY_ENTER)){
// console.log(current_step);
// execute if the step contains a message (m)
if (undefined !== current_step.m){
this.textcontent.element.text = current_step.m;
console.log(answerYes);
console.log(answerNo);
this.answerYes.enabled = false;
this.answerNo.enabled = false;
if (undefined !== current_step.next) {
current_line = story.map(e => e.label).indexOf(current_step.next);
} else {
current_line = current_line + 1;
}
}
// execute if the step contains a question (q)
else if (undefined !== current_step.question){
this.textcontent.element.text = current_step.question;
console.log('Were displaying a question here');
this.answerYes.enabled = true;
this.answerNo.enabled = true;
}
}
// register yes answer if a question is displayed
else if ((this.app.keyboard.wasPressed(pc.KEY_Y)) && (undefined !== current_step.question)){
console.log('key press was registered!');
current_line = story.map(e => e.label).indexOf('take_yes');
}
// register no answer if a question is displayed
else if ((this.app.keyboard.wasPressed(pc.KEY_N)) && (undefined !== current_step.question)){
console.log('key press was registered!');
current_line = story.map(e => e.label).indexOf('take_no');
}
break;
}
}
};
// swap method called for script hot-reloading
// inherit your script state here
// Dialogue.prototype.swap = function(old) { };
// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/
Here is the script component under DialogueBox in the Editor:
Oh it works now! I still have errors on other areas but I can work on that. Thank you very much!
One small question before closing the topic, I’m trying to execute the dialogue using buttons instead of keyboard inputs. I’m planning to replace some parts of the script with this method - do you think enabling / disabling the text entity with button inputs would work or would it be impractical?
Buttons or keyboard inputs, or both would work fine, not impractical at all. Having multiple options are better for the player’s experience too, they don’t have to guess about the controls as much.
My little bit of advice would be to separate the dialogue progression logic from the input event listeners. For example, make a _progressDialogue() function and then you can call it from a variety of input listeners, buttons, keyboard keys, etc.