自包含 HTML 实现可行走的 ASCII 赛博朋克城市:Canvas 渲染与程序化地图

发布时间:2026/9/3 18:32:56
自包含 HTML 实现可行走的 ASCII 赛博朋克城市:Canvas 渲染与程序化地图 用一个自包含 HTML 文件做成一座可以行走的 ASCII 赛博朋克城市看起来像是一个玩具项目实际上很适合用来练习 Canvas 渲染、程序化地图、键盘输入和摄像机跟随这套完整链路。你不需要安装框架不需要打包工具也不需要后端服务。只要把一份 HTML 文件保存到本地双击打开浏览器就能在城市街道上移动看到由#、、*这些字符组成的建筑、霓虹灯和雨水。这个项目的核心并不复杂城市是一张二维字符地图浏览器把地图里的每个字符画到 Canvas 上玩家通过方向键或 WASD 控制在城市里移动。整个过程全部在一个 HTML 文件内完成因此也特别适合作为学习前端游戏开发或程序化生成的入门案例。读完本文后你不仅能跑通项目还能知道每个字符、每个参数和每个函数背后存在的理由。1. 先理解这个项目要解决什么问题1.1 什么是“可行走的 ASCII 城市”“可行走”不是说玩家在一个静态图片里滚动背景而是玩家有一个真实坐标可以在城市中移动并且会被建筑挡住。城市的每个位置都由一个字符表示例如.表示地面或空地。表示道路。#表示建筑外墙。表示亮着的窗户。*表示霓虹灯。|表示路灯。^表示楼顶天线。表示玩家所在位置。从数据结构上看这些字符都放在一个二维数组map[y][x]中。y是行号x是列号。渲染时把数组中的字符逐格画出来移动时判断目标位置是不是可通行地面。这个设计非常接近经典 roguelike 游戏的地图模型。1.2 读者需要什么前置知识这个项目不依赖 Vue、React也不需要构建工具。阅读本文前你只需要了解最基础的 HTML、CSS 和 JavaScript 语法知道document.getElementById、addEventListener、数组和for循环的用法就够了。需要提前接触的几个浏览器 API 也很集中canvas.getContext(2d)获得 Canvas 绘制上下文。ctx.fillText在 Canvas 上绘制字符。requestAnimationFrame按浏览器刷新率驱动动画循环。keydown事件监听键盘输入。这些 API 都是浏览器内置功能所以文件可以完全自包含。1.3 这个项目能带来什么结果完成之后你会得到一份可运行的ascii-city.html。游戏运行效果包括一个约 100 列乘以 40 行的字符城市。玩家可以用方向键或 WASD 在街道上走动。摄像机跟随玩家保证屏幕始终以玩家为中心。建筑、路灯、霓虹灯、天线等字符按不同颜色绘制。雨线从屏幕上方落下增加赛博朋克氛围。页面右下角显示玩家当前坐标。如果只是展示静态 ASCII 图写一个pre标签就够了。但一旦需要角色移动、摄像机跟随、动态雨和霓虹灯发光Canvas 才是更合适的方案。2. 技术选型为什么用 Canvas 绘制字符2.1 自包含 HTML 的硬限制“自包含”意味着三个硬性要求不能引用外部图片、字体或 CSS 文件。不能依赖 CDN 上的 JavaScript 库。必须保存成单个 HTML 文件双击即可运行。这些限制排除了很多方案但也让项目更容易理解。你不需要关心打包、跨域和资源加载顺序只需要关注地图数据和渲染逻辑。2.2pre、Canvas、图片三种方案对比在不允许外部依赖的情况下最自然的三种渲染方案是渲染方式是否自包含逐字符变色动画和下雨发光效果实现复杂度preinnerHTML是可以但要用大量span较麻烦可以用 CSStext-shadow较低CanvasfillText是容易容易可以配合shadowBlur中等外部图片否不是字符难需要编辑图片低但不符合要求WebGL可能可以较复杂好好高pre方案不是不能做但每个字符都要包成span才能控制颜色而城市地图有成百上千个字符高频更新 DOM 的成本明显更高。Canvas 方案把字符当成图形绘制颜色、发光、雨线、摄像机偏移都变成了数学计算整体更统一。2.3 地图的本质二维字符数组不要被“ASCII 城市”这个名字吓到。它并不是要你用二进制码表去拼接字符而是把城市抽象成一个二维字符网格。常见项目中地图无非是下面这种结构const MAP [ ##########, #........#, #....#, #..#....#, ########## ];第一行是#表示围墙。第二行的.是地面。第三行的是道路。玩家则出现在某个可通行位置。但手写 100 行 100 列的字符串会非常痛苦所以本文采用程序化生成先用代码创建整张空白地图再