【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79447.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux操作系统系统编程:x86-64架构下的系统调用

在Linux操作系统里&#xff0c;系统编程如同精密仪器的核心部件&#xff0c;掌控着系统运行的关键。而 x86-64 架构下的系统调用&#xff0c;更是连接用户空间程序与内核的关键桥梁。你可以把用户空间的程序想象成一个个 “工匠”&#xff0c;它们有着各式各样的需求&#xff0…

理解数据湖

目录 一、数据湖的定义与相关概念 二、数据湖出现的背景 三、数据湖关键技术 (一)存储技术

前端应用开发技术历程的简要概览

前端应用开发技术详解 一、萌芽期&#xff08;1990s - 2004&#xff09; 技术特征 HTML 3.2 / HTML 4.01 是主流版本。 样式用 CSS1/CSS2&#xff0c;但大部分样式写在 <style> 标签甚至行内。 动态效果主要通过 JavaScript 控制 DOM&#xff0c;兼容性极差。 代表事…

交换机配置DHCP

交换机配置DHCP 背景先关闭路由器的DHCPconsole口连接到交换机配置交换机 背景 路由器的dhcp分配IP地址变慢&#xff0c;怎么处理 先关闭路由器的DHCP 查看路由器中DHCP地址池范围; 关闭路由器的DHCP console口连接到交换机 协议Serial端口COMX波特率9600流控无 配置交换机…

解决Flutter项目中Gradle构建Running Gradle task ‘assembleDebug‘卡顿问题的终极指南

解决Flutter项目中Gradle构建Running Gradle task ‘assembleDebug‘卡顿问题的终极指南 前言 在开发Flutter应用时,经常会遇到Gradle构建卡在Running Gradle task assembleDebug阶段的问题。本文将分享如何通过配置华为云镜像和使用自定义脚本下载依赖的方法解决这些问题。…

AI驱动文字冒险游戏

github地址&#xff1a;https://github.com/thornbsj/ImmenseSimGame 虽然游戏比较简陋&#xff0c;但是由于笔者不想对游戏做过多的“剧透”&#xff0c;因此本文只粗略讲一下大致逻辑以及部分代码&#xff0c;有兴趣的朋友可以看上面的仓库获得更详细的部分。 一、状态机改…

springboot中有关数据库信息转换的处理

现代项目一般都是前后端分离的&#xff0c;前端只负责展示数据&#xff0c;不负责对数据处理&#xff0c;所以所有数据处理工作都由后端进行 比如在仿京东中的status&#xff0c;审核信息展示&#xff0c;数据库中是以0/1显示&#xff0c;但是前端需要以"审核/未审核&quo…

提示词版本化管理:AI开发中被忽视的关键环节

当我的提示词"消失"在团队协作中 上周五下午&#xff0c;我经历了一场小型"灾难"。作为一名AI产品经理&#xff0c;我花了整整三天精心打磨的客服机器人提示词&#xff0c;在周末更新后突然"失效"了。机器人不再能够准确识别用户意图&#xff0…

Centos Ubuntu RedOS系统类型下查看系统信息

文章目录 一、项目背景二、页面三、说明四、代码1.SysInfo2.EmsSysConfig3.HostInformationController4.HostInfo 一、项目背景 公司项目想展示当前部署系统的&#xff1a;操作系统&#xff0c;软件版本、IP、主机名。 二、页面 三、说明 说明点1&#xff1a;查询系统类型及…

阿里云自动备份网站,阿里云自动备份网站的方法

阿里云提供了多种自动备份网站的方法&#xff0c;适用于不同场景和需求&#xff0c;用户可根据自身技术能力和业务要求选择合适的方案。以下是几种主流的自动备份方法及操作要点&#xff1a; 一、基于云服务器ECS的自动快照备份 适用场景&#xff1a;适用于基于ECS部署的网站…

输入输出(python)

open&#xff08;&#xff09;需要和close&#xff08;&#xff09;配合使用 with open () as 不需要用close&#xff08;&#xff09;函数 在python3.0中的一些变动&#xff1a; eval 是编程语言中用于动态执行字符串形式代码的内置函数 &#xff0c;名称源于英文 “evaluate”…

Arduino逻辑控制详细解答,一点自己的想法记录

一、逻辑控制的基础概念与核心语法 1.1 逻辑控制的基本原理 逻辑控制是嵌入式系统中最常见的功能之一,其核心在于通过条件判断(if-else)、循环(for/while)和布尔运算(&&/||)实现对硬件的精确控制。例如,通过按键状态切换LED亮度、根据传感器数据调整电机转速…

字符串的相关方法

1. equals方法的作用 方法介绍 public boolean equals(String s) 比较两个字符串内容是否相同、区分大小写 示例代码 public class StringDemo02 {public static void main(String[] args) {//构造方法的方式得到对象char[] chs {a, b, c};String s1 new String(chs);…

JAVA基础:Collections 工具类实战指南-从排序到线程安全

在 Java 开发中&#xff0c;集合类几乎贯穿每一个项目&#xff0c;而Collections工具类提供了一系列强大的方法&#xff0c;用于操作和增强集合的功能。无论是排序、查找还是线程安全的封装&#xff0c;Collections工具类都是提升代码效率和质量的重要工具。 一、Collections …

ReLU函数及其Python实现

ReLU函数及其Python实现 文章目录 ReLU函数及其Python实现1. ReLU函数定义2. Python实现3. 在深度学习中的应用总结 1. ReLU函数定义 ReLU&#xff08;Rectified Linear Unit&#xff0c;修正线性单元&#xff09;函数是深度学习中常用的激活函数之一。它的定义非常简单&#…

2505ahk,wmi学习

检索每个服务的状态和启动类型 wbemServices : ComObjGet("winmgmts:\\.") //.代表本地计算机. wbemObjectSet : wbemServices.InstancesOf("Win32_Service")For wbemObject In wbemObjectSetMsgBox, % "Display Name: " wbemObject.DisplayNam…

大语言模型能力评定探讨

有标准答案的评估&#xff08;选择题&#xff09; 评估语言模型能力的基本思路是准备输入和标准答案&#xff0c;比较不同模型对相同输入的输出 由于AI答题有各种各样答案&#xff0c;因此现在是利用选择题考察。 有一个知名的选择题的基准叫做Massive Multitask Language Und…

数字智慧方案5874丨智慧交通收费稽核管理体系的构建与思考(44页PPT)(文末有下载方式)

资料解读&#xff1a;智慧交通收费稽核管理体系的构建与思考 详细资料请看本解读文章的最后内容。 随着高速公路收费系统的不断升级&#xff0c;特别是撤站后的新形势&#xff0c;收费稽核管理体系的构建显得尤为重要。本文将对辽宁省在联网收费新形势下的收费稽核管理体系进…

3.Java转义字符

Java转义字符 转义字符以\开头&#xff0c;常见的转义字符&#xff1a; 转义字符作用\t &#x1f31f;水平制表符&#xff08;Tab&#xff09;\r &#x1f31f;“回车&#xff08;Carriage Return&#xff09;”\n换行&#xff08;New Line&#xff09;\\输出一个反斜杠 \\&q…

【凑修电脑的小记录】vscode打不开

想把vscode的数据和环境从c盘移到d盘 大概操作和这篇里差不多 修改『Visual Studio Code&#xff08;VS Code&#xff09;』插件默认安装路径的方法 - 且行且思 - 博客园 在原地址保留了个指向新地址的链接文件。 重新安装vscode后双击 管理员身份运行均无法打开&#xff0…