☰
基于 RigidBody2D 的 Godot 2D 物理平台跳跃教程:godot-demo-projects 中 Physics Platformer 的完整实现解析
2026/10/2 8:02:30 网站建设 项目流程
  • 示例工程

【免费下载链接】godot-demo-projects

Demonstration and Template Projects

项目地址:https://gitcode.com/GitHub_Trending/go/godot-demo-projects
点击查看免费下载

导读

本文以 2d/physics_platformer 官方演示项目为蓝本,完整讲解如何用RigidBody2D(刚体)构建 2D 平台跳跃游戏的角色控制器。与常见的CharacterBody2D(运动体)方案不同,刚体角色天然融入物理引擎,可以实现跷跷板、骑乘敌人、被移动平台带动等真实交互,代价是所有移动逻辑必须写在_integrate_forces()回调中手动修改刚体速度。读完本文,你将掌握刚体角色控制器的参数体系、地板判定与速度传导、敌人 AI 与子弹交互、物理关卡搭建等完整实战方案,并了解该项目在 project.godot 中的配套物理设置。

项目概览:一个完全基于物理的角色平台跳跃演示

Physics Platformer(项目名 "Physics-Based Platformer 2D",见 project.godot)是 godot-demo-projects 仓库 2D 分类下的官方演示,核心卖点正如其 README 所述:玩家与敌人全部使用RigidBody2D动态角色控制器,它们能"完美地与物理交互"——场景里有可踩踏的跷跷板(seesaw),玩家甚至可以跳到敌人头上"骑"着它移动。

该演示使用 GDScript 编写,渲染器为 Compatibility(renderer/rendering_method="gl_compatibility"),关卡主场景为 stage.tscn,窗口分辨率 800×480。场景中通过实例化(instancing)把 player/player.tscn、enemy/enemy.tscn、coin/coin.tscn 以及各种平台道具组合到同一关卡,充分体现了"场景独立编辑、关卡统一实例化"的工程组织方式(stage.tscn 开头的 Label 提示文字也明确说明了这一点)。

为什么用 RigidBody2D 而不是 CharacterBody2D

README 明确指出:刚体角色控制器比CharacterBody2D更强大,但更难以处理,因为它要求手动修改刚体的速度。

维度RigidBody2D(本演示)CharacterBody2D
移动方式在_integrate_forces()中读写PhysicsDirectBodyState2D的线性速度move_and_slide()直接移动并自动处理碰撞
物理交互原生参与刚体模拟,可被推、可压在跷跷板上、可被移动平台带动运动体与刚体交互需额外配置(move_and_slide默认不推动刚体)
旋转控制默认会翻滚,需lock_rotation默认不旋转
重力处理需要手动叠加get_total_gravity()自动受重力影响(除非gravity_scale = 0)
适用场景强调真实物理反馈的玩法(跷跷板、骑乘、弹射)传统平台跳跃、追求精确控制的角色

从 player.tscn 可以看到刚体角色的关键配置:mass = 1.5、physics_material_override摩擦为 0(避免地面摩擦拖慢移动)、lock_rotation = true(锁定旋转防止倒地翻滚)、custom_integrator = true(由脚本接管积分逻辑,配合_integrate_forces)、contact_monitor = true+max_contacts_reported = 4(开启接触监控用于地板检测)。

核心机制:一切移动都在 _integrate_forces() 中完成

README 强调:所有移动必须与物理引擎同步,在_integrate_forces()内部进行。这是刚体角色控制器与传统脚本控制的本质区别——你不能在_process/_physics_process里直接设置刚体位置或速度,因为物理引擎每帧会基于受力重新求解刚体状态,直接赋值会被覆盖且造成物理不同步。

player/player.gd 展示了标准写法:

func _integrate_forces(state: PhysicsDirectBodyState2D) -> void: var velocity := state.get_linear_velocity() var step := state.get_step() # 本帧物理步长 # 1. 读取输入 var move_left := Input.is_action_pressed(&"move_left") var move_right := Input.is_action_pressed(&"move_right") var jump := Input.is_action_pressed(&"jump") # 2. 计算目标水平速度(地面/空中分别处理) # 3. 手动叠加重力 velocity += state.get_total_gravity() * step # 4. 写回刚体 state.set_linear_velocity(velocity)

核心要点:

  • 通过state而非节点属性读写速度:state.get_linear_velocity()/state.set_linear_velocity(),配合custom_integrator = true,脚本完全接管移动积分;
  • 手动叠加重力:state.get_total_gravity()返回项目级重力矢量(本项目为 900),乘以step后累加,这是 README 所说"手动修改速度"最直接的一步;
  • 使用step而非delta:state.get_step()是物理步长(项目配置 120 ticks/s 时为 1/120 秒),确保速度换算与物理引擎完全一致;
  • call_deferred处理场景变更:生成子弹、生成敌人等会修改场景树的操作,通过_shot_bullet.call_deferred()/_spawn_enemy_above.call_deferred()延迟执行,避免在物理回调中修改场景造成冲突。

玩家控制详解:参数体系与手感调校

玩家控制器由一系列常量集中定义手感参数,位于 player/player.gd:

常量值作用
WALK_ACCEL1000.0地面水平加速度
WALK_DEACCEL1000.0地面水平减速度(松键减速)
WALK_MAX_VELOCITY200.0地面最大水平速度
AIR_ACCEL250.0空中水平加速度(远小于地面,体现"空中操控差"的手感)
AIR_DEACCEL250.0空中水平减速度
JUMP_VELOCITY380.0跳跃初速度(向上为负)
STOP_JUMP_FORCE450.0松键跳衰减力(实现可变跳高度)
MAX_SHOOT_POSE_TIME0.3射击姿势动画保持时长
MAX_FLOOR_AIRBORNE_TIME0.15地板判定容忍时间(跳跃缓冲/物理误差补偿)

地面与空中的差异化处理

在_integrate_forces()中,代码根据是否在地板上分支处理水平移动(player/player.gd):

  • 地面移动:按住方向键时按WALK_ACCEL * step累加速度并钳制在 ±WALK_MAX_VELOCITY;未按键时用WALK_DEACCEL逐步衰减到 0;
  • 空中移动:同样结构但使用小得多的AIR_ACCEL/AIR_DEACCEL(250 vs 1000),使玩家在空中转向迟缓、更真实;
  • 跳跃:在地面且按下跳跃时一次性赋予velocity.y = -JUMP_VELOCITY(-380,向上);若跳跃中松键,则通过STOP_JUMP_FORCE * step向上加速度持续抵消上升速度,实现按住跳得高、轻点跳得矮的可变跳跃;
  • 可变跳跃状态机:jumping、stopping_jump两个布尔配合管理跳起、衰减、下落三个阶段的转换,并伴随sound_jump.play()播放跳跃音效(player/player.gd)。

地板检测:基于接触法线的判定

刚体没有is_on_floor()可用,因此玩家通过遍历接触点、检查碰撞法线方向来判断是否"站在地板上"(player/player.gd):

for contact_index in state.get_contact_count(): var collision_normal := state.get_contact_local_normal(contact_index) if collision_normal.dot(Vector2(0, -1)) > 0.6: found_floor = true floor_index = contact_index

法线与竖直向上方向(0, -1)的点积大于 0.6,即法线基本朝上,就视为地板接触。配合MAX_FLOOR_AIRBORNE_TIME = 0.15秒的"空中宽容期"(airborne_time < 0.15仍算站在地面),可以补偿物理引擎的微小误差和人类反应延迟——这正是 README 与代码注释共同强调的刚体角色实现技巧(player/player.gd)。

移动平台与地板速度传导

这是一个刚体角色特有的关键细节:当玩家站在移动平台上时,必须把平台的水平速度叠加到玩家速度上,否则会被平台"甩下"。

代码用floor_h_velocity变量实现(player/player.gd):

# 每帧先移除上一帧传导的平台速度,再重新计算 velocity.x -= floor_h_velocity floor_h_velocity = 0.0 ... if found_floor: floor_h_velocity = state.get_contact_collider_velocity_at_position(floor_index).x velocity.x += floor_h_velocity

state.get_contact_collider_velocity_at_position(floor_index)直接读取所站碰撞体的水平速度并叠加到自己身上,于是玩家能稳稳站在平台上随其移动。项目中的移动平台本身是CharacterBody2D(见 platform/moving_platform.tscn),由 stage.tscn 中的AnimationPlayer(autoplay "move" 动画,时长 6 秒)驱动,3 块平台在关卡中按设定路径往复运动。

敌人 AI:状态机 + 射线 + 子弹交互

敌人同样是RigidBody2D(enemy/enemy.gd),其 AI 展示了刚体角色如何配合RayCast2D做寻路与转向:

状态机:WALKING / DYING

enum State { WALKING, DYING, }
  • WALKING:设置velocity.x = direction * WALK_SPEED(WALK_SPEED = 50,enemy/enemy.gd),并根据两类检测翻转方向:
    • 接触法线检测:遍历接触点,若水平法线分量绝对值大于 0.9(说明撞墙)则反向(enemy/enemy.gd);
    • 悬空检测:左右各挂一个RayCast2D(rc_left/rc_right),当行进方向前方脚下没有碰撞体(not rc_left.is_colliding())而另一侧有碰撞时,判定即将掉下平台边缘,立即掉头(enemy/enemy.gd),同时翻转 Sprite 的scale.x实现朝向切换;
  • DYING:播放 "explode" 动画,通过_pre_explode()移除 3 个碰撞形状防止后续碰撞,并播放爆炸音效,最终_die()调用queue_free()释放(enemy/enemy.gd)。

子弹交互与"死亡旋转"

在_integrate_forces中敌人检测接触对象是否为Bullet(enemy/enemy.gd):

var collider := state.get_contact_collider_object(collider_index) if collider is Bullet and not (collider as Bullet).disabled: _bullet_collider.call_deferred(collider, state, collision_normal) break

命中后(enemy/enemy.gd):

  • 状态切到DYING;
  • 根据碰撞法线水平分量的符号,设置刚体角速度state.set_angular_velocity(signf(collision_normal.x) * 33.0),让敌人被子弹击中后旋转着倒地——这是 RigidBody2D 独有的物理反馈;
  • 将physics_material_override.friction设为 1 增大摩擦帮助停稳;
  • 调用collider.disable()让子弹"熄火"并播放命中音效。

子弹本身也是RigidBody2D(player/bullet.gd),发射时赋予初速度Vector2(400.0 * speed_scale, -40)(向右 400、略向上,speed_scale根据朝向取 ±1),并在_shot_bullet()中通过add_collision_exception_with(bullet)让子弹与玩家互不碰撞(player/player.gd)。子弹带有Timer与disable()逻辑,被命中后播放 "shutdown" 动画并标记disabled = true,避免重复触发。

收集物与物理交互:金币、跷跷板、单向平台

金币:Area2D 检测刚体玩家

金币使用Area2D(coin/coin.gd)而非刚体,通过body_entered信号检测:

func _on_body_enter(body: Node2D) -> void: if not taken and body is Player: ($AnimationPlayer as AnimationPlayer).play(&"taken")

taken标志防止重复触发,动画 "taken" 播放后金币消失。关卡中摆放了 40+ 个金币实例(见 stage.tscn 的Coins节点),沿平台与跳跃路线分布,作为收集目标。

跷跷板:PinJoint2D 的真实物理反馈

这是 README 特别点名的亮点交互。跷跷板场景 background/seesaw.tscn 由三部分组成:

  • Plank(木板):RigidBody2D,mass = 5.10204,带矩形碰撞体;
  • Pillar(支柱):StaticBody2D,固定在地面;
  • PinJoint2D(销钉关节):node_a指向木板、node_b指向支柱,把两者铰接成可转动的杠杆。

当玩家跳到木板一端,刚体物理自动产生力矩让另一端翘起——完全无需手写任何跷跷板逻辑,这正是"刚体角色与物理完美交互"的直观体现。同理,玩家也可以跳到敌人头上骑乘,敌人移动时会把玩家一起带走。

单向平台:one_way_collision

platform/one_way_platform.tscn 中CollisionShape2D开启one_way_collision = true,实现经典的"只能从下往上穿过、落到上面站住"的单向平台效果,用于设计从下方跳入的路线。

TileMap 关卡与物理层

地面由TileMapLayer(Godot 4 中TileMap的图层节点)铺成,tileset 定义在 tileset.tres。其中每个图块的physics_layer_0/polygon_0/points都显式给出了碰撞多边形(如(-16, -12, 16, -12, 16, 16, -16, 16)这类 32×32 内的矩形区域,见 tileset.tres),并通过flip_h/flip_v/transpose组合出全方向图块(上下左右与角块),保证铺出的地面处处有真实碰撞。

如果想修改基础图块,关卡内提示文字建议打开 tileset_edit.tscn 在 TileSet 编辑器中调整,再回到 stage.tscn 查看效果。

项目物理配置:为刚体角色调优的全局参数

project.godot 中的物理设置是整套刚体方案能否"跑顺"的前提:

[physics] common/physics_ticks_per_second=120 2d/default_gravity=900 common/physics_interpolation=true
  • physics_ticks_per_second = 120:物理步长提升到 120Hz(默认 60),使快速移动的刚体角色碰撞检测更精确、运动更平滑,代价是 CPU 开销更高——这正是_integrate_forces中step对应 1/120 秒的原因;
  • 2d/default_gravity = 900:2D 重力加速度调得比默认值大,配合JUMP_VELOCITY = 380得到干脆利落的跳跃手感(跳跃高度 ≈ v²/(2g) ≈ 80px);
  • common/physics_interpolation = true:开启物理插值,让 120Hz 的物理结果在低刷新率屏幕上渲染更平滑,是刚体角色画面流畅性的关键开关。

输入映射同样在 project.godot 定义:jump(W / 空格?实际为物理键 W + 方向键上 + 手柄 A)、move_left(A + 左方向键 + 手柄)、move_right(D + 右方向键 + 手柄)、shoot(空格 + 鼠标左键 + 手柄)、spawn(R + 手柄 Y,可在头顶生成一个敌人供测试)。此外 player.tscn 还提供了 4 个TouchScreenButton(Left/Right/Jump/Fire)映射到同名 action,支持触屏移动端操作,并跟随visibility_mode自动显示。

运行与体验

在 Godot 4.x(项目config/features标注 4.7,project.godot)中导入2d/physics_platformer目录,直接运行主场景 stage.tscn 即可:

  • 移动:A/D 或方向键左右;跳跃:W / 上方向键(按住跳更高);射击:空格或鼠标左键;生成敌人:R 键在头顶生成一只敌人,方便随时测试骑乘与击杀。
  • 观察点:跳到移动平台上随其升降;踩跷跷板一端看另一端翘起;跳到敌人头上骑着它巡逻;开枪击杀敌人看其旋转倒地动画。
  • 关卡自带滚动视差背景(background/parallax_bg.tscn 使用多层云、前景贴图滚动),背景音乐为循环播放的audio/music.ogg。

音乐资源

按 README 说明,背景音乐 "Pompy" 由 Hubert Lamontagne(madbr)创作,音效文件(跳跃、射击、命中、爆炸、金币)均位于 2d/physics_platformer/audio 目录,通过AudioStreamPlayer/AudioStreamPlayer2D挂载到对应场景节点上播放,可在编辑器中替换为自有资源。

参考资料

  • 演示 README:2d/physics_platformer/README.md
  • 玩家控制器:2d/physics_platformer/player/player.gd、2d/physics_platformer/player/player.tscn
  • 敌人 AI:2d/physics_platformer/enemy/enemy.gd
  • 子弹:2d/physics_platformer/player/bullet.gd
  • 关卡组装与物理道具:2d/physics_platformer/stage.tscn、2d/physics_platformer/background/seesaw.tscn、2d/physics_platformer/platform/one_way_platform.tscn、2d/physics_platformer/platform/moving_platform.tscn
  • 项目与瓦片配置:2d/physics_platformer/project.godot、2d/physics_platformer/tileset.tres

如果你希望角色拥有"脚踏实地的精确操控感",可以继续对比仓库中同样位于 2d 分类下使用CharacterBody2D的 kinematic_character 与 platformer 演示,两者对照即可直观理解两种角色控制器在手感与物理交互上的取舍。

  • 示例工程

【免费下载链接】godot-demo-projects

Demonstration and Template Projects

项目地址:https://gitcode.com/GitHub_Trending/go/godot-demo-projects
点击查看免费下载
上一篇:猫抓资源嗅探插件:网页视频音频一键下载的完整指南
下一篇:rofi-sensible-terminal 终端启动器使用指南:原理、配置与自定义

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询