ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

矩形移动入门教程:Canvas与Pygame双实现,掌握游戏循环核心原理

矩形移动入门教程:Canvas与Pygame双实现,掌握游戏循环核心原理 刚开始接触游戏绘图或前端动画时“矩形移动”算得上最值得动手练一遍的入门案例。虽然它看起来只是把一个方块从画面 A 点移到 B 点但背后已经涉及事件监听、状态管理、绘制循环、边界处理这些后续开发必须掌握的底层能力。我结合自己平时的练习习惯把 Day3 这个“矩形移动”整理成一份完整教程包含浏览器端和 Python 两种实现方式代码可以直接复制运行。这篇文章适合刚学完基础语法、想通过小案例巩固知识的初学者也适合准备做小游戏或交课程设计的学生参考。读完后你会理解矩形移动的核心原理能在浏览器里用方向键控制矩形也能用 Python 独立写出一版同样效果的窗口程序。1. 背景与核心概念1.1 矩形移动是什么矩形移动简单说就是让一个矩形在画布或窗口中改变位置。现实中常见的形态包括游戏里的角色移动、编辑器里的选区拖动、页面上飘动的广告框、图表里可拖拽的标注块等。本质上这些都是“图形的位置状态发生变化”并伴随用户输入或自动动画来驱动变化。在编程实现时矩形移动通常包含三个部分位置状态矩形的 x、y 坐标以及宽和高。变化逻辑根据键盘、鼠标或时间变化更新 x、y 的值。重绘机制清掉旧画面在新位置重新绘制矩形。很多刚接触的人会把“绘制”和“移动”混在一起理解。其实移动本身只是修改坐标数值真正让用户看到移动效果的是“反复重绘”。每秒钟刷新足够多次视觉上就形成了连续移动。1.2 为什么拿矩形练手最合适矩形是 2D 图形中最简单的几何体。它不需要复杂的顶点计算不需要图片资源绘制一个矩形只需要提供“左上角坐标 宽 高”或者“中心点坐标 宽 高”即可。这使得我们可以把主要精力放在核心逻辑上而不是被图形学细节分散注意力。同时矩形移动涉及的知识点具有高度复用性。无论是后续做俄罗斯方块、贪吃蛇、飞机大战还是前端 Canvas 图表交互都用同一套“状态更新 重绘”思路。把 Day3 这个案例吃透后面接触更复杂的碰撞检测、动画缓动、物理运动时会轻松很多。1.3 常见实现技术选型实现矩形移动有很多种方式常见的有技术方案适用场景特点Canvas JavaScript网页小游戏、前端动画无需安装环境浏览器直接运行PygamePython 桌面小游戏语法简单适合教学和个人练手CSS animation / transition纯页面动画不需要脚本适合简单位移Java Swing / AWT桌面教学项目适合 Java 课程设计本文重点演示 Canvas JavaScript 和 Pygame 两种方案。前者零依赖复制代码到 HTML 文件就能跑后者能作为 Python 学习者的图形入门两者原理完全相通。2. 环境准备与版本说明这个项目对环境要求很低几乎是“有浏览器就能运行”。2.1 浏览器端环境操作系统Windows、macOS、Linux 均可。浏览器Chrome、Edge、Firefox 等现代浏览器都可以推荐 Chrome。开发工具VS Code、Sublime Text、Notepad 都可以甚至系统自带的记事本也能写。运行方式双击打开 HTML 文件或者使用 VS Code 的 Live Server 插件启动本地服务。2.2 Python 端环境Python 版本建议 Python 3.8 及以上。Pygame 版本以 2.x 版本为例安装命令如下pip install pygame如果下载速度较慢可以使用国内镜像源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以在 Python 交互环境中验证import pygame print(pygame.ver)2.3 项目结构为了保持代码清晰浏览器端我采用三个文件分离的结构day3-rect-move/ ├── index.html ├── css/ │ └── style.css └── js/ └── main.js如果你更喜欢单文件也可以把所有代码写进一个 HTML 文件里效果相同。分开写主要是为了养成工程化习惯方便后续扩展。3. 核心技术原理解析在写完整代码之前先拆解几个关键概念。明白了这些后面看代码就不会觉得是在“背代码”。3.1 Canvas 坐标系Canvas 的坐标系和数学里的坐标系不太一样。它的原点在画布左上角x 轴向右为正方向y 轴向下为正方向。(0,0) ------------------- x 增加 | | | y 增加 | | v这意味着想让矩形向右移动x 坐标增加。想让矩形向下移动y 坐标增加。想让矩形向左移动x 坐标减少。想让矩形向上移动y 坐标减少。很多新手在写上下移动时容易把“向上”误写成 y 坐标增加结果发现矩形反而往下跑了。牢记坐标系方向这个坑就能避开。3.2 游戏循环输入、更新、绘制动画的本质是“不断刷新画面”。一个标准的动画循环包括三个步骤读取用户输入例如键盘哪个键被按下。更新游戏状态例如矩形坐标发生变化。清屏并绘制新画面。在 JavaScript 中常用requestAnimationFrame来驱动循环在 Pygame 中常用while循环配合pygame.display.flip()来刷新窗口。两者虽然语法不同思路完全一致。3.3 按键状态表的作用初学者最容易犯的一个错误是在keydown事件里直接修改矩形位置window.addEventListener(keydown, (e) { rect.x 5; // 这种写法不是不行但问题很多 });这种做法会出现两个问题按住方向键不放时键盘事件会先触发一次间隔片刻后才连续触发移动起来一顿一顿的。同时按两个方向键时最后一次触发的事件会覆盖前一个难以实现斜向移动。更稳重的做法是维护一个按键状态表const keys {}; window.addEventListener(keydown, (e) { keys[e.key] true; }); window.addEventListener(keyup, (e) { keys[e.key] false; });然后在更新循环里统一判断if (keys[ArrowLeft]) rect.x - speed; if (keys[ArrowRight]) rect.x speed;这样不管是单键还是多键移动都会平滑连续也不会互相覆盖。3.4 清屏与绘制绘制矩形前必须把上一帧的画面清除掉否则会留下残影。Canvas 中使用ctx.clearRect(0, 0, canvas.width, canvas.height);Pygame 中使用screen.fill((45, 45, 68))前者是把画布内容清空成透明后者是用背景色覆盖整个窗口。两者效果类似但 Pygame 的fill会有一个“刷背景色”的过程所以背景色需要自己指定。4. 浏览器端实战Canvas JavaScript 实现矩形移动下面通过完整项目来演示“矩形移动”在浏览器中的实现。4.1 创建 HTML 页面先创建index.html负责承载画布和引入样式、脚本。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleDay3 矩形移动/title link relstylesheet hrefcss/style.css /head body h1Day3 矩形移动/h1 p按下键盘方向键控制矩形移动/p canvas idgameCanvas width800 height500/canvas script srcjs/main.js/script /body /html这里需要注意canvas标签上的width和height属性是画布的实际分辨率不写的话默认是 300×150。如果后续发现画面模糊优先检查这里。4.2 编写页面样式创建css/style.css把页面居中并给画布一个深色背景方便观察矩形。* { margin: 0; padding: 0; box-sizing: border-box; } body { background: #1e1e2e; color: #f0f0f0; font-family: Microsoft YaHei, PingFang SC, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; } h1 { font-size: 24px; margin-bottom: 8px; } p { margin-bottom: 16px; color: #aaa; } canvas { background: #2d2d44; border: 2px solid #aaa; border-radius: 8px; }样式不是重点但一个舒适的页面观感会提升调试体验。4.3 编写矩形移动核心逻辑创建js/main.js这是整个项目的核心。const canvas document.getElementById(gameCanvas); const ctx canvas.getContext(2d); // 矩形初始参数 const rect { x: 375, // 左上角 x 坐标 y: 225, // 左上角 y 坐标 width: 50, // 矩形宽度 height: 50, // 矩形高度 speed: 5, // 移动速度 color: #ff6b6b }; // 按键状态表 const keys {}; // 监听键盘按下事件 window.addEventListener(keydown, (e) { const key e.key; if ([ArrowUp, ArrowDown, ArrowLeft, ArrowRight].includes(key)) { // 阻止方向键滚动页面 e.preventDefault(); } keys[key] true; }); // 监听键盘抬起事件 window.addEventListener(keyup, (e) { const key e.key; keys[key] false; if ([ArrowUp, ArrowDown, ArrowLeft, ArrowRight].includes(key)) { e.preventDefault(); } }); // 更新矩形位置 function update() { if (keys[ArrowLeft]) rect.x - rect.speed; if (keys[ArrowRight]) rect.x rect.speed; if (keys[ArrowUp]) rect.y - rect.speed; if (keys[ArrowDown]) rect.y rect.speed; // 边界检测限制矩形不能移出画布 if (rect.x 0) rect.x 0; if (rect.y 0) rect.y 0; if (rect.x rect.width canvas.width) { rect.x canvas.width - rect.width; } if (rect.y rect.height canvas.height) { rect.y canvas.height - rect.height; } } // 绘制画面 function draw() { // 清除上一帧内容 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制矩形 ctx.fillStyle rect.color; ctx.fillRect(rect.x, rect.y, rect.width, rect.height); } // 动画循环 function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); } // 启动游戏循环 gameLoop();关键点说明clearRect在每次绘制前清空画布避免矩形拖出残影。update负责根据按键状态修改坐标同时做边界限制。draw只负责画不参与逻辑计算。requestAnimationFrame会以约 60 帧每秒的频率调用gameLoop形成平滑动画。4.4 运行与验证保存以上三个文件后在浏览器中打开index.html你会看到如下页面页面标题为“Day3 矩形移动”。中间有一个 800×500 的深色画布。画布中心有一个红色矩形。此时按下键盘方向键按左键矩形向左移动。按右键矩形向右移动。按上键矩形向上移动。按下键矩形向下移动。同时按右键和下键矩形向右下方斜向移动。当矩形到达画布边缘时它会停在边界位置不会“跑丢”。4.5 扩展思路加入移动速度调整如果想体验不同的移动手感可以临时修改rect.speed的值speed: 5, // 改成 1 会非常慢改成 20 会非常快也可以在页面中加入一个滑块控件运行时动态调整速度这里留作练习。5. Python 界面版Pygame 实现矩形移动如果你正在学习 Python想体验桌面窗口程序的开发可以用 Pygame 实现同样的效果。为了和浏览器版形成对照我先创建一个rect_move.py文件然后逐步解释。5.1 完整代码import pygame import sys # 初始化 Pygame pygame.init() # 创建窗口 screen pygame.display.set_mode((800, 500)) pygame.display.set_caption(Day3 矩形移动) # 控制帧率 clock pygame.time.Clock() # 矩形参数 rect_x 375 rect_y 225 rect_width 50 rect_height 50 speed 5 color (255, 107, 107) running True while running: # 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 获取按键状态 keys pygame.key.get_pressed() if keys[pygame.K_LEFT]: rect_x - speed if keys[pygame.K_RIGHT]: rect_x speed if keys[pygame.K_UP]: rect_y - speed if keys[pygame.K_DOWN]: rect_y speed # 边界检测 if rect_x 0: rect_x 0 if rect_y 0: rect_y 0 if rect_x rect_width screen.get_width(): rect_x screen.get_width() - rect_width if rect_y rect_height screen.get_height(): rect_y screen.get_height() - rect_height # 填充背景 screen.fill((45, 45, 68)) # 绘制矩形 pygame.draw.rect(screen, color, (rect_x, rect_y, rect_width, rect_height)) # 刷新画面 pygame.display.flip() # 控制 60 帧每秒 clock.tick(60) # 退出程序 pygame.quit() sys.exit()5.2 代码解释这段 Python 代码和 JavaScript 版本的结构非常相似pygame.key.get_pressed()返回一个包含所有按键状态的元组按下为True抬起为False。这一行代码就替代了 JavaScript 版的keys对象。pygame.draw.rect(screen, color, (rect_x, rect_y, rect_width, rect_height))用于绘制矩形参数依次为目标窗口、颜色、矩形位置和大小。clock.tick(60)让循环每秒最多执行 60 次避免程序在性能好的电脑上跑得太快。5.3 运行方式在终端中进入文件所在目录执行python rect_move.py运行后会出现一个标题为“Day3 矩形移动”的窗口。你可以用方向键移动矩形点击窗口右上角的关闭按钮即可退出程序。注意需要先确认 Pygame 已经安装成功如果提示ModuleNotFoundError: No module named pygame说明没有安装依赖。6. 常见问题与排查思路不管是前端 Canvas 还是 Pygame初学者都会遇到一些相似的问题。下面列出高频报错和解决思路。问题现象常见原因解决思路浏览器中看不到矩形JavaScript 文件路径错误或 Canvas 高度为 0确认script标签是否正确检查 Canvas 的 width/height 是否设置按方向键时页面跟着滚动没有调用preventDefault()在keydown事件中对方向键调用e.preventDefault()矩形移动一顿一顿的不流畅在keydown事件里直接修改坐标改为使用按键状态表 游戏循环更新坐标矩形拖出残影没有在绘制前清除画布检查是否调用了clearRect或screen.fill同时按两个键时只有一个方向生效没有维护按键状态表后触发的事件覆盖了前面的使用keys对象记录所有按键状态在循环中统一判断Python 运行后窗口一闪而过代码中缺少while循环或者没有调用pygame.quit()确认事件循环结构是否完整窗口中文标题乱码文件编码不是 UTF-8在文件头部添加# -*- coding: utf-8 -*-或统一保存为 UTF-8 编码Canvas 画面模糊使用 CSS 设置了画布尺寸但未设置标签属性在canvas标签上设置width和height属性矩形超出边界后消失缺少边界检测逻辑在更新坐标后判断是否超出画布并修正坐标排查小建议先用最简单的代码跑通“显示矩形”然后再逐步加入键盘控制、边界检测。每加一个功能就运行一次出了问题能快速定位到是哪一步引入的。7. 工程实践与扩展建议当“矩形移动”已经从能跑到跑得顺畅之后可以继续从这几个方向做工程化提升。7.1 让代码结构更清晰如果只是几十行的练习代码全部写在全局作用域里没有问题。但当矩形数量变多、逻辑变复杂后建议把矩形抽象成一个类。这里提供一个 JavaScript 的封装思路class RectObject { constructor(x, y, width, height, speed, color) { this.x x; this.y y; this.width width; this.height height; this.speed speed; this.color color; } update(keys, canvas) { if (keys[ArrowLeft]) this.x - this.speed; if (keys[ArrowRight]) this.x this.speed; if (keys[ArrowUp]) this.y - this.speed; if (keys[ArrowDown]) this.y this.speed; if (this.x 0) this.x 0; if (this.y 0) this.y 0; if (this.x this.width canvas.width) { this.x canvas.width - this.width; } if (this.y this.height canvas.height) { this.y canvas.height - this.height; } } draw(ctx) { ctx.fillStyle this.color; ctx.fillRect(this.x, this.y, this.width, this.height); } }这样主循环会变得非常简洁const player new RectObject(375, 225, 50, 50, 5, #ff6b6b); function gameLoop() { player.update(keys, canvas); ctx.clearRect(0, 0, canvas.width, canvas.height); player.draw(ctx); requestAnimationFrame(gameLoop); }从“面向过程”过渡到“面向对象”是写小型游戏项目时很自然的一步。7.2 合理处理帧率与速度在 Pygame 中clock.tick(60)保证了固定帧率因此速度值可以直接理解为“每帧移动的像素数”。但在 JavaScript 中不同设备的刷新率可能不同有的电脑是 60Hz有的是 144Hz。如果速度固定为每帧 5 像素高刷新率屏幕上的矩形会比低刷新率屏幕快得多。解决思路是引入“时间增量”也就是计算上一帧到这一帧的间隔时间进而把速度单位改为“每秒移动像素数”。这是游戏开发中比较成熟的做法。下面是一个简化示例let lastTime 0; function gameLoop(timestamp) { const deltaTime (timestamp - lastTime) / 1000; // 单位秒 lastTime timestamp; // speed 表示每秒移动多少像素 const moveDistance rect.speed * deltaTime; if (keys[ArrowLeft]) rect.x - moveDistance; if (keys[ArrowRight]) rect.x moveDistance; // 后续绘制逻辑省略 requestAnimationFrame(gameLoop); } requestAnimationFrame(gameLoop);这个优化对小型练手项目不是必须的但提前了解能避免以后做复杂动画时遇到“不同电脑速度不一样”的困惑。7.3 边界检测的策略边界检测有两种常见策略限制坐标不能超出画布适用于矩形移动类游戏。碰到边界反弹适用于打砖块、弹球类游戏。Day3 案例使用的是第一种。第二种实现也很简单当坐标超出边界时把速度方向反过来即可if (rect.x 0 || rect.x rect.width canvas.width) { rect.vx -rect.vx; // vx 为水平速度 }这个思路可以留作下一篇练习。7.4 拓展为“多矩形移动”掌握了单个矩形移动后可以尝试维护一个矩形列表让多个矩形以不同颜色、速度在同一画布中运动。关键在于把“状态更新”和“绘制”逻辑抽取成可复用的函数通过循环遍历所有矩形执行。这一步做完你对数组操作和函数抽象的理解也会加深。7.5 结合碰撞检测矩形移动最常见的配套知识点是碰撞检测。比如判断两个矩形是否重叠可以用 AABB 碰撞检测算法Axis-Aligned Bounding Box。核心判断条件是function checkCollision(a, b) { return ( a.x b.x b.width a.x a.width b.x a.y b.y b.height a.y a.height b.y ); }这套公式在所有 2D 游戏开发中都很常见值得收藏。7.6 代码管理习惯即使是小练习也建议从一开始就养成好习惯常量集中定义不要散落在代码各处。更新和绘制分开避免逻辑混乱。变量命名要做到“看到名字就知道含义”。每次改动只做一件事方便回滚调试。这些习惯在写几百行代码时看不出明显优势等到了几千行、上万行的项目里会直接影响你的开发效率。8. 总结与下一步方向Day3 的矩形移动虽然只是一个很小的案例但它串起了图形编程中最基础也最重要的一整条链路坐标系理解、用户输入处理、状态更新、绘制循环、边界检测。这些能力在任何图形界面项目里都会反复使用。通过本文你已经掌握了两套实现方案。浏览器端使用 Canvas JavaScript零依赖、轻量适合快速验证想法Python 端使用 Pygame代码结构直观适合理解窗口程序运行机制。两种方案的原理互通不需要死记硬背只要记住“输入 - 更新 - 绘制”这个循环就够了。下一步的练习方向有很多给矩形加入加速度和摩擦力模拟物理移动为矩形添加图片或纹理代替纯色填充加入第二个矩形并实现碰撞检测把移动目标从键盘控制改成鼠标点击寻路。每一方向都会进一步锻炼你的编程能力。如果运行过程中遇到了文章里没有列出的报错建议先看控制台或终端的错误信息再倒推是哪一部分代码产生的异常。技术学习中最快成长的方式就是遇到问题、拆解问题、解决问题。希望这份笔记对你的学习有所帮助动手敲一遍代码感受会比只看文章深刻得多。
返回列表