当前位置: 首页 > news >正文

【javascript】竞速游戏前端优化:高频操作与并发请求的解决方案

文章目录

  • 前言
  • 一、性能痛点分析
  • 二、核心技术方案
    • 1.Web Worker
    • 2.Promise高级控制
    • 3.智能队列系统
    • 4.游戏化节流设计
  • 三、最佳实践选择


前言

在竞速类网页游戏中,玩家高频点击与服务器实时交互会引发两大核心挑战:

客户端性能瓶颈:频繁操作导致UI卡顿、请求堆积
服务端压力激增:突发性并发请求可能击穿后端接口

本文将深入解析:
✅ Web Worker多线程计算分流
✅ Promise并发控制的精准策略
✅ 请求队列的优先级调度机制
✅ 防抖节流在游戏场景的进阶用法
通过四维一体的解决方案,实现毫秒级响应与服务器负载平衡的完美兼顾。


一、性能痛点分析

  1. 竞速游戏特有的操作模式
    极限点击频率(每秒10+次操作)
    胜负毫秒级判定的实时性要求

  2. 传统方案的缺陷
    主线程阻塞导致的输入延迟
    请求瀑布流造成的网络延迟叠加

二、核心技术方案

1.Web Worker

Web Worker 不受主线程繁忙影响,可实现高精度的计算,下面为毫秒级倒计为例,通过webworker和setInterval实现稳定的毫秒级计时器计算,通过RAF(requestAnimationFram)更新ui显示(见图1),代码代码:

//countdownWorker.ts
let timer: number = 0;
let remaining: number = 0;
let startTime: number = 0;
let paused = false;
self.onmessage = ({ data }) => {if (data.type === 'START') {remaining = data.duration; // 初始倒计时毫秒数startTime = performance.now(); // 记录开始时间paused = false;updateTimer();} else if (data.type === 'STOP') {paused = true;clearInterval(timer);} else if (data.type === 'CONTINUE') {paused = false;updateTimer();}
};function updateTimer() {if (paused) return;timer = setInterval(() => {const now = performance.now();const elapsed = now - startTime; // 已经过去的时间const remainingTime = remaining - elapsed; //校准时间差if (remainingTime <= 0) {clearInterval(timer);self.postMessage({ remaining: 0 });return;}self.postMessage({ remaining: remainingTime });startTime = now; // 更新开始时间,用于误差补偿remaining = remainingTime}, 16.7); // 按60fps帧率递减,
}
// 主线程 <script lang="ts" setup>里let countdownWorker: any = null;
let remainingTime: number | null = null;
let animationFrameId: number | null = null;
let timeDom: HTMLElement | null;const startTimerWorker = () => {countdownWorker = new Worker(new URL("@/utils/countdownWorker.ts", import.meta.url));timeDom = document.getElementById("timer2");countdownWorker.onmessage = ({ data }) => {// 更新剩余时间remainingTime = data.remaining;};// 开始3秒倒计时countdownWorker.postMessage({type: "START",duration: 5 * 60 * 1000,});// 启动 RAFupdateUI();
};const updateUI=()=>{
//RAF节流if (remainingTime !== null && timeDom) {timeDom.textContent = formatTime(remainingTime);}if (animationFrameId !== null) {cancelAnimationFrame(animationFrameId);}// 持续请求下一帧animationFrameId = requestAnimationFrame(updateUI);
}const stopTimerWorker = () => {countdownWorker.postMessage({ type: "STOP" });
};const continueTimerWorker = () => {countdownWorker.postMessage({ type: "CONTINUE" });
};onUnmounted(() => {countdownWorker?.terminate();if (animationFrameId !== null) {cancelAnimationFrame(animationFrameId);}
});

(1)如果1ms间隔,浏览器也会强制最小间隔(通常4-10ms),实际可能还是还是16ms左右,1ms不会带来视觉上改进,16.7ms间隔比1ms间隔节省约94%的计算资源
(2)使用performance.now()获取高精度时间戳
(3)使用requestAnimationFrame自动匹配刷新率,在Web Worker中通常不可用(大多数环境不支持Worker中的RAF),在主线程里使用

图1-倒计时效果图

2.Promise高级控制

Promise.all竞速模式(取最快有效响应)

动态权重调整的Promise.allSettled策略

const requestList = [{url: '/api/request',data: { text: 'hello world1' },...}];const testPromises = () => {const requestPromise: any[] = [];requestList.forEach((item) => {requestPromise.push(fetch(item.url, { method: "POST", body: JSON.stringify(item.data) }));});// Promise.all (适合独立请求)Promise.all(requestPromise).then(([userRes, postsRes]) => {console.log(userRes.data, postsRes.data);}).catch((error) => {console.error("任一请求失败", error);});// Promise.allSettled (需处理部分失败)Promise.allSettled(requestPromise).then((results) => {results.forEach((result) => {if (result.status === "fulfilled") {console.log("成功:", result.value);} else {console.error("失败:", result.reason);}});});
};//async await顺序请求处理 (适合依赖关系)
const sequentialRequests = async () => {const results = [];for (const url of requestList) {const res: any = await fetch(url, { method: 'GET' });results.push(res.data);}console.log(results);
}

3.智能队列系统

双队列架构(即时操作队列+批量更新队列)

基于游戏状态的动态优先级算法

在连连看竞速游戏里,用户快速点击时,为保证点击选中、高亮显示、连线判断、消除方块和页面刷新等动作按顺序处理,可借助请求队列机制。下面参考 GameRequestQueue 类,给出具体实现方案。

(1)定义动作类型:明确不同操作的动作类型,如 SELECT_BLOCK、CHECK_CONNECTION 等。
(2)优先级设置:依据操作的重要性和实时性为不同动作分配优先级。
(3)请求队列管理:创建队列类,将用户操作请求按优先级添加到队列,按顺序处理。
(4)游戏逻辑集成:在游戏点击事件里调用队列方法添加请求。

// 定义连连看游戏的动作类型
type Match3Action = 'SELECT_BLOCK' | 'CHECK_CONNECTION' | 'ELIMINATE_BLOCKS' | 'REFRESH_PAGE';
export class GameRequestQueue {private immediateQueue: Array<{ action: string, priority: number, data: any }> = []; // 即时队列,存储高优先级请求,每个元素是包含动作和优先级的对象(如高亮等)private batchQueue: Map<string, any[]> = new Map(); // 批量队列,键为动作名,值为请求数据数组private isSending = false; // 标志位,指示是否正在发送请求,避免并发处理队列// 动态优先级计算private getPriority(action: Match3Action): number {const priorityMap: Record<Match3Action, number> = {'SELECT_BLOCK': 100, // 选中方块最高优先级'CHECK_CONNECTION': 90, // 连线判断次高优先级'ELIMINATE_BLOCKS': 80, // 消除方块优先级'REFRESH_PAGE': 70 // 刷新页面优先级};return priorityMap[action];}// 向队列中添加请求,根据优先级分配到不同队列addRequest(action: Match3Action, data: any) {const priority = this.getPriority(action);if (priority > 50) {// 高优先级请求添加到即时队列,并按优先级降序排序this.immediateQueue.push({ action, priority, data });this.immediateQueue.sort((a, b) => b.priority - a.priority);} else {// 低优先级请求添加到批量队列if (!this.batchQueue.has(action)) {this.batchQueue.set(action, []);}this.batchQueue.get(action)!.push(data);}// 处理队列this.processQueue();}// 处理队列:先处理即时队列,再处理批量队列private async processQueue() {// 如果正在发送请求,直接返回if (this.isSending) return;this.isSending = true;// 优先发送即时队列while (this.immediateQueue.length > 0) {const { action, data } = this.immediateQueue.shift()!;await this.handleAction(action, data);}// 合并批量队列const batchData: Record<Match3Action, any[]> = {};this.batchQueue.forEach((values, key) => {batchData[key] = values;});this.batchQueue.clear();// 发送批量请求for (const [action, values] of Object.entries(batchData)) {await this.handleAction(action as Match3Action, values);}// 标记请求处理完成this.isSending = false;}private async handleAction(action: Match3Action, data: any) {switch (action) {case 'SELECT_BLOCK':// 选中方块,高亮显示console.log('选中方块:', data);break;case 'CHECK_CONNECTION':// 判断是否连线const isConnected = await this.checkConnection(data);if (isConnected) {// 满足连线,消除方块await this.handleAction('ELIMINATE_BLOCKS', data);// 刷新页面await this.handleAction('REFRESH_PAGE', null);} else {// 不满足连线,取消选中await this.handleAction('UNSELECT_BLOCK', data);}break;case 'ELIMINATE_BLOCKS':// 满足连线消除方块,模拟异步操作console.log('开始消除方块:', data);await this.eliminateBlocks(data);console.log('方块消除完成');break;case 'REFRESH_PAGE':// 刷新页面,模拟异步操作console.log('开始刷新页面');await this.refreshPage();console.log('页面刷新完成');break;case 'UNSELECT_BLOCK':// 取消选中console.log('取消选中:', data);break;}}
}// 在游戏点击事件中使用
const match3Queue = new Match3RequestQueue();function onBlockClick(blockId: number) {// 添加选中方块请求match3Queue.addRequest('SELECT_BLOCK', blockId);// 添加连线判断请求match3Queue.addRequest('CHECK_CONNECTION', blockId);
}// 示例调用
onBlockClick(1);
onBlockClick(2);

4.游戏化节流设计

连击奖励机制下的动态节流阈值

技能冷却时间的防抖适配

(1)使用传统的debounce函数

优点:在任何JavaScript环境中使用,不依赖Vue
缺点:需要手动管理值和回调,在事件处理函数中显式调用

const debounce = (fn: any, delay: any) => {let timer: anyreturn (...args: any[]) => {if (timer) {clearTimeout(timer) // 清除前一个定时器}timer = setTimeout(() => {fn(...args) // 调用函数,传递参数}, delay)}
}const debounceStartRecord = debounce(() => {recordButton();
}, 500);//<button class="recordBtn" @pointerdown="debounceStartRecord">按下录音抬起停止</button>

(2)使用基于Vue响应式系统的debounceRef

优点:可以直接在模板中绑定使用;可以方便地使用watch监听变化;
缺点:只能在Vue环境中使用;只能用于值的防抖,不能直接用于函数;

//debounceRef.ts
import { customRef } from "vue";export function debounceRef(value: any, delay: number = 1000) {let timer: number | undefined;return customRef((track, trigger) => {return {get() {// 依赖收集track();return value;},set(newValue) {clearTimeout(timer);timer = setTimeout(() => {value = newValue;// 派发更新trigger();}, delay);},};});
}//示例
//<input type="text" class="testinput" v-model="testinput" />
//const testinput = debounceRef("");

三、最佳实践选择

场景推荐方案
并行独立请求Promise.all
需容忍部分失败Promise.allSettled
顺序依赖请求Async/Await 循环
高频事件触发防抖/节流
实时大数据处理Web Worker

相关文章:

  • jaffree 封装ffmpeg 转换视频格式,获取大小,时间,封面
  • 汤晓鸥:计算机视觉的开拓者与AI产业化的先行者
  • python数据分析(五):Pandas 数据检索技术
  • Android学习总结之Java篇(一)
  • 关于https请求丢字符串导致收到报文解密失败问题
  • java.lang.AssertionError: Binder ProxyMap has too many entries: 问题处理
  • 深入理解链表:从基础操作到高频面试题解析
  • Linux[开发工具]
  • 主流AI推理模型的详细说明、对比及总结表格
  • android录音生成wav
  • 铭记之日(3)——4.28
  • 【软件工程】需求分析详解
  • maven私服配置
  • 利用Python打印有符号十进制数的二进制原码、反码、补码
  • std::print 和 std::println
  • 万亿参数大模型网络瓶颈突破:突破90%网络利用率的技术实践
  • 【力扣刷题实战】丢失的数字
  • Java大师成长计划之第6天:Java流式API(Stream API)
  • Redis 小记
  • Cursor + Figma-Context-MCP ,让 Cursor 获取 Figma 设计图信息,实现 AI 生成页面的高度还原
  • 专访|首夺天元头衔创生涯历史,王星昊打算一步一步慢慢来
  • 初步结果显示,卡尼领导的加拿大自由党在联邦众议院选举中获胜
  • 我国成功发射卫星互联网低轨卫星
  • 文化润疆|让新疆青少年成为“小小博物家”
  • 十四届全国人大常委会第十五次会议继续审议民营经济促进法草案
  • 北上广深还是小城之春?“五一”想好去哪玩了吗