Почему функция работает в PIE в одиночке, но ломается в мультиплеере?
Подбор предмета работает в PIE в одиночке. С двумя клиентами предмет исчезает только у того, кто подобрал (у второго остаётся висеть в мире), а счётчик инвентаря иногда не обновляется на удалённых клиентах.
void AItem::OnPickup(APawn* Picker)
{
Picker->FindComponentByClass<UInventoryComponent>()->Count++;
Destroy();
}
// UInventoryComponent.h
UPROPERTY()
int32 Count = 0;
// UInventoryComponent.cpp — GetLifetimeReplicatedProps не переопределён
Найдите и исправьте ошибку.
В одиночке одна машина — и сервер, и клиент, поэтому нереплицируемое состояние и клиентские изменения просто работают. В мультиплеере сервер и клиенты разделены, что вскрывает отсутствие репликации, запись не с тем авторитетом и невыверенные вызовы RPC.
- ✗Считать, что работающий в одиночном PIE код уже сетево-корректен
- ✗Писать геймплейное состояние на клиенте, раз локально это сработало
- ✗Тестировать только с одним игроком вместо режима нескольких клиентов в PIE
- →Как режим нескольких клиентов в PIE помогает рано ловить эти ошибки?
- →Почему
HasAuthority()— ключевая проверка при переносе одиночной логики?
Сценарий
Подбор предмета работает идеально при тестировании в PIE с одним игроком. Запускаете PIE с двумя клиентами — предмет исчезает только у того, кто подобрал, а у второго клиента остаётся висеть в мире. Иногда счётчик инвентаря вообще не обновляется.
Почему в одиночке это пряталось
В PIE с одним игроком сервер и клиент — одна и та же машина и один и тот же UWorld. Когда код на «клиенте» меняет переменную, он на самом деле меняет её и на «сервере» — потому что это один объект. Нереплицируемое состояние выглядит синхронным, потому что синхронизировать нечего.
В мультиплеере это два разных процесса. Изменение на клиенте остаётся на клиенте; сервер его не видит и при следующей репликации перезаписывает.
Диагностика
воспроизводит разделение сервер/клиент.
стороне она выполняется. Окажется, что предмет уничтожался на клиенте.
регистрации DOREPLIFETIME.
- Запустите PIE → Number of Players: 2, режим Play As Client. Это
- Оберните логику подбора в
if (HasAuthority())и логируйте, на какой - Проверьте
UPROPERTYсчётчика инвентаря — у него нетReplicatedи
Первопричина
Destroy(), поэтому видимый эффект был только локальным.
- Уничтожение предмета вызывалось на клиенте — у клиента нет авторитета на
- Счётчик инвентаря менялся на клиенте и не реплицировался.
Исправление
void AItem::OnPickup(APawn* Picker)
{
if (!HasAuthority()) return; // только сервер
Picker->FindComponentByClass<UInventoryComponent>()->Count++;
Destroy(); // авторитетное уничтожение реплицируется
}
void UInventoryComponent::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& Out) const
{
Super::GetLifetimeReplicatedProps(Out);
DOREPLIFETIME(UInventoryComponent, Count);
}
Уничтожение на сервере реплицируется всем клиентам; реплицируемый Count обновляет UI через колбэк OnRep.