Godot имеет встроенный физический движок, который автоматически:
Не нужно писать физику с нуля — достаточно правильно настроить узлы.
StaticBody2D
└── CollisionShape2D
# StaticBody2D не требует скрипта для базового поведения —
# достаточно добавить CollisionShape2D
RigidBody2D
├── Sprite2D
└── CollisionShape2D
Свойства в инспекторе:
| Свойство | Описание |
|---|---|
mass |
Масса (кг). Влияет на инерцию |
gravity_scale |
Множитель гравитации (0 = невесомость) |
linear_damp |
Замедление линейного движения |
angular_damp |
Замедление вращения |
physics_material_override |
Материал (трение, упругость) |
CharacterBody2D
├── Sprite2D
└── CollisionShape2D
move_and_slide()extends CharacterBody2D
@export var speed: float = 200.0
func _physics_process(delta: float) -> void:
var direction := Vector2.ZERO
if Input.is_action_pressed("ui_right"):
direction.x += 1
if Input.is_action_pressed("ui_left"):
direction.x -= 1
velocity = direction.normalized() * speed
move_and_slide()
Area2D
└── CollisionShape2D
extends Area2D
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node2D) -> void:
print(body.name, " вошёл в зону!")
func _on_body_exited(body: Node2D) -> void:
print(body.name, " покинул зону!")
Каждому физическому телу (StaticBody2D, RigidBody2D, CharacterBody2D, Area2D) нужен дочерний CollisionShape2D, определяющий форму.
| Форма | Класс | Когда использовать |
|---|---|---|
| Прямоугольник | RectangleShape2D |
Ящики, стены, кнопки |
| Круг | CircleShape2D |
Мячи, монеты, магниты |
| Капсула | CapsuleShape2D |
Персонажи |
| Отрезок | SegmentShape2D |
Тонкие линии (лазеры) |
| Полигон | ConvexPolygonShape2D |
Произвольные формы |
CollisionShape2D как дочерний узел физического телаВажно: без CollisionShape2D физическое тело не будет участвовать в столкновениях!
Слои и маски определяют, какие объекты взаимодействуют друг с другом.
| Слой | Объекты |
|---|---|
| 1 | Стены, границы поля |
| 2 | Игровые элементы (размещаемые) |
| 3 | Триггеры (Area2D) |
Настройка:
Настраивается в инспекторе: раздел Collision → Layer / Mask (сетка чекбоксов).
extends RigidBody2D
func _physics_process(delta: float) -> void:
# Постоянная сила — действует каждый кадр
apply_force(Vector2(100, 0)) # Сила вправо
apply_central_force(Vector2(0, -500)) # Сила вверх (в центр масс)
# Однократный толчок — вызывается один раз
func push_right() -> void:
apply_impulse(Vector2(300, 0)) # Импульс вправо
func push_up() -> void:
apply_central_impulse(Vector2(0, -500)) # Импульс вверх
Разница:
apply_force() — как ветер (действует постоянно)apply_impulse() — как удар (действует один раз)extends RigidBody2D
# Отключаем стандартную гравитацию
func _ready() -> void:
gravity_scale = 0.0
# Применяем собственную силу
func _physics_process(delta: float) -> void:
var magnet_pos := Vector2(500, 300) # Позиция магнита
var direction := magnet_pos - global_position
var distance := direction.length()
if distance > 10.0: # Защита от деления на ноль
# Сила обратно пропорциональна квадрату расстояния
var force_strength := 50000.0 / (distance * distance)
var force := direction.normalized() * force_strength
apply_central_force(force)
PhysicsMaterial настраивает поверхность объекта:
| Свойство | Описание | Значение |
|---|---|---|
friction |
Трение (0 = лёд, 1 = резина) | 0.0 — 1.0 |
bounce |
Упругость (0 = не отскакивает, 1 = полный отскок) | 0.0 — 1.0 |
Как назначить:
friction и bounce# Или через код:
var mat := PhysicsMaterial.new()
mat.friction = 0.2
mat.bounce = 0.8
physics_material_override = mat
RayCast2D выпускает невидимый луч и сообщает, с чем он столкнулся.
extends Node2D
@onready var ray := $RayCast2D
func _physics_process(delta: float) -> void:
if ray.is_colliding():
var collider := ray.get_collider() # Объект, в который попал луч
var point := ray.get_collision_point() # Точка столкновения
var normal := ray.get_collision_normal() # Нормаль поверхности
print("Луч попал в: ", collider.name)
print("Точка: ", point)
print("Нормаль: ", normal)
extends Node2D
@onready var ray := $RayCast2D
@onready var line := $Line2D
func _physics_process(delta: float) -> void:
if ray.is_colliding():
var hit_point := ray.get_collision_point()
# Line2D рисуется в локальных координатах
line.points = [Vector2.ZERO, to_local(hit_point)]
else:
# Луч уходит на максимальную дальность
line.points = [Vector2.ZERO, ray.target_position]
# Вычисляем направление отражённого луча
func reflect_ray(incoming_direction: Vector2, surface_normal: Vector2) -> Vector2:
return incoming_direction.bounce(surface_normal)
Эти сигналы встроены — их НЕ нужно объявлять, они уже есть у Area2D:
| Сигнал | Когда срабатывает |
|---|---|
body_entered(body: Node2D) |
Физическое тело вошло в зону |
body_exited(body: Node2D) |
Физическое тело покинуло зону |
area_entered(area: Area2D) |
Другая Area2D пересеклась |
area_exited(area: Area2D) |
Другая Area2D перестала пересекаться |
Встроенные сигналы (тоже НЕ нужно объявлять):
| Сигнал | Когда срабатывает |
|---|---|
body_entered(body: Node) |
Столкновение с другим телом |
body_exited(body: Node) |
Конец столкновения |
Важно для RigidBody2D: чтобы сигналы работали, в инспекторе включите:
Contact Monitor = OnMax Contacts Reported = 4 (или больше)По умолчанию сигналы не работают при столкновениях.
Max Contacts Reported— сколько контактов одновременно обрабатывать.
| Узел | Роль |
|---|---|
StaticBody2D |
Неподвижные объекты (стены, зеркала) |
RigidBody2D |
Динамические объекты (мяч, магнитный объект) |
CharacterBody2D |
Управляемые объекты |
Area2D |
Триггер-зоны (обнаружение пересечений) |
CollisionShape2D |
Форма столкновения (обязательный дочерний) |
RayCast2D |
Лучевые проверки |
| Действие | Метод |
|---|---|
| Постоянная сила | apply_force(), apply_central_force() |
| Мгновенный толчок | apply_impulse(), apply_central_impulse() |
| Слои/маски | Определяют, кто с кем сталкивается |
| Лучевая проверка | RayCast2D.is_colliding(), get_collider() |