本项目是一个基于多智能体系统的AI驱动互动叙事游戏,后端使用Flask + SocketIO实现多Agent协作,前端需要对接后端API实现实时对话、游戏状态管理和角色互动功能。我负责前端Vue项目的完整开发,包括Vuex状态管理、WebSocket实时通信、API接口对接、UI组件开发等。这周我的主要工作内容是在我之前完善的前端框架的基础上,实现与我队友完成的后端部分进行对接。

一、核心功能实现

API接口封装

HTTP接口处理:我封装了统一的axios实例,配置了请求拦截器和响应拦截器。请求拦截器中自动添加sessionId到请求头,这样后续的所有请求都能识别当前游戏会话。响应拦截器统一处理错误码和异常情况,让业务代码更加简洁。

跨域问题解决:在对接过程中遇到了CORS跨域问题,这是因为前端的请求头中包含了自定义字段X-Session-Id,而后端的CORS配置中没有允许这个头。我协调后端同学在Flask的CORS配置中添加了该请求头的白名单,同时前端也做好了兼容处理。

难点:后端存在CORS跨域问题,需要配置请求头白名单。

// src/api/index.js
import axios from 'axios'

const API_BASE_URL = process.env.VUE_APP_API_URL || 'http://localhost:5000'

const request = axios.create({
  baseURL: API_BASE_URL,
  timeout: 30000,
  headers: { 'Content-Type': 'application/json' }
})

// 请求拦截器 - 添加sessionId
request.interceptors.request.use(config => {
  const sessionId = localStorage.getItem('sessionId')
  if (sessionId) {
    config.headers['X-Session-Id'] = sessionId
  }
  return config
})

// 游戏初始化API
export const initGameSession = (data) => {
  return request.post('/api/chat/init', data)
}

// 发送消息API(HTTP备用)
export const sendMessage = (data) => {
  return request.post('/api/chat/send', data)
}

 WebSocket实时通信管理器

连接管理:当用户进入游戏页面后,前端首先调用初始化接口获取sessionId,然后用这个sessionId建立WebSocket连接。连接成功后,后端会返回确认消息,前端更新连接状态指示器。

消息收发:用户输入消息后,前端通过WebSocket发送user_message事件,携带sessionId和消息内容。后端处理后会通过message事件返回AI的回复,前端监听该事件并将消息添加到对话列表中。

断线重连机制:网络不稳定可能导致WebSocket断开。我实现了自动重连机制,当连接断开时,前端会尝试重新连接,并保留原有的sessionId,确保游戏进度不会丢失。同时界面上显示连接状态指示器,让用户了解当前网络状况。

难点:WebSocket连接超时、断线重连、事件管理。

// src/api/websocket.js
import { io } from 'socket.io-client'

class WebSocketManager {
  constructor() {
    this.socket = null
    this.sessionId = null
    this.listeners = new Map()
    this.connectionStatus = 'disconnected'
  }

  connect(sessionId) {
    this.socket = io(WS_BASE_URL, {
      transports: ['polling', 'websocket'],
      reconnection: true,
      reconnectionAttempts: 5,
      timeout: 20000
    })
    
    this.socket.on('connect', () => {
      console.log('WebSocket connected')
      this.connectionStatus = 'connected'
    })
    
    this.socket.on('message', (data) => {
      this._emit('message', data)
    })
  }

  sendMessage(content) {
    this.socket.emit('user_message', {
      sessionId: this.sessionId,
      content
    })
  }
}

export const wsManager = new WebSocketManager()

Vuex状态管理设计

游戏的状态非常复杂,我将其划分为以下几个维度:

会话状态:包括sessionId、连接状态等,用于标识当前游戏会话。

玩家状态:包括玩家名称、选择的角色、属性值(生命值、心厄值)、背包物品、已知线索等。

世界状态:包括当前场景、场景描述、游戏回合数、任务标记等。

NPC状态:包括所有NPC的信任值、怀疑值、情绪状态等。

对话历史:存储所有用户和AI的消息记录。

难点:游戏状态复杂,需要合理划分模块。

// src/store/index.js
export default new Vuex.Store({
  state: {
    sessionId: null,           // 游戏会话ID
    player: {                  // 玩家信息
      name: '',
      avatar: '👑',
      selectedCharacter: 'hua_jianke'
    },
    gameState: {               // 游戏状态
      world: { location: '渡厄峰', scene: '', tick: 0 },
      player: { inventory: [], known_clues: [], stats: {} },
      npcs: {}
    },
    messages: [],              // 对话历史
    quests: [],                // 任务列表
    storyProgress: 0,          // 剧情进度
    isLoading: false,
    isTyping: false
  },
  
  mutations: {
    SET_GAME_STATE(state, gameState) {
      state.gameState = { ...state.gameState, ...gameState }
    },
    ADD_MESSAGE(state, message) {
      state.messages.push({ id: Date.now(), ...message })
    }
  },
  
  actions: {
    async initGame({ commit }, { selectedCharacter }) {
      const data = await initGameSession({ selected_character: selectedCharacter })
      commit('SET_SESSION_ID', data.sessionId)
      commit('SET_GAME_STATE', data.initial_state)
    },
    
    async sendMessage({ commit, state }, content) {
      const data = await sendMessage({ sessionId: state.sessionId, message: content })
      // 处理响应...
    }
  }
})

二、遇到的问题与解决方案

 无限递归调用问题

问题现象:页面加载后控制台报错“Maximum call stack size exceeded”,导致页面白屏。

问题分析:经过调试发现,mounted钩子中调用了initGame方法,而initGame方法内部又触发了某些操作导致自身被重复调用。这是因为我将组件方法命名为initGame,同时Vuex的action也叫initGame,两者发生了冲突。

解决方案:我将组件中的方法重命名为initGameSession,避免与Vuex action同名。同时添加了isInitialized标志,防止重复初始化。这个问题的教训是:在Vue组件中使用mapActions时,要注意避免方法名冲突。

 WebSocket连接超时

问题现象:WebSocket频繁连接超时,控制台不断输出timeout错误。

问题分析:经过排查,原因是后端的SocketIO配置不够完善,且前端超时设置过短。此外,在某些网络环境下,WebSocket连接可能被防火墙阻挡。

解决方案:我采取了多层次的处理。首先协调后端同学安装eventlet库并调整async_mode配置。其次增加前端的超时时间到20秒,并调整传输协议顺序(先使用polling再升级到websocket)。最后实现了HTTP备用模式,当WebSocket连接失败时自动降级使用HTTP API,确保游戏可以正常运行。

 右侧面板滚动问题

问题现象:任务进度内容较多时,右侧面板无法滚动查看完整内容。

问题分析:这是因为右侧面板没有设置overflow-y: auto样式,导致内容溢出容器但没有滚动条。

解决方案:我为.sidebar-right添加了overflow-y: auto属性,并为任务进度区域单独设置了max-height: 350px和overflow-y: auto。同时美化了滚动条样式,让它更符合整体设计风格。

 CORS跨域问题

问题现象:浏览器控制台报错“Request header field x-session-id is not allowed”。

问题分析:前端在请求头中添加了X-Session-Id字段,但后端CORS配置中没有允许这个自定义头。

解决方案:我协调后端同学在Flask的CORS配置的allow_headers列表中添加了X-Session-Id。同时前端也做好了错误处理,当请求失败时给出友好提示。

三、UI设计理念

交互细节优化

消息动画:新消息出现时有淡入动画,让界面变化更加自然。输入框获得焦点时有边框高亮效果,提升操作反馈感。

状态指示:连接状态用不同颜色的圆点表示——绿色已连接、黄色连接中、红色离线。用户发送消息后,AI响应前会显示动态打字动画,让用户知道系统正在处理。

响应式设计:针对不同屏幕尺寸做了适配。在大屏幕上使用三栏布局,在小屏幕(如平板、手机)上自动切换为垂直布局,确保移动端也能获得良好的游戏体验。

信息层级组织

左侧角色信息区按重要性排列——玩家信息在最上方,然后是角色列表,最后是背包和线索。右侧剧情面板同样如此——剧情进度在最显眼位置,接着是当前任务,最后是详细的任务标记。这样的信息层级让用户能快速找到关心的内容。

成果总结

目前实现界面如图,中间为对话区域,ai根据用户行为以及发言作出回应,分别有旁白以及npc发言,随着对话进行,完成目标以及任务后,用户得知的线索会增加,最终走向剧情关键节点。

技术收获

  1. WebSocket深入理解:掌握了Socket.IO客户端的使用、断线重连、事件管理等

  2. Vuex状态管理:大型应用状态管理的最佳实践

  3. 组件化设计:可复用组件的设计与封装

  4. 错误处理:网络异常、WebSocket超时等的降级方案

Logo

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

更多推荐