尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用 JavaScript 事件驱动贪吃蛇:一个 setTimeout 游戏循环怎么跑起来

用 JavaScript 事件驱动贪吃蛇:一个 setTimeout 游戏循环怎么跑起来 用 JavaScript 事件驱动贪吃蛇一个 setTimeout 游戏循环怎么跑起来【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum把一条蛇放进 40×40 的网格里让它跟着方向键走、吃到食物就变长、撞上自己就出局——一个能玩的贪吃蛇就成形了。这篇文章只写三样东西键盘事件监听怎么接住输入、setTimeout 回调函数怎么撑起游戏循环、网格与蛇身这类状态对象怎么建模。写完之后你会同时带走两样东西一份可以直接在浏览器里跑的代码和一套事件驱动编程的思维模型。 底层逻辑游戏开始前脑子里先摆好三张桌子别急着写代码先想清楚谁负责什么。第一张桌子等单的人。把浏览器想象成一家餐厅。服务员不会每隔半秒冲到每张桌子问一遍要不要加水那样整家餐厅会瘫痪。他站在过道里等哪桌铃响了他就去处理哪桌。键盘事件就是这个铃。你写下的监听回调就是那个去处理的动作而事件对象里装着本次响铃的全部信息比如按下的是哪个键。第二张桌子定时闹钟。蛇要自己往前走但不能用死循环把页面卡死。闹钟每次只响一次响完你得决定要不要再设下一个。setTimeout干的就是这件事每回合只调度一次移动回合结束后由你自己决定下一步的节奏。第三张桌子账本。你不需要记住蛇走过的每一步只需要知道它现在长什么样。把整条蛇存成一串坐标对移动就是头部加一格、尾部减一格吃食物就是这一回合尾部不减少。想清楚这三件事之后代码其实很薄。把模型落进代码蛇对象长这样先放在这里后面的代码都围着它转var snake { position: [20, 20], direction: r, body: [[20, 20]] };position和direction是蛇头的快照body才是真正的蛇身数组第一个元素永远是头。40×40 的棋盘在内存里长什么样这一步解决数据从哪来。实现思路是这样的——var grid []; for (var row 0; row 40; row) { grid[row] []; for (var col 0; col 40; col) { grid[row][col] ; } }网格就是一个二维数组每个格子初始只放一个空格。后面蛇身用O、食物用F占位判断谁在哪个格子全靠查它。屏幕上的 1600 个格子谁来画这一步解决数据怎么变成画面。把上面的模型落到代码里核心就这几行function render() { var container document.getElementById(grid); container.innerHTML ; // 整块重画旧的 div 全部清掉 for (var row 0; row 40; row) { for (var col 0; col 40; col) { var cell document.createElement(div); cell.className cell; if (grid[row][col] O) { cell.classList.add(snake); } else if (grid[row][col] F) { cell.classList.add(food); } container.appendChild(cell); } } }这段在做网格转 DOM每回合把 1600 个 div 整个重画一遍。注意innerHTML 是刻意的不清空旧格子就会越叠越多。HTML 侧只需要一个空的 div 容器和三行 CSS 控制格子尺寸全部渲染压力都在这个函数里。方向键从键盘到蛇身的路上这一步解决玩家怎么控制蛇。真正在跑的逻辑其实很短document.addEventListener(keydown, function (event) { if (event.key ArrowUp) snake.direction u; if (event.key ArrowDown) snake.direction d; if (event.key ArrowLeft) snake.direction l; if (event.key ArrowRight) snake.direction r; });这段在做键盘事件监听回调被注册后一直待命按键真正发生才由浏览器调它event.key里读得到键名。注意监听挂在document上而不是某个具体元素为什么这样也行下面单独说。谁在按固定节奏推蛇走这一步解决蛇自己怎么动起来。实现思路是这样的——var gameOver false; var speed 120; function gameLoop() { move(); render(); if (isOutOfBoard(snake.position) || hitsItself(snake.position)) { gameOver true; alert(Game Over!); return; } setTimeout(gameLoop, speed); }这段在做回合调度移动一格、重画一遍然后才决定要不要预约下一回合。注意调度永远放在最后撞墙或自撞的那一帧依然会渲染出来玩家能看到蛇最后停在哪儿。function move() { var head snake.body[0].slice(); if (snake.direction u) head[0]--; if (snake.direction d) head[0]; if (snake.direction l) head[1]--; if (snake.direction r) head[1]; snake.body.unshift(head); if (!eatFood(head)) { snake.body.pop(); } snake.position head; }这段在做坐标数组建模里最核心的一手头插新格子、尾弹旧格子蛇就走了。注意slice()是复制而不是引用否则新蛇头和旧蛇头会指向同一份数据。食物放在哪下一颗又怎么来这一步解决变长的燃料。把上面的模型落到代码里核心就这几行var food { position: [0, 0] }; function placeFood() { food.position [ Math.floor(Math.random() * 40), Math.floor(Math.random() * 40) ]; grid[food.position[0]][food.position[1]] F; } function eatFood(head) { if (head[0] food.position[0] head[1] food.position[1]) { placeFood(); return true; } return false; }这段在做进食协议蛇头坐标对上食物坐标就重放一颗食物并放行不弹尾。注意grid里同步写入F下一轮render()才能把红色格子画出来。撞墙和撞自己怎么判这一步解决什么时候该停。真正在跑的逻辑其实很短function isOutOfBoard(pos) { return pos[0] 0 || pos[0] 40 || pos[1] 0 || pos[1] 40; } function hitsItself(pos) { for (var i 0; i snake.body.length - 1; i) { var seg snake.body[i]; if (seg[0] pos[0] seg[1] pos[1]) { return true; } } return false; }这段在做两种出局判定都只查蛇头一个点。注意hitsItself的循环故意停在倒数第二个元素尾巴这一回合本来就要弹走不算在碰撞范围内。 容易卡住的点你看到格子排到第 20 个就开始换行整盘棋歪了。其实是默认盒模型下边框会加在width之外20px 的格子实际占了 22px。改法给格子加box-sizing: border-box让边框吃进 20px 里。你看到keydown明明挂在document上却总能收到方向键。其实是按键先落在焦点元素上再沿 DOM 一层层冒泡到顶层这就是事件冒泡。改法不用改知道它能收到就行DOM_manipulation_and_events.md 对这条链路有完整讲解。你看到蛇回头时明明没压到身体程序却报 Game Over。其实是自撞检测把即将弹走的尾巴也算进了身体。改法判断时跳过最后一个坐标对只比对前面每一节。你看到想用setInterval或while循环写完才发现没法优雅地停。其实是固定间隔的定时器不关心游戏是否已经结束。改法每回合末尾才调setTimeoutgameOver置位后自然不再预约下一回合event_driven.md 讲过回调就是把信息抛向未来。你看到蛇撞墙的瞬间页面还是白的最后的位置没显示出来。其实是把gameOver检查放在了render()之前最后一帧没画。改法顺序固定为move()→render()→ 判定停循环只影响下一回合。 再玩两笔跑通之后别急着收工有两笔很值得玩。改eatFood里的计分规则让蛇越长、单颗食物的分值越高体验滚雪球的爽感。改gameLoop末尾的speed每吃一颗食物就缩短下一回合的延迟体验逐渐失控的紧张感。把render()里的O换成动态插入的img元素体验 DOM 渲染的另一种用法。等这条蛇彻底跑顺了下一步很自然把随机放食物换成异步请求与 API 交互让棋盘从网络里长出来。到这里键盘事件监听、setTimeout 游戏循环、坐标数组建模这三块就拼完整了事件管输入定时回调管节奏网格和蛇对象管状态。仓库里这份作业出自 project_events_effects.md事件与冒泡的细节在 events_effects.md 里脚本加载时机可以参考 js_to_browser.md。照上面几段代码自己敲一遍不翻文章也能拼出一个能玩的原型。写完这个可以接着看看异步请求与 API 交互那一块。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表