如何编程 3D 汽车和摩托(赛车和越野摩托)
深入 3D 赛车和越野摩托示例:载具物理、追逐视角、路径点(那些数字列表)、对手自动驾驶、圈数和山地。
1. 玩家变成载具
在这两个游戏里,你控制的不是一个走路的角色:玩家驾驶 积木会把 3D 世界里的玩家换成一辆汽车或一辆摩托。
• evt_bandeira • m3d_jog_veiculo [VEIC: carro, COR: #3B6FD8] • m3d_camera [MODO: perseguicao] • m3d_jog_vel [VEL: 4.3] • m3d_controles [MODO: sim]
在内部,载具保存着一个 速度(向前的速度),并且每一帧(每秒 60 次):
- W 增加加速度;S 刹车(停下后则倒车);不按键时,摩擦力 会让车慢慢停下。这就是 惯性 的感觉。
- A/D 改变方向,但转弯幅度 和速度成正比:停着的车不会转弯(和真车一样)。
- 速度会变成向前的一步:x 改变 sin(方向) × 速度,z 改变 −cos(方向) × 速度。
- 遇到 1 格 高的台阶,载具会爬上去;撞到墙会损失速度,并触发 当玩家的载具撞车时。
- 摩托 加速更快、转弯更快,按空格可以跳;转弯时会倾斜,车轮会随速度转动。
追逐 摄像机待在载具后面,并且 带一点延迟 地转动(它在“追赶”车的方向),所以转弯看起来很平滑。
2. 对手:那些数字是什么?
在对手的代码里,你会看到这样一个列表:
36, 41, 46.1, 51.1, 56.2, 61.2, 66.3, 71.3, 76.4, 81.6, 86.6, 91, 94.3, 96.4, 97, 96.1, 93.9, 90.3, 85.9, 80.8, 75.6, 70.5, 65.5, 60.4, 55.4, 50.3, 45.3, 40.2, 35.2, 30.1, 25, 19.8, 15.1, 11.2, 8.5, 7.1, 7.3, 8.8, 11.8, 15.8, 20.6, 25.9, 31
它们是绿色对手 路径点(*waypoints*)的 x 坐标。第二个列表 路径 Z 存放 z 坐标。每个列表的第 1 个数字组成第 1 个点,第 2 个数字组成第 2 个点,依此类推:赛道一圈共有 43 个点,就像一路撒下的面包屑。
仔细读这个列表,你就能看出赛道的样子:
- 36 → 91 每次增加 5:这是 上方的直道,车向右开(x 变大)。
- 94.3 → 97 → 85.9 慢慢变大,到达 97(最右边的点)再变回来:这是 右侧弯道(半个圆)。在弯道中 x 变化不大,因为车更多是在 z 方向上移动。
- 80.8 → 15.1 每次减少 5:这是 下方的直道,往左开回去。
- 11.2 → 7.1 → 31:这是 左侧弯道(7.1 是最左边的点),然后回到起点附近。
这些数字从哪来? 赛道是一个“体育场”形状的椭圆:两条 52 格长的直道和两个半径为 18 的半圆(一圈大约 217 格)。程序沿着赛道走了一圈,每隔约 5 格记下一个点(217 ÷ 5 ≈ 43)。在直道上只是简单地相加;在弯道上使用余弦和正弦(x = 圆心 + 半径 × cos(角度))。每个对手在不同的 车道 上行驶(稍微靠内或靠外),所以三个列表很像,但不完全一样。这些列表以逗号分隔的文字保存,用 "," 分割文字 积木会把文字变成列表。
3. 自动驾驶(对手的积木)
• evt_bandeira
• m3d_corpo [FORMA: carro, COR: #3E9E3A, TAM: 100]
• m3d_corpo_fisica [MODO: sim]
• var_definir [VAR: caminhoX, VALOR: (arr_dividir)]
• var_definir [VAR: caminhoZ, VALOR: (arr_dividir)]
• var_definir [VAR: ponto, VALOR: 1]
• evt_receber
• ctl_repetir_ate [COND: (op_maior)]
• m3d_corpo_dirigir [X: (arr_item), Z: (arr_item), VEL: 12]
• ctl_se [COND: (op_menor)]
• var_adicionar [VAR: ponto, VALOR: 1]
• ctl_se [COND: (op_maior)]
• var_definir [VAR: ponto, VALOR: 1]
• var_adicionar [VAR: voltas, VALOR: 1]- 以速度驶向 x z 会让车朝当前的点 一点一点地 转向(像方向盘一样)并前进。它不会瞬移,所以能转出漂亮的弯。
- 当 我到 x z 的距离 小于 3.5 格时,对手就到达了,转向 下一个点(点 + 1)。
- 超过最后一个点了?回到第 1 个点,并 增加一圈。
- 跑完 3 圈后,“完赛的对手”加 1。当你冲过终点线时,你的 名次 就是“完赛的对手 + 1”。
不同的速度(12 / 12.6 / 13.2)让对手有不同的水平。想要更难的对手?提高它的速度。
4. 做自己的赛道,开车录制路径
不需要数学!开着车绕你的赛道跑一圈,让一个脚本每半秒记下一个点:
• evt_tecla [TECLA: g] • var_definir [VAR: caminhoX, VALOR: (arr_vazia)] • var_definir [VAR: caminhoZ, VALOR: (arr_vazia)] • ctl_repetir [VEZES: 60] • arr_adicionar [VALOR: (m3d_jog_pos), VAR: caminhoX] • arr_adicionar [VALOR: (m3d_jog_pos), VAR: caminhoZ] • ctl_esperar [SEG: 0.5]
然后在舞台上显示 caminhoX 和 caminhoZ 变量(或者用“说”),把这些数字用到对手身上。
5. 圈数、草地和撞车(你的车)
- 防作弊计圈: 只有先经过 赛道中段(下方直道),再经过 黑白格终点线,才算一圈。这样就不能在线上来回开来刷圈。
- 草地会减速: 游戏会检查车 下面 的方块(y − 0.5)。如果是草地,速度就会下降。
• ctl_sempre
• ctl_se_senao [COND: (op_igual)]
• m3d_jog_vel [VEL: 2.4]
…:
• m3d_jog_vel [VEL: 4.3]- 时间 和 最快圈速 使用 计时器 积木(侦测),它很精确。如果用等待 0.1 秒再加 0.1 的方法,会越来越慢,因为每次重复都会多花一点时间。
6. 摩托和山地
这座山是用代码搭出来的:山路从左往右爬升,高度大约是 8 + x × 0.2(每 5 格升高 1 格)。程序保证山路 从一列到下一列最多只升高 1 格,否则摩托会卡住。一边是石墙(让你留在山路上),另一边是 悬崖。
• evt_bandeira
• m3d_jog_veiculo [VEIC: moto, COR: #FF8C1A]
• m3d_camera [MODO: perseguicao]
• ctl_sempre
• ctl_se [COND: (op_menor)]
• m3d_jog_inicio
• ctl_se [COND: (op_maior)]
• m3d_jog_marcar- 掉下去了? 如果摩托远低于山路(低于 4 + x × 0.2),就会回到上一个存档点。
- 存档点: 经过黄色标杆后(x 大于 41),把出生点设在玩家所在位置 会把新的起点保存在那里。
- 侧面视角: 按 C。摄像机会从侧面看山,就像登山赛车类游戏一样。
7. 挑战
- 做一条 8 字形赛道,并开车录制对手的路径(第 4 节)。
- 做一个 加速:按下 Shift 时,“玩家速度 6”持续 2 秒。
- 在越野摩托中,添加一个沿着山路点行驶的“幽灵”对手。
- 把摄像机切换为 俯视,做一个遥控车游戏。