320 lines
9.7 KiB
Markdown
320 lines
9.7 KiB
Markdown
---
|
|
name: godot-physics
|
|
description: "Physics systems for Godot 4.x: collision layers/masks, rigid body dynamics, character controllers (CharacterBody2D/3D), area nodes for triggers, physics interpolation, bulk queries, and performance optimization. Use when implementing physics, collisions, movement, or triggers in Godot."
|
|
license: MIT
|
|
compatibility:
|
|
- godot-4.0
|
|
- godot-4.1
|
|
- godot-4.2
|
|
- godot-4.3
|
|
- godot-4.4
|
|
- godot-4.5
|
|
- godot-4.6
|
|
- godot-4.7
|
|
metadata:
|
|
author: vl4dt
|
|
version: 0.1.0
|
|
tags:
|
|
- godot
|
|
- physics
|
|
- collision
|
|
- character-body
|
|
- rigid-body
|
|
- area
|
|
created: 2026-06-20
|
|
---
|
|
|
|
# Physics Systems for Godot 4.x
|
|
|
|
Comprehensive patterns for physics, collisions, and movement in Godot 4.x.
|
|
|
|
## Collision Layers and Masks (Godot 4.x Typed System)
|
|
|
|
Godot 4.x uses bit-based collision layer/mask system for efficient spatial queries.
|
|
|
|
### Configuring Collision Layers
|
|
|
|
Set layers in the editor via Inspector > CollisionShape3D > Collision Layer (bits 1-32).
|
|
|
|
```gdscript
|
|
# Recommended layer layout
|
|
const LAYER_PLAYER = 0x1
|
|
const LAYER_ENEMIES = 0x2
|
|
const LAYER_PROJECTILES = 0x4
|
|
const LAYER_ENVIRONMENT = 0x8
|
|
const LAYER_PICKUPS = 0x10
|
|
const LAYER_TRIGGERS = 0x20
|
|
|
|
collision_layer = LAYER_PLAYER
|
|
collision_mask = LAYER_ENVIRONMENT | LAYER_PICKUPS
|
|
```
|
|
|
|
### Layer Design Best Practices
|
|
|
|
| Layer | Bit | Use Case |
|
|
|-------|-----|----------|
|
|
| 1 | `0x1` | Player characters |
|
|
| 2 | `0x2` | Enemies and NPCs |
|
|
| 4 | `0x4` | Projectiles and weapons |
|
|
| 8 | `0x8` | Environment / terrain |
|
|
| 16 | `0x10` | Pickups and collectibles |
|
|
| 32 | `0x20` | Triggers (Area-only) |
|
|
|
|
### Typed Collision Queries
|
|
|
|
```gdscript
|
|
var ray_query := PhysicsRayQueryParameters3D.new()
|
|
ray_query.from = camera.global_position
|
|
ray_query.to = global_position + forward * range
|
|
ray_query.collision_mask = LAYER_ENVIRONMENT
|
|
ray_query.exclude = [self]
|
|
|
|
var result = get_world_3d().direct_space.intersect_ray(ray_query)
|
|
if result:
|
|
var collider = result.get("collider")
|
|
print("Hit: ", collider.get_name())
|
|
```
|
|
|
|
## Rigid Body Dynamics
|
|
|
|
### Basic Rigid Body Setup
|
|
|
|
```gdscript
|
|
class_name PlayerRigidBody extends CharacterBody3D
|
|
|
|
@export var gravity_scale: float = 1.0
|
|
@export var jump_force: float = 8.0
|
|
@export var move_speed: float = 5.0
|
|
|
|
var velocity := Vector3.ZERO
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
velocity.y -= 9.8 * gravity_scale * delta
|
|
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
|
velocity.x = input_dir.x * move_speed
|
|
velocity.z = input_dir.y * move_speed
|
|
move_and_slide()
|
|
if is_on_floor() and Input.is_action_just_pressed("jump"):
|
|
velocity.y = jump_force
|
|
```
|
|
|
|
### Physics Material
|
|
|
|
```gdscript
|
|
var bouncy_material := PhysicsMaterial.new()
|
|
bouncy_material.bounce = 0.8
|
|
bouncy_material.friction = 0.2
|
|
collision_shape.shape.material = bouncy_material
|
|
```
|
|
|
|
### Rigid Body with Forces
|
|
|
|
```gdscript
|
|
class_name Projectile extends RigidBody3D
|
|
|
|
@export var lifetime: float = 3.0
|
|
@export var damage: int = 10
|
|
|
|
func _ready() -> void:
|
|
linear_velocity = transform.basis.z * 20.0
|
|
get_tree().create_timer(lifetime).timeout.connect(queue_free)
|
|
|
|
func _on_body_entered(body: Node3D) -> void:
|
|
if body is Enemy:
|
|
body.take_damage(damage)
|
|
queue_free()
|
|
```
|
|
|
|
## Character Controllers
|
|
|
|
### CharacterBody3D Movement Pattern
|
|
|
|
```gdscript
|
|
class_name ThirdPersonController extends CharacterBody3D
|
|
|
|
@export var walk_speed: float = 4.0
|
|
@export var run_speed: float = 8.0
|
|
@export var jump_velocity: float = 4.5
|
|
@export var gravity_amount: float = 9.8
|
|
@export var sprint_factor: float = 2.0
|
|
|
|
var _is_sprinting := false
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if not is_on_floor():
|
|
velocity.y -= gravity_amount * delta
|
|
if is_on_floor() and Input.is_action_just_pressed("jump"):
|
|
velocity.y = jump_velocity
|
|
var input_dir := Vector2.ZERO
|
|
input_dir = Vector2(
|
|
Input.get_axis("move_left", "move_right"),
|
|
Input.get_axis("move_up", "move_down")
|
|
)
|
|
_is_sprinting = Input.is_action_pressed("sprint")
|
|
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
|
if direction:
|
|
var spd = run_speed * sprint_factor if _is_sprinting else walk_speed
|
|
velocity.x = direction.x * spd
|
|
velocity.z = direction.z * spd
|
|
else:
|
|
velocity.x = move_toward(velocity.x, 0, walk_speed)
|
|
velocity.z = move_toward(velocity.z, 0, walk_speed)
|
|
move_and_slide()
|
|
```
|
|
|
|
### CharacterBody2D Platformer Pattern
|
|
|
|
```gdscript
|
|
class_name PlatformerCharacter extends CharacterBody2D
|
|
|
|
@export var speed: float = 200.0
|
|
@export var jump_force: float = -450.0
|
|
@export var gravity: float = 1200.0
|
|
@export var max_fall_speed: float = 700.0
|
|
@export var acceleration: float = 800.0
|
|
@export var friction: float = 600.0
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if not is_on_floor():
|
|
velocity.y += gravity * delta
|
|
velocity.y = min(velocity.y, max_fall_speed)
|
|
if is_on_floor() and Input.is_action_just_pressed("jump"):
|
|
velocity.y = jump_force
|
|
var move_input := Input.get_axis("move_left", "move_right")
|
|
if move_input:
|
|
velocity.x = move_toward(velocity.x, move_input * speed, acceleration * delta)
|
|
else:
|
|
velocity.x = move_toward(velocity.x, 0, friction * delta)
|
|
move_and_slide()
|
|
```
|
|
|
|
## Area Nodes for Triggers and Detection
|
|
|
|
### Basic Pickup Zone
|
|
|
|
```gdscript
|
|
class_name PickupZone extends Area3D
|
|
|
|
@export var pickup_scene: PackedScene
|
|
@export var min_items: int = 1
|
|
@export var max_items: int = 3
|
|
var _has_been_activated := false
|
|
|
|
func _ready() -> void:
|
|
body_entered.connect(_on_body_entered)
|
|
|
|
func _on_body_entered(body: Node3D) -> void:
|
|
if _has_been_activated: return
|
|
if body is CharacterBody3D:
|
|
_spawn_items()
|
|
_has_been_activated = true
|
|
queue_free()
|
|
|
|
func _spawn_items() -> void:
|
|
var count = randi() % (max_items - min_items + 1) + min_items
|
|
for i in range(count):
|
|
var item = pickup_scene.instantiate()
|
|
item.position = global_position + Vector3(randf() * 2 - 1, 0.5, randf() * 2 - 1)
|
|
get_tree().root.add_child(item)
|
|
```
|
|
|
|
### Enemy Detection Sensor
|
|
|
|
```gdscript
|
|
class_name EnemySensor extends Area3D
|
|
|
|
signal player_detected(player: CharacterBody3D)
|
|
signal player_lost(player: CharacterBody3D)
|
|
|
|
@export var detection_radius: float = 10.0
|
|
@export var detection_layers: int = 0x1
|
|
|
|
func _ready() -> void:
|
|
body_entered.connect(_on_body_entered)
|
|
body_exited.connect(_on_body_exited)
|
|
var sphere := SphereShape3D.new()
|
|
sphere.radius = detection_radius
|
|
$CollisionShape3D.shape = sphere
|
|
collision_mask = detection_layers
|
|
|
|
func _on_body_entered(body: Node3D) -> void:
|
|
if body is CharacterBody3D:
|
|
player_detected.emit(body)
|
|
|
|
func _on_body_exited(body: Node3D) -> void:
|
|
if body is CharacterBody3D:
|
|
player_lost.emit(body)
|
|
```
|
|
|
|
## Physics Interpolation (Godot 4.x Feature)
|
|
|
|
Godot 4.x supports physics interpolation for smooth rendering between physics ticks.
|
|
|
|
### Enabling Physics Interpolation
|
|
|
|
Enable in Project Settings > Rendering > Other > Physics Interpolation set to "Interpolate". The engine automatically interpolates transforms between physics ticks when rendering. No extra code needed for standard nodes.
|
|
|
|
For custom rendering, use the built-in interpolation factor available through the rendering pipeline.
|
|
|
|
## Performance: Bulk Physics Queries
|
|
|
|
### Space State Batched Queries
|
|
|
|
```gdscript
|
|
func get_all_collisions_in_area(origin: Vector3, radius: float, mask: int) -> Array:
|
|
var query := PhysicsShapeQueryParameters3D.new()
|
|
var sphere := SphereShape3D.new()
|
|
sphere.radius = radius
|
|
query.shape = sphere
|
|
query.transform = Transform3D.IDENTITY.translated(origin)
|
|
query.collision_mask = mask
|
|
query.exclude = [self]
|
|
var space = get_world_3d().direct_space
|
|
return space.intersect_shape(query)
|
|
|
|
func aoe_attack(center: Vector3, radius: float, damage: int) -> void:
|
|
var hits := get_all_collisions_in_area(center, radius, LAYER_ENEMIES | LAYER_PROJECTILES)
|
|
for hit in hits:
|
|
var collider = hit.get("collider")
|
|
if collider is Enemy:
|
|
collider.take_damage(damage)
|
|
```
|
|
|
|
### Collision Layer Optimization
|
|
|
|
- Only check layers you need — avoid `collision_mask = 0xFFFFFFFF`
|
|
- Use `NavigationServer3D` for pathfinding queries
|
|
- Use small-radius `Area3D` nodes for proximity checks
|
|
- Avoid querying the entire world every frame
|
|
|
|
## Godot 4.7 Physics Notes
|
|
|
|
- Physics interpolation is more robust with improved accuracy
|
|
- Use `AreaLight3D` for dynamic light casting on visible surfaces
|
|
- Test physics performance with Perfetto tracing on mobile targets
|
|
|
|
## Quick Reference: Movement Methods
|
|
|
|
| Method | Use Case | Returns |
|
|
|--------|----------|---------|
|
|
| `move_and_slide()` | Standard character movement | SlideFlags |
|
|
| `move_and_collide()` | When you need collision info | CollisionData or null |
|
|
| `move_and_snap()` | Platformer with snap-to-floor | SlideFlags |
|
|
| `move_teleport()` | Teleportation (no sliding) | CollisionData or null |
|
|
|
|
## MCP Bridge Tools (Optional — Live Editor Integration)
|
|
|
|
When the MCP bridge is running (Phase 2), these tools enhance physics debugging:
|
|
|
|
- **`physics_debug_draw`** — Toggle collision shape visualization in editor
|
|
- **`physics_query_shape`** — Run shape queries from any position without placing nodes
|
|
- **`rigid_body_apply_force`** — Apply forces to rigid bodies for testing
|
|
- **`physics_stats`** — View active physics bodies, sleep count, and frame cost
|
|
|
|
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
|
|
|
|
## References
|
|
|
|
- [CharacterBody3D Documentation](references/characterbody3d-guide.md)
|
|
- [Collision Layer Design Patterns](references/collision-layers.md)
|
|
- [Physics Material Reference](references/physics-materials.md) |