
第7章 物理与碰撞7.1 物理节点概览CollisionObject2D碰撞对象基类 ├── StaticBody2D # 静态体不移动的碰撞体墙壁、地板 ├── RigidBody2D # 刚体受物理引擎控制箱子、石头 ├── CharacterBody2D # 角色体由代码控制移动玩家、敌人 └── Area2D # 区域检测重叠不阻挡移动金币、触发器7.2 CharacterBody2D 角色控制器CharacterBody2D 是玩家角色和敌人的首选节点。extends CharacterBody2D export var speed: float 300.0 export var jump_force: float -500.0 export var gravity: float 980.0 export var friction: float 2000.0 func _physics_process(delta: float) - void: # 重力 if not is_on_floor(): velocity.y gravity * delta # 水平移动 var input_dir : Input.get_axis(move_left, move_right) if input_dir ! 0: velocity.x input_dir * speed else: # 摩擦力减速 velocity.x move_toward(velocity.x, 0, friction * delta) # 跳跃 if Input.is_action_just_pressed(jump) and is_on_floor(): velocity.y jump_force # 可变跳跃高度 if Input.is_action_just_released(jump) and velocity.y 0: velocity.y * 0.5 # 移动并处理碰撞 move_and_slide()move_and_slide() 详解# move_and_slide() 自动处理 # 1. 根据 velocity 移动 # 2. 碰到墙壁时自动滑动不会卡住 # 3. 碰到地板/天花板时停止对应方向的速度 # 4. 支持斜坡移动 # 相关属性 floor_max_angle: float 0.785398 # 最大地板角度约45度 up_direction: Vector2 Vector2.UP # 上的方向用于判断地板 floor_stop_on_slope: bool true # 斜坡上是否停止滑动 floor_constant_speed: bool false # 斜坡上是否保持速度 max_slides: int 6 # 最大滑动次数 wall_min_slide_angle: float 0.26 # 最小墙壁滑动角度 # 碰撞检测 func _physics_process(delta: float) - void: # ... 移动代码 ... move_and_slide() # 检查碰撞信息 for i in get_slide_collision_count(): var collision : get_slide_collision(i) var normal : collision.get_normal() var collider : collision.get_collider() # 判断碰撞方向 if normal.dot(Vector2.UP) 0.7: print(踩到地板) elif normal.dot(Vector2.DOWN) 0.7: print(撞到天花板) elif absf(normal.x) 0.7: print(撞到墙壁)斜坡处理# 设置 up_direction 后is_on_floor() / is_on_wall() / is_on_ceiling() 会自动判断 up_direction Vector2.UP # 默认 # 调整最大可攀爬斜坡角度 floor_max_angle deg_to_rad(45) # 45度以内的斜坡可以行走 # 斜坡上的行为 floor_stop_on_slope true # 在斜坡上不滑动适合平台游戏 floor_constant_speed false # 斜坡上速度是否恒定7.3 RigidBody2D 刚体extends RigidBody2D # RigidBody2D 由物理引擎控制不需要手动处理重力和碰撞 # 常用属性 mass: float 1.0 # 质量 gravity_scale: float 1.0 # 重力倍率 linear_damp: float 0.0 # 线性阻尼空气阻力 angular_damp: float 0.0 # 角度阻尼 physics_material_override: PhysicsMaterial # 物理材质 constant_force: Vector2 # 持续施加的力 constant_torque: float # 持续施加的扭矩 # 物理模式 freeze: bool false # 冻结不参与物理模拟 freeze_mode: FreezeMode # 冻结模式 # 应用力和冲量 func apply_central_force(force: Vector2) - void: # 施加力持续受质量影响 pass func apply_central_impulse(impulse: Vector2) - void: # 施加冲量一次性受质量影响 pass func apply_force(force: Vector2, offset: Vector2 Vector2.ZERO) - void: # 在指定位置施加力会产生旋转 pass # 使用示例 func _ready() - void: # 施加初始冲量弹射 apply_central_impulse(Vector2(500, -300)) func hit_by_explosion(explosion_pos: Vector2, force: float) - void: var direction : (global_position - explosion_pos).normalized() apply_central_impulse(direction * force)物理材质# 创建物理材质 var phys_mat : PhysicsMaterial.new() phys_mat.friction 0.3 # 摩擦系数0无摩擦1最大摩擦 phys_mat.bounce 0.8 # 弹性系数0不弹1完美弹跳 phys_mat.rough false # 粗糙表面影响摩擦计算方式 physics_material_override phys_mat # 实用制作弹力球 func make_bouncy() - void: var mat : PhysicsMaterial.new() mat.bounce 1.0 # 完美弹跳 mat.friction 0.0 physics_material_override mat7.4 StaticBody2D 静态体# StaticBody2D 不会移动用于墙壁、地板、平台等 # 性能最好物理引擎不需要计算它的运动 # 典型结构 StaticBody2D ├── CollisionShape2D # 碰撞形状 └── Sprite2D # 视觉显示 # 移动平台使用 AnimatableBody2D 替代 # AnimatableBody2D 可以通过动画/代码移动会推动其他物理体7.5 Area2D 区域检测Area2D 用于检测物体进入/离开某个区域不阻挡移动。extends Area2D # 常见用途 # - 金币拾取 # - 伤害区域 # - 触发器进入新区域触发事件 # - 视野检测 func _ready() - void: # 连接信号 body_entered.connect(_on_body_entered) body_exited.connect(_on_body_exited) area_entered.connect(_on_area_entered) area_exited.connect(_on_area_exited) func _on_body_entered(body: Node2D) - void: # 有物理体进入区域 if body.is_in_group(player): body.collect_item(self) queue_free() func _on_body_exited(body: Node2D) - void: # 物理体离开区域 pass func _on_area_entered(area: Area2D) - void: # 另一个 Area2D 进入区域 if area.is_in_group(hitboxes): take_damage(area.damage) func _on_area_exited(area: Area2D) - void: pass # 监控设置 monitoring true # 是否检测其他物体进入默认true monitorable true # 是否能被其他区域检测到默认true伤害区域示例# HitboxComponent.gd - 攻击判定组件 extends Area2D export var damage: int 10 export var knockback_force: float 300.0 # 碰撞层设置 # Layer: Hitbox所在层 # Mask: Hurtbox检测目标层# HurtboxComponent.gd - 受击区域组件 extends Area2D signal hurt(damage: int, source: Area2D) # Layer: Hurtbox所在层 # Mask: Hitbox检测目标层 func _ready() - void: area_entered.connect(_on_area_entered) func _on_area_entered(hitbox: Area2D) - void: if hitbox.get(damage) ! null: var damage: int hitbox.damage var knockback: float hitbox.knockback_force hurt.emit(damage, hitbox)7.6 碰撞层与碰撞掩码碰撞层Layer我是什么 ← 我是谁 碰撞掩码Mask我能碰到什么 ← 我能检测谁 图示碰撞关系 玩家 (Layerplayer) 敌人 (Layerenemy) Maskenemy,wall,pickup Maskplayer,wall │ │ │ ┌───────────────────┐ │ └──→│ 碰撞检测系统 │←──┘ │ 检查 Mask↔Layer │ └───────────────────┘ 例如 - 玩家Layer1(player), Mask2,3,4(enemy,wall,pickup) - 敌人Layer2(enemy), Mask1,3(player,wall) - 墙壁Layer3(wall), Mask0(不主动检测任何东西但被别人检测) - 道具Layer4(pickup), Mask1(player) - 子弹Layer5(projectile), Mask2,3(enemy,wall) 碰撞发生条件 只需一方的 Mask 包含另一方的 Layer 即可触发碰撞检测 例如子弹(Maskenemy) 碰到 敌人(Layerenemy) → 子弹检测到敌人 敌人(Mask不包含projectile) → 敌人不会检测到子弹推荐的层命名项目设置 → 物理 → 2D → 层命名 Layer 1: player # 玩家 Layer 2: enemy # 敌人 Layer 3: wall # 墙壁/地板 Layer 4: pickup # 可拾取物品 Layer 5: projectile # 子弹/弹幕 Layer 6: hitbox # 攻击判定 Layer 7: hurtbox # 受击区域 Layer 8: trigger # 触发器 Layer 9: platform # 单向平台 Layer 10: npc # NPC7.7 碰撞形状# CollisionShape2D基本碰撞形状 # 形状类型 # - CircleShape2D # 圆形 # - RectangleShape2D # 矩形 # - CapsuleShape2D # 胶囊形 # - SegmentShape2D # 线段 # - ConvexPolygonShape2D # 凸多边形 # - ConcavePolygonShape2D # 凹多边形仅静态体 # 代码创建碰撞形状 func create_circle_hitbox(radius: float) - CollisionShape2D: var shape : CollisionShape2D.new() var circle : CircleShape2D.new() circle.radius radius shape.shape circle return shape单向碰撞平台跳跃必备单向碰撞示意图 角色可以站在上面 ✅ ↓ ══════════════════ ← 平台One Way Collision true ↑ 角色可以从下方穿过 ✅ # 设置 CollisionShape2D 的 One Way Collision 为 true # 可以从下方穿过站在上面 # Godot 4.7 新增自定义单向碰撞方向 # One Way Collision Direction可以设置非默认的穿透方向 # 例如可以设置为水平方向实现只能从左向右穿过的墙壁7.8 RayCast2D 射线检测extends Node2D onready var ray: RayCast2D $RayCast2D func _ready() - void: # 设置射线 ray.target_position Vector2(200, 0) # 向右200像素 ray.enabled true # 碰撞设置 ray.collision_mask 0b00000000_00000000_00000000_00000011 # 检测第1层和第2层二进制位对应层号 ray.collide_with_areas true # 检测 Area2D ray.collide_with_bodies true # 检测物理体 func _physics_process(delta: float) - void: # 强制更新如果不在 _physics_process 中使用需要手动更新 ray.force_raycast_update() if ray.is_colliding(): var collider : ray.get_collider() var collision_point : ray.get_collision_point() var collision_normal : ray.get_collision_normal() print(碰到, collider.name) print(碰撞点, collision_point) print(碰撞法线, collision_normal)实用视线检测# 推荐使用 PhysicsDirectSpaceState2D无需创建节点 func has_line_of_sight(target: Node2D) - bool: var space_state : get_world_2d().direct_space_state var query : PhysicsRayQueryParameters2D.create( global_position, target.global_position, 0b00000000_00000000_00000000_00000100 # 只检测第3层墙壁 ) var result : space_state.intersect_ray(query) return result.is_empty() # 没有碰撞 视线畅通7.9 PhysicsDirectSpaceState2D 直接查询# 物理空间查询不依赖节点直接查询物理世界 func _physics_process(delta: float) - void: var space_state : get_world_2d().direct_space_state # 射线查询 var ray_query : PhysicsRayQueryParameters2D.create( global_position, # 起点 global_position Vector2(200, 0), # 终点 0b00000000_00000000_00000000_00000100 # 碰撞掩码 ) var ray_result : space_state.intersect_ray(ray_query) if not ray_result.is_empty(): print(射线碰撞到, ray_result.collider.name) print(碰撞点, ray_result.position) # 形状查询检测某个区域内的所有碰撞体 var shape_query : PhysicsShapeQueryParameters2D.new() shape_query.shape CircleShape2D.new() shape_query.shape.radius 100.0 shape_query.transform global_transform var shape_results : space_state.intersect_shape(shape_query, 32) # 最多返回32个 for result in shape_results: print(范围内物体, result.collider.name) # 点查询检测某个点是否有碰撞体 var point_query : PhysicsPointQueryParameters2D.new() point_query.position get_global_mouse_position() var point_results : space_state.intersect_point(point_query) for result in point_results: print(鼠标下的物体, result.collider.name)7.10 物理常见陷阱穿透问题Tunneling# 问题高速移动的物体可能穿过薄墙壁 # 原因一帧移动距离大于墙壁厚度 # 解决方案1使用 CharacterBody2D 的 continuous_cd continuous_cd true # 连续碰撞检测 # 解决方案2使用射线预测 func _physics_process(delta: float) - void: var ray : PhysicsRayQueryParameters2D.create( global_position, global_position velocity * delta ) var result : get_world_2d().direct_space_state.intersect_ray(ray) if not result.is_empty(): # 提前处理碰撞 global_position result.position velocity velocity.bounce(result.normal) else: move_and_slide() # 解决方案3增加碰撞体厚度抖动问题# 问题两个物体互相推挤时产生抖动 # 原因物理帧和渲染帧不同步 # 解决方案启用物理插值4.7 改进 # 项目设置 → Physics → 2D → Physics Tick Common Physics Interpolation ✅ # 方式2手动插值高级用法 # 通常不需要手动插值启用项目设置中的物理插值即可 # 如果需要更精细的控制可以使用 # var fraction : Engine.get_physics_interpolation_fraction() # 然后在 _process 中根据 fraction 插值位置实操练习练习带物理的角色控制器extends CharacterBody2D export var speed: float 300.0 export var jump_force: float -500.0 export var gravity: float 980.0 export var wall_jump_force: Vector2 Vector2(300, -400) export var max_fall_speed: float 800.0 onready var coyote_timer: Timer $CoyoteTimer var was_on_floor: bool false var can_coyote_jump: bool false func _physics_process(delta: float) - void: # 重力 if not is_on_floor(): velocity.y minf(velocity.y gravity * delta, max_fall_speed) # 土狼时间 was_on_floor is_on_floor() move_and_slide() if was_on_floor and not is_on_floor() and velocity.y 0: coyote_timer.start() can_coyote_jump true # 水平移动 var input_dir : Input.get_axis(move_left, move_right) velocity.x input_dir * speed # 跳跃 if Input.is_action_just_pressed(jump): if is_on_floor() or can_coyote_jump: velocity.y jump_force can_coyote_jump false elif is_on_wall(): # 墙跳 var wall_normal : get_wall_normal() velocity Vector2(-wall_normal.x * wall_jump_force.x, wall_jump_force.y) func _on_coyote_timer_timeout() - void: can_coyote_jump false