How to create a moving platform that carries players and objects using Godot’s physics system

Godot Version: 4.5
Renderer: GL Compatibility
Project Type: 3D Game with Moving Platforms
Introduction
Moving platforms are a common feature in games—elevators, lifts, conveyor belts, and moving floors. The challenge is making objects on top of the platform move with it naturally, using physics rather than manual position manipulation.
This article explains how to create a lift platform that uses CharacterBody3D and move_and_slide() to automatically carry players and physics objects.
The Key Insight: Use CharacterBody3D
The secret is making the platform itself a CharacterBody3D instead of a Node3D. When a CharacterBody3D moves using move_and_slide(), objects on top automatically move with it due to friction and collision detection.
Why CharacterBody3D?
- Automatic Physics Interaction: Bodies on top move naturally with the platform
- No Manual Tracking: No need to track objects and manually apply velocities
- Friction Works: High friction keeps objects from sliding off
- Collision Detection: Proper collision with players and other bodies
Setup Steps
1. Create the Platform Node
In your scene, create a CharacterBody3D node for the platform:
Lift (Node3D)
└─ Platform (CharacterBody3D) ← This is the moving platform
└─ CollisionShape3D
└─ FloorTile (visual mesh)
└─ LiftButton
2. Add Collision Shape
Add a CollisionShape3D child to the Platform with:
- Shape: BoxShape3D (or appropriate shape)
- Physics Material: High friction (e.g., friction = 10.0)
The collision shape defines what the platform physically is—this is what objects collide with.
3. Script the Movement
Create a script that moves the platform using velocity and move_and_slide():
extends Node3D
class_name Lift
@onready var platform: CharacterBody3D = get_node_or_null("Platform")
@export var start_point: Node3D
@export var end_point: Node3D
@export var move_duration: float = 2.0
var _target_position: Vector3 = Vector3.ZERO
var _is_moving: bool = false
var _fixed_y_position: float = 0.0 # Lock Y to prevent falling
func _ready() -> void:
if platform and start_point:
platform.global_position = start_point.global_position
platform.velocity = Vector3.ZERO
_fixed_y_position = start_point.global_position.y
func _move_to(target: Vector3) -> void:
_target_position = target
_is_moving = true
func _physics_process(delta: float) -> void:
if not platform or not _is_moving:
return
# Calculate direction (only X and Z, ignore Y)
var current_pos = platform.global_position
var direction = Vector3(
_target_position.x - current_pos.x,
0.0,
_target_position.z - current_pos.z
).normalized()
# Calculate speed
var total_distance = start_point.global_position.distance_to(end_point.global_position)
var speed = total_distance / move_duration
# Set velocity and move
platform.velocity = direction * speed
platform.velocity.y = 0.0 # Lock Y axis
platform.move_and_slide() # This moves bodies on top automatically!
# Restore fixed Y position (prevent gravity)
platform.global_position.y = _fixed_y_position
# Check if reached target
var distance_to_target = Vector3(
_target_position.x - current_pos.x,
0.0,
_target_position.z - current_pos.z
).length()
if distance_to_target < 0.1:
var final_pos = _target_position
final_pos.y = _fixed_y_position
platform.global_position = final_pos
platform.velocity = Vector3.ZERO
_is_moving = false
Key Points
1. Lock the Y Axis
If your platform moves horizontally, lock the Y axis to prevent gravity from affecting it:
platform.velocity.y = 0.0 # No vertical movement
platform.global_position.y = _fixed_y_position # Restore after movement
2. Use move_and_slide()
move_and_slide() is the magic function. It:
- Moves the CharacterBody3D
- Handles collisions
- Automatically moves bodies on top due to friction
3. High Friction Material
Set a high friction value on the platform’s physics material. This keeps objects from sliding off during movement.
4. Calculate Speed from Duration
Instead of hardcoding speed, calculate it from the desired duration:
var speed = total_distance / move_duration
This ensures consistent timing regardless of distance.
How It Works
When move_and_slide() is called:
- The platform moves based on its velocity
- Godot’s physics engine detects collisions
- Objects on top (players, crates) are pushed along by friction
- No manual tracking or velocity application needed!
Comparison: Old vs New Approach
Old Approach (Doesn’t Work Well)
- Platform is a
Node3Dmoved by tween - Manual tracking of objects with Area3D
- Manually applying velocities to each object
- Complex and error-prone
New Approach (Works Great)
- Platform is a
CharacterBody3D - Uses
move_and_slide()for movement - Physics handles everything automatically
- Simple and reliable
Testing
To test your lift:
- Place a player on the platform
- Place a crate on the platform
- Trigger the lift to move
- Both should move smoothly with the platform
If objects slide off or don’t move, check:
- Platform has a CollisionShape3D
- Physics material has high friction
- Platform is actually a CharacterBody3D (not Node3D)
- Y axis is locked if moving horizontally
Summary
Moving platforms in Godot are simple when you use the right approach:
- Make the platform a CharacterBody3D
- Add a CollisionShape3D with high friction
- Use velocity + move_and_slide() for movement
- Lock Y axis if moving horizontally
That’s it! The physics engine handles the rest automatically.
Leave a Reply