Files
ai_game/client/js/scenes/EndingScene.js

861 lines
27 KiB
JavaScript
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 BaseScene from './BaseScene';
export default class EndingScene extends BaseScene {
constructor(main, params) {
super(main, params);
this.storyId = params.storyId;
this.ending = params.ending;
console.log('EndingScene 接收到的结局:', JSON.stringify(this.ending));
this.showButtons = false;
this.fadeIn = 0;
this.particles = [];
this.isLiked = false;
this.isCollected = false;
// AI改写面板
this.showRewritePanel = false;
this.rewritePrompt = '';
this.rewriteTags = ['主角逆袭', '甜蜜HE', '虐心BE', '反转剧情', '意外重逢'];
this.selectedTag = -1;
// 改写历史
this.rewriteHistory = [];
this.currentHistoryIndex = -1;
// 配额信息
this.aiQuota = { daily: 3, used: 0, purchased: 0 };
// 加载状态
this.isRewriting = false;
this.rewriteProgress = 0;
this.initParticles();
this.loadQuota();
}
init() {
setTimeout(() => {
this.showButtons = true;
}, 1500);
}
async loadQuota() {
try {
const quota = await this.main.userManager.getAIQuota();
if (quota) {
this.aiQuota = quota;
}
} catch (e) {
console.error('加载配额失败:', e);
}
}
initParticles() {
for (let i = 0; i < 50; i++) {
this.particles.push({
x: Math.random() * this.screenWidth,
y: Math.random() * this.screenHeight,
size: Math.random() * 3 + 1,
speedY: Math.random() * 0.5 + 0.2,
alpha: Math.random() * 0.5 + 0.3
});
}
}
update() {
if (this.fadeIn < 1) {
this.fadeIn += 0.02;
}
this.particles.forEach(p => {
p.y -= p.speedY;
if (p.y < 0) {
p.y = this.screenHeight;
p.x = Math.random() * this.screenWidth;
}
});
}
render(ctx) {
this.renderBackground(ctx);
this.renderParticles(ctx);
this.renderEndingContent(ctx);
if (this.showButtons) {
this.renderButtons(ctx);
}
// AI改写面板
if (this.showRewritePanel) {
this.renderRewritePanel(ctx);
}
}
renderBackground(ctx) {
const gradient = ctx.createLinearGradient(0, 0, 0, this.screenHeight);
switch (this.ending?.type) {
case 'good':
gradient.addColorStop(0, '#0f2027');
gradient.addColorStop(0.5, '#203a43');
gradient.addColorStop(1, '#2c5364');
break;
case 'bad':
gradient.addColorStop(0, '#1a0a0a');
gradient.addColorStop(0.5, '#3a1515');
gradient.addColorStop(1, '#2d1f1f');
break;
case 'hidden':
gradient.addColorStop(0, '#1a1a0a');
gradient.addColorStop(0.5, '#3a3515');
gradient.addColorStop(1, '#2d2d1f');
break;
case 'rewrite':
gradient.addColorStop(0, '#1a0a2e');
gradient.addColorStop(0.5, '#2d1b4e');
gradient.addColorStop(1, '#4a1942');
break;
default:
gradient.addColorStop(0, '#0f0c29');
gradient.addColorStop(0.5, '#302b63');
gradient.addColorStop(1, '#24243e');
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
}
renderParticles(ctx) {
this.particles.forEach(p => {
ctx.fillStyle = `rgba(255, 255, 255, ${p.alpha * this.fadeIn})`;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
}
renderEndingContent(ctx) {
const centerX = this.screenWidth / 2;
const alpha = this.fadeIn;
const padding = 20;
// 结局卡片背景
const cardY = 80;
const cardHeight = 320;
ctx.fillStyle = `rgba(255, 255, 255, ${0.05 * alpha})`;
this.roundRect(ctx, padding, cardY, this.screenWidth - padding * 2, cardHeight, 20);
ctx.fill();
// 装饰线
const lineGradient = ctx.createLinearGradient(padding + 30, cardY + 20, this.screenWidth - padding - 30, cardY + 20);
lineGradient.addColorStop(0, 'transparent');
lineGradient.addColorStop(0.5, this.getEndingColorRgba(alpha * 0.5));
lineGradient.addColorStop(1, 'transparent');
ctx.strokeStyle = lineGradient;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding + 30, cardY + 20);
ctx.lineTo(this.screenWidth - padding - 30, cardY + 20);
ctx.stroke();
// 结局标签
ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.6})`;
ctx.font = '13px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('— 达成结局 —', centerX, cardY + 50);
// 结局名称(自动调整字号)
const endingName = this.ending?.name || '未知结局';
let fontSize = 24;
ctx.font = `bold ${fontSize}px sans-serif`;
while (ctx.measureText(endingName).width > this.screenWidth - padding * 2 - 40 && fontSize > 14) {
fontSize -= 2;
ctx.font = `bold ${fontSize}px sans-serif`;
}
ctx.fillStyle = this.getEndingColorRgba(alpha);
ctx.fillText(endingName, centerX, cardY + 90);
// 结局类型标签
const typeLabel = this.getTypeLabel();
if (typeLabel) {
ctx.font = '11px sans-serif';
const labelWidth = ctx.measureText(typeLabel).width + 20;
ctx.fillStyle = this.getEndingColorRgba(alpha * 0.3);
this.roundRect(ctx, centerX - labelWidth / 2, cardY + 100, labelWidth, 22, 11);
ctx.fill();
ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.9})`;
ctx.fillText(typeLabel, centerX, cardY + 115);
}
// 评分
if (this.ending?.score !== undefined) {
this.renderScore(ctx, centerX, cardY + 155, alpha);
}
// 结局描述(居中显示,限制在卡片内)
ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.6})`;
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
const content = this.ending?.content || '';
const lastParagraph = content.split('\n').filter(p => p.trim()).pop() || '';
const maxWidth = this.screenWidth - padding * 2 - 30;
// 限制只显示2行居中
this.wrapTextCentered(ctx, lastParagraph, centerX, cardY + 250, maxWidth, 20, 2);
}
getTypeLabel() {
switch (this.ending?.type) {
case 'good': return '✨ 完美结局';
case 'bad': return '💔 悲伤结局';
case 'hidden': return '🔮 隐藏结局';
case 'rewrite': return '🤖 AI改写结局';
default: return '📖 普通结局';
}
}
truncateText(ctx, text, maxWidth) {
if (!text) return '';
if (ctx.measureText(text).width <= maxWidth) return text;
let truncated = text;
while (truncated.length > 0 && ctx.measureText(truncated + '...').width > maxWidth) {
truncated = truncated.slice(0, -1);
}
return truncated + '...';
}
renderScore(ctx, x, y, alpha) {
const score = this.ending?.score || 0;
const stars = Math.ceil(score / 20);
// 星星
const starSize = 22;
const gap = 6;
const totalWidth = 5 * starSize + 4 * gap;
const startX = x - totalWidth / 2;
for (let i = 0; i < 5; i++) {
const filled = i < stars;
ctx.fillStyle = filled ? `rgba(255, 215, 0, ${alpha})` : `rgba(100, 100, 100, ${alpha * 0.5})`;
ctx.font = `${starSize}px sans-serif`;
ctx.textAlign = 'left';
ctx.fillText(filled ? '★' : '☆', startX + i * (starSize + gap), y);
}
// 分数
ctx.fillStyle = `rgba(255, 215, 0, ${alpha})`;
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(`${score}`, x, y + 28);
}
getEndingColorRgba(alpha) {
switch (this.ending?.type) {
case 'good': return `rgba(100, 255, 150, ${alpha})`;
case 'bad': return `rgba(255, 100, 100, ${alpha})`;
case 'hidden': return `rgba(255, 215, 0, ${alpha})`;
case 'rewrite': return `rgba(168, 85, 247, ${alpha})`;
default: return `rgba(150, 150, 255, ${alpha})`;
}
}
renderButtons(ctx) {
const padding = 15;
const buttonHeight = 38;
const buttonMargin = 8;
const startY = this.screenHeight - 220;
// AI改写按钮带配额提示
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
const aiBtnText = remaining > 0 ? '✨ AI改写结局' : '⚠️ 次数不足';
this.renderGradientButton(ctx, padding, startY, this.screenWidth - padding * 2, buttonHeight, aiBtnText, ['#a855f7', '#ec4899']);
// 分享按钮
const row2Y = startY + buttonHeight + buttonMargin;
const buttonWidth = (this.screenWidth - padding * 2 - buttonMargin) / 2;
this.renderGradientButton(ctx, padding, row2Y, buttonWidth, buttonHeight, '分享结局', ['#ff6b6b', '#ffd700']);
// 章节选择按钮
this.renderGradientButton(ctx, padding + buttonWidth + buttonMargin, row2Y, buttonWidth, buttonHeight, '章节选择', ['#667eea', '#764ba2']);
// 从头开始
const row3Y = row2Y + buttonHeight + buttonMargin;
ctx.fillStyle = 'rgba(255,255,255,0.1)';
this.roundRect(ctx, padding, row3Y, buttonWidth, buttonHeight, 19);
ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
this.roundRect(ctx, padding, row3Y, buttonWidth, buttonHeight, 19);
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('从头开始', padding + buttonWidth / 2, row3Y + 24);
// 返回首页
ctx.fillStyle = 'rgba(255,255,255,0.1)';
this.roundRect(ctx, padding + buttonWidth + buttonMargin, row3Y, buttonWidth, buttonHeight, 19);
ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
this.roundRect(ctx, padding + buttonWidth + buttonMargin, row3Y, buttonWidth, buttonHeight, 19);
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.fillText('返回首页', padding + buttonWidth + buttonMargin + buttonWidth / 2, row3Y + 24);
// 点赞和收藏
const actionY = row3Y + buttonHeight + 18;
const centerX = this.screenWidth / 2;
ctx.font = '20px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(this.isLiked ? '❤️' : '🤍', centerX - 40, actionY);
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.font = '10px sans-serif';
ctx.fillText('点赞', centerX - 40, actionY + 16);
ctx.font = '20px sans-serif';
ctx.fillText(this.isCollected ? '⭐' : '☆', centerX + 40, actionY);
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.font = '10px sans-serif';
ctx.fillText('收藏', centerX + 40, actionY + 16);
}
renderGradientButton(ctx, x, y, width, height, text, colors) {
const gradient = ctx.createLinearGradient(x, y, x + width, y);
gradient.addColorStop(0, colors[0]);
gradient.addColorStop(1, colors[1]);
ctx.fillStyle = gradient;
this.roundRect(ctx, x, y, width, height, 22);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(text, x + width / 2, y + height / 2 + 5);
}
renderRewritePanel(ctx) {
const padding = 20;
const panelWidth = this.screenWidth - padding * 2;
const panelHeight = 450;
const panelX = padding;
const panelY = (this.screenHeight - panelHeight) / 2;
// 遮罩层
ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
ctx.fillRect(0, 0, this.screenWidth, this.screenHeight);
// 面板背景渐变
const panelGradient = ctx.createLinearGradient(panelX, panelY, panelX, panelY + panelHeight);
panelGradient.addColorStop(0, '#1a1a3e');
panelGradient.addColorStop(1, '#0d0d1a');
ctx.fillStyle = panelGradient;
this.roundRect(ctx, panelX, panelY, panelWidth, panelHeight, 20);
ctx.fill();
// 面板边框渐变
const borderGradient = ctx.createLinearGradient(panelX, panelY, panelX + panelWidth, panelY);
borderGradient.addColorStop(0, '#a855f7');
borderGradient.addColorStop(1, '#ec4899');
ctx.strokeStyle = borderGradient;
ctx.lineWidth = 2;
this.roundRect(ctx, panelX, panelY, panelWidth, panelHeight, 20);
ctx.stroke();
// 标题栏
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 18px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('✨ AI改写结局', this.screenWidth / 2, panelY + 35);
// 配额提示
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
ctx.fillStyle = remaining > 0 ? 'rgba(255,255,255,0.6)' : 'rgba(255,100,100,0.8)';
ctx.font = '11px sans-serif';
ctx.textAlign = 'right';
ctx.fillText(`剩余次数:${remaining}`, panelX + panelWidth - 15, panelY + 35);
// 副标题
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('输入你想要的剧情走向AI将为你重新创作', this.screenWidth / 2, panelY + 58);
// 分隔线
const lineGradient = ctx.createLinearGradient(panelX + 20, panelY + 75, panelX + panelWidth - 20, panelY + 75);
lineGradient.addColorStop(0, 'transparent');
lineGradient.addColorStop(0.5, 'rgba(168,85,247,0.5)');
lineGradient.addColorStop(1, 'transparent');
ctx.strokeStyle = lineGradient;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(panelX + 20, panelY + 75);
ctx.lineTo(panelX + panelWidth - 20, panelY + 75);
ctx.stroke();
// 快捷标签标题
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.font = '13px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('快捷选择:', panelX + 15, panelY + 105);
// 快捷标签
const tagStartX = panelX + 15;
const tagY = panelY + 120;
const tagHeight = 32;
const tagGap = 8;
let currentX = tagStartX;
let currentY = tagY;
this.tagRects = [];
this.rewriteTags.forEach((tag, index) => {
ctx.font = '12px sans-serif';
const tagWidth = ctx.measureText(tag).width + 24;
// 换行
if (currentX + tagWidth > panelX + panelWidth - 15) {
currentX = tagStartX;
currentY += tagHeight + tagGap;
}
// 标签背景
const isSelected = index === this.selectedTag;
if (isSelected) {
const tagGradient = ctx.createLinearGradient(currentX, currentY, currentX + tagWidth, currentY);
tagGradient.addColorStop(0, '#a855f7');
tagGradient.addColorStop(1, '#ec4899');
ctx.fillStyle = tagGradient;
} else {
ctx.fillStyle = 'rgba(255,255,255,0.1)';
}
this.roundRect(ctx, currentX, currentY, tagWidth, tagHeight, 16);
ctx.fill();
// 标签边框
ctx.strokeStyle = isSelected ? 'transparent' : 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
this.roundRect(ctx, currentX, currentY, tagWidth, tagHeight, 16);
ctx.stroke();
// 标签文字
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText(tag, currentX + tagWidth / 2, currentY + 21);
// 存储标签位置
this.tagRects.push({ x: currentX, y: currentY, width: tagWidth, height: tagHeight, index });
currentX += tagWidth + tagGap;
});
// 自定义输入提示
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.font = '13px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('或自定义输入:', panelX + 15, panelY + 215);
// 输入框背景
const inputY = panelY + 230;
const inputHeight = 45;
ctx.fillStyle = 'rgba(255,255,255,0.08)';
this.roundRect(ctx, panelX + 15, inputY, panelWidth - 30, inputHeight, 12);
ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
this.roundRect(ctx, panelX + 15, inputY, panelWidth - 30, inputHeight, 12);
ctx.stroke();
// 输入框文字或占位符
ctx.font = '14px sans-serif';
ctx.textAlign = 'left';
if (this.rewritePrompt) {
ctx.fillStyle = '#ffffff';
ctx.fillText(this.rewritePrompt, panelX + 28, inputY + 28);
} else {
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.fillText('点击输入你的改写想法...', panelX + 28, inputY + 28);
}
// 按钮
const btnY = panelY + panelHeight - 70;
const btnWidth = (panelWidth - 50) / 2;
const btnHeight = 44;
// 取消按钮
ctx.fillStyle = 'rgba(255,255,255,0.1)';
this.roundRect(ctx, panelX + 15, btnY, btnWidth, btnHeight, 22);
ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.lineWidth = 1;
this.roundRect(ctx, panelX + 15, btnY, btnWidth, btnHeight, 22);
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('取消', panelX + 15 + btnWidth / 2, btnY + 28);
// 确认按钮
const confirmGradient = ctx.createLinearGradient(panelX + 35 + btnWidth, btnY, panelX + 35 + btnWidth * 2, btnY);
confirmGradient.addColorStop(0, '#a855f7');
confirmGradient.addColorStop(1, '#ec4899');
ctx.fillStyle = confirmGradient;
this.roundRect(ctx, panelX + 35 + btnWidth, btnY, btnWidth, btnHeight, 22);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 14px sans-serif';
ctx.fillText('✨ 开始改写', panelX + 35 + btnWidth + btnWidth / 2, btnY + 28);
// 存储按钮区域
this.cancelBtnRect = { x: panelX + 15, y: btnY, width: btnWidth, height: btnHeight };
this.confirmBtnRect = { x: panelX + 35 + btnWidth, y: btnY, width: btnWidth, height: btnHeight };
this.inputRect = { x: panelX + 15, y: inputY, width: panelWidth - 30, height: inputHeight };
}
// 圆角矩形
roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
// 文字换行
wrapText(ctx, text, x, y, maxWidth, lineHeight) {
if (!text) return;
let line = '';
let lineY = y;
for (let char of text) {
const testLine = line + char;
if (ctx.measureText(testLine).width > maxWidth) {
ctx.fillText(line, x, lineY);
line = char;
lineY += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, x, lineY);
}
// 限制行数的文字换行
wrapTextLimited(ctx, text, x, y, maxWidth, lineHeight, maxLines) {
if (!text) return;
let line = '';
let lineY = y;
let lineCount = 0;
for (let i = 0; i < text.length; i++) {
const char = text[i];
const testLine = line + char;
if (ctx.measureText(testLine).width > maxWidth) {
lineCount++;
if (lineCount >= maxLines) {
// 最后一行加省略号
while (line.length > 0 && ctx.measureText(line + '...').width > maxWidth) {
line = line.slice(0, -1);
}
ctx.fillText(line + '...', x, lineY);
return;
}
ctx.fillText(line, x, lineY);
line = char;
lineY += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, x, lineY);
}
// 居中显示的限制行数文字换行
wrapTextCentered(ctx, text, centerX, y, maxWidth, lineHeight, maxLines) {
if (!text) return;
// 先分行
const lines = [];
let line = '';
for (let i = 0; i < text.length; i++) {
const char = text[i];
const testLine = line + char;
if (ctx.measureText(testLine).width > maxWidth) {
lines.push(line);
line = char;
if (lines.length >= maxLines) break;
} else {
line = testLine;
}
}
if (line && lines.length < maxLines) {
lines.push(line);
}
// 如果超出行数,最后一行加省略号
if (lines.length >= maxLines && line) {
let lastLine = lines[maxLines - 1];
while (lastLine.length > 0 && ctx.measureText(lastLine + '...').width > maxWidth) {
lastLine = lastLine.slice(0, -1);
}
lines[maxLines - 1] = lastLine + '...';
}
// 居中绘制
lines.slice(0, maxLines).forEach((l, i) => {
ctx.fillText(l, centerX, y + i * lineHeight);
});
}
onTouchEnd(e) {
const touch = e.changedTouches[0];
const x = touch.clientX;
const y = touch.clientY;
// 如果改写面板打开,优先处理
if (this.showRewritePanel) {
this.handleRewritePanelTouch(x, y);
return;
}
if (!this.showButtons) return;
const padding = 15;
const buttonHeight = 38;
const buttonMargin = 8;
const startY = this.screenHeight - 220;
const buttonWidth = (this.screenWidth - padding * 2 - buttonMargin) / 2;
// AI改写按钮
if (this.isInRect(x, y, padding, startY, this.screenWidth - padding * 2, buttonHeight)) {
this.handleAIRewrite();
return;
}
// 分享按钮
const row2Y = startY + buttonHeight + buttonMargin;
if (this.isInRect(x, y, padding, row2Y, buttonWidth, buttonHeight)) {
this.handleShare();
return;
}
// 章节选择按钮
if (this.isInRect(x, y, padding + buttonWidth + buttonMargin, row2Y, buttonWidth, buttonHeight)) {
this.handleChapterSelect();
return;
}
// 从头开始
const row3Y = row2Y + buttonHeight + buttonMargin;
if (this.isInRect(x, y, padding, row3Y, buttonWidth, buttonHeight)) {
this.handleReplay();
return;
}
// 返回首页
if (this.isInRect(x, y, padding + buttonWidth + buttonMargin, row3Y, buttonWidth, buttonHeight)) {
this.main.sceneManager.switchScene('home');
return;
}
// 点赞收藏
const actionY = row3Y + buttonHeight + 18;
const centerX = this.screenWidth / 2;
if (this.isInRect(x, y, centerX - 70, actionY - 20, 60, 45)) {
this.handleLike();
return;
}
if (this.isInRect(x, y, centerX + 10, actionY - 20, 60, 45)) {
this.handleCollect();
return;
}
}
isInRect(x, y, rx, ry, rw, rh) {
return x >= rx && x <= rx + rw && y >= ry && y <= ry + rh;
}
handleShare() {
wx.shareAppMessage({
title: `我在《星域故事汇》达成了「${this.ending?.name}」结局!`,
imageUrl: '',
query: `storyId=${this.storyId}`
});
}
handleChapterSelect() {
this.main.sceneManager.switchScene('chapter', { storyId: this.storyId });
}
handleAIRewrite() {
// 检查配额
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
if (remaining <= 0) {
this.showQuotaModal();
return;
}
// 显示AI改写面板
this.showRewritePanel = true;
this.rewritePrompt = '';
this.selectedTag = -1;
}
handleRewritePanelTouch(x, y) {
// 点击标签
if (this.tagRects) {
for (const tag of this.tagRects) {
if (this.isInRect(x, y, tag.x, tag.y, tag.width, tag.height)) {
this.selectedTag = tag.index;
this.rewritePrompt = this.rewriteTags[tag.index];
return true;
}
}
}
// 点击输入框
if (this.inputRect && this.isInRect(x, y, this.inputRect.x, this.inputRect.y, this.inputRect.width, this.inputRect.height)) {
this.showCustomInput();
return true;
}
// 点击取消
if (this.cancelBtnRect && this.isInRect(x, y, this.cancelBtnRect.x, this.cancelBtnRect.y, this.cancelBtnRect.width, this.cancelBtnRect.height)) {
this.showRewritePanel = false;
return true;
}
// 点击确认
if (this.confirmBtnRect && this.isInRect(x, y, this.confirmBtnRect.x, this.confirmBtnRect.y, this.confirmBtnRect.width, this.confirmBtnRect.height)) {
if (this.rewritePrompt) {
this.showRewritePanel = false;
this.callAIRewrite(this.rewritePrompt);
} else {
wx.showToast({ title: '请选择或输入改写内容', icon: 'none' });
}
return true;
}
return false;
}
showCustomInput() {
wx.showModal({
title: '输入改写想法',
editable: true,
placeholderText: '例如:让主角获得逆袭',
content: this.rewritePrompt,
success: (res) => {
if (res.confirm && res.content) {
this.rewritePrompt = res.content;
this.selectedTag = -1;
}
}
});
}
async callAIRewrite(prompt) {
// 检查配额
const remaining = this.aiQuota.daily - this.aiQuota.used + this.aiQuota.purchased;
if (remaining <= 0) {
this.showQuotaModal();
return;
}
this.isRewriting = true;
this.rewriteProgress = 0;
// 显示加载动画
wx.showLoading({
title: 'AI创作中...',
mask: true
});
// 模拟进度条效果
const progressInterval = setInterval(() => {
this.rewriteProgress += Math.random() * 20;
if (this.rewriteProgress > 90) this.rewriteProgress = 90;
}, 500);
try {
const result = await this.main.storyManager.rewriteEnding(
this.storyId,
this.ending,
prompt
);
clearInterval(progressInterval);
this.rewriteProgress = 100;
wx.hideLoading();
if (result && result.content) {
// 记录改写历史
this.rewriteHistory.push({
prompt: prompt,
content: result.content,
timestamp: Date.now()
});
this.currentHistoryIndex = this.rewriteHistory.length - 1;
// 扣除配额
this.aiQuota.used += 1;
// 成功提示
wx.showToast({
title: '改写成功!',
icon: 'success',
duration: 1500
});
// 延迟跳转到故事场景播放新内容
setTimeout(() => {
this.main.sceneManager.switchScene('story', {
storyId: this.storyId,
aiContent: result
});
}, 1500);
} else {
wx.showToast({ title: '改写失败,请重试', icon: 'none' });
}
} catch (error) {
clearInterval(progressInterval);
wx.hideLoading();
console.error('改写失败:', error);
wx.showToast({ title: error.message || '网络错误', icon: 'none' });
} finally {
this.isRewriting = false;
this.rewriteProgress = 0;
}
}
showQuotaModal() {
wx.showModal({
title: 'AI次数不足',
content: `今日剩余免费次数已用完。\n\n观看广告可获得1次 AI改写机会`,
confirmText: '看广告',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.watchAdForQuota();
}
}
});
}
watchAdForQuota() {
// 模拟看广告获得配额
wx.showToast({
title: '获得1次 AI 次数',
icon: 'success'
});
this.aiQuota.purchased += 1;
}
handleReplay() {
this.main.storyManager.resetStory();
this.main.sceneManager.switchScene('story', { storyId: this.storyId });
}
handleLike() {
this.isLiked = !this.isLiked;
this.main.userManager.likeStory(this.storyId, this.isLiked);
this.main.storyManager.likeStory(this.isLiked);
}
handleCollect() {
this.isCollected = !this.isCollected;
this.main.userManager.collectStory(this.storyId, this.isCollected);
}
}