2_Физика движка
Урок 2 : Физика движка
Разбираем физическую систему Unity: коллайдеры и триггеры как инструменты обнаружения, Rigidbody как физическое тело, и активные запросы к физическому движку (Raycast, OverlapSphere). Именно эти инструменты позволят персонажу не проваливаться сквозь пол и реагировать на мир вокруг.
1. Коллайдеры и триггеры
Что такое коллайдер
Коллайдер (Collider) - компонент, который задаёт физическую форму объекта. Без коллайдера объект невидим для физического движка: он будет проваливаться сквозь пол и пропускать сквозь себя другие объекты.
Важно: коллайдер не двигает объект. Это только форма для обнаружения столкновений. Движением управляет Rigidbody (раздел 2).
| Коллайдер | Когда использовать |
|---|---|
BoxCollider |
Ящики, стены, платформы, двери |
SphereCollider |
Снаряды, сферические объекты, дешёвые приближения |
CapsuleCollider |
Персонажи (стандарт - капсула охватывает тело) |
MeshCollider |
Точное повторение меша - дорого, использовать осторожно |
Триггеры - зоны без физической реакции
Если у коллайдера включён флаг Is Trigger, он перестаёт быть твёрдым телом и становится «зоной обнаружения». Объекты проходят сквозь него, но движок фиксирует вход и выход.
Типичные применения: зона сбора предметов, зона телепортации, зона смерти, детектор врагов.
// Этот скрипт - на объекте с коллайдером в режиме Is Trigger
public class CollectibleZone : MonoBehaviour
{
// OnTriggerEnter - вызывается в первый кадр, когда объект входит в зону
void OnTriggerEnter(Collider other)
{
// other - коллайдер объекта, который вошёл
if (other.CompareTag("Player"))
{
Debug.Log("Игрок вошёл в зону сбора");
Destroy(gameObject);
}
}
// OnTriggerStay - вызывается каждый физический кадр, пока объект внутри
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
Debug.Log("Игрок в зоне урона - наносим урон");
}
// OnTriggerExit - вызывается когда объект покидает зону
void OnTriggerExit(Collider other)
{
Debug.Log($"{other.name} покинул зону");
}
}
Для срабатывания триггерных событий нужно выполнить три условия: оба объекта имеют коллайдер, один из коллайдеров - Is Trigger, хотя бы один из объектов имеет компонент Rigidbody. Без Rigidbody триггер не сработает.
Слои и управление коллизиями
Каждый объект принадлежит одному слою (Layer). В Project Settings → Physics находится Layer Collision Matrix: таблица, где можно включить или отключить коллизии между любыми парами слоёв.
Например: слой Bullet не должен сталкиваться со слоем Player (пуля летит от игрока). Просто снимаем галочку Bullet × Player в матрице.
Из кода можно отключить коллизию между конкретными коллайдерами:
void Start()
{
var playerCollider = GetComponent<Collider>();
var weaponCollider = GetComponentInChildren<Collider>();
// Оружие не будет сталкиваться с владельцем
Physics.IgnoreCollision(playerCollider, weaponCollider);
}
2. Rigidbody и физика
Что добавляет Rigidbody
Rigidbody - компонент, который подключает объект к физическому движку Unity. Без него объект статичен (или движется только через код). С Rigidbody объект получает:
- Массу (Mass) - влияет на импульс при столкновениях
- Гравитацию - по умолчанию включена, тянет вниз
- Drag - сопротивление воздуха (линейное и угловое)
- Полноценную реакцию на силы и импульсы
Kinematic режим
isKinematic = true - объект управляется кодом, а не физикой. Гравитация не действует, силы игнорируются. При этом объект всё ещё участвует в триггерах и отталкивает не-кинематические Rigidbody при столкновении.
Используй kinematic для: платформ, дверей, управляемых персонажей (CharacterController + Rigidbody kinematic).
Ограничения и режимы обнаружения
void Start()
{
var rb = GetComponent<Rigidbody>();
// Заморозить вращение по осям X и Z - персонаж не заваливается
rb.constraints = RigidbodyConstraints.FreezeRotationX
| RigidbodyConstraints.FreezeRotationZ;
// Для быстрых объектов (пули, быстрые машины)
// Discrete (стандарт) может «проскочить» сквозь тонкую стену за один кадр
// Continuous - проверяет непрерывный путь, не пропускает столкновения
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
Коллизии - полноценные столкновения
В отличие от триггеров, OnCollisionEnter вызывается при физическом столкновении (оба коллайдера - твёрдые тела):
public class Ball : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
Debug.Log($"Столкновение с {collision.gameObject.name}");
// Точка первого контакта в мировых координатах
Vector3 contactPoint = collision.contacts[0].point;
// Относительная скорость столкновения
float impactSpeed = collision.relativeVelocity.magnitude;
Debug.Log($"Скорость удара: {impactSpeed:F1}");
}
void OnCollisionStay(Collision collision) { }
void OnCollisionExit(Collision collision) { }
}
Порядок событий в физическом тике: FixedUpdate() → движок рассчитывает физику → обнаружение столкновений → OnCollisionEnter/Stay/Exit и OnTriggerEnter/Stay/Exit. Поэтому весь код, влияющий на физику, должен быть в FixedUpdate.
3. Методы перемещения через Rigidbody
Управлять объектом с Rigidbody нужно именно через него, а не через Transform - иначе физика не успевает реагировать на изменения позиции.
public class PhysicsMovement : MonoBehaviour
{
[SerializeField] private float _speed = 5f;
[SerializeField] private float _jumpForce = 7f;
private Rigidbody _rb;
void Awake()
{
_rb = GetComponent<Rigidbody>();
_rb.constraints = RigidbodyConstraints.FreezeRotationX
| RigidbodyConstraints.FreezeRotationZ;
}
void FixedUpdate()
{
// MovePosition - плавное кинематическое перемещение
// Корректно взаимодействует с физикой: не проходит сквозь стены
Vector3 move = new Vector3(1f, 0f, 0f); // направление задаст Input в следующем уроке
_rb.MovePosition(_rb.position + move * _speed * Time.fixedDeltaTime);
}
public void Jump()
{
// AddForce с Impulse - мгновенный единовременный импульс
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}
}
| Метод | Где вызывать | Физика | Коллизии | Когда использовать |
|---|---|---|---|---|
rb.MovePosition |
FixedUpdate |
Да | Корректно | Основное движение персонажа |
rb.AddForce |
FixedUpdate |
Да | Корректно | Прыжок, взрыв, применение сил |
rb.velocity = v |
FixedUpdate |
Частично | Корректно | Быстрый контроль горизонтальной скорости |
transform.position = p |
Update |
Нет | Игнорируются | Спавн, телепорт, объекты без физики |
transform.Translate |
Update |
Нет | Игнорируются | Визуальные объекты без физики |
Перемещение через transform.Translate или прямое присвоение transform.position не учитывает коллайдеры. При высокой скорости объект пройдёт сквозь стену. Для объектов с Rigidbody используй методы самого Rigidbody.
4. Физические запросы
Коллайдеры и триггеры срабатывают реактивно - движок сам вызывает методы при контакте. Иногда нужен противоположный подход: активно опросить пространство из кода в конкретный момент. Для этого служат физические запросы.
Physics.Raycast - луч
Physics.Raycast пускает невидимый луч из точки в направлении и возвращает информацию о первом объекте с коллайдером, который луч пересёк.
void Update()
{
// Луч из позиции объекта вперёд, максимальная длина 10 единиц
if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 10f))
{
Debug.Log($"Попал в: {hit.collider.name}");
Debug.Log($"Точка попадания: {hit.point}");
Debug.Log($"Нормаль поверхности: {hit.normal}");
Debug.Log($"Расстояние: {hit.distance:F2}");
}
}
Луч можно ограничить слоями через LayerMask, чтобы он реагировал только на нужные объекты:
[SerializeField] private LayerMask _interactableLayers;
void Update()
{
// Луч из центра экрана (через камеру) в мир
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 100f, _interactableLayers))
{
if (Input.GetMouseButtonDown(0))
hit.collider.GetComponent<IInteractable>()?.Interact();
}
}
Physics.CheckSphere и OverlapSphere
Physics.CheckSphere проверяет, есть ли коллайдер в сфере заданного радиуса. Используется для проверки земли под ногами персонажа:
// Возвращает true, если в точке pos есть коллайдер на слое groundLayer
bool isGrounded = Physics.CheckSphere(groundCheck.position, 0.2f, groundLayer);
Physics.OverlapSphere возвращает массив всех коллайдеров в сфере - удобно для зоны взрыва:
// Все коллайдеры в радиусе 5 единиц от точки взрыва
Collider[] inBlastZone = Physics.OverlapSphere(explosionPoint, blastRadius);
foreach (var col in inBlastZone)
{
col.GetComponent<IDamageable>()?.TakeDamage(50f);
}
| Метод | Возвращает | Когда использовать |
|---|---|---|
Physics.Raycast |
bool + RaycastHit |
Стрельба, интерактивный объект под курсором |
Physics.RaycastAll |
RaycastHit[] |
Луч сквозь несколько объектов (пробивная пуля) |
Physics.CheckSphere |
bool |
Быстрая проверка: есть ли что-то рядом (земля под ногами) |
Physics.OverlapSphere |
Collider[] |
Все объекты в радиусе (взрыв, агро-зона) |
Physics.Raycast невидим во вьюпорте. Добавь рядом Debug.DrawRay(origin, direction * maxDistance, Color.red) - линия отобразится в Scene view во время Play Mode и покажет точное направление и длину луча.