- StoryManager: 剧情管理器 - DialogueBox: 对话框组件(带打字机效果) - CharacterView: 立绘组件 - ChoiceButton: 选项按钮 - AffectionSystem: 好感度系统 - chapter1.json: 示例剧情
184 lines
4.9 KiB
TypeScript
184 lines
4.9 KiB
TypeScript
import { _decorator, Component, Node, Sprite, SpriteFrame, resources, tween, Vec3 } from 'cc';
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
// 角色位置
|
|
type CharacterPosition = 'left' | 'center' | 'right';
|
|
|
|
// 角色数据
|
|
interface CharacterData {
|
|
id: string;
|
|
name: string;
|
|
emotion: string;
|
|
position: CharacterPosition;
|
|
visible: boolean;
|
|
}
|
|
|
|
@ccclass('CharacterView')
|
|
export class CharacterView extends Component {
|
|
@property(Node)
|
|
leftCharacter: Node;
|
|
|
|
@property(Node)
|
|
centerCharacter: Node;
|
|
|
|
@property(Node)
|
|
rightCharacter: Node;
|
|
|
|
// 角色资源缓存
|
|
private characterAssets: Map<string, Map<string, SpriteFrame>> = new Map();
|
|
|
|
onLoad() {
|
|
// 初始化所有角色隐藏
|
|
this.hideAllCharacters();
|
|
}
|
|
|
|
/**
|
|
* 显示角色
|
|
*/
|
|
showCharacter(characterId: string, name: string, emotion: string, position: CharacterPosition) {
|
|
const node = this.getCharacterNode(position);
|
|
if (!node) return;
|
|
|
|
// 加载角色立绘
|
|
this.loadCharacterSprite(characterId, emotion, (spriteFrame) => {
|
|
if (spriteFrame) {
|
|
const sprite = node.getComponent(Sprite);
|
|
if (sprite) {
|
|
sprite.spriteFrame = spriteFrame;
|
|
}
|
|
|
|
// 显示节点
|
|
node.active = true;
|
|
|
|
// 播放出场动画
|
|
this.playEnterAnimation(node);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 隐藏角色
|
|
*/
|
|
hideCharacter(characterId: string) {
|
|
// 遍历所有位置查找并隐藏
|
|
[this.leftCharacter, this.centerCharacter, this.rightCharacter].forEach(node => {
|
|
if (node && node.active) {
|
|
node.active = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 隐藏所有角色
|
|
*/
|
|
hideAllCharacters() {
|
|
[this.leftCharacter, this.centerCharacter, this.rightCharacter].forEach(node => {
|
|
if (node) {
|
|
node.active = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 切换角色表情
|
|
*/
|
|
setEmotion(emotion: string) {
|
|
// 这个方法需要在具体场景中根据当前显示的角色来实现
|
|
// 可以通过在场景中保存当前角色ID来调用
|
|
console.log('切换表情:', emotion);
|
|
}
|
|
|
|
/**
|
|
* 获取角色节点
|
|
*/
|
|
private getCharacterNode(position: CharacterPosition): Node {
|
|
switch (position) {
|
|
case 'left':
|
|
return this.leftCharacter;
|
|
case 'center':
|
|
return this.centerCharacter;
|
|
case 'right':
|
|
return this.rightCharacter;
|
|
default:
|
|
return this.centerCharacter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 加载角色立绘
|
|
*/
|
|
private loadCharacterSprite(characterId: string, emotion: string, callback: (spriteFrame: SpriteFrame) => void) {
|
|
const path = `characters/${characterId}/${emotion}`;
|
|
|
|
// 检查缓存
|
|
if (this.characterAssets.has(characterId)) {
|
|
const emotionMap = this.characterAssets.get(characterId);
|
|
if (emotionMap && emotionMap.has(emotion)) {
|
|
callback(emotionMap.get(emotion));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 加载新资源
|
|
resources.load(path, SpriteFrame, (err, spriteFrame) => {
|
|
if (err) {
|
|
console.warn(`加载角色立绘失败: ${path}`, err);
|
|
callback(null);
|
|
return;
|
|
}
|
|
|
|
// 缓存
|
|
if (!this.characterAssets.has(characterId)) {
|
|
this.characterAssets.set(characterId, new Map());
|
|
}
|
|
this.characterAssets.get(characterId).set(emotion, spriteFrame);
|
|
|
|
callback(spriteFrame);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 播放出场动画
|
|
*/
|
|
private playEnterAnimation(node: Node) {
|
|
// 从下方进入
|
|
const originalPos = node.position.clone();
|
|
node.setPosition(originalPos.x, originalPos.y - 200, originalPos.z);
|
|
|
|
tween(node)
|
|
.to(0.3, { position: originalPos }, { easing: 'backOut' })
|
|
.start();
|
|
}
|
|
|
|
/**
|
|
* 播放退场动画
|
|
*/
|
|
playExitAnimation(node: Node, callback?: () => void) {
|
|
tween(node)
|
|
.to(0.3, { position: new Vec3(node.position.x, node.position.y - 200, node.position.z) })
|
|
.call(() => {
|
|
node.active = false;
|
|
if (callback) callback();
|
|
})
|
|
.start();
|
|
}
|
|
|
|
/**
|
|
* 预加载角色资源
|
|
*/
|
|
preloadCharacter(characterId: string, emotions: string[]) {
|
|
emotions.forEach(emotion => {
|
|
const path = `characters/${characterId}/${emotion}`;
|
|
resources.preload(path, SpriteFrame);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 清除缓存
|
|
*/
|
|
clearCache() {
|
|
this.characterAssets.clear();
|
|
}
|
|
}
|