- StoryManager: 剧情管理器 - DialogueBox: 对话框组件(带打字机效果) - CharacterView: 立绘组件 - ChoiceButton: 选项按钮 - AffectionSystem: 好感度系统 - chapter1.json: 示例剧情
281 lines
7.1 KiB
TypeScript
281 lines
7.1 KiB
TypeScript
import { _decorator, Component, Node, resources, JsonAsset, Sprite, SpriteFrame, Label, Button, director } from 'cc';
|
|
import { DialogueBox } from './DialogueBox';
|
|
import { CharacterView } from './CharacterView';
|
|
import { ChoiceButton } from './ChoiceButton';
|
|
import { AffectionSystem } from './AffectionSystem';
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
// 剧情数据结构
|
|
interface StoryData {
|
|
title: string;
|
|
scenes: SceneData[];
|
|
}
|
|
|
|
interface SceneData {
|
|
id: string;
|
|
background?: string;
|
|
characters: CharacterShowData[];
|
|
dialogue: DialogueData[];
|
|
choices?: ChoiceData[];
|
|
nextScene?: string;
|
|
}
|
|
|
|
interface CharacterShowData {
|
|
id: string;
|
|
name: string;
|
|
emotion: string;
|
|
position: 'left' | 'center' | 'right';
|
|
visible: boolean;
|
|
}
|
|
|
|
interface DialogueData {
|
|
speaker?: string;
|
|
text: string;
|
|
emotion?: string;
|
|
}
|
|
|
|
interface ChoiceData {
|
|
text: string;
|
|
nextScene: string;
|
|
affectionChange?: { [key: string]: number };
|
|
}
|
|
|
|
@ccclass('StoryManager')
|
|
export class StoryManager extends Component {
|
|
private static _instance: StoryManager = null;
|
|
public static get instance(): StoryManager {
|
|
return StoryManager._instance;
|
|
}
|
|
|
|
@property(DialogueBox)
|
|
dialogueBox: DialogueBox;
|
|
|
|
@property(CharacterView)
|
|
characterView: CharacterView;
|
|
|
|
@property(Node)
|
|
choiceContainer: Node;
|
|
|
|
@property(AffectionSystem)
|
|
affectionSystem: AffectionSystem;
|
|
|
|
private storyData: StoryData = null;
|
|
private currentScene: SceneData = null;
|
|
private currentDialogueIndex: number = 0;
|
|
private isTyping: boolean = false;
|
|
|
|
onLoad() {
|
|
StoryManager._instance = this;
|
|
}
|
|
|
|
start() {
|
|
// 自动加载第一章
|
|
this.loadChapter('chapter1');
|
|
}
|
|
|
|
/**
|
|
* 加载剧情章节
|
|
*/
|
|
loadChapter(chapterId: string) {
|
|
resources.load(`story/${chapterId}`, JsonAsset, (err, jsonAsset) => {
|
|
if (err) {
|
|
console.error('加载剧情失败:', err);
|
|
return;
|
|
}
|
|
this.storyData = jsonAsset.json as StoryData;
|
|
console.log('剧情加载成功:', this.storyData.title);
|
|
|
|
// 开始播放第一个场景
|
|
if (this.storyData.scenes.length > 0) {
|
|
this.playScene(this.storyData.scenes[0].id);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 播放指定场景
|
|
*/
|
|
playScene(sceneId: string) {
|
|
const scene = this.storyData.scenes.find((s) => s.id === sceneId);
|
|
if (!scene) {
|
|
console.error('未找到场景:', sceneId);
|
|
return;
|
|
}
|
|
|
|
this.currentScene = scene;
|
|
this.currentDialogueIndex = 0;
|
|
|
|
// 设置背景
|
|
if (scene.background) {
|
|
this.loadBackground(scene.background);
|
|
}
|
|
|
|
// 更新角色显示
|
|
this.updateCharacters(scene.characters);
|
|
|
|
// 隐藏选项
|
|
this.hideChoices();
|
|
|
|
// 开始对话
|
|
this.showNextDialogue();
|
|
}
|
|
|
|
/**
|
|
* 显示下一句对话
|
|
*/
|
|
showNextDialogue() {
|
|
if (!this.currentScene) return;
|
|
|
|
// 检查是否还有对话
|
|
if (this.currentDialogueIndex >= this.currentScene.dialogue.length) {
|
|
this.onDialogueEnd();
|
|
return;
|
|
}
|
|
|
|
const dialogue = this.currentScene.dialogue[this.currentDialogueIndex];
|
|
|
|
// 更新对话框
|
|
this.dialogueBox.show(dialogue.speaker, dialogue.text, () => {
|
|
this.isTyping = false;
|
|
});
|
|
|
|
// 更新立绘表情
|
|
if (dialogue.emotion && this.characterView) {
|
|
this.characterView.setEmotion(dialogue.emotion);
|
|
}
|
|
|
|
this.currentDialogueIndex++;
|
|
this.isTyping = true;
|
|
}
|
|
|
|
/**
|
|
* 点击对话区域继续
|
|
*/
|
|
onDialogueClicked() {
|
|
if (this.isTyping) {
|
|
// 打字时点击直接显示完整文本
|
|
this.dialogueBox.finishTyping();
|
|
this.isTyping = false;
|
|
} else {
|
|
// 显示下一句
|
|
this.showNextDialogue();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 对话结束处理
|
|
*/
|
|
private onDialogueEnd() {
|
|
if (!this.currentScene) return;
|
|
|
|
// 检查是否有选项
|
|
if (this.currentScene.choices && this.currentScene.choices.length > 0) {
|
|
this.showChoices(this.currentScene.choices);
|
|
}
|
|
// 检查是否有下一场景
|
|
else if (this.currentScene.nextScene) {
|
|
this.playScene(this.currentScene.nextScene);
|
|
}
|
|
else {
|
|
console.log('剧情结束');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 显示选项
|
|
*/
|
|
private showChoices(choices: ChoiceData[]) {
|
|
this.hideChoices();
|
|
|
|
choices.forEach((choice, index) => {
|
|
const choiceNode = this.choiceContainer.children[index];
|
|
if (choiceNode) {
|
|
choiceNode.active = true;
|
|
const choiceBtn = choiceNode.getComponent(ChoiceButton);
|
|
if (choiceBtn) {
|
|
choiceBtn.setup(choice.text, () => {
|
|
this.onChoiceSelected(choice);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 隐藏选项
|
|
*/
|
|
private hideChoices() {
|
|
this.choiceContainer.children.forEach(child => {
|
|
child.active = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 选择选项
|
|
*/
|
|
private onChoiceSelected(choice: ChoiceData) {
|
|
// 更新好感度
|
|
if (choice.affectionChange) {
|
|
this.affectionSystem.changeAffection(choice.affectionChange);
|
|
}
|
|
|
|
// 跳转到下一场景
|
|
if (choice.nextScene) {
|
|
this.playScene(choice.nextScene);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载背景图
|
|
*/
|
|
private loadBackground(bgPath: string) {
|
|
resources.load(`backgrounds/${bgPath}`, SpriteFrame, (err, spriteFrame) => {
|
|
if (err) {
|
|
console.error('加载背景失败:', err);
|
|
return;
|
|
}
|
|
// 假设有一个背景节点
|
|
const bgNode = this.node.getChildByName('Background');
|
|
if (bgNode) {
|
|
const sprite = bgNode.getComponent(Sprite);
|
|
if (sprite) {
|
|
sprite.spriteFrame = spriteFrame;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新角色显示
|
|
*/
|
|
private updateCharacters(characters: CharacterShowData[]) {
|
|
if (!this.characterView) return;
|
|
|
|
characters.forEach(char => {
|
|
if (char.visible) {
|
|
this.characterView.showCharacter(char.id, char.name, char.emotion, char.position);
|
|
} else {
|
|
this.characterView.hideCharacter(char.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 跳转到指定场景(可外部调用)
|
|
*/
|
|
public goToScene(sceneId: string) {
|
|
this.playScene(sceneId);
|
|
}
|
|
|
|
/**
|
|
* 重新开始
|
|
*/
|
|
public restart() {
|
|
if (this.storyData && this.storyData.scenes.length > 0) {
|
|
this.affectionSystem.reset();
|
|
this.playScene(this.storyData.scenes[0].id);
|
|
}
|
|
}
|
|
}
|