AI群演请就位—个人博客(五)
这周我主要完成了结局展示与分享页面。该界面是玩家完成游戏旅程后的最终呈现页面,需要展示AI生成的个性化结局文本、角色羁绊数据、成就系统等内容,是整个游戏体验的收官之作。
设计界面如下图所示(下面关于此界面的截图均为测试本地模拟数据):

开发目标
-
界面风格:采用简洁白色风格,提升可读性和美观度
-
世界观适配:结局内容需适配四个世界观(古代宫廷、修仙世界、现代校园、末世生存)
-
数据联动:实现与主对话界面的数据联动,确保角色羁绊信息一致
-
个性化生成:根据玩家的实际游戏进程(对话历史、角色关系、关键决策)生成专属结局
-
完整功能:保留图表展示、截图分享、角色羁绊可视化、成就系统等附加功能
技术栈
除去之前使用的栈外,如Vue.js、Vuex、Vue router、Axios,新增了下面两个栈
| 技术 | 版本 | 用途 |
|---|---|---|
| ECharts | 6.0.0 | 数据可视化图表 |
| html2canvas | 1.4.1 | 截图保存功能 |
详细实现过程
1.页面布局设计
整体结构
布局设计采用了三层嵌套式响应式架构,结合Flexbox和Grid布局技术,采用居中卡片式布局,最大宽度900px,白色背景,圆角设计
| 层级 | 作用 | 实现技术 | 性能考量 |
|---|---|---|---|
| 外层容器 | 页面背景、主题切换 | 动态class绑定 + CSS变量 | 主题切换时只更新CSS变量,避免重绘 |
| 中层容器 | 内容宽度约束、居中 | max-width + margin: auto | 利用浏览器原生布局,减少JS计算 |
| 内层模块 | 功能分区 | Flex/Grid + 模块化CSS | 每个模块独立,支持懒加载 |
各模块详细布局实现
结局标题模块(Ending Header)—— Flexbox居中布局
标题模块采用垂直居中 + 水平居中的Flexbox布局,确保保持视觉焦点。
故事文本模块(Ending Story)—— 滚动容器 + 打字机动画
故事文本模块是页面的核心内容区,需要支持长文本滚动和打字机动画效果。
角色羁绊模块(Character Bond)展示玩家与所有游戏角色的关系值,包含以下核心功能:
角色基本信息展示:展示角色头像(Emoji图标)、角色名称、角色身份标识(如“太医之女”、“摄政王”等),让玩家快速识别各个角色。
羁绊值可视化:采用水平进度条 + 百分比数值的双重展示方式,进度条填充宽度与羁绊值线性对应,数值范围0-100。进度条颜色随羁绊值变化:高羁绊(≥70)显示暖红色,中等羁绊(40-69)显示橙色,低羁绊(<40)显示冷蓝色。
角色类型标识:通过视觉元素区分三类角色——盟友(🤝标识 + 绿色边框)、敌对(⚔️标识 + 红色边框)、隐藏角色(🕯️标识 + 紫色虚边框 + 揭示进度条)。隐藏角色初始为问号占位,揭示进度达20%后逐步解锁信息。
羁绊值排序:角色列表默认按羁绊值从高到低降序排列,高羁绊盟友优先展示在列表顶端,让玩家一目了然最重要的关系。
类型筛选:支持按角色类型筛选显示,用户可分别查看盟友、敌对或隐藏角色,便于针对性回顾特定类型角色的关系发展。

结局成就模块 (Ending Achievements)
记录玩家在游戏过程中达成的各类成就,包含以下核心功能:
成就信息展示:每个成就卡片包含成就图标(Emoji)、成就名称、描述文案、解锁状态(已解锁/未解锁)。未解锁成就卡片半透明显示并叠加锁形图标。
解锁状态标识:已解锁成就卡片背景高亮(浅绿色/金色),显示解锁时间戳;未解锁成就卡片半透明灰色显示,成就名称显示为问号或模糊文字。
解锁条件判定:采用事件监听模式,监听游戏中的对话、选择、战斗等行为事件,满足条件时自动触发成就解锁。
完成度统计:页面顶部显示成就整体完成度环形进度条,公式为已解锁数/总成就数,中心显示百分比,达100%时播放全成就特效。
结局关联成就:每种结局类型绑定专属成就,达成特定结局时自动解锁对应成就。

数据回顾模块(Data Review)
可视化展示玩家游戏历程的各项统计数据,包含以下核心功能:
羁绊演变曲线:折线图展示羁绊值随游戏进程的变化趋势,横轴为剧情阶段(序章→发展→转折→高潮→结局),纵轴为羁绊值0-100。数据点标记关键事件,悬停显示事件详情和变化幅度。
角色关系网络:力导向图展示角色间关系网络,玩家位于中心,节点大小与羁绊值正相关。盟友绿色描边,敌对红色描边,隐藏角色紫色虚线边框,支持拖拽缩放。


2.数据联动实现
在主对话界面(MainChat.vue)中,实现了游戏数据的自动保存。结局页面对存储的数据进行读取:
// EndingPage.vue 中的数据加载
async loadGameData() {
// 从 Vuex 获取世界信息
const world = this.$store.state.selectedWorld;
this.worldName = world?.name || localStorage.getItem('currentWorld') || '古代宫廷';
this.playerName = this.$store.state.playerCharacter?.name || localStorage.getItem('playerName') || '穿越者';
// 从 localStorage 获取游戏存档
const savedGame = localStorage.getItem('gameSave');
if (savedGame) {
const gameData = JSON.parse(savedGame);
// 合并角色数据
this.finalCharacterStates = this.mergeCharacterData(
gameData.allyCharacters || [],
gameData.rivalCharacters || [],
gameData.hiddenCharacters || []
);
// 提取关键决策
this.gameMessages = gameData.messages || [];
this.keyDecisions = this.extractDecisionsFromMessages(this.gameMessages);
// 获取剧情进度
this.progressHistory = this.extractProgressHistory(gameData.mainProgress || 0);
}
// 计算游戏时长
this.calculatePlayDuration();
}
3.世界观定制结局
为四个世界观分别设计了差异化的结局文本模板:
| 世界观 | Good结局 | Bad结局 | Hidden结局 | Normal结局 |
|---|---|---|---|---|
| 古代宫廷 | 君临天下 | 宫墙孤影 | 暗影守护 | 凡尘一梦 |
| 修仙世界 | 羽化登仙 | 坠落凡尘 | 天地棋局 | 逍遥游 |
| 现代校园 | 青春无悔 | 后会无期 | 平行交织 | 平凡之路 |
| 末世生存 | 重建希望 | 孤独行者 | 暗涌真相 | 活下去 |
结局类型判定:
determineEndingType() {
const avgRelationship = this.finalCharacterStates.reduce((sum, c) => sum + c.relationship, 0) / this.finalCharacterStates.length;
const hasHighBond = this.finalCharacterStates.some(c => c.relationship >= 80);
const hasLowBond = this.finalCharacterStates.some(c => c.relationship <= 20);
const hasHiddenRevealed = this.finalCharacterStates.some(c => c.type === 'hidden');
if (hasHighBond && avgRelationship >= 70) return 'good';
if (hasLowBond && avgRelationship <= 30) return 'bad';
if (hasHiddenRevealed && avgRelationship >= 50) return 'hidden';
return 'normal';
}
遇到的问题及解决方案
角色数据不一致问题
问题描述
结局页面显示的角色与主对话界面的角色信息不一致:
-
角色名称不匹配
-
羁绊值不同步
-
隐藏角色显示逻辑混乱
原因分析
-
主界面使用
allyCharacters、rivalCharacters、hiddenCharacters存储角色 -
结局页面独立维护角色数据,没有建立数据同步机制
-
隐藏角色的揭示进度没有传递到结局页面
解决方案
步骤1:统一数据存储格式
// 主界面保存时使用统一格式
const saveData = {
allyCharacters: this.allyCharacters.map(c => ({
id: c.id,
name: c.name,
identity: c.identity,
relationship: c.relationship || c.value,
// ... 其他字段
})),
// ... 其他数据
};
步骤2:结局页面读取并合并
mergeCharacterData(allies, rivals, hidden) {
// 使用Map去重并合并
const characterMap = new Map();
[...allies, ...rivals, ...hidden].forEach(char => {
if (characterMap.has(char.id)) {
// 合并相同角色的数据
const existing = characterMap.get(char.id);
characterMap.set(char.id, { ...existing, ...char });
} else {
characterMap.set(char.id, char);
}
});
return Array.from(characterMap.values());
}
角色关系网络图显示不全
问题描述:
关系网络图中的节点挤在一起,文字标签相互重叠,无法完整查看角色关系。
原因分析:
-
容器高度不足(仅250px),力导向图需要更多空间
-
节点数量过多时布局拥挤
-
力导向图的斥力和边长参数设置不合理
-
缺少窗口resize自适应
解决方案:增大容器高度,限制节点数量,
优化力导向图配置,
force: {
repulsion: 500, // 增大节点斥力,使节点分散
edgeLength: 200, // 增大边长,增加节点间距
gravity: 0.05, // 减小重力,让节点自由分布
layoutAnimation: true
}
添加窗口适配
window.addEventListener('resize', () => {
if (this.relationGraph) this.relationGraph.resize();
});
总结与反思
-
开发流程规范化:先梳理依赖清单,避免遗漏安装
-
组件设计要完整:模态框等交互组件需考虑各种状态
-
性能优化不能忘:长文本、大数据量场景需特殊处理
-
调试工具要善用:Vue Devtools、浏览器控制台帮助定位问题
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)