ue4-Network相关-组件及数据同步

  • UActorComponent 及 Non-UActorComponent 同步
  • Non-UActorComponent同步 可以理解为继承 UObject 的类,用来装数据的同时又需要暴露给蓝图调用,且带同步功能。

组件的同步和 Actor 的同步非常相似

Actor同步的传送门:ue4-Network相关-变量同步

  • 新建一个组件类 UMyComp

    • MyComp.h

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      #pragma once
      #include "MyComp.generated.h"
      UCLASS(Blueprintable, BlueprintType)
      class UMyComp : public UTextRenderComponent
      {
      GENERATED_BODY()
      public:
      UMyComp();

      UFUNCTION(BlueprintCallable, Category = AMyNetCharacter)
      virtual void OnRep_Length();

      public:
      UPROPERTY(ReplicatedUsing = OnRep_Length, EditAnywhere, BlueprintReadWrite, Category = UMyComp)
      int32 Length;
      };
    • MyComp.cpp

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      #include "MyNet.h"
      #include "MyComp.h"
      #include "Net/UnrealNetwork.h"

      UMyComp::UMyComp() : Super()
      {
      UE_LOG(LogMyNet, Warning, TEXT("--- UMyComp::UMyComp"));
      Length = 789;
      bReplicates = true;
      }

      void UMyComp::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
      {
      Super::GetLifetimeReplicatedProps(OutLifetimeProps);
      DOREPLIFETIME(UMyComp, Length);
      }

      void UMyComp::OnRep_Length()
      {
      SetText(FText::FromString(FString::Printf(TEXT("Length:%d"), Length)));
      }

  • 组件的同步分为两大类:静态组件 同步 和 动态组件 同步

    1. 静态组件:直接在 c++中构造函数中 CreateDefaultSubobject 构造出来的,or 蓝图中直接 Add Comonent 上去的,在实例化 Actor 后就在 Actor 身上的,这种情况下,只要 Actor 是 Replicated 类型的,就自动会在其他客户端同步该 静态组件,无需指定该 静态组件Replicated 类型。只有在该 静态组件 里面有需要同步的变量或者事件时,才需要指定为 Replicated 类型

      • 蓝图

        1. 这里新建了一个蓝图类 MyCompBp 继承 UMyComp,再起一个 RepNotify 类型的 Num 变量,在改变时修改展示内容。然后在 Actor 中 Add Componen 挂上去
          这里写图片描述
          这里写图片描述
          这里写图片描述

        2. 测试蓝图中的变量是否能同步
          这里写图片描述

        3. 结果:(在Client2中操作)
          这里写图片描述

      • c++

        1. 在 Actor 的构造函数中创建,在 Actor 的蓝图就可以看见该组件

          1
          2
          3
          4
          5
          6
          7
          AMyNetCharacter::AMyNetCharacter()
          {
          CusComp = CreateDefaultSubobject<UMyComp>(TEXT("CusComp"));
          CusComp->SetupAttachment(RootComponent);
          //CusComp->SetNetAddressable();
          CusComp->SetIsReplicated(true);
          }

          这里写图片描述

        2. 测试c++中的变量是否能同步
          这里写图片描述

        • 结果:(就不贴了)
    2. 动态组件:游戏运行时中动态实例化出来或删除的组件(必须在服务端上Spawn or Destroy)。必须指定为 Replicated 类型才能同步给所有客户端。

      • 蓝图

        1. 蓝图中动态实例化组件接口 Add Component,实例化出来一定要调用 SetIsReplicated 接口,同时 DyCusCompBp 变量也必须是 Replicated 类型
          这里写图片描述

        2. 在测试一下变量同步
          这里写图片描述

        • 结果:(就不贴了)
      • c++

        1. 在actor中写个接口给蓝图调用

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          UPROPERTY(Replicated, EditAnywhere, BlueprintReadWrite, Category = AMyNetCharacter)
          UMyComp* DynamicCusComp22;

          UFUNCTION(BlueprintCallable, Category = AMyNetCharacter)
          virtual void SpawnMyComp();

          void AMyNetCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
          {
          DOREPLIFETIME(AMyNetCharacter, DynamicCusComp22);
          }

          void AMyNetCharacter::SpawnMyComp()
          {
          if (DynamicCusComp22)
          DynamicCusComp22->DestroyComponent();

          DynamicCusComp22 = NewObject<UMyComp>(this, UMyComp::StaticClass());
          DynamicCusComp22->RegisterComponent();
          FAttachmentTransformRules AttachmentRules(EAttachmentRule::KeepRelative, false);
          DynamicCusComp22->AttachToComponent(GetRootComponent(), AttachmentRules);
          DynamicCusComp22->SetRelativeLocation(FVector(0.f, 100.f, 50.f));
          //DynamicCusComp22->SetNetAddressable(); //绝逼不能调这个,否则所有客户端断线
          DynamicCusComp22->SetIsReplicated(true);
          }
          • 官网说要手动调用这个标记 Manually marked (via UActorComponent::SetNetAddressable),实测不行,有待研究源码。会报错:Stably named sub-object not found. Component
            这里写图片描述
        2. 在测试一下变量同步
          这里写图片描述

        • 结果:(就不贴了)

自定义类(继承UObject)的同步,可用作数据同步

  1. 新建个继承 UObject 的数据类 UMyItem

    • MyItem.h
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      #pragma once
      #include "MyItem.generated.h"

      UCLASS(Blueprintable, BlueprintType)
      class UMyItem : public UObject
      {
      GENERATED_BODY()
      public:
      UMyItem();
      virtual ~UMyItem();
      public:
      UFUNCTION()
      virtual void OnRep_Count();

      virtual bool IsSupportedForNetworking() const override
      {
      return true;
      }

      public:
      UPROPERTY(ReplicatedUsing = OnRep_Count, EditAnywhere, BlueprintReadWrite, Category = UMyItem)
      int32 Count;
      };
    • MyItem.cpp
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      #include "MyNet.h"
      #include "MyItem.h"

      #include "Net/UnrealNetwork.h"

      UMyItem::UMyItem() : Super()
      {
      UE_LOG(LogMyNet, Warning, TEXT("--- UMyItem::UMyItem"));
      Count = 32;
      }

      UMyItem::~UMyItem()
      {
      UE_LOG(LogMyNet, Warning, TEXT("--- UMyItem::~UMyItem"),);
      }

      void UMyItem::OnRep_Count()
      {
      FString msg = FString::Printf(TEXT("--- OnRep_Count Count:%d"), Count);
      GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, *msg);
      }

      void UMyItem::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
      {
      Super::GetLifetimeReplicatedProps(OutLifetimeProps);
      DOREPLIFETIME(UMyItem, Count);
      }
      • 规则也是和 Actor 同步差不多,没有了 bReplicates = true; 成员,需要同步的变量加上 Replicated 标记,实现 GetLifetimeReplicatedProps 方法。需要实现 IsSupportedForNetworking
  2. 在 Replicated Actor 中重写几个函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    	virtual bool ReplicateSubobjects(class UActorChannel *Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override;
    //virtual void OnSubobjectCreatedFromReplication(UObject *NewSubobject); 不知道为啥重写这两个函数会造成不能同步,逻辑上看起来没问题,有待探究
    //virtual void OnSubobjectDestroyFromReplication(UObject *Subobject);

    bool AMyNetCharacter::ReplicateSubobjects(class UActorChannel *Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags)
    {
    bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
    if (CusObj != nullptr)
    {
    WroteSomething |= Channel->ReplicateSubobject(CusObj, *Bunch, *RepFlags);
    }
    return WroteSomething;
    }
    /*
    void AMyNetCharacter::OnSubobjectCreatedFromReplication(UObject *NewSubobject)
    {
    Super::OnSubobjectCreatedFromReplication(NewSubobject);
    FString msg = FString::Printf(TEXT("--- OnSubobjectCreatedFromReplication ClassName:%s"), *NewSubobject->GetClass()->GetName());
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *msg);
    }

    void AMyNetCharacter::OnSubobjectDestroyFromReplication(UObject *Subobject)
    {
    FString msg = FString::Printf(TEXT("--- OnSubobjectDestroyFromReplication ClassName:%s"), *Subobject->GetClass()->GetName());
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *msg);
    Super::OnSubobjectDestroyFromReplication(Subobject);
    }
    */
    • 从源码来看,network 会每帧调用到 actor 的 ReplicateSubobjects
      这里写图片描述
  3. 在服务端生产一个对象,然后会同步到所有客户端
    这里写图片描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    	UFUNCTION(BlueprintCallable, Category = AMyNetCharacter)
    virtual void SpawnMyItem();

    void AMyNetCharacter::SpawnMyItem()
    {
    if (!HasAuthority()) //确保是服务端去new组件
    return;

    if (CusObj)
    CusObj->ConditionalBeginDestroy();

    CusObj = NewObject<UMyItem>(this, UMyItem::StaticClass());
    }
  4. 测试修改一下里面的数据是否能同步到其他客户端
    这里写图片描述

  5. 结果:(在Client2中操作)
    这里写图片描述

  • 在 Replicated Actor 中示例化 UMyItem 时会在 所有客户端同步回调

    1
    virtual void OnSubobjectCreatedFromReplication(UObject *NewSubobject);
  • 在销毁 Replicated Actor 的时候会会在所有客户端 销毁 UMyItem

    1
    virtual void OnSubobjectDestroyFromReplication(UObject *Subobject);
  • 在源码中没有找到 动态销毁UMyItem 的方法,它只有在 Actor 销毁的时候跟着被销毁。所以还不能完全控制(应该是我还找到)。想要完全控制的话可以用 UActorComponent。


参考资料