资讯详情

资讯详情

5 步做出可玩的贪吃蛇:JavaScript 事件监听与 setTimeout 实战教程

5 步做出可玩的贪吃蛇JavaScript 事件监听与 setTimeout 实战教程【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum「Events Effects」是《Web 开发 101》里专门练习事件监听的实战项目目标是用纯 JavaScript 把贪吃蛇做出来并真正跑起来。你会先搭一个 40×40 的网格再用键盘事件监听方向键控制蛇的转向最后用 setTimeout 游戏循环驱动它一格一格地移动撞墙或咬到自己就结束。先看见成品这个项目到底在练什么这一节先把目标摆到台面上避免你写着写着丢了方向。你最终要得到一个能在浏览器里真正常驻的画面一块 40×40 的方格棋盘蛇头按方向键转弯吃到食物就变长撞墙或咬到自己就停止。它练的不是画得好看而是把数据—事件—画面这条链路彻底打通数据是网格对象事件是键盘和定时器画面是render铺出来的一堆div。数据先行网格、蛇与食物的建模写任何渲染或移动逻辑前先把三样状态定下来棋盘、蛇、食物。想清楚它们各自由什么数据结构承载后面的move()和render()会顺很多。40×40 网格对象二维数组与坐标顺序约定这一小节解决棋盘长什么样。项目要求一个 40×40 的方格对象每个格子初始是一个空格 。用二维数组最直白外层是行内层是列。var grid []; for (var row 0; row 40; row) { grid[row] []; for (var col 0; col 40; col) { grid[row][col] ; // 空格表示空位 } }坐标统一用[row, col]行在前、列在后来记。你之后在move()、render()、碰撞判定里都要读这个下标所以全篇务必保持顺序一致否则蛇会朝奇怪的方向跑。蛇对象初始位置 [20,20]、方向 r、蛇身数组蛇不能只记一个坐标。单点存不下蛇身有多长这个信息也没法判断蛇头是不是撞上了自己的身体——这两点后面都要用。所以把蛇建模成一组字段起始位置[20,20]、方向rr表示向右再加一个坐标对的数组来描述整条蛇身。var snake { position: [20, 20], // 起点在棋盘中央附近 direction: r, // 初始向右 body: [[20, 20]] // 蛇身 坐标对数组首元素是蛇头 };把蛇身写成坐标对的数组是吃食物变长和自碰撞检测能不能实现的前提移动时头插尾删蛇就往前走吃到食物时只插不删长度自然 1。render()把网格翻译成 div含 border 撑大盒子的布局坑这一小节解决画面怎么画出来。render()的职责是遍历网格把每个格子变成一个div铺进容器用到了 DOM_manipulation_and_events.md 里讲的createElement、appendChild这类原生方法。style .cell { width: 20px; height: 20px; border: 1px solid #ccc; box-sizing: border-box; } .snake { background: green; } .food { background: red; } /stylefunction render() { var box document.getElementById(grid); box.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); box.appendChild(cell); } } }注意这里特意埋了个坑给格子加边框会把盒子撑大。默认box-sizing是content-box意味着宽度 边框才算实际占位1600 个格子排下来就会换行错位。统一写上box-sizing: border-box让边框算在设定的宽高之内最省事。输入与心跳键盘事件 setTimeout 游戏循环蛇不会自己动。它靠两样东西活着你的方向键和一个按固定节奏推进回合的定时器。keydown 监听事件回调与 event.key这一小节解决怎么接收方向键输入。在document上挂一个keydown监听按键真正发生时浏览器才会执行你传入的函数——这个被抛向未来、到时才被调用的函数叫回调函数回调是 JS 事件模型的核心。document.addEventListener(keydown, function(event) { switch (event.key) { case ArrowUp: snake.direction u; break; case ArrowDown: snake.direction d; break; case ArrowLeft: snake.direction l; break; case ArrowRight: snake.direction r; break; } console.log(direction:, snake.direction); });用console.log盯一下方向变化能确认监听真的生效。另外keydown事件会沿 DOM 一层层向上传播——从内层节点冒到外层、最终到document的过程叫事件冒泡所以挂在document上就能捕获任意焦点位置的按键不必盯着具体某个元素。把监听挂在父级、让子元素的事件冒上来统一处理再结合e.target判断来源就是事件委托对动态生成的元素尤其好用。为什么用 setTimeout 而不是 setInterval / 死循环这一小节解释游戏的心跳为什么用setTimeout而不用别的。死循环while(true)忙等会把主线程占满页面直接卡死渲染也刷不出来setInterval是固定间隔盲跑到点了就执行既不好在游戏该结束时停也不方便在吃食物加速时改下一次延迟setTimeout每次只调度一次执行完这一回合才安排下一回合于是回合末能判断gameOver就return终止加速时也能动态读取新的speed。function gameLoop() { move(); render(); if (gameOver) { alert(Game Over!); return; } // 该停就停 setTimeout(gameLoop, speed); // 每回合结束才排下一回合 }关键在最后一行把下一次循环的调度放在这一回合的末尾。这样是否继续和下次等多久都能在这一刻决定加速、结束都随读随改。move()头插尾删完成一个回合这一小节解决蛇一步怎么走。move()每回合把蛇头向当前方向推进一格新头插到蛇身数组前端再从尾部弹出一格——长度不变蛇就走了。function move() { var head snake.body[0]; var newHead [head[0], head[1]]; // 复制坐标别共用引用 switch (snake.direction) { case u: newHead[0]--; break; case d: newHead[0]; break; case l: newHead[1]--; break; case r: newHead[1]; break; } snake.body.unshift(newHead); // 新头进数组 if (!eatFood(newHead)) { snake.body.pop(); // 没吃到就弹掉尾部保持长度 } }注意newHead必须是新数组用[head[0], head[1]]或head.slice()复制。如果直接拿body[0]的引用去改蛇头和蛇身第一节会被同一份数据绑死走两步就出 bug。规则即代码进食、撞墙与自碰撞可玩的贪吃蛇有三条硬规则吃到食物变长、撞墙结束、咬到自己结束。这一节把它们逐条写成判断。食物随机放置与 eatFood()先有食物才有进食。食物也是一个对象负责把自己随机放到棋盘上的一个格子。var food { position: [rand(40), rand(40)], place: function() { this.position [rand(40), rand(40)]; grid[this.position[0]][this.position[1]] F; } }; function rand(n) { return Math.floor(Math.random() * n); } function eatFood(newHead) { if (newHead[0] food.position[0] newHead[1] food.position[1]) { score 1; food.place(); // 吃到就重新撒一份 return true; } return false; }eatFood在蛇头坐标等于食物坐标时返回true加一分并调用place()撒出下一份食物。move()里就是靠它的返回值决定这一回合要不要保留尾部从而让蛇变长。越界判定与自碰撞必须讲跳过蛇尾的坑结束条件有两个分别对应出界和撞自己。出界好判断蛇头坐标一旦离开 0~39 的棋盘就是 Game Over。function isOutOfBoard(pos) { return pos[0] 0 || pos[0] 40 || pos[1] 0 || pos[1] 40; }自碰撞稍微棘手一点。蛇身本来就是坐标对数组所以判断新蛇头是否撞到自己就是看newHead是否已经在蛇身里。这里有个必须绕开的坑要跳过蛇尾——因为这一回合尾部马上就要被pop()弹掉它其实不是障碍。function hitsItself(newHead) { for (var i 0; i snake.body.length - 1; i) { // 少比一节 跳过尾部 var seg snake.body[i]; if (seg[0] newHead[0] seg[1] newHead[1]) return true; } return false; }循环上限取length - 1等于主动忽略即将离场的蛇尾。漏掉这一步的后果是蛇每走一步都可能撞上自己正要空出来的尾巴游戏动不动就误判结束。进阶三件套贴图蛇身、动态计分、逐帧加速 可玩之后原项目给了三个扩展方向恰好都是事件与效果的延伸练习。贴图蛇身用img替换O练动态插入图片元素与样式控制动态计分让每个食物随蛇变长而更值钱例如score snake.body.length把对象状态接到游戏数值上⚡逐帧加速每吃一份食物让speed略降。这正体现了setTimeout的调度优势——下一回合在上一回合末尾才排期加速参数随读随改不用重建定时器。// 在 eatFood 命中处追加 speed Math.max(50, speed - 5); // 有下限防止快到人眼跟不上 score snake.body.length; // 蛇越长这份食物越值钱仓库延伸阅读 项目只是练习的落点配套课件把每个知识点拆得更细。按你卡住的地方去查主题文档与本文关系事件与冒泡events_effects.md绑定、冒泡、阻止默认行为、解绑、键盘监听回调机制event_driven.mdsetTimeout与事件监听背后都是回调脚本加载时机js_to_browser.md脚本放body末尾或用defer保证 DOM 就绪原生 API 对照jq_basics.md老项目常用 jQuery 写法可对照原生 API动手验收清单做完别急着交。按这份清单逐条勾能过再往上推。40×40 网格能正确铺满 1600 个div无换行错位蛇头初始在[20,20]、方向r页面加载即调用render()四个方向键都能改变方向console里能看到变化每回合蛇前进一格吃到食物长度 1蛇头离开 0~39 边界时游戏停止咬到自己会停止且不误判蛇尾那一格每吃一份食物移动间隔speed明显变短代码已推到仓库并对照项目文档留了链接到这里事件监听、回调、定时器、对象建模这四样东西已经在你手里串成了一条能跑的线原始作业单见 project_events_effects.md。想继续练可以直接去看同目录的 project_ajax_forms.md 和project_apis.md把事件从页面内交互延伸到跨网络的异步请求。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →