如何用Vue 2.x + Vuex实现放置游戏的游戏循环?
解决Vue2+Vuex放置游戏循环频繁dispatch的性能问题
我之前在Vue2搭配Vuex做文字放置游戏时,也碰到过几乎一模一样的问题——掉帧补更时要触发几十次dispatch,不仅性能拉胯,还容易让Vue的响应式更新队列挤爆。给你几个亲测有效的解决方案:
1. 批量提交mutation,合并多次dispatch
每次dispatch本质是触发一次Vuex调度和Vue响应式更新,几十次连续调用肯定会有性能瓶颈。不如把补更的所有操作收集起来,一次性提交到Vuex处理:
// 游戏循环中先收集所有待更新操作 const batchOperations = []; const updatePerTick = 16.67; // 60fps单帧时长(ms) let remainingTime = deltaTime; let updateCount = 0; // 执行掉帧补更的计算逻辑,但不触发dispatch while (remainingTime >= updatePerTick && updateCount < 60) { const resourceChanges = calculatePerTickChanges(); // 计算单帧资源变化 batchOperations.push(resourceChanges); remainingTime -= updatePerTick; updateCount++; } // 单次dispatch提交批量操作 store.dispatch('batchUpdateGameState', batchOperations);
然后在Vuex里实现对应的action和mutation:
// Vuex actions actions: { batchUpdateGameState({ commit }, operations) { commit('BATCH_UPDATE_STATE', operations); } } // Vuex mutations mutations: { BATCH_UPDATE_STATE(state, operations) { operations.forEach(op => { // 统一处理所有单帧更新逻辑 state.resources.gold += op.gold; state.resources.wood += op.wood; // 其他如任务进度、成就判定等逻辑也在这里批量处理 }); } }
这种方式把几十次dispatch压缩成一次,能大幅减少Vuex调度和响应式更新的开销。
2. 把循环计算逻辑移出Vuex,只同步最终状态
掉帧补更的核心是纯计算逻辑,完全不需要每一步都走Vuex的响应式流程。可以把游戏循环的计算部分放在独立的JS模块里,计算完最终状态后再一次性同步到Vuex:
// 独立的游戏循环模块(非Vuex) export function runGameLoop(deltaTime) { const timePerTick = 16.67; let remainingTime = deltaTime; let updateCount = 0; // 取当前状态快照,避免循环中读取响应式数据 const currentState = { ...store.state.game }; // 纯JS计算补更逻辑,不触发任何Vuex操作 while (remainingTime >= timePerTick && updateCount < 60) { currentState.gold += currentState.goldPerTick; currentState.wood += currentState.woodPerTick; // 处理怪物刷新、任务完成判定等逻辑 checkTaskCompletion(currentState); remainingTime -= timePerTick; updateCount++; } // 计算完成后,一次性同步到Vuex store.dispatch('syncGameState', currentState); }
Vuex里只需要一个同步状态的mutation:
mutations: { SYNC_GAME_STATE(state, newState) { // 用Object.assign合并状态,保留响应式 Object.assign(state.game, newState); } }
这种方案把所有计算逻辑从Vuex的响应式流程中剥离,性能提升最明显。
3. 优化Vuex响应式范围,减少不必要监听
如果你的游戏状态里有大量中间计算数据(比如临时的怪物属性、技能冷却倒计时),可以把这些不需要实时展示的字段放在非响应式区域,避免Vue对其做监听:
// Vuex state state: { // 仅保留需要页面展示的响应式状态 displayState: { gold: 0, wood: 0, currentTask: '' }, // 用Object.freeze标记为非响应式,存储循环计算的中间数据 _gameCache: Object.freeze({ goldPerTick: 0, monsterHp: 0, skillCooldowns: {} }) }
游戏循环中更新_gameCache不会触发响应式更新,只有当需要更新页面展示的displayState时才触发dispatch。
内容的提问来源于stack exchange,提问作者TotomInc
相关产品推荐
相关产品推荐

