Unreal Engine 的 Actor 同步

UE中Actor上的同步设置

Unreal在默认情况下Actor的网络同步是关闭的。要开启特定Actor的网络同步需要手动设置ActorbReplicates属性为true,也可以调用AActor::SetReplicates方法来设置该变量。


AShooterCharacter::AShooterCharacter ()
{
    SetReplicates(true);
}

一个World中参与网络同步的Actor数量可以达到非常多,因此我们不能将此Actor同步到当前连接到服务器的所有客户端,我们可以在Actor上设置一些参数以方便服务端针对每个客户端连接计算出一个合适的同步Actor集合:

  1. 设置为AlwaysRelevant,这样此Actor会同步到所有客户端
  2. 设置NetCullDistanceSquared让距离Actor的一定范围的人得到同步,注意这里是同步范围的平方,主要是为了避免后续计算距离的时候使用开平方,直接使用距离的平方进行比较即可,默认设置下值为225000000,即同步范围为15000cm
  3. 设置bOnlyRelevantToOwnerActor所有者同步信息 例如APlayerController
  4. 重写IsNetRelevantFor 自定义网络相关性设置要不要给特定客户端连接进行同步
bool AActor::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation);
  1. Net Dormancy 这个配置此Actor是否参与Connection的网络同步
    1. DORM_Awake状态时才会计算这个Actor是否与某个Connection关联,这个是默认状态
    2. DORM_Initial时代表Connection只会对此Actor做最初始的同步,后续不再参与该Connection的同步集合计算
    3. DORM_DormantAll时不再参与同步
    4. DORM_DormantPartial代表此Actor内部维护了一个Connection集合,只参与集合内的Connection的同步集合计算 运行时可以修改这个字段的值来切换休眠状态,或者临时性的同步一些状态到客户端之后继续休眠,常用来管理更新频率极低的Actor
  2. 网络更新频率Net Update Frequency、网络最小更新频率Min Net Update Frequency和网络优先级Net Priority都会参与Connection的同步Actor集合的优先级排序

ue4 actor同步相关配置

值得注意的是这里的Actor同步范围不再是由客户端连接来确定,而是每个参与网络同步的Actor自己通过NetCullDistanceSquared来往这个距离内的客户端连接进行推送。这个与前面我们介绍的AOI算法中的aoi_entity->radius是不一样的,aoi_entity->radius代表与当前aoi_entity(M)会拉取距离小于radius的其他aoi_entity(N)并通过网络同步到aoi_entity(M)的客户端上。下图中的左边就是基于拉取半径的网络对象同步,其中A, B是拥有客户端连接的对象,因此有半径,而C, D则是无客户端连接的对象,其半径为0。而图右则代表了UE采取的推送模式,A,B,C,D每个对象都有一个推送半径,如果一个Actor(M)的推送半径内有一个客户端操纵的Actor(N),则Actor(M)就会被加入到Actor(N)的同步Actor集合之中。

AOI的推送与拉取

UE中Actor的同步处理流程

UE中,往客户端同步Actor的入口函数为NetDriver::ServerReplicateActors,这个函数由UNetDriver::TickFlush来驱动每一帧都调用一次。在这个函数中首先收集当前World中参与网络同步的Actor集合:

TArray<FNetworkObjectInfo*> ConsiderList;
ConsiderList.Reserve( GetNetworkObjectList().GetActiveObjects().Num() );

// Build the consider list (actors that are ready to replicate)
ServerReplicateActors_BuildConsiderList( ConsiderList, ServerTickTime );

然后再遍历当前连接过来的所有ClientConnection,执行从ConsiderList中筛选合适的Actor往下同步的相关逻辑。

对于每个ClientConnection, 根据其所控制的角色相机的ViewTarget来构造一个FNetView对象,值得注意的是一个客户端Connection可能有多个虚拟的ChildConnection来对应客户端的多个ViewPort,这些ChildConnection也需要创建对应的FNetViewer:

// Make a list of viewers this connection should consider (this connection and children of this connection)
TArray<FNetViewer>& ConnectionViewers = WorldSettings->ReplicationViewers;

ConnectionViewers.Reset();
new( ConnectionViewers )FNetViewer( Connection, DeltaSeconds );
for ( int32 ViewerIndex = 0; ViewerIndex < Connection->Children.Num(); ViewerIndex++ )
{
	if ( Connection->Children[ViewerIndex]->ViewTarget != NULL )
	{
		new( ConnectionViewers )FNetViewer( Connection->Children[ViewerIndex], DeltaSeconds );
	}
}

有了ConnectionViewer数组之后,开始执行当前客户端所关心的Actor的收集与优先级排序:

FActorPriority* PriorityList	= NULL;
FActorPriority** PriorityActors = NULL;

// Get a sorted list of actors for this connection
const int32 FinalSortedCount = ServerReplicateActors_PrioritizeActors( Connection, ConnectionViewers, ConsiderList, bCPUSaturated, PriorityList, PriorityActors );

// Process the sorted list of actors for this connection
const int32 LastProcessedActor = ServerReplicateActors_ProcessPrioritizedActors( Connection, ConnectionViewers, PriorityActors, FinalSortedCount, Updated );

ServerReplicateActors_PrioritizeActors的内部会对ConsiderList里的每个Actor进行遍历,先过滤掉一些不参与当前ClientConnection的,相关过滤条件有:

  1. Actor标记为了bOnlyRelevantToOwner,且当前Connection不是该ActorOwnerConnection,则忽略
  2. Actor在当前Connection中被标记为了Dormant,即休眠状态,则忽略
  3. Actor对应的Level在客户端还未加载完成,则忽略
  4. Actor对当前的ConnectionViewers遍历执行AActor::IsNetRelevantFor都返回false,则忽略,这个IsNetRelevantFor的核心判定是检查Viewer与当前Actor的距离的平方是否小于Actor::NetCullDistanceSquared

另外对于之前参与了当前Connection的同步但是现在已经被销毁或者休眠的对象,也需要加入到待排序列表中,因为需要给客户端发送一个关闭对应ActorChannel的网络同步包。

收集完所有需要考虑的Actor列表之后,使用FCompareFActorPriority这个比较器对这个列表进行优先级排序:

struct FCompareFActorPriority
{
	FORCEINLINE bool operator()( const FActorPriority& A, const FActorPriority& B ) const
	{
		return B.Priority < A.Priority; // 注意这里的比较方式 会导致优先级高的排在前面
	}
};
// Sort by priority
Sort( OutPriorityActors, FinalSortedCount, FCompareFActorPriority() );

而这里的Priority字段的计算是这样的:

FActorPriority::FActorPriority(UNetConnection* InConnection, UActorChannel* InChannel, FNetworkObjectInfo* InActorInfo, const TArray<struct FNetViewer>& Viewers, bool bLowBandwidth)
	: ActorInfo(InActorInfo), Channel(InChannel), DestructionInfo(NULL)
{	
	const float Time = Channel ? (InConnection->Driver->GetElapsedTime() - Channel->LastUpdateTime) : InConnection->Driver->SpawnPrioritySeconds;
	// take the highest priority of the viewers on this connection
	Priority = 0;
	for (int32 i = 0; i < Viewers.Num(); i++)
	{
		Priority = FMath::Max<int32>(Priority, FMath::RoundToInt(65536.0f * ActorInfo->Actor->GetNetPriority(Viewers[i].ViewLocation, Viewers[i].ViewDir, Viewers[i].InViewer, Viewers[i].ViewTarget, InChannel, Time, bLowBandwidth)));
	}
}

这里会遍历所有的Viewer,使用AActor::GetNetPriority计算当前Actor在此Viewer里的优先级,然后获取其中的最大值,作为在当前Connection里的优先级。这个GetNetPriority的优先级计算综合了距离和朝向两个位置因素,下图就是其优先级计算规则,最高优先级为与当前view_target的正前方向夹角小于45的区域,此时优先级为2,最低优先级为当前view_target后方且距离最远的一个半圆弧:

ue4的actor优先级排序

根据优先级排好顺序之后,PriorityActors里的Actors就会按照优先级从高到低进行排列。然后执行ServerReplicateActors_ProcessPrioritizedActors,这里会对每个Actor进行处理:

  1. 对于对应Level已经在客户端加载的Actor,每1秒钟调用一次 IsActorRelevantToConnection,检查Actor是否与当前连接相关,如果不相关的时间达到 RelevantTimeout(默认为5) ,则关闭通道
  2. 对于对应Level还未在客户端加载完成的Actor,设置一个随机间隔之后再检查
ActorInfo->NextUpdateTime = World->TimeSeconds + 0.2f * FMath::FRand();
  1. 对于要参与同步的Actor, 如果没有ActorChannel就给其创建一个ActorChannel并绑定此Actor
// Create a new channel for this actor.
Channel = (UActorChannel*)Connection->CreateChannelByName( NAME_Actor, EChannelCreateFlags::OpenedLocally );
if ( Channel )
{
	Channel->SetChannelActor(Actor, ESetChannelActorFlags::None);
}
  1. 对于要参与同步的ActorChannel,增加其有效时间,增量为一个随机值
Channel->RelevantTime = ElapsedTime + 0.5 * UpdateDelayRandomStream.FRand();

上面有多个地方使用了随机值,主要是为了避免同一帧处理大量的Actor。在上述操作结束之后,才真正进入Actor同步的入口:

// if the channel isn't saturated
if ( Channel->IsNetReady( 0 ) )
{
	// replicate the actor
	UE_LOG( LogNetTraffic, Log, TEXT( "- Replicate %s. %d" ), *Actor->GetName(), PriorityActors[j]->Priority );
	double ChannelLastNetUpdateTime = Channel->LastUpdateTime;

	if ( Channel->ReplicateActor() )
	{
		ActorUpdatesThisConnectionSent++;
		// Calculate min delta (max rate actor will upate), and max delta (slowest rate actor will update)
		const float MinOptimalDelta				= 1.0f / Actor->NetUpdateFrequency;
		const float MaxOptimalDelta				= FMath::Max( 1.0f / Actor->MinNetUpdateFrequency, MinOptimalDelta );
		const float DeltaBetweenReplications	= ( World->TimeSeconds - ActorInfo->LastNetReplicateTime );

		// Choose an optimal time, we choose 70% of the actual rate to allow frequency to go up if needed
		ActorInfo->OptimalNetUpdateDelta = FMath::Clamp( DeltaBetweenReplications * 0.7f, MinOptimalDelta, MaxOptimalDelta );
		ActorInfo->LastNetReplicateTime = World->TimeSeconds;
	}
	ActorUpdatesThisConnection++;
	OutUpdated++;
}

这里检查对应的ActorChannel是否可以发送数据,如果可以发送则调用Channel->ReplicateActor将此Actor的最新属性状态构造Bunch数据包进行发送,然后再计算此Actor的最优更新间隔。

Actor的同步频率处理

每个参与同步的Actor都会有一个FNetworkObjectInfo来代理同步频率的计算:

/** Next time to consider replicating the actor. Based on FPlatformTime::Seconds(). */
double NextUpdateTime;

/** Last absolute time in seconds since actor actually sent something during replication */
double LastNetReplicateTime;

/** Optimal delta between replication updates based on how frequently actor properties are actually changing */
float OptimalNetUpdateDelta;

/** Last time this actor was updated for replication via NextUpdateTime
* @warning: internal net driver time, not related to WorldSettings.TimeSeconds */
UE_DEPRECATED(4.25, "Please use LastNetUpdateTimestamp instead.")
float LastNetUpdateTime;
double LastNetUpdateTimestamp;

核心就是这里的NextUpdateTime,代表下一次当前Actor参与网络同步的时间戳,在服务器时间到达此时间戳之前,不再参与网络同步相关处理。因此在ServerReplicateActors_BuildConsiderList收集ConsiderList时,会根据此时间戳过滤掉:

if ( !ActorInfo->bPendingNetUpdate && World->TimeSeconds <= ActorInfo->NextUpdateTime )
{
	continue;		// It's not time for this actor to perform an update, skip it
}

一般情况下,这个NextUpdateTime字段是根据这段代码算出来的:

const float NextUpdateDelta = bUseAdapativeNetFrequency ? ActorInfo->OptimalNetUpdateDelta : 1.0f / Actor->NetUpdateFrequency;

// then set the next update time
ActorInfo->NextUpdateTime = World->TimeSeconds + UpdateDelayRandomStream.FRand() * ServerTickTime + NextUpdateDelta;

// and mark when the actor first requested an update
//@note: using Time because it's compared against UActorChannel.LastUpdateTime which also uses that value
PRAGMA_DISABLE_DEPRECATION_WARNINGS
ActorInfo->LastNetUpdateTime = ElapsedTime;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
ActorInfo->LastNetUpdateTimestamp = ElapsedTime;

这里根据全局设置bUseAdapativeNetFrequency来选择以Actor上的固定频率还是动态频率来计算下一次更新的间隔,然后在此间隔之上在加入一点随机性避免在同一次Tick中处理太多。

FNetworkGuid

每个参与网络同步的对象在往客户端发送数据的时候,都会赋予其一个FNetworkGuid,其构造时机为UActorChannel::SetChannelActor的时候,其内部在创建这个UObject对应的FObjectReplicator时,就会通过FNetGUIDCache::GetOrAssignNetGUID来创建一个新的FNetworkGuid,并注册好这个UObjectFNetworkGuid的映射:

// ServerReplicateActors_ProcessPrioritizedActors
Channel->SetChannelActor(Actor, ESetChannelActorFlags::None);

// void UActorChannel::SetChannelActor(AActor* InActor, ESetChannelActorFlags Flags)
ActorReplicator = FindOrCreateReplicator(Actor);

// TSharedRef<FObjectReplicator>& UActorChannel::FindOrCreateReplicator(UObject* Obj, bool* bOutCreated)
// Start replicating with this replicator
NewRef->StartReplicating(this);

// void FObjectReplicator::StartReplicating(class UActorChannel * InActorChannel)
ObjectNetGUID = ConnectionNetDriver->GuidCache->GetOrAssignNetGUID( Object );

/** Gets or assigns a new NetGUID to this object. Returns whether the object is fully mapped or not */
FNetworkGUID FNetGUIDCache::GetOrAssignNetGUID(UObject* Object, const TWeakObjectPtr<UObject>* WeakObjectPtr)

同一个Actor在不同的NetConnectionActorChannel中设置的FNetworkGuid是不共享的,因为FNetworkGuid的值创建使用了两个全局的计数器,分别对应了静态对象和动态对象,值的最后一位代表是否是动态对象。

#define COMPOSE_NET_GUID( Index, IsStatic )	( ( ( Index ) << 1 ) | ( IsStatic ) )
#define ALLOC_NEW_NET_GUID( IsStatic )		( COMPOSE_NET_GUID( ++UniqueNetIDs[ IsStatic ], IsStatic ) )

/**
 *	Generate a new NetGUID for this object and assign it.
 */
FNetworkGUID FNetGUIDCache::AssignNewNetGUID_Server( UObject* Object )
{
	check( IsNetGUIDAuthority() );

	// Generate new NetGUID and assign it
	const int32 IsStatic = IsDynamicObject( Object ) ? 0 : 1;

	const FNetworkGUID NewNetGuid( ALLOC_NEW_NET_GUID( IsStatic ) );

	RegisterNetGUID_Server( NewNetGuid, Object );

	UE_NET_TRACE_ASSIGNED_GUID(Driver->GetNetTraceId(), NewNetGuid, Object->GetClass()->GetFName(), 0);

	return NewNetGuid;
}

静态对象指的是可以通过名字路径查找到的对象,例如场景里摆放好的Actor及其子对象,是否是静态对象根据下面的代码来判定:

bool UObject::IsNameStableForNetworking() const
{
	return HasAnyFlags(RF_WasLoaded | RF_DefaultSubObject) || IsNative() || IsDefaultSubobject();
}

/** IsFullNameStableForNetworking means an object can be referred to its full path name over the network */
bool UObject::IsFullNameStableForNetworking() const
{
	if ( GetOuter() != NULL && !GetOuter()->IsNameStableForNetworking() )
	{
		return false;	// If any outer isn't stable, we can't consider the full name stable
	}

	return IsNameStableForNetworking();
}

/** IsSupportedForNetworking means an object can be referenced over the network */
bool UObject::IsSupportedForNetworking() const
{
	return IsFullNameStableForNetworking();
}

bool FNetGUIDCache::IsDynamicObject( const UObject* Object )
{
	check( Object != NULL );
	check( Object->IsSupportedForNetworking() );

	// Any non net addressable object is dynamic
	return !Object->IsFullNameStableForNetworking();
}

Actor的初始创建信息

UActorChannel::ReplicateActor中向客户端发送该Actor的初始信息的时候,会先写入对应的FNetworkGuidBunch中,然后根据其是否是动态对象发送不同的数据:

// ----------------------------------------------------------
// If initial, send init data.
// ----------------------------------------------------------

if (RepFlags.bNetInitial && OpenedLocally)
{
	UE_NET_TRACE_SCOPE(NewActor, Bunch, GetTraceCollector(Bunch), ENetTraceVerbosity::Trace);

	Connection->PackageMap->SerializeNewActor(Bunch, this, Actor);
	WroteSomethingImportant = true;

	Actor->OnSerializeNewActor(Bunch);
}

// SerializeNewActor
FNetworkGUID NetGUID;
UObject *NewObj = Actor;
SerializeObject(Ar, AActor::StaticClass(), NewObj, &NetGUID);
// bool UPackageMapClient::SerializeObject( FArchive& Ar, UClass* Class, UObject*& Object, FNetworkGUID *OutNetGUID)
FNetworkGUID NetGUID = GuidCache->GetOrAssignNetGUID( Object );

// Write out NetGUID to caller if necessary
if (OutNetGUID)
{
	*OutNetGUID = NetGUID;
}

// Write object NetGUID to the given FArchive
InternalWriteObject( Ar, NetGUID, Object, TEXT( "" ), NULL );

// If we need to export this GUID (its new or hasnt been ACKd, do so here)
if (!NetGUID.IsDefault() && ShouldSendFullPath(Object, NetGUID))
{
	check(IsNetGUIDAuthority());
	if ( !ExportNetGUID( NetGUID, Object, TEXT(""), NULL ) )
	{
		UE_LOG( LogNetPackageMap, Verbose, TEXT( "Failed to export in ::SerializeObject %s"), *Object->GetName() );
	}
}

// bool UPackageMapClient::ShouldSendFullPath( const UObject* Object, const FNetworkGUID &NetGUID )
if ( !Object->IsNameStableForNetworking() )
{
	checkf( !NetGUID.IsDefault(), TEXT("Non-stably named object %s has a default NetGUID. %s"), *GetFullNameSafe(Object), *Connection->Describe() );
	checkf( NetGUID.IsDynamic(), TEXT("Non-stably named object %s has static NetGUID [%s]. %s"), *GetFullNameSafe(Object), *NetGUID.ToString(), *Connection->Describe() );
	return false;		// We only export objects that have stable names
}

如果是静态Actor,执行ExportNetGUID,这里会将静态对象的OuterFNetworkGuid和当前静态对象的名字写入到Bunch:

ObjectPathName = Object->GetName();
ObjectOuter = Object->GetOuter();
// Serialize reference to outer. This is basically a form of compression.
FNetworkGUID OuterNetGUID = GuidCache->GetOrAssignNetGUID( ObjectOuter );

InternalWriteObject( Ar, OuterNetGUID, ObjectOuter, TEXT( "" ), NULL );

// Serialize Name of object
Ar << ObjectPathName;

此时不需要带上当前Actor的位置朝向等信息,因为静态Actor的初始信息在Level里已经设置好了。

如果是动态的Actor则比较复杂了,需要带上许多额外的各种数据:

if ( NetGUID.IsDynamic() )
{
	UObject* Archetype = nullptr; // 这里是该Actor的类型默认对象
	UObject* ActorLevel = nullptr;
	FVector Location;
	FVector Scale;
	FVector Velocity;
	FRotator Rotation;
	bool SerSuccess;
}

写入这些基本信息之后,UE还添加了这个Actor的最新属性信息到Bunch中,入口为ReplicateProperties:

// replicateActor
// The Actor
{
	UE_NET_TRACE_OBJECT_SCOPE(ActorReplicator->ObjectNetGUID, Bunch, GetTraceCollector(Bunch), ENetTraceVerbosity::Trace);
	WroteSomethingImportant |= ActorReplicator->ReplicateProperties(Bunch, RepFlags);
}
// The SubObjects
WroteSomethingImportant |= Actor->ReplicateSubobjects(this, &Bunch, &RepFlags);

除了Actor的属性之外,还会通过ReplicateSubobjects将挂载在当前Actor上的ComponentSubObject也统一打包相关数据进行下发。

有了这些信息之后,客户端就可以创建出一个与服务端等价的Actor了。

Actor的组件与子对象同步

Actor身上的组件和子对象同步需要借用这个Actor的通道来进行,借助ActorChannel创建自己的FObjectReplicator以及属性同步的相关数据结构。

bool AActor::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags)
{
	check(Channel);
	check(Bunch);
	check(RepFlags);

	bool WroteSomething = false;

	for (UActorComponent* ActorComp : ReplicatedComponents)
	{
		if (ActorComp && ActorComp->GetIsReplicated())
		{
			WroteSomething |= ActorComp->ReplicateSubobjects(Channel, Bunch, RepFlags);		// Lets the component add subobjects before replicating its own properties.
			WroteSomething |= Channel->ReplicateSubobject(ActorComp, *Bunch, *RepFlags);	// (this makes those subobjects 'supported', and from here on those objects may have reference replicated)		
		}
	}
	return WroteSomething;
}

在同步一个子对象的时候,也会分配一个唯一的FNetworkGuid,并且会构造一个对应的FObjectReplicator,然后调用这个ReplicatorReplicateProperties方法将这个UObject上的同步属性的最新值进行下发:

bool UActorChannel::ReplicateSubobject(UObject *Obj, FOutBunch &Bunch, const FReplicationFlags &RepFlags)
{
	TWeakObjectPtr<UObject> WeakObj(Obj);

	
	if ( !Connection->Driver->GuidCache->SupportsObject( Obj, &WeakObj ) )
	{
		Connection->Driver->GuidCache->AssignNewNetGUID_Server( Obj );	//Make sure he gets a NetGUID so that he is now 'supported'
	}

	bool NewSubobject = false;
	bool bCreatedReplicator = false;

   	TSharedRef<FObjectReplicator>& ObjectReplicator = FindOrCreateReplicator(Obj, &bCreatedReplicator);
   	bool WroteSomething = ObjectReplicator.Get().ReplicateProperties(Bunch, RepFlags);
	if (NewSubobject && !WroteSomething)
	{
		// 如果当前属性与默认属性相同 也需要强制写一些数据下去
		FNetBitWriter EmptyPayload;
		WriteContentBlockPayload( Obj, Bunch, false, EmptyPayload );
		WroteSomething= true;
	}

	return WroteSomething;
}

客户端Actor的创建

客户端接收到服务器端的ActorChannel创建通知之后,创建一个新的ActorChannel,并调用ProcessBunch来处理这个Channel内的所有数据。如果发现当前ActorChannel还未绑定Actor,则会认为第一个Bunch中包含了服务端调用SerializeNewActor写入的各种信息,此时我们在客户端再次调用此函数,不过此时Bunch在客户端是以Loading形式调用的,在服务端则是以Saving形式调用:

void UActorChannel::ProcessBunch( FInBunch & Bunch )
{
	if( Actor == NULL )
	{

		UE_NET_TRACE_SCOPE(NewActor, Bunch, Connection->GetInTraceCollector(), ENetTraceVerbosity::Trace);

		AActor* NewChannelActor = NULL;
		bSpawnedNewActor = Connection->PackageMap->SerializeNewActor(Bunch, this, NewChannelActor);
	}
}

SerializeNewActor内部会将这个ActorFNetworkGuid解析出来,然后将ActorFNetworkGuid之间的映射关系存储到FNetGUIDCache之中。

创建出对应的Actor之后,再开始处理后续的所有属性同步数据:

// ----------------------------------------------
//	Read chunks of actor content
// ----------------------------------------------
while ( !Bunch.AtEnd() && Connection != NULL && Connection->State != USOCK_Closed )
{
	FNetBitReader Reader( Bunch.PackageMap, 0 );
	// Read the content block header and payload
	UObject* RepObj = ReadContentBlockPayload( Bunch, Reader, bHasRepLayout );
	TSharedRef< FObjectReplicator > & Replicator = FindOrCreateReplicator( RepObj );

	bool bHasUnmapped = false;

	if ( !Replicator->ReceivedBunch( Reader, RepFlags, bHasRepLayout, bHasUnmapped ) )
	{

	}
}

这里的while循环里不仅会处理Actor自身的属性数据,还会处理其ActorComponent和子对象的属性数据,所以属性数据在打包的时候会调用WriteContentBlockPayload来加上属性所有者的标识:

// ReplicateProperties
const bool WroteImportantData = Writer.GetNumBits() != 0;

if ( WroteImportantData )
{
	OwningChannel->WriteContentBlockPayload( Object, Bunch, bHasRepLayout, Writer );
}

return WroteImportantData;

int32 UActorChannel::WriteContentBlockPayload( UObject* Obj, FNetBitWriter &Bunch, const bool bHasRepLayout, FNetBitWriter& Payload )
{
	const int32 StartHeaderBits = Bunch.GetNumBits();

	WriteContentBlockHeader( Obj, Bunch, bHasRepLayout );
}
void UActorChannel::WriteContentBlockHeader( UObject* Obj, FNetBitWriter &Bunch, const bool bHasRepLayout )
{
	const int NumStartingBits = Bunch.GetNumBits();

	Bunch.WriteBit( bHasRepLayout ? 1 : 0 );

	// If we are referring to the actor on the channel, we don't need to send anything (except a bit signifying this)
	const bool IsActor = Obj == Actor;

	Bunch.WriteBit( IsActor ? 1 : 0 );

	if ( IsActor )
	{
		return;
	}

	Bunch << Obj; // 这里会调用UObject的序列化操作

	if ( Connection->Driver->IsServer() )
	{
		// 如果是静态对象 这个bit写入1
		if ( Obj->IsNameStableForNetworking() )
		{
			Bunch.WriteBit( 1 );
		}
		else
		{
			// 如果是动态对象 这个bit写入0 同时将这个对象的UClass数据写入 以方便客户端创建对应类型的UObject
			Bunch.WriteBit( 0 );
			UClass *ObjClass = Obj->GetClass();
			Bunch << ObjClass;
		}
	}
}

每段属性数据的所有者通过ReadContentBlockPayload来获取,这里面会执行ActorComponent或者子对象的反序列化操作:

UObject* UActorChannel::ReadContentBlockPayload( FInBunch &Bunch, FNetBitReader& OutPayload, bool& bOutHasRepLayout )
{
	const int32 StartHeaderBits = Bunch.GetPosBits();
	bool bObjectDeleted = false;
	UObject* RepObj = ReadContentBlockHeader( Bunch, bObjectDeleted, bOutHasRepLayout );
	uint32 NumPayloadBits = 0;
	Bunch.SerializeIntPacked( NumPayloadBits );
	OutPayload.SetData( Bunch, NumPayloadBits );

	return RepObj;
}
UObject* UActorChannel::ReadContentBlockHeader( FInBunch & Bunch, bool& bObjectDeleted, bool& bOutHasRepLayout )
{
	const bool IsServer = Connection->Driver->IsServer();
	bObjectDeleted = false;

	bOutHasRepLayout = Bunch.ReadBit() != 0 ? true : false;

	const bool bIsActor = Bunch.ReadBit() != 0 ? true : false;


	if ( bIsActor )
	{
		// If this is for the actor on the channel, we don't need to read anything else
		return Actor;
	}

	//
	// We need to handle a sub-object
	//

	// Note this heavily mirrors what happens in UPackageMapClient::SerializeNewActor
	FNetworkGUID NetGUID;
	UObject* SubObj = NULL;

	// Manually serialize the object so that we can get the NetGUID (in order to assign it if we spawn the object here)
	Connection->PackageMap->SerializeObject( Bunch, UObject::StaticClass(), SubObj, &NetGUID );

	const bool bStablyNamed = Bunch.ReadBit() != 0 ? true : false;

	if ( bStablyNamed )
	{
		return SubObj;
	}

	// Serialize the class in case we have to spawn it.
	// Manually serialize the object so that we can get the NetGUID (in order to assign it if we spawn the object here)
	FNetworkGUID ClassNetGUID;
	UObject* SubObjClassObj = NULL;
	Connection->PackageMap->SerializeObject( Bunch, UObject::StaticClass(), SubObjClassObj, &ClassNetGUID );
	UClass * SubObjClass = Cast< UClass >( SubObjClassObj );
	SubObj = NewObject< UObject >(Actor, SubObjClass);
	// Notify actor that we created a component from replication
	Actor->OnSubobjectCreatedFromReplication( SubObj );
		
	// Register the component guid
	Connection->Driver->GuidCache->RegisterNetGUID_Client( NetGUID, SubObj );
	// Track which sub-object guids we are creating
	CreateSubObjects.Add( SubObj );
}

至此,服务端向下同步的一个Actor及其ActorComponent和子对象都被创建出来了,并设置好了相关的属性。此ActorChannel后续主要负责接受相关的RPC数据与属性同步数据,这些数据的构造、发送、接收、解析等具体细节将在后续的章节中介绍。