Вступление#
Добавить больше реплицируемых сущностей и сделать сценарий ближе к реальной игровой ситуации.
Говоря это в прошлый раз, я немного слукавил — с реальной игровой ситуацией это ничего общего иметь не будет. Зато сущностей действительно станет больше.
Я решил засадить игровую локацию лесом: каждое дерево — это реплицируемый AActor. Каждое дерево получит параметр здоровья и будет терять его раз в секунду. Просто реплицировать float или int показалось скучным, поэтому я добавил на каждое дерево UAbilitySystemComponent с UHealthAttrSet.
Подготовка#
Для начала нам потребуется актор дерева, на котором будет визуально отображаться его состояние здоровья.
Чем меньше значение здоровья, тем краснее становится дерево. Для этого будем менять параметр HealthRatio в MaterialInstance.
void ATreeActor::BeginPlay()
{
// Создаём MaterialInstance, чтобы управлять цветом дерева в зависимости от здоровья
ForEachComponent(false,
[this](UActorComponent* Component)
{
if (auto Primitive = Cast<UPrimitiveComponent>(Component))
if (auto Instance = Primitive->CreateAndSetMaterialInstanceDynamic(0))
MaterialInstances.Add(Instance);
});
// Подписываемся на изменение здоровья
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(HealthAttrSet->GetHealthAttribute())
.AddUObject(this, &ThisClass::OnHealthValueChanged);
}
void ATreeActor::OnHealthValueChanged(const FOnAttributeChangeData& Data)
{
auto HealthRatio = Data.NewValue / FMath::Max(HealthAttrSet->GetHealthMax(), UE_KINDA_SMALL_NUMBER);
for (auto Instance : MaterialInstances)
Instance->SetScalarParameterValue(TEXT("HealthRatio"), HealthRatio);
}
UAbilitySystemComponent не требует включённого тика, поэтому тик у ATreeActor можно отключить.Так выглядит класс UHealthAttrSet:
UCLASS()
class THEGAME_API UHealthAttrSet : public UAttributeSet
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_HealthMax)
FGameplayAttributeData HealthMax;
ATTRIBUTE_ACCESSORS(UHealthAttrSet, Health)
ATTRIBUTE_ACCESSORS(UHealthAttrSet, HealthMax)
};
Чтобы отнимать здоровье, я создал GE_DamageOverTime, который раз в секунду уменьшает значение здоровья на 1 единицу.
Этот эффект я накладываю на каждое дерево через 30 секунд после старта сервера.
Технически это не самое удачное решение, поскольку все деревья начинают терять здоровье одновременно, что вызывает подтормаживания во время игры.
Ну и конечно, нам нужен сам меш дерева.
Поскольку деревьев будет много, я решил сделать максимально простой вариант — с минимальным количеством полигонов.
Кроме того, у дерева следует отключить коллизии, чтобы клиенты не сталкивались с ним, и тени — чтобы во время тестов FPS был выше.

Чтобы разместить большое количество деревьев, я использую Procedural Content Generation (PCG) с простым графом, который создаёт около 10 000 деревьев на площади 2 км². При плотности 0.0026 деревьев на м² я получил 10 201 дерево.
Тестирование#
Все будет примерно как в прошлый раз
Iris Replication System#
Запускаем тестирование и подключаемся к серверу. Предчувствие сразу не самое лучшее — ощущаются лаги и сетевые коррекции.
Когда возникает сильный рассинхрон между положением персонажа на сервере и на клиенте, сервер принудительно “возвращает” клиента в правильную позицию.
Это проявляется резким дёрганьем камеры и персонажа.
Лог завален предупреждениями, которые указывают на то, что сервер не справляется с обработкой входящих пакетов:
LogNetPlayerMovement: Warning: ServerMove: TimeStamp expired: 53.433105, CurrentTimeStamp: 54.618511, Character: BP_PlayerCharacter_C_2147471470
LogNetPlayerMovement: Warning: ServerMove: TimeStamp expired: 53.433105, CurrentTimeStamp: 54.643517, Character: BP_PlayerCharacter_C_2147471470
LogNetPlayerMovement: Warning: ServerMove: TimeStamp expired: 53.433105, CurrentTimeStamp: 54.668522, Character: BP_PlayerCharacter_C_2147471470
Результат, конечно, разочаровывающий…
Но если взглянуть с другой стороны — вдохновляющий!
Стандартная система репликации по умолчанию передаёт данные только об акторах, находящихся в радиусе 150 метров вокруг игрока.
Iris же реплицирует так только APawn, а все остальные акторы по умолчанию помечены как AlwaysRelevant — то есть реплицируются всем игрокам и всегда.
; BaseEngine.ini from Engine
[/Script/IrisCore.ObjectReplicationBridgeConfig]
RequiredNetDriverChannelClassName=/Script/Engine.DataStreamChannel
; Filters
DefaultSpatialFilterName=Spatial
; Filter configs should typically be configured per game
; Set the class-based dynamic filters configs here. Using None means the objects of this class are always relevant.
!FilterConfigs=ClearArray
; NotRouted means that this type will not be replicated
;+FilterConfigs=(ClassName=/Script/Engine.LevelScriptActor, DynamicFilterName=NotRouted)
; By default classes derived from AActor are always relevant (because not filtered dynamically)
+FilterConfigs=(ClassName=/Script/Engine.Actor, DynamicFilterName=None))
; Info types aren't supposed to have physical representation and rely on a static priority
+FilterConfigs=(ClassName=/Script/Engine.Info, DynamicFilterName=None)
+FilterConfigs=(ClassName=/Script/Engine.PlayerState, DynamicFilterName=None)
; Pawns can be spatially filtered...
+FilterConfigs=(ClassName=/Script/Engine.Pawn, DynamicFilterName=Spatial))
By default classes derived from AActor are always relevant (because not filtered dynamically)
Все настройки Iris выполняются через файл конфигурации DefaultEngine.ini.
Добавим наш ATreeActor в Spatial-фильтр.
; DefaultEngine.ini
[/Script/IrisCore.ObjectReplicationBridgeConfig]
+FilterConfigs=(ClassName=/Script/TheGame.TreeActor, DynamicFilterName=Spatial))
Вот это результат — Iris демонстрирует впечатляющую производительность.
Никаких заметных сетевых коррекций не наблюдается — всё работает плавно.
Когда персонаж приближается к зелёным деревьям (они зелёные, потому что находятся за пределами радиуса репликации),
те сразу становятся красными, как только попадают в зону видимости, реплицируя своё текущее здоровье.
Пришло время сравнить её с стандартной реализацией.
Default Replication System#
Здесь ситуация, мягко говоря, далека от идеала. Стандартная система репликации с трудом удерживает стабильность, выдавая около 7 кадров в секунду. На выполнение игровой логики не остаётся времени — сервер перегружен подготовкой сетевых данных.
Этот эксперимент наглядно показывает, насколько Iris превосходит стандартную систему.
Остаётся лишь дождаться, когда Iris перейдёт из статуса Experimental в Beta и станет полноценным решением для продакшена.
Выводы#
В таком сценарии Iris демонстрирует впечатляющий результат — система эффективно фильтрует и обрабатывает огромное количество реплицируемых сущностей без огромных потерь производительности.
Про графики#
Как вы навреное заметили, на графиках в статье данные выглядят плавно, поскольку я применяю сглаживание значений для большей наглядности и удобства чтения.
В данной статье меня интересует в первую очередь площадь под графиком, а не отдельные пики.
Резкие скачки времени кадра, возникающие при одновременном отнимании здоровья у всех 10 000 деревьев (достигающие 200 мс), не оказывают существенного влияния на общий анализ.
Вот пример:
Как можно заметить, сглаженный график немного смещается вверх, переходя выше линии бюджета в 60 FPS, в то время как исходный, несглаженный - имеет точки, расположенные ниже этой границы.
