这周我主要完成了结局展示与分享页面。该界面是玩家完成游戏旅程后的最终呈现页面,需要展示AI生成的个性化结局文本、角色羁绊数据、成就系统等内容,是整个游戏体验的收官之作。

设计界面如下图所示(下面关于此界面的截图均为测试本地模拟数据):

开发目标

  1. 界面风格:采用简洁白色风格,提升可读性和美观度

  2. 世界观适配:结局内容需适配四个世界观(古代宫廷、修仙世界、现代校园、末世生存)

  3. 数据联动:实现与主对话界面的数据联动,确保角色羁绊信息一致

  4. 个性化生成:根据玩家的实际游戏进程(对话历史、角色关系、关键决策)生成专属结局

  5. 完整功能:保留图表展示、截图分享、角色羁绊可视化、成就系统等附加功能

技术栈

除去之前使用的栈外,如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';
}

遇到的问题及解决方案

角色数据不一致问题

问题描述

结局页面显示的角色与主对话界面的角色信息不一致:

  • 角色名称不匹配

  • 羁绊值不同步

  • 隐藏角色显示逻辑混乱

原因分析

  1. 主界面使用allyCharactersrivalCharactershiddenCharacters存储角色

  2. 结局页面独立维护角色数据,没有建立数据同步机制

  3. 隐藏角色的揭示进度没有传递到结局页面

解决方案

步骤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());
}

角色关系网络图显示不全

问题描述:
关系网络图中的节点挤在一起,文字标签相互重叠,无法完整查看角色关系。

原因分析:

  1. 容器高度不足(仅250px),力导向图需要更多空间

  2. 节点数量过多时布局拥挤

  3. 力导向图的斥力和边长参数设置不合理

  4. 缺少窗口resize自适应

解决方案:增大容器高度,限制节点数量,

优化力导向图配置,

force: {
  repulsion: 500,        // 增大节点斥力,使节点分散
  edgeLength: 200,       // 增大边长,增加节点间距
  gravity: 0.05,         // 减小重力,让节点自由分布
  layoutAnimation: true
}

添加窗口适配

window.addEventListener('resize', () => {
  if (this.relationGraph) this.relationGraph.resize();
});

总结与反思

  1. 开发流程规范化:先梳理依赖清单,避免遗漏安装

  2. 组件设计要完整:模态框等交互组件需考虑各种状态

  3. 性能优化不能忘:长文本、大数据量场景需特殊处理

  4. 调试工具要善用:Vue Devtools、浏览器控制台帮助定位问题

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐