NPC交互设计(对话)
全部demo源自于here
所有设计依赖加载插件rexuiplugin
- Install rex plugins from npm
npm i phaser3-rex-plugins
相关封装对象有,BBCode,Customshapes,
1.Dialog类型,实现全屏点击出现对话框,点击choice后,在点击位置留下选中choice
参考代码:

class Demo extends Phaser.Scene {
constructor() {
super({
key: 'examples'
})
}
preload() {
this.load.scenePlugin({
key: 'rexuiplugin',
url: '../../rexuiplugin.min.js',
sceneKey: 'rexUI'
});
}
create() {
this.print = this.add.text(0, 580, 'Click to pop-up dialog');
var scene = this,
dialog = undefined;
this.input.on('pointerdown', function (pointer) {
var x = pointer.x,
y = pointer.y;
if (dialog === undefined) {
// 没有对话文本则新建
dialog = createDialog(this, x, y, function (color) {
scene.add.circle(x, y, 20, color);
scene.print.text = 'Add object at (' + x + ',' + y + ')';
// 渐变消失
dialog.scaleDownDestroy(100);
dialog = undefined;
});
scene.print.text = 'Click (' + x + ',' + y + ')';
} else if (!dialog.isInTouching(pointer)) {
// 有则销毁已有
dialog.scaleDownDestroy(100);
dialog = undefined;
}
}, this);
}
update() {}
}
var createDialog = function (scene, x, y, onClick) {
var dialog = scene.rexUI.add.dialog({
x: x,
y: y,
// 背景
background: scene.rexUI.add.roundRectangle(0, 0, 100, 100, 20, 0xf57f17),
// 对话文本
title: scene.rexUI.add.label({
background: scene.rexUI.add.roundRectangle(0, 0, 100, 40, 20, 0xbc5100),
text: scene.add.text(0, 0, 'Pick a color', {
fontSize: '20px'
}),
space: {
left: 15,
right: 15,
top: 10,
bottom: 10
}
}),
actions: [
scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0xe91e63),
scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0x673ab7),
scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0x2196f3),
scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0x00bcd4),
scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0x4caf50),
scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0xcddc39),
],
actionsAlign: 'left',
space: {
title: 10,
action: 5,
left: 10,
right: 10,
top: 10,
bottom: 10,
}
})
.layout()
.pushIntoBounds()
//.drawBounds(this.add.graphics(), 0xff0000)
.popUp(500);
dialog
.on('button.click', function (button, groupName, index) {
onClick(button.fillColor);
})
.on('button.over', function (button, groupName, index) {
button.setStrokeStyle(2, 0xffffff);
})
.on('button.out', function (button, groupName, index) {
button.setStrokeStyle();
});
return dialog;
}
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Demo
};
var game = new Phaser.Game(config);
2.Dialog类型,实现选项对话。
参考代码:

const GetValue = Phaser.Utils.Objects.GetValue; const data = { title: 'Question 1', content: '1 + 1 + 1 + 1 = ', choices: [3, 4, 5], actions: ['OK', 'Cancel'] }; class Demo extends Phaser.Scene { constructor() { super({ key: 'examples' }) } preload() { this.load.scenePlugin({ key: 'rexuiplugin', url: '../../rexuiplugin.min.js', sceneKey: 'rexUI' }); } create() { this.print = this.add.text(0, 0, ''); var dialog = createDialog(this) .layout() .on('button.click', function (button, groupName, index, pointer, event) { this.print.text += index + ': ' + button.text + '\n'; }, this) .on('button.over', function (button, groupName, index, pointer, event) { button.getElement('background').setStrokeStyle(1, 0xffffff); }) .on('button.out', function (button, groupName, index, pointer, event) { button.getElement('background').setStrokeStyle(); }) this.input.once('pointerdown', function () { setDialog(dialog, data).layout(); }); this.add.text(0, 580, 'Click.once to reset buttons'); } update() { } } var createDialog = function (scene) { return scene.rexUI.add.dialog({ x: 400, y: 300, width: 360, background: scene.rexUI.add.roundRectangle(0, 0, 100, 100, 20, 0x3e2723), title: scene.rexUI.add.label({ background: scene.rexUI.add.roundRectangle(0, 0, 100, 40, 20, 0x1b0000), text: scene.add.text(0, 0, ' ', { fontSize: '24px' }), space: { left: 15, right: 15, top: 10, bottom: 10 } }), content: scene.add.text(0, 0, ' ', { fontSize: '24px' }), choices: [ createLabel(scene, ' ', 0x6a4f4b), createLabel(scene, ' ', 0x6a4f4b), createLabel(scene, ' ', 0x6a4f4b), createLabel(scene, ' ', 0x6a4f4b), createLabel(scene, ' ', 0x6a4f4b) ], // Support 5 choices actions: [ createLabel(scene, '', 0x1b0000), createLabel(scene, '', 0x1b0000), createLabel(scene, '', 0x1b0000) ], // Support 3 actions space: { title: 25, content: 25, choices: 20, choice: 15, action: 15, left: 25, right: 25, top: 25, bottom: 25, }, expand: { content: false, // Content is a pure text object } }); } var createLabel = function (scene, text, backgroundColor) { return scene.rexUI.add.label({ background: scene.rexUI.add.roundRectangle(0, 0, 100, 40, 20, backgroundColor), text: scene.add.text(0, 0, text, { fontSize: '24px' }), space: { left: 10, right: 10, top: 10, bottom: 10 } }); } var setDialog = function (dialog, config) { // Set title dialog.getElement('title').text = GetValue(config, 'title', ' '); // Set content dialog.getElement('content').text = GetValue(config, 'content', ' '); // Set choices var choiceTextArray = GetValue(config, 'choices', []), choiceText; var choices = dialog.getElement('choices'); for (var i = 0, cnt = choices.length; i < cnt; i++) { choiceText = choiceTextArray[i]; if (choiceText != null) { dialog.showChoice(i); choices[i].text = choiceText; } else { dialog.hideChoice(i); } } // Set actions var actionTextArray = GetValue(config, 'actions', []), actionText; var actions = dialog.getElement('actions'); for (var i = 0, cnt = actions.length; i < cnt; i++) { actionText = actionTextArray[i]; if (actionText != null) { dialog.showAction(i); actions[i].text = actionText; } else { dialog.hideAction(i); } } return dialog; } var config = { type: Phaser.AUTO, parent: 'phaser-example', width: 800, height: 600, scene: Demo }; var game = new Phaser.Game(config);
3.Dialog类型,实现yes/no双选择对话。
参考代码:

class Demo extends Phaser.Scene { constructor() { super({ key: 'examples' }) } preload() { this.load.scenePlugin({ key: 'rexuiplugin', url: '../rexuiplugin.min.js', sceneKey: 'rexUI' }); this.load.image('classroom', '../../home.jpg'); } create() { var print = this.add.text(0, 0, '').setDepth(1); this.add.image(400, 300, 'classroom') .setInteractive() .on('pointerup', function () { print.text += 'Click bottom image\n'; }) this.rexUI.modalPromise( // Game object CreateDialog(this).setPosition(400, 300), // Config { manaulClose: true, duration: { in: 500, out: 500 } } ) .then(function (result) { print.text += `Click button ${result.index}: ${result.text}\n`; }) } update() { } } var CreateDialog = function (scene) { var dialog = scene.rexUI.add.dialog({ background: scene.rexUI.add.roundRectangle(0, 0, 100, 100, 20, 0x1565c0), title: scene.rexUI.add.label({ background: scene.rexUI.add.roundRectangle(0, 0, 100, 40, 20, 0x003c8f), text: scene.add.text(0, 0, 'Title', { fontSize: '24px' }), space: { left: 15, right: 15, top: 10, bottom: 10 } }), content: scene.add.text(0, 0, 'Do you want to build a snow man?', { fontSize: '24px' }), actions: [ CreateLabel(scene, 'Yes'), CreateLabel(scene, 'No') ], space: { title: 25, content: 25, action: 15, left: 20, right: 20, top: 20, bottom: 20, }, align: { actions: 'right', // 'center'|'left'|'right' }, expand: { content: false, // Content is a pure text object } }) .layout(); dialog .on('button.click', function (button, groupName, index, pointer, event) { dialog.emit('modal.requestClose', { index: index, text: button.text }); }) .on('button.over', function (button, groupName, index, pointer, event) { button.getElement('background').setStrokeStyle(1, 0xffffff); }) .on('button.out', function (button, groupName, index, pointer, event) { button.getElement('background').setStrokeStyle(); }); return dialog; } var CreateLabel = function (scene, text) { return scene.rexUI.add.label({ // width: 40, // height: 40, background: scene.rexUI.add.roundRectangle(0, 0, 0, 0, 20, 0x5e92f3), text: scene.add.text(0, 0, text, { fontSize: '24px' }), space: { left: 10, right: 10, top: 10, bottom: 10 } }); } var config = { type: Phaser.AUTO, parent: 'phaser-example', width: 800, height: 600, scene: Demo }; var game = new Phaser.Game(config);
4.textBox类型,剧情说明对话(固定上边界位置)。
参考代码:

const COLOR_PRIMARY = 0x4e342e; const COLOR_LIGHT = 0x7b5e57; const COLOR_DARK = 0x260e04; var content = `Phaser is a fast, free, and fun open source HTML5 game framework that offers WebGL and Canvas rendering across desktop and mobile web browsers. Games can be compiled to iOS, Android and native apps by using 3rd party tools. You can use JavaScript or TypeScript for development.`; class Demo extends Phaser.Scene { constructor() { super({ key: "examples" }); } preload() { this.load.scenePlugin({ key: "rexuiplugin", url: "./rexuiplugin.min.js", sceneKey: "rexUI" }); this.load.image( "nextPage", "../../resource/apple_20220918.png" ); } create() { createTextBox(this, 100, 100, { wrapWidth: 500 }).start(content, 50); createTextBox(this, 100, 400, { wrapWidth: 500, fixedWidth: 500, fixedHeight: 65 }).start(content, 50); } update() {} } const GetValue = Phaser.Utils.Objects.GetValue; var createTextBox = function (scene, x, y, config) { var wrapWidth = GetValue(config, "wrapWidth", 0); var fixedWidth = GetValue(config, "fixedWidth", 0); var fixedHeight = GetValue(config, "fixedHeight", 0); var textBox = scene.rexUI.add .textBox({ x: x, y: y, background: scene.rexUI.add .roundRectangle(0, 0, 2, 2, 20, COLOR_PRIMARY) .setStrokeStyle(2, COLOR_LIGHT), icon: scene.rexUI.add.roundRectangle(0, 0, 2, 2, 20, COLOR_DARK), // text: getBuiltInText(scene, wrapWidth, fixedWidth, fixedHeight), text: getBBcodeText(scene, wrapWidth, fixedWidth, fixedHeight), action: scene.add .image(0, 0, "nextPage") .setTint(COLOR_LIGHT) .setVisible(false), space: { left: 20, right: 20, top: 20, bottom: 20, icon: 10, text: 10 } }) .setOrigin(0) .layout(); textBox .setInteractive() .on( "pointerdown", function () { var icon = this.getElement("action").setVisible(false); this.resetChildVisibleState(icon); if (this.isTyping) { this.stop(true); } else { this.typeNextPage(); } }, textBox ) .on( "pageend", function () { if (this.isLastPage) { return; } var icon = this.getElement("action").setVisible(true); this.resetChildVisibleState(icon); icon.y -= 30; var tween = scene.tweens.add({ targets: icon, y: "+=30", // '+=100' ease: "Bounce", // 'Cubic', 'Elastic', 'Bounce', 'Back' duration: 500, repeat: 0, // -1: infinity yoyo: false }); }, textBox ); //.on('type', function () { //}) return textBox; }; var getBuiltInText = function (scene, wrapWidth, fixedWidth, fixedHeight) { return scene.add .text(0, 0, "", { fontSize: "20px", wordWrap: { width: wrapWidth }, maxLines: 3 }) .setFixedSize(fixedWidth, fixedHeight); }; var getBBcodeText = function (scene, wrapWidth, fixedWidth, fixedHeight) { return scene.rexUI.add.BBCodeText(0, 0, "", { fixedWidth: fixedWidth, fixedHeight: fixedHeight, fontSize: "20px", wrap: { mode: "word", width: wrapWidth }, maxLines: 3 }); }; var config = { type: Phaser.AUTO, parent: "phaser-example", width: 800, height: 600, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }, scene: Demo }; var game = new Phaser.Game(config);
5.textBox类型,剧情说明对话(固定下边界位置)
参考代码:

const COLOR_PRIMARY = 0x4e342e; const COLOR_LIGHT = 0x7b5e57; const COLOR_DARK = 0x260e04; var content = `Phaser is a fast, free, and fun open source HTML5 game framework that offers WebGL and Canvas rendering across desktop and mobile web browsers. Games can be compiled to iOS, Android and native apps by using 3rd party tools. You can use JavaScript or TypeScript for development.`; class Demo extends Phaser.Scene { constructor() { super({ key: 'examples' }) } preload() { this.load.scenePlugin({ key: 'rexuiplugin', url: './rexuiplugin.min.js', sceneKey: 'rexUI' }); this.load.image('nextPage', '../../resource/apple_20220918.png'); } create() { createTextBox(this, 100, 200, { wrapWidth: 500, }) .start(content, 50); createTextBox(this, 100, 500, { wrapWidth: 500, fixedWidth: 500, fixedHeight: 65, }) .start(content, 50); } update() {} } const GetValue = Phaser.Utils.Objects.GetValue; var createTextBox = function (scene, x, y, config) { var wrapWidth = GetValue(config, 'wrapWidth', 0); var fixedWidth = GetValue(config, 'fixedWidth', 0); var fixedHeight = GetValue(config, 'fixedHeight', 0); var textBox = scene.rexUI.add.textBox({ x: x, y: y, background: CreateSpeechBubbleShape(scene) .setFillStyle(COLOR_PRIMARY, 1) .setStrokeStyle(2, COLOR_LIGHT, 1), icon: scene.rexUI.add.roundRectangle(0, 0, 2, 2, 20, COLOR_DARK), // text: getBuiltInText(scene, wrapWidth, fixedWidth, fixedHeight), text: getBBcodeText(scene, wrapWidth, fixedWidth, fixedHeight), action: scene.add.image(0, 0, 'nextPage').setTint(COLOR_LIGHT).setVisible(false), space: { left: 10, right: 10, top: 10, bottom: 25, icon: 10, text: 10, } }) .setOrigin(0, 1) .layout(); textBox .setInteractive() .on('pointerdown', function () { var icon = this.getElement('action').setVisible(false); this.resetChildVisibleState(icon); if (this.isTyping) { this.stop(true); } else { this.typeNextPage(); } }, textBox) .on('pageend', function () { if (this.isLastPage) { return; } var icon = this.getElement('action').setVisible(true); this.resetChildVisibleState(icon); icon.y -= 30; var tween = scene.tweens.add({ targets: icon, y: '+=30', // '+=100' ease: 'Bounce', // 'Cubic', 'Elastic', 'Bounce', 'Back' duration: 500, repeat: 0, // -1: infinity yoyo: false }); }, textBox) //.on('type', function () { //}) return textBox; } var getBuiltInText = function (scene, wrapWidth, fixedWidth, fixedHeight) { return scene.add.text(0, 0, '', { fontSize: '20px', wordWrap: { width: wrapWidth }, maxLines: 3 }) .setFixedSize(fixedWidth, fixedHeight); } var getBBcodeText = function (scene, wrapWidth, fixedWidth, fixedHeight) { return scene.rexUI.add.BBCodeText(0, 0, '', { fixedWidth: fixedWidth, fixedHeight: fixedHeight, fontSize: '20px', wrap: { mode: 'word', width: wrapWidth }, maxLines: 3 }) } var CreateSpeechBubbleShape = function (scene, fillColor, strokeColor) { return scene.rexUI.add.customShapes({ create: { lines: 1 }, update: function () { var radius = 20; var indent = 15; var left = 0, right = this.width, top = 0, bottom = this.height, boxBottom = bottom - indent; this.getShapes()[0] .lineStyle(this.lineWidth, this.strokeColor, this.strokeAlpha) .fillStyle(this.fillColor, this.fillAlpha) // top line, right arc .startAt(left + radius, top).lineTo(right - radius, top).arc(right - radius, top + radius, radius, 270, 360) // right line, bottom arc .lineTo(right, boxBottom - radius).arc(right - radius, boxBottom - radius, radius, 0, 90) // bottom indent .lineTo(left + 60, boxBottom).lineTo(left + 50, bottom).lineTo(left + 40, boxBottom) // bottom line, left arc .lineTo(left + radius, boxBottom).arc(left + radius, boxBottom - radius, radius, 90, 180) // left line, top arc .lineTo(left, top + radius).arc(left + radius, top + radius, radius, 180, 270) .close(); } }) } var config = { type: Phaser.AUTO, parent: 'phaser-example', width: 800, height: 600, scene: Demo }; var game = new Phaser.Game(config);
6.textBox类型,分页设计对话
参考代码:

const COLOR_PRIMARY = 0x4e342e; const COLOR_LIGHT = 0x7b5e57; const COLOR_DARK = 0x260e04; class Demo extends Phaser.Scene { constructor() { super({ key: 'examples' }) } preload() { this.load.scenePlugin({ key: 'rexuiplugin', url: './rexuiplugin.min.js', sceneKey: 'rexUI' }); this.load.image('nextPage', '../../resource/apple_20220918.png'); } create() { var lines = []; for (var i = 0; i < 3; i++) { lines.push(`Page ${i}\n`) for (var l = 0; l < 6; l++) { lines.push(`- Line ${l}\n`) } if (i < 2) { lines.push('\f\n'); // Page-break } } var content = lines.join(''); CreateTextBox(this, 400, 100, { wrapWidth: 500, fixedWidth: 500, fixedHeight: 65, }).start(content, 50); } update() { } } const GetValue = Phaser.Utils.Objects.GetValue; var CreateTextBox = function (scene, x, y, config) { var wrapWidth = GetValue(config, 'wrapWidth', 0); var fixedWidth = GetValue(config, 'fixedWidth', 0); var fixedHeight = GetValue(config, 'fixedHeight', 0); var textBox = scene.rexUI.add.textBox({ x: x, y: y, background: scene.rexUI.add.roundRectangle(0, 0, 2, 2, 20, COLOR_PRIMARY) .setStrokeStyle(2, COLOR_LIGHT), icon: scene.rexUI.add.roundRectangle(0, 0, 2, 2, 20, COLOR_DARK), text: GetBBcodeText(scene, wrapWidth, fixedWidth, fixedHeight), action: scene.add.image(0, 0, 'nextPage').setTint(COLOR_LIGHT).setVisible(false), space: { left: 20, right: 20, top: 20, bottom: 20, icon: 10, text: 10, }, // page: { // pageBreak: '\f\n' // } }) .layout(); textBox .setInteractive() .on('pointerdown', function () { var icon = this.getElement('action').setVisible(false); this.resetChildVisibleState(icon); if (this.isTyping) { this.stop(true); } else if (!this.isLastPage) { this.typeNextPage(); } else { // Next actions } }, textBox) .on('pageend', function () { if (this.isLastPage) { return; } var icon = this.getElement('action').setVisible(true); this.resetChildVisibleState(icon); icon.y -= 30; var tween = scene.tweens.add({ targets: icon, y: '+=30', // '+=100' ease: 'Bounce', // 'Cubic', 'Elastic', 'Bounce', 'Back' duration: 500, repeat: 0, // -1: infinity yoyo: false }); }, textBox) .on('complete', function () { console.log('all pages typing complete') }) //.on('type', function () { //}) return textBox; } var GetBBcodeText = function (scene, wrapWidth, fixedWidth, fixedHeight) { return scene.rexUI.add.BBCodeText(0, 0, '', { fixedWidth: fixedWidth, fixedHeight: fixedHeight, fontSize: '20px', wrap: { mode: 'word', width: wrapWidth }, maxLines: 3 }) } var config = { type: Phaser.AUTO, parent: 'phaser-example', width: 800, height: 600, scene: Demo }; var game = new Phaser.Game(config);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!