UProjectileMovementComponent组件同步

UProjectileMovementComponent是UE中的抛体组件,可以用来仿真抛体运动,比如子弹,手榴弹,甚至是带有动力和追踪功能的导箭。但是UProjectileMovementComponent默认情况下仅仅本地有效,没有对网络进行Transform同步。

如果大家使用过这个组件,会发现在联网游戏中客户端和服务器的对象会出现位置不一致的情况,还有可能会出现对象跳动的现象,这些都是没有同步的表现,需要我们进行处理。

Actor的同步

在研究抛体组件同步之前,先来看看Actor是如何进行Transform同步的。

Actor的Transform实际上是根组件的Transform,因此对Actor的同步实际上就是对根组件的同步。

在客户端和服务器的连接后,会在连接的Socket上面建立一个个Channel,服务器上面的每一个对象都对应着一个ActorChannel,通过这个Channel,客户端和服务器的Actor建立通信通道,然后会进行数据同步和RPC调用,以及发起属性通知。

在Actor中,有一个标记bReplicateMovement被用来标记Actor是否同步,这个属性在蓝图的属性面板上面也有,如果标记为True,那么会进行相关的同步操作。

/**
 * If true, replicate movement/location related properties.
 * Actor must also be set to replicate.
 * @see SetReplicates()
 * @see https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Replication/
 */
UPROPERTY(ReplicatedUsing=OnRep_ReplicateMovement, Category=Replication, EditDefaultsOnly)
uint8 bReplicateMovement:1; 

Actor的Transform属性是通过一个特殊的结构体ReplicatedMovement来进行传递的,里面包含了相关的需要同步的属性,在ReplicatedMovement中的属性值发生改变的时候,会调用OnRep_ReplicatedMovement进行事件通知。

/** Used for replication of our RootComponent's position and velocity */
UPROPERTY(EditDefaultsOnly, ReplicatedUsing=OnRep_ReplicatedMovement, Category=Replication, AdvancedDisplay)
struct FRepMovement ReplicatedMovement;

/** Replicated movement data of our RootComponent.
  * Struct used for efficient replication as velocity and location are generally replicated together (this saves a repindex) 
  * and velocity.Z is commonly zero (most position replications are for walking pawns). 
  */
USTRUCT()
struct ENGINE_API FRepMovement
{
	GENERATED_BODY()

	/** Velocity of component in world space */
	UPROPERTY(Transient)
	FVector LinearVelocity;

	/** Velocity of rotation for component */
	UPROPERTY(Transient)
	FVector AngularVelocity;
	
	/** Location in world space */
	UPROPERTY(Transient)
	FVector Location;

	/** Current rotation */
	UPROPERTY(Transient)
	FRotator Rotation;

	/** If set, RootComponent should be sleeping. */
	UPROPERTY(Transient)
	uint8 bSimulatedPhysicSleep : 1;

	/** If set, additional physic data (angular velocity) will be replicated. */
	UPROPERTY(Transient)
	uint8 bRepPhysics : 1;

	/** Allows tuning the compression level for the replicated location vector. You should only need to change this from the default if you see visual artifacts. */
	UPROPERTY(EditDefaultsOnly, Category=Replication, AdvancedDisplay)
	EVectorQuantization LocationQuantizationLevel;

	/** Allows tuning the compression level for the replicated velocity vectors. You should only need to change this from the default if you see visual artifacts. */
	UPROPERTY(EditDefaultsOnly, Category=Replication, AdvancedDisplay)
	EVectorQuantization VelocityQuantizationLevel;

	/** Allows tuning the compression level for replicated rotation. You should only need to change this from the default if you see visual artifacts. */
	UPROPERTY(EditDefaultsOnly, Category=Replication, AdvancedDisplay)
	ERotatorQuantization RotationQuantizationLevel;
}

ReplicatedMovement 仅仅是一个用来同步的中间值,并不是Actor的原始数据,对Actor的Transform操作并不会直接作用于 ReplicatedMovement,那么Actor的真实数据是怎么同步到 ReplicatedMovement然后再同步到客户端的呢?

在服务器对Actor进行同步的时候,会调用PreReplication事件,在这个事件中会使用GatherCurrentMovement函数从当前的Actor信息填充ReplicatedMovement结构体。

void AActor::PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker )
{
	// Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment.
	AttachmentReplication.AttachParent = nullptr;
	AttachmentReplication.AttachComponent = nullptr;

	GatherCurrentMovement();

	DOREPLIFETIME_ACTIVE_OVERRIDE( AActor, ReplicatedMovement, bReplicateMovement );

	// Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it.
	DOREPLIFETIME_ACTIVE_OVERRIDE( AActor, AttachmentReplication, RootComponent && !RootComponent->GetIsReplicated() );

	UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass());
	if (BPClass != nullptr)
	{
		BPClass->InstancePreReplication(this, ChangedPropertyTracker);
	}
}

ReplicatedMovement同步到客户端之后,会调用OnRep_ReplicatedMovement事件通知,在这个事件中,通过PostNetReceiveVelocity和PostNetReceiveLocationAndRotation来设置位置、旋转和速度。按理说通过这些操作之后,Actor的同步应该就可以了,不应该出现问题的,那么为什么还会有不同步的现象出现呢?

void AActor::OnRep_ReplicatedMovement()
{
	// Since ReplicatedMovement and AttachmentReplication are REPNOTIFY_Always (and OnRep_AttachmentReplication may call OnRep_ReplicatedMovement directly),
	// this check is needed since this can still be called on actors for which bReplicateMovement is false - for example, during fast-forward in replay playback.
	// When this happens, the values in ReplicatedMovement aren't valid, and must be ignored.
	if (!bReplicateMovement)
	{
		return;
	}

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
		if (CVarDrawDebugRepMovement->GetInt() > 0)
		{
			DrawDebugCapsule(GetWorld(), ReplicatedMovement.Location, GetSimpleCollisionHalfHeight(), GetSimpleCollisionRadius(), ReplicatedMovement.Rotation.Quaternion(), FColor(100, 255, 100), true, 1.f);
		}
#endif

	if (RootComponent)
	{
		if (ActorReplication::SavedbRepPhysics != ReplicatedMovement.bRepPhysics)
		{
			// Turn on/off physics sim to match server.
			SyncReplicatedPhysicsSimulation();
		}

		if (ReplicatedMovement.bRepPhysics)
		{
			// Sync physics state
			checkSlow(RootComponent->IsSimulatingPhysics());
			// If we are welded we just want the parent's update to move us.
			UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(RootComponent);
			if (!RootPrimComp || !RootPrimComp->IsWelded())
			{
				PostNetReceivePhysicState();
			}
		}
		else
		{
			// Attachment trumps global position updates, see GatherCurrentMovement().
			if (!RootComponent->GetAttachParent())
			{
				if (Role == ROLE_SimulatedProxy)
				{
#if ENABLE_NAN_DIAGNOSTIC
					if (ReplicatedMovement.Location.ContainsNaN())
					{
						logOrEnsureNanError(TEXT("AActor::OnRep_ReplicatedMovement found NaN in ReplicatedMovement.Location"));
					}
					if (ReplicatedMovement.Rotation.ContainsNaN())
					{
						logOrEnsureNanError(TEXT("AActor::OnRep_ReplicatedMovement found NaN in ReplicatedMovement.Rotation"));
					}
#endif

					PostNetReceiveVelocity(ReplicatedMovement.LinearVelocity);
					PostNetReceiveLocationAndRotation();
				}
			}
		}
	}
}

跟进PostNetReceiveVelocity,发现代码并没有实现,原来这就是问题所在,缺少了速度向量。。。那么,重写这个函数,在里面把代码补上就行了。当然,Actor必须要是同步的,并且要勾选 bReplicateMovement 。

void AActor::PostNetReceiveVelocity(const FVector& NewVelocity)
{
}
//修改后的代码
void AActor::PostNetReceiveVelocity(const FVector& NewVelocity)
{
     if (MovementComp)
     {
         MovementComp->Velocity = NewVelocity;
     }
}
上一篇:好程序员大数据学习路线分享Actor学习笔记


下一篇:什么是用例测试?