Найти в Дзене
4 подписчика

#2 - GODOT 4. ПРОДОЛЖАЕМ РАЗРАБОТКУ MULTIPLAYER — ДОБАВЛЯЕМ СЕТЕВУЮ ФИЗИКУ И ПИНАЕМ КУБИКИ


Продолжаем работу над сетевой частью проекта. В прошлом посте мы разобрались с базовым движением: настроили контроллер так, чтобы клиент не спорил с сервером. Персонаж бегает и синхронизируется. Но мир пока статичен. Пришло время добавить интерактивности — физические объекты, которые можно толкать.
В синглплеере (или если бы мы делали локальный контроллер) растолкать раскиданные по уровню коробки (RigidBody3D) проще простого.
Посмотрите, как выглядит обычное толкание объектов при столкновении:

func _physics_process(delta):
move_and_slide()

# Проверяем всё, с чем столкнулся игрок
for i in get_slide_collision_count():
var collision = get_slide_collision(i)
var collider = collision.get_collider()

# Если это физический объект — толкаем его
if collider is RigidBody3D:
var push_dir = -collision.get_normal()
collider.apply_central_impulse(push_dir * push_force)

Всё интуитивно: сдвинулись, проверили коллизии, нашли физический объект, пнули его в противоположную от нормали сторону.
Теперь возвращаемся к нашему мультиплееру. Если оставить этот код как есть, начнется хаос. Каждый подключенный клиент будет сам пинать кубик на своем экране. Из-за микрозадержек (пинга) у одного игрока кубик улетит вправо, у другого — влево, а у третьего вообще застрянет в текстурах.
Поэтому мы снова вспоминаем наше золотое правило: сервер всегда прав.
Адаптируем код. Клиент просто упирается в кубик (для него это стена), а расчетом импульса занимается исключительно сервер:

func _physics_process(delta):
move_and_slide()

# Физику толкания просчитывает только сервер!
if is_multiplayer_authority():
for i in get_slide_collision_count():
var collision = get_slide_collision(i)
var collider = collision.get_collider()

if collider is RigidBody3D:
var push_dir = -collision.get_normal()

# Небольшой секрет: даем легкий импульс вверх,
# чтобы кубик оторвался от пола и красиво покатился, а не просто скользил
push_dir.y = 0.3
collider.apply_central_impulse(push_dir.normalized() * push_force)

Что здесь происходит?
Сервер замечает, что игрок врезался в кубик. Сервер сам применяет силу (плюс мы добавили хитрость — push_dir.y = 0.3, небольшой "апперкот", чтобы коробка реалистично подпрыгивала, а не терлась пузом об пол).
А что делает клиент? Клиент ничего не считает. Как и в случае с передвижением игрока, на самом кубике (RigidBody3D) висит наша любимая нода MultiplayerSynchronizer. Сервер пнул кубик -> физический движок сервера рассчитал его полет -> MultiplayerSynchronizer автоматически разослал новые координаты и угол поворота всем клиентам.
В итоге у всех игроков на экранах кубик красиво и абсолютно синхронно отлетает в сторону. Никаких рассинхронов, сервер должен всё контролировать. В следующей статье по "сетевой физике" покажу мультиплеерный баг который обнаружил...
2 минуты