做了大半年的少儿编程课,我发现一个挺有意思的现象:同样一个3D跑酷玩法,先用Scratch搭一版、再用Python重写一版,拿给两批刚入门的孩子看,反应完全不一样。Scratch那版十分钟就有人跑出成绩,Python那版第一天几乎没人跑通,但一周之后,Python那批人开始自己改重力、调关卡、加道具了。这篇文章我就把这两版的做法完整拆一遍,从透视投影的数学,到克隆体的坑,再到Python引擎选型和无限跑道的生成逻辑,不管你是刚摸Scratch的小学生,还是刚装完Python的成年人,都能照着做出来。核心不是让你背代码,而是搞明白"同样的3D跑酷,换个工具为什么就得换一套思路"。顺手把环境配置、模型资源、性能排查这些新手最容易卡住的地方也一并讲清楚。
1. 选3D跑酷做迁移项目,我图的是什么
很多家长和初学编程的朋友都问过同一个问题:Scratch学到一定程度,到底该拿什么项目过渡到Python?我的答案一直是3D跑酷。原因不复杂,跑酷这个题材把"数学"、"物理"、"游戏循环"三件在编程里躲不开的事,全都摆在明面上了,你没法绕过去。而且它天生有正反馈——跑得远不远,一眼就能看出来,不用等老师给分。
1.1 跑酷这个玩法为什么特别适合练手
先说它到底在练什么。一个3D跑酷游戏,本质上是在不停地做四件事:把三维空间里的坐标算成屏幕上的像素、让玩家在每一帧都读一次输入、让物体沿着时间轴运动、判断两个东西有没有撞上。这四件事分别对应投影数学、事件驱动、帧循环和碰撞检测,恰好是编程入门里最核心的四个骨架。
跑酷还有一个天然优势:它的玩法边界特别清楚。你不需要设计复杂的剧情、对话、背包、任务系统,只要"往前跑、躲障碍、吃金币"三条规则,就能做出一个能玩的东西。对初学者来说,边界清楚意味着你能把精力全部压在"怎么实现"上,而不是"要做什么"上。我见过太多人一上来就想做开放世界,结果三个月连个能跑的角色都没做出来。
另外,跑酷的难度曲线是可以自己拧的。跑道宽一点、障碍稀一点,小孩能玩;换成三车道、加速下坡、加分段障碍,成年人也觉得刺激。同一套代码,换个参数就是不同的游戏,这种"一套骨架撑起多种玩法"的体验,对理解代码复用非常有帮助。
还有一点常被忽略:跑酷的视觉效果很容易做得"看起来像3D"。哪怕你其实只是把一张图放大缩小,只要透视关系对了,大脑就会自动补出纵深。这正是Scratch能做出3D效果的根本原因,也是它和真3D引擎之间最省力的那条桥。
1.2 Scratch版和Python版各自该承担什么任务
我个人的分工是这样的:Scratch版负责把"玩法"跑通,Python版负责把"工程"搭起来。
Scratch那一版,我要求能玩、手感顺、有分数,但允许它全是"假的"——假的3D、假的跑道、假的模型,能骗过眼睛就行。因为Scratch的强项是快速试错,几个积木一拖,五分钟就能知道这个跳跃高度合不合适。这个阶段最重要的是把手感调对,而不是把画面做真。
Python那一版,我要求代码能拆成模块、场景能无限延伸、参数能写进配置文件。也就是说,Python版的重点从"能不能玩"变成了"能不能改、能不能扩"。因为一旦进入真3D,模型、光照、相机、坐标系全都要自己管理,如果代码还是一坨,改一个参数就崩一片。
我常跟学生讲一句糙话:Scratch版是草稿纸,Python版是施工图。草稿纸上的东西可以歪,但位置得对;施工图上的东西必须齐,但画得丑没关系。理解了这层分工,后面所有的技术选型就都有依据了。
2. Scratch版:用假3D骗过眼睛
大部分Scratch的"3D"其实是伪3D。舞台本身是平的,所有角色都在一个二维平面上,所谓纵深,全靠缩放和位移模拟出来的。想明白了这一点,做起来就简单了——你要做的不是建一个三维世界,而是维护一个虚拟的深度值 z,然后每一帧根据 z 把物体的位置和大小重新算一遍。
2.1 透视投影的三个公式,手算一遍就懂了
真实相机成像遵循一个非常朴素的规律:离得越近,看起来越大。写成公式就是屏幕尺寸等于焦距乘以物体实际尺寸,再除以距离。放到Scratch里,舞台宽480、高360,我一般把焦距 f 取30左右,相机高度 h 取40。公式就三条:
- 屏幕尺寸 = f × 物体实际高度 ÷ z
- 屏幕纵坐标 = 地平线坐标 − f × 相机高度 ÷ z
- 屏幕横坐标 = 消失点横坐标 + (世界横坐标 − 相机横坐标) × f ÷ z
地平线我放在 y = 60 的位置,这样天空留得下,地面也看得见。消失点横坐标就是0,舞台正中。下面这张表是我实际算出来的一组值,物体实际高度按60算,你可以直接对照着看:
| 深度 z | 屏幕尺寸 | 屏幕纵坐标 | 视觉效果 |
|---|---|---|---|
| 100 | 18 | -12 | 远处小点,刚冒头 |
| 50 | 36 | -24 | 能看出轮廓 |
| 25 | 72 | -48 | 形状清晰 |
| 12.5 | 144 | -96 | 快要撞上 |
| 6 | 300 | -200 | 冲出屏幕,判定完成 |
看懂这张表,伪3D就懂了一半。你会发现尺寸和纵坐标都在随 z 的减小而急剧变大,这种"加速膨胀"的感觉正是透视带来的压迫感。实际做的时候,障碍物的 z 值每帧减2到4,越靠近减得越"显大",玩家自然会产生速度感。
有个细节要特别提醒:尺寸和纵坐标用的除法,很容易出现除以零。所以 z 必须设一个下限,比如小于3就判为"已通过"并回收,绝对不能让它跑到0甚至负数。我第一次做的时候没设下限,障碍物瞬间变成无限大铺满舞台,卡得整个项目动不了。
2.2 跑道、障碍与克隆体的分工
Scratch里最省事的跑道做法,是用两条向消失点收拢的线表示路边,再在地平线以下铺几条横向的分割线,随着 z 变化调整间距。这两条线用画笔模块画,每帧重画一遍,比用造型切换灵活得多。横向分割线的间距也要按透视规律来,越靠近相机间距越大,这个用同一个公式算就行。
障碍物我全部用克隆体来做。本体留在舞台外当模板,启动时广播一下,让克隆体依次出现,每个克隆体自己维护变量 z、车道、类型。这里有个Scratch的经典坑必须讲:克隆体不会自动跟着本体跑。很多人以为改了本体的位置,克隆体就跟着动了,其实每个克隆体是独立的实例,它有自己的一套坐标和变量。
正确的做法是给每个克隆体用"仅适用于当前角色"的局部变量保存 z,然后在克隆体的循环里自己算位置、自己更新 z、自己判断回收。如果某些数据需要所有克隆体共享,比如全局速度、当前分数、游戏状态,就用普通变量,并且记住克隆体读普通变量时读到的是全局值,谁改都会影响所有克隆体。这个区别搞混了,会出现"撞一个障碍物所有障碍物一起消失"这种诡异现象。
我还习惯给每个克隆体加一个"出生延迟",让障碍物错开出现,而不是同一帧全部生成。否则所有障碍物 z 值一样,视觉上就是一堵墙一起压过来,看着很假。延迟用随机数就行,比如0到40帧之间随机。
2.3 用亮度和大小做空气透视
这条是我个人最得意的技巧,也正好用上了Scratch的亮度特效。真实世界里,远处的物体不只是小,还会发灰、发蓝、对比度降低,这叫空气透视。如果你只在Scratch里做缩放,画面会显得很"塑料",因为远处的障碍物依然颜色鲜艳。
我的做法是:给每个克隆体根据 z 值动态设置亮度。z 很大时,亮度设成负的,比如 −40,让障碍物偏暗、偏灰;z 减小的过程中,亮度逐渐回到0。这样障碍物冲过来的时候,颜色会从灰蒙蒙一步步变得鲜艳,纵深感立刻就出来了。如果障碍物本身是红色,我还会让色相稍微往蓝色偏一点,模拟大气散射。
除了亮度,还有个更省事的办法:在消失点位置放一个和天空同色的矩形,越远越小、越透明,用来遮住远处障碍物的"出生瞬间"。障碍物从 z=100 开始移动,其实它刚生成的时候已经在画面里了,如果不用雾化遮挡,你会看到它凭空出现。用一层同色渐变的遮挡层盖住 z 在80以上的区域,视觉上就变成了"从雾里钻出来"。
这两个技巧加起来,成本几乎为零,效果提升非常明显。我一直觉得Scratch做3D的精髓不在于把画面做真,而在于用最少的资源骗过眼睛,亮度和遮挡就是性价比最高的两招。
2.4 手感调优:重力、跳跃与判定
画面骗过去了,接下来是手感。手感这东西很玄,但拆开来看其实就是三组数字:重力加速度、跳跃初速度、判定框大小。
重力这块,我不用"每帧减固定值"这种线性方式,因为那样跳起来像电梯。真实的重力是速度每帧增加一个加速度值。我会设一个竖直速度 vy,每帧执行 vy = vy − 重力,然后角色的屏幕纵坐标加上 vy。起跳时给 vy 一个正的初速度,它就自己上升、减速、到达顶点、再加速下落,形成一条抛物线。这条曲线的形状由"重力"和"初速度"两个参数决定,初速度越大跳得越高,重力越大落得越快,试几次就能调出舒服的弧线。
判定框要注意,不能直接拿角色的造型尺寸当判定框。跑酷游戏里玩家对角色的视觉大小和判定大小感受是不一样的,通常判定框要略小于视觉尺寸,玩家才会觉得"我明明躲过去了"。我的经验是判定框取视觉尺寸的80%左右比较友好。如果用克隆体做障碍物,判断碰撞时也别用"碰到角色"这个积木,而是自己算坐标距离,因为克隆体的碰撞判定在高频移动时很容易漏检。
还有个细节:跳跃过程中最好屏蔽二次起跳,除非你想做二段跳。我见过不少学生的作品,按住空格能一直往上飞,就是因为没有加"是否在地面"这个状态变量。加一个"着地"标志位,只有着地时才能起跳,问题就解决了。
3. 迁移到Python:环境与引擎选型
Scratch版的玩法确认没问题了,接下来就是重写。这一步新手最容易卡住的其实不是代码,而是环境。Python装不上、编辑器配不好、库装不上、装上了又跑不起来,这些琐事能耗掉一整天的热情。我先把环境这块讲透,再谈引擎选型。
3.1 先把环境搭稳:Python、VS Code、PyCharm怎么选
Python安装本身不复杂,去官网下载对应系统的安装包,安装时务必勾选"Add Python to PATH"。这个勾选框如果漏了,之后在命令行敲 python 会提示找不到命令,很多人就卡在这一步。装完之后在命令行敲 python --version,能打印出版本号就算成功。如果同时装了多个版本,用 py -3.12 这种带版本号的方式调用,别让版本打架。
编辑器我一般推荐三档。零基础、只想写几十行脚本的,直接用 Python 自带的 IDLE,开箱即用,不用配置。想认真学、要写多文件项目的,用 VS Code,装一个 Python 扩展就够,它轻、启动快、对新手友好。需要调试大型项目、接手别人代码的,用 PyCharm 社区版,它的调试器和代码跳转确实强,但首次启动慢,配置项也多。
不管用哪个编辑器,有个共同的关键配置叫"解释器路径"。你得明确告诉编辑器,用哪个Python来解释你的代码。VS Code里按 Ctrl+Shift+P,输入 Python: Select Interpreter,选你刚装的那个版本。PyCharm里在设置里找到解释器一项,同样指定路径。这一步不配,编辑器会用系统里某个你不知道的Python,装库的和跑代码的不是同一个,就会出现"我明明装了库却提示找不到模块"这种经典问题。
另外强烈建议每个项目单独建一个虚拟环境。命令行在项目目录下执行 python -m venv venv,然后激活它,之后所有 pip install 都装在项目内部,互不污染。这个习惯看着麻烦,但能省掉未来无数次"库版本冲突"的崩溃。
3.2 四个3D方案横向对比
Python做3D,路子不止一条。我把常见的四个方案列在下面,你可以按自己的目标挑:
| 方案 | 上手难度 | 适合场景 | 主要缺点 |
|---|---|---|---|
| Ursina | 低 | 快速做能玩的小3D游戏 | 文档偏少,高级效果受限 |
| Panda3D | 中 | 需要精细控制的3D项目 | 概念多,学习曲线陡 |
| Pygame自写投影 | 中高 | 想搞懂3D原理 | 什么都得自己写,产出慢 |
| Three.js(浏览器) | 中 | 想直接发到网页、手机能玩 | 用JavaScript,不是Python |
如果你的目标是"一周内做出能跑的3D跑酷",我直接推荐 Ursina。它把相机、模型、场景、输入全封装好了,一行代码就能创建一个立方体,三行代码就能让相机跟着玩家跑。初学者最需要的不是控制力,而是尽快看到东西动起来。
如果你想真正搞懂3D是怎么算出来的,那就用 Pygame 自己写投影。把第二章那三个公式换成代码实现一遍,你会对"3D其实是2D的障眼法"这句话有全新的理解。这条路慢,但学完通透。
如果作品最终要发给同学用手机玩,那 Three.js 是唯一合理的答案,因为它跑在浏览器里,发一个链接就行,不用让别人装Python。代价是你要多学一门JavaScript,而且第二章的数学要重新实现一遍。我通常建议先把Python版做完,再把逻辑翻译到Three.js,两者的思路是通的。
3.3 模型、贴图与免费资源的处理
真3D和伪3D最大的区别是,你得处理实际的三维模型文件。常见格式是glTF和GLB,前者是文本、后者是二进制打包版,Ursina两者都能直接加载。找免费模型的时候注意看授权协议,很多网站的资源只允许学习使用,商用要另外授权,给作品集用一般没问题,但别直接拿去卖。
模型拿回来之后经常要处理三件事:缩放、朝向、锚点。不同软件导出的模型单位不一样,有的默认1单位是1厘米,有的1单位是1米,直接加载可能大得离谱或者小得看不见。朝向也是一个坑,有的模型默认朝前,有的朝侧,你要么在代码里旋转,要么在建模软件里改好锚点再导出。我的习惯是统一导出前把所有模型的锚点定在"脚底中心",这样在代码里放位置直接用坐标就行,不用额外补偿。
贴图别追求高清。跑酷游戏里障碍物一闪而过,贴图超过1024像素纯属浪费显存。能用纯色就用纯色,能用简单材质就用简单材质,颜色区分车道比精细贴图有效得多。我做过对比,把障碍物从写实贴图换成纯色方块,帧率提升明显,而玩家的辨认可读性反而更好。
4. Python版3D跑酷动手实现
环境搭好、方案定了,下面进正题。我以 Ursina 为例,因为它是新手最快的路径。整个游戏拆成四块:场景骨架、玩家控制、跑道生成、计分与碰撞。
4.1 场景骨架与相机
先把最小的能跑起来的骨架搭出来。Ursina的入口是创建一个App对象,之后所有东西都挂在这个应用上。相机默认是透视相机,位置可以自己设。跑酷最好用斜后方的第三人称视角,相机在玩家后方偏上,稍微向下俯视,这样既看得清前方障碍,又能看到玩家动作。
from ursina import * app = Ursina() # 玩家:一个蓝色方块,锚点设在脚底 player = Entity(model='cube', color=color.azure, scale=(0.8, 1.6, 0.8), position=(0, 0.8, 0)) # 相机放在玩家后上方,俯视前方 camera.position = (0, 5, -8) camera.rotation_x = 20 def update(): # 让相机跟随玩家横向移动 camera.x = player.x * 0.5 app.run()先别急着加逻辑,把这段跑起来,你应该能看见一个蓝色方块和它前方的空场景。确认能跑,再往下加。这一步很关键,我见过太多人一口气写完两百行,结果一个错误都定位不到。增量开发在游戏里尤其重要,因为3D的坐标系错了,你连方向都分不清。
场景的地面我先用几个长条立方体拼,而不是一次性铺一大块,因为后面做无限跑道时,这些长条要循环回收。每个长条长10个单位、宽8个单位、厚度0.5,沿Z轴依次排开。相机俯视20度,配合地面就很有跑道的纵深感了。
4.2 玩家移动与换道的手感实现
跑酷的移动分两个维度:横向换道和纵向跳跃。横向我不用连续移动,而是用三车道切换,手感更干脆,也更容易做判定。车道位置固定为 −2、0、2 三个X坐标,按左右方向键时目标车道加一或减一,然后用平滑插值把玩家从当前X移到目标X。
LANE_X = [-2, 0, 2] player.lane = 1 # 从中间车道开始 def input(key): if key == 'left arrow' and player.lane > 0: player.lane -= 1 if key == 'right arrow' and player.lane < 2: player.lane += 1 if key == 'space' and player.on_ground: player.vy = 9 # 起跳初速度 player.on_ground = False def update(): # 横向平滑换道 target_x = LANE_X[player.lane] player.x = lerp(player.x, target_x, time.dt * 12) # 纵向重力 player.vy -= 30 * time.dt player.y += player.vy * time.dt if player.y <= 0.8: player.y = 0.8 player.vy = 0 player.on_ground = True这里的数字都是我试出来的。重力30配上初速度9,跳跃高度大概在1.5到2个单位,滞空时间约0.6秒,越过一个标准障碍刚好够用,也不会飘。lerp 的系数12决定换道快慢,太小会觉得拖沓,太大又像瞬移。time.dt 是上一帧到这一帧的时间差,用它做乘法,游戏在不同帧率的机器上速度才一致,这一点很关键——我见过太多作品在同学的电脑上快得没法玩,就是因为忘了乘 dt。
还有个手感细节:换道的时候最好加一点点身体倾斜。把玩家绕Z轴旋转个10度,朝向目标车道方向,眼睛会立刻觉得"这个人是在拐弯"而不是"平移"。视觉上的小作弊,感受差很多。
4.3 无限跑道的生成与回收
跑酷要一直跑下去,地面就必须无限延伸。但你不能真的生成无限多的地面,那样显存会爆。标准做法是对象池:预先生成固定数量的地砖,排成一列,当某块地砖跑到玩家身后一定距离后,把它挪到最前面去。
TILE_LEN = 10 TILE_COUNT = 15 tiles = [] for i in range(TILE_COUNT): t = Entity(model='cube', color=color.gray, scale=(8, 0.5, TILE_LEN), position=(0, -0.25, i * TILE_LEN)) tiles.append(t) def update(): for t in tiles: # 地砖相对玩家向后移动,制造前进感 t.z -= speed * time.dt if t.z < camera.z - TILE_LEN: t.z += TILE_COUNT * TILE_LEN原理其实很简单:玩家和相机不动,让整个世界往后倒。这样坐标系不会随着玩家跑远而变得巨大,也就避免了浮点数精度问题。这个技巧在真3D里非常常用,因为坐标一旦跑到几千几万,小数的精度就跟不上了,物体会开始抖动。
障碍物的生成我用一个计时器,每隔一小段随机时间在前方生成一组。生成位置取三个车道之一,然后让它们跟着地面一起往后移动。注意障碍物的生成不能用随机位置,必须落在车道坐标上,否则玩家根本躲不开——这是新手最容易犯的错误,出了一个"理论上能躲但实际上躲不掉"的游戏。
回收逻辑要写清楚:障碍物 z 值小于相机 z 减去一点余量时,就销毁它,同时判定为"成功通过",加分。这里要注意,销毁和加分的判断要分开写,否则会出现你还没看见障碍它就消失了的情况。
4.4 碰撞检测、计分与UI
Ursina自带的碰撞检测基于包围盒,对跑酷这种方块障碍够用了。给玩家和障碍物都加上碰撞体,然后在 update 里判断相交就行。但是要注意性能,每帧遍历所有障碍物做一次包围盒判断,障碍物多了会掉帧。我的优化办法是只检查玩家附近一定Z范围内的障碍物,远处的跳过,因为远处的还没到接触距离。
def check_collision(): for o in obstacles: if abs(o.z - player.z) > 1.5: continue # 距离太远,跳过 if abs(o.x - player.x) < 0.7 and abs(o.y - player.y) < 1.2: game_over()计分就用一个全局变量,每通过一个障碍加1分,每帧显示在屏幕文字上。血条或者生命值我一般给3条命,撞一次扣一条,扣完游戏结束。这样比一撞就死友好得多,新手也能多玩几把,体验上有很大差别。
游戏结束的处理也值得说一句:暂停游戏循环、显示最终得分、按空格重开。重开的时候别忘了把玩家位置、分数、所有障碍物状态全部复位,否则重开之后场景是乱的。我踩过的坑就是只复位了分数,没清障碍物,结果一重开就撞死。
5. 踩过的坑与排查清单
这一块是我最想写的,因为前面那些内容书上都有,但坑只有做过才知道。下面按Scratch和Python分开列。
5.1 Scratch侧的五个高频问题
第一,克隆体不跟本体动。前面提过,这是最高频的问题。记住每个克隆体都是独立实例,要自己维护 z 和位置,本体只负责当模板。如果非要让克隆体跟随某个统一状态,就把这个状态做成全局变量,而不是改本体的坐标。
第二,所有克隆体一起消失。这通常是判定逻辑写在了本体的循环里,或者用了全局变量当判定标志。每个克隆体要自己判断自己的 z 是否小于下限,自己删除自己。
第三,除法导致画面爆炸。z 值没有下限保护,一旦接近0,屏幕尺寸趋于无限大。给 z 设一个3的下限,小于3直接回收。
第四,分数一直加或一直不加。多半是"通过判定"写在了每帧执行的位置,导致一个障碍物被重复加分,或者回收时没触发加分。解决办法是给每个克隆体加一个"已计分"标志,只加一次。
第五,画面卡顿。Scratch的克隆体数量上限是300,如果障碍物生成太快又不及时回收,克隆体越堆越多,项目会越来越卡。控制生成频率,并且在回收时真的删除克隆体,而不是把它藏起来。
5.2 Python侧的高频问题
Python这边的坑更"技术"一些,但总结起来也就几条。
| 现象 | 大概率原因 | 解决方向 |
|---|---|---|
| 提示找不到模块 | 装库和跑代码用的不是同一个解释器 | 指定解释器路径,用项目虚拟环境 |
| 游戏在不同电脑上速度不同 | 移动量没乘 dt | 所有和时间相关的位移都乘以 time.dt |
| 物体跑到远处开始抖动 | 世界坐标数值太大,浮点精度不够 | 用世界翻滚法,让玩家原地不动 |
| 帧率越来越低 | 每帧遍历全部障碍物、或对象没销毁 | 空间裁剪,及时销毁,用对象池 |
| 模型看不见 | 缩放太小、朝向不对、相机没对准 | 先加载到原点附近放大观察,再调位置 |
我重点说下抖动这个问题。很多人在Python里做跑酷,会让玩家沿Z轴一直跑,跑个几千单位之后,模型开始一抖一抖的。这不是显卡的问题,是单精度浮点数在数值较大时精度不够。解决办法就是前面说的"世界往后倒",玩家坐标永远在0附近,精度问题迎刃而解。
还有一条经验:出错时先看报错信息的最后一行,那通常才是真正的问题,前面的都是调用栈。Python的报错信息很长,新手容易被吓到,其实只要看最后一句,百分之八十的问题一眼就能定位。
5.3 性能与发布
性能优化在跑酷里主要盯三处:绘制调用数量、每帧的循环次数、纹理大小。前两个靠对象池和空间裁剪基本能解决,纹理大小靠克制——能用纯色就别贴图。
发布这块,如果只是自己玩,直接运行脚本就行。如果要给同学玩,可以用打包工具把项目打成一个可执行文件,对方双击就能运行,不用装Python。但打包出来的文件通常比较大,而且要针对不同操作系统分别打。如果想让手机也能玩,那还是回到网页方案更省事,把逻辑翻译成 Three.js,发一个链接就搞定。
我个人的建议是:学习阶段别纠结发布,把游戏做出来、跑得顺、能自己改,比发出去一百个人玩更重要。发布是产品思维,做游戏是工程思维,两者混在一起容易两头都做不好。
6. 后续还能怎么扩展
一个能跑的3D跑酷只是起点,后面能加的东西非常多,而且每加一样都是新的知识点。
6.1 从跑酷到更多玩法的复用
跑酷的骨架其实就是"玩家 + 前进 + 障碍 + 计分"。把这套骨架稍微改一改,就能变成别的玩法。把障碍换成靶子、把前进方向改成上下、把玩家改成一条鱼,它就变成了飞行射击。把地面换成赛道、加上左右弯道,它就是赛车载具。我经常让学生用同一套代码改出三个不同的小游戏,目的是让他们体会"代码复用"这件事。
还有个进阶方向是让地形真的起伏。前面的地面是平的,如果把地砖的高度按一条噪声函数来生成,就能做出上下坡。这个技术的名字叫过程化生成,同一套原理还可以用来生成随机地图、随机云层、随机树。
如果你对更硬核的3D技术感兴趣,可以顺着这条线去了解三维点云和三维卷积这类概念。跑酷里的障碍物是规则方块,而真实世界的三维数据往往是成千上万个点,处理它们的思路和游戏引擎完全不同,但第二章那套投影数学依然是基础。
6.2 从本地到网页
最后说一个让作品"出圈"的办法:把Python版本翻译成网页版。核心逻辑几乎一模一样,都是维护一组物体、每帧更新位置、做碰撞判断,区别只是语言和API。网页版的优势是分享成本极低,一个链接,手机点开就能玩,还能自动做手机适配——把触摸滑动映射成左右换道、点击映射成跳跃,逻辑完全不用改。
我的建议是先把Python版做扎实,把玩法、数值、节奏都调舒服了,再动手翻译。千万别两个版本同时写,否则改一个数值要改两遍,很容易改乱。等Python版稳定了,翻译过去就是个体力活。
回过头看整个流程,从Scratch的假3D到Python的真3D,最大的变化不是画面变好了,而是你从"搭积木"变成了"管系统"。Scratch让你专注玩法,Python逼你面对坐标、时间、性能这些底层问题。我个人的体会是,能把同一个跑酷从Scratch完整搬到Python的人,基本上已经跨过了编程入门最难的那道坎——不是学会了语法,而是学会了在脑子里同时装着一个运行中的世界。有空的话,建议你也动手搬一遍,哪怕画得丑、跑得慢,搬完那一刻你会明显感觉到不一样。