cocos-dating-game/assets/scripts/DialogueBox.ts
openclaw_frontend_developer 41e7ca05f1 Initial commit: Cocos Creator恋爱游戏引擎
- StoryManager: 剧情管理器
- DialogueBox: 对话框组件(带打字机效果)
- CharacterView: 立绘组件
- ChoiceButton: 选项按钮
- AffectionSystem: 好感度系统
- chapter1.json: 示例剧情
2026-03-11 04:54:43 +00:00

178 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { _decorator, Component, Node, Label, Sprite, RichText, tween, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('DialogueBox')
export class DialogueBox extends Component {
@property(Label)
nameLabel: Label;
@property(RichText)
textRichText: RichText;
@property(Sprite)
nameBg: Sprite;
@property(Node)
continueIndicator: Node;
private currentText: string = '';
private currentSpeaker: string = '';
private typingInterval: number = null;
private onCompleteCallback: () => void = null;
private typingSpeed: number = 30; // 毫秒每个字符
onLoad() {
this.nameBg = this.nameLabel.node.parent.getComponent(Sprite);
}
start() {
// 默认隐藏
this.node.active = false;
}
/**
* 显示对话
*/
show(speaker: string | undefined, text: string, onComplete?: () => void) {
this.node.active = true;
this.currentText = text;
this.currentSpeaker = speaker || '';
this.onCompleteCallback = onComplete || null;
// 设置角色名
if (this.currentSpeaker) {
this.nameLabel.string = this.currentSpeaker;
this.nameBg.node.active = true;
} else {
this.nameBg.node.active = false;
}
// 隐藏继续提示
if (this.continueIndicator) {
this.continueIndicator.active = false;
}
// 开始打字机效果
this.typewriterEffect(text);
}
/**
* 打字机效果
*/
private typewriterEffect(text: string) {
// 清除之前的定时器
if (this.typingInterval) {
clearInterval(this.typingInterval);
}
let index = 0;
this.textRichText.string = '';
this.typingInterval = window.setInterval(() => {
if (index < text.length) {
// 使用 RichText 来支持富文本
this.textRichText.string = this.escapeXml(text.substring(0, index + 1));
index++;
} else {
this.finishTyping();
}
}, this.typingSpeed);
}
/**
* 完成打字(立即显示完整文本)
*/
finishTyping() {
if (this.typingInterval) {
clearInterval(this.typingInterval);
this.typingInterval = null;
}
this.textRichText.string = this.escapeXml(this.currentText);
// 显示继续提示
if (this.continueIndicator) {
this.continueIndicator.active = true;
this.playContinueIndicatorAnim();
}
// 回调
if (this.onCompleteCallback) {
this.onCompleteCallback();
}
}
/**
* 播放继续提示动画
*/
private playContinueIndicatorAnim() {
if (!this.continueIndicator) return;
// 简单的上下浮动动画
tween(this.continueIndicator)
.repeatForever(
tween()
.to(0.5, { position: new Vec3(0, 10, 0) })
.to(0.5, { position: new Vec3(0, 0, 0) })
)
.start();
}
/**
* 隐藏对话框
*/
hide() {
this.node.active = false;
if (this.typingInterval) {
clearInterval(this.typingInterval);
this.typingInterval = null;
}
}
/**
* 重置
*/
reset() {
this.nameLabel.string = '';
this.textRichText.string = '';
this.nameBg.node.active = false;
if (this.continueIndicator) {
this.continueIndicator.active = false;
}
}
/**
* 转义 XML 特殊字符(用于 RichText
*/
private escapeXml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
/**
* 设置打字速度
*/
setTypingSpeed(speed: number) {
this.typingSpeed = speed;
}
/**
* 获取当前说话者
*/
getCurrentSpeaker(): string {
return this.currentSpeaker;
}
/**
* 获取当前文本
*/
getCurrentText(): string {
return this.currentText;
}
}