> 本篇把前三级串成一条完整的 2D 主线:移动手感、瓦片地图、相机、碰撞分层。读完你会有一个能玩的小游戏雏形——这是整个系列最重要的里程碑。
一、2D 的世界单位:像素即真理
Godot 2D 的单位就是像素,默认 gravity 980(像素/秒²),一个 128×128 的窗口一格就能放下角色。3D 才是「米」的世界。这个「所见即像素」的直白,是 Godot 2D 工作流口碑好的根本原因。
第一步永远是项目设置三连:
- 显示 → 窗口 → 视口宽高:设成你想要的逻辑分辨率(如 640×360,像素风标配);
- 拉伸模式:
canvas_items、宽高比keep——窗口怎么缩放,游戏内像素都清晰锐利; - 默认纹理过滤:像素风选 Nearest(4.7 还给 3D 视口加了最近邻缩放选项,复古味 3D 也能锐利)。
二、移动手感:CharacterBody2D 的调参艺术
第一级那个方块已经会跑会跳了,但「会动」和「手感好」差着十倍距离。手感三要素:
- Coyote Time(土狼时间):离开平台后仍可跳窗的宽限期,通常 0.1 秒。人类反应有延迟,没这个宽限的跳跃永远「差半拍」。
- Jump Buffer(跳跃缓冲):落地前 0.1 秒按跳,落地瞬间自动起跳。手感廉价感的最大来源就是「按了没跳」。
- 可变跳跃高度:松开跳跃键就削减上升速度,短按小跳、长按大跳。
extends CharacterBody2D
const SPEED := 300.0
const ACCEL := 1500.0
const FRICTION := 2200.0
const JUMP_VELOCITY := -420.0
const COYOTE_TIME := 0.1
const JUMP_BUFFER := 0.1
var coyote_counter := 0.0
var buffer_counter := 0.0
func _physics_process(delta: float) -> void:
if is_on_floor():
coyote_counter = COYOTE_TIME
else:
coyote_counter -= delta
velocity += get_gravity() * delta
if Input.is_action_just_pressed("jump"):
buffer_counter = JUMP_BUFFER
else:
buffer_counter -= delta
if buffer_counter > 0.0 and coyote_counter > 0.0:
velocity.y = JUMP_VELOCITY
buffer_counter = 0.0
coyote_counter = 0.0
if Input.is_action_just_released("jump") and velocity.y < 0.0:
velocity.y *= 0.5 # 可变跳跃高度
var dir := Input.get_axis("move_left", "move_right")
if dir:
velocity.x = move_toward(velocity.x, dir * SPEED, ACCEL * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, FRICTION * delta)
move_and_slide()
这三个常量没有任何一个「正确值」——手感是调出来的,把上面六个参数全 @export,运行时在远程检查器里现场拧,是打磨手感的标准姿势。
三、TileMapLayer:一张图铺一个世界
4.x 的瓦片系统由 TileSet(资源:图集 + 物理形状 + 地形规则)与 TileMapLayer(节点:一层瓦片)组成。4.0 曾用 TileMap,4.3 改为多层 TileMapLayer,官方明确建议一瓦片层一个节点。
关键进化在 4.5:TileMapLayer 的物理从「一格一刚体」改成了相邻格碰撞形状自动合并——一整片地面在物理引擎里不再是几千个小刚体,而是少数几个大形状。官方公告原话是重写(reworked from the ground up),对依赖物理的 2D 场景是量级级优化。升级注意:get_coords_for_body_rid() 返回值会变,依赖旧值的老代码要看迁移指南。
TileSet 编辑器里的高频操作:
- 给图集勾选物理层,逐格画碰撞多边形(地形可以只给顶面碰撞,做「从下往上穿过」的平台);
- 地形集(Terrain Set)画好连接规则后,铅笔刷一笔刷出自动拼接的地块;
- 4.6 起场景瓦片可以 90 度旋转,动画宝箱、火把直接当瓦片摆。
四、碰撞分层:矩阵决定谁撞谁
项目设置 → 层名称(Layer Names → 2D Physics),把 1-5 层命名成 player / enemy / world / pickup / hazard,之后每个物理节点的 Collision → Layer/Mask 直接勾语义名。
规则一句话:Layer 是「我是谁」,Mask 是「我关心谁」。玩家 Layer=player、Mask=world|enemy|pickup;拾取物改成 Area2D + Mask=player,敌我互撞就永远不会发生。新人把 Layer 和 Mask 搞反是 2D 篇第一事故,遇到「怎么都检测不到」先查这里。
五、相机跟随:平滑不是弹簧越软越好
Camera2D 设为 Player 子节点即可自动跟随,但直接挂上去是「像素级死锁」,画面硬邦邦。调法:
- Position Smoothing 开启(速度 5-8 之间开始试);
- Drag Margins 开启后,画面只在角色拖到边缘时才动,适合大场景;
- Limits 四项必设(左/右/上/下边界),否则关卡边缘会露出「世界之外的虚空」——平台游戏的经典穿帮。
Tween 或 lerp 平滑过去,游戏立刻有了「镜头语言」。这个效果 30 行内可写完,建议自己试。六、把方块换成角色:Sprite2D 与 AnimatedSprite2D
- 单图角色:
Sprite2D够用,翻转用flip_h; - 帧动画角色:
AnimatedSprite2D+ SpriteFrames 资源,在编辑器里把精灵表切成帧、命名 idle/run/jump/fall 动画; - 联动逻辑:在移动脚本里按状态切换播放——
if not is_on_floor(): play("jump" if velocity.y < 0 else "fall")。
AnimationPlayer 与状态机,AnimatedSprite2D 是轻量方案,两者不冲突——像素游戏的帧动画走 AnimatedSprite2D,骨骼/属性/位移混合动画走 AnimationPlayer。七、本篇交付物清单
完成本篇,你手上应该有:
- 一个带土狼时间、跳跃缓冲、可变跳跃的玩家场景;
- 一张用 TileMapLayer 铺的关卡,含自动拼接地形与单向平台;
- 一个带平滑、边界限制、前瞻偏移的相机;
- 碰撞层语义化命名、敌人场景雏形。
*本篇配图:小方块跳跃平台与碰撞层。*