Announcement

Collapse
No announcement yet.

Replication Hell - the gift that keeps on giving

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Replication Hell - the gift that keeps on giving

    Right about the time I started thinking I was getting the hang of Unreal and replication in general, I ran into an issue that I have not been able to overcome.

    When spawning a new actor, I'm unable to get replication to the clients. I've spent a lot of time on this and just can't figure it out.

    PickupBase - this class is the base class for all future pickup classes in my game

    RiflePickup - this class is a weapon pickup class derived from PickupBase

    ShooterCPPGameMode - this is my Game Mode class and where I am spawning my pickups for now.

    The spawning of my weapon pickups (RiflePickup) is working fine on the server, but it won't replicate to the clients.

    APickupBase.h

    Code:
    UENUM(BlueprintType)        
    enum class EWeapon : uint8
    {
        EW_AK47     UMETA(DisplayName = "AK-47"),
        EW_AR15     UMETA(DisplayName = "AR-15"),
        EW_SMG11    UMETA(DisplayName = "SMG-11"),
        EW_ASVAL    UMETA(DisplayName = "AS VAL"),
        EW_SUB        UMETA(DisplayName = "Sub")
    };
    
    UCLASS()
    class SHOOTERCPP_API APickupBase : public AActor
    {
        GENERATED_BODY()
    
    public:    
        // Sets default values for this actor's properties
        APickupBase();
    
        virtual void Initialize(EWeapon weapon);
    
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;    
    
        UPROPERTY(Replicated)
        UStaticMeshComponent* weaponMeshComponent;
        UPROPERTY(Replicated)
        UBoxComponent* boxComponent;    
    
    public:    
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
    private:    
    
    };

    APickupBase.cpp

    Code:
    #include "PickupBase.h"
    #include "ConstructorHelpers.h"
    #include "Components/BoxComponent.h"
    #include "Components/StaticMeshComponent.h"
    #include "UnrealNetwork.h"
    
    // Sets default values
    APickupBase::APickupBase()
    {
        // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
        PrimaryActorTick.bCanEverTick = true;
        bReplicates = true;
        bReplicateMovement = true;
    
        // Every weapon in the game will have a box collider, static mesh and an actual type to spawn for use
        boxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("Box Comp"));
        boxComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    
        weaponMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Weapon Mesh Comp"));
        weaponMeshComponent->SetupAttachment(boxComponent);    
    
        boxComponent->SetWorldScale3D(FVector(1.f, 1.0f, 1.0f));
        boxComponent->SetBoxExtent(FVector(60.f, 32.f, 32.f));    
    }
    
    void APickupBase::Initialize(EWeapon weapon)
    {
        // nothing to do here in the base class
    }
    
    // Called when the game starts or when spawned
    void APickupBase::BeginPlay()
    {
        Super::BeginPlay();
    }
    
    // Called every frame
    void APickupBase::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
    }
    
    void APickupBase::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
    {
        Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
        DOREPLIFETIME(APickupBase, weaponMeshComponent);
        DOREPLIFETIME(APickupBase, boxComponent);
    }

    ARiflePickup.h (notice this is derived from APickupBase)

    Code:
    #include "CoreMinimal.h"
    #include "PickupBase.h"
    #include "RiflePickup.generated.h"
    
    /**
     * 
     */
    UCLASS()
    class SHOOTERCPP_API ARiflePickup : public APickupBase
    {
        GENERATED_BODY()
    
    public:
        ARiflePickup();
    
        virtual void Initialize(EWeapon weapon) override;
    
    protected:
    
        // Weapon meshes
        UStaticMesh* ak47Mesh;
        UStaticMesh* ar15Mesh;
        UStaticMesh* kaValMesh;
        UStaticMesh* smg11Mesh;
        UStaticMesh* ak47uMesh;
    
    };

    ARiflePickup.cpp (notice this is derived from APickupBase)

    Code:
    #include "RiflePickup.h"
    #include "ConstructorHelpers.h"
    #include "Components/BoxComponent.h"
    #include "Components/StaticMeshComponent.h"
    #include "UnrealNetwork.h"
    
    ARiflePickup::ARiflePickup()
    {
        bReplicates = true;
        bReplicateMovement = true;
    
        static ConstructorHelpers::FObjectFinder<UStaticMesh> ak47Asset(TEXT("/Game/FPS_Weapon_Bundle/Weapons/Meshes/Ka47/SM_KA47_X"));
        ak47Mesh = ak47Asset.Object;
    
        static ConstructorHelpers::FObjectFinder<UStaticMesh> ar15Asset(TEXT("/Game/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SM_AR4_X"));
        ar15Mesh = ar15Asset.Object;
    
        static ConstructorHelpers::FObjectFinder<UStaticMesh> kaValAsset(TEXT("/Game/FPS_Weapon_Bundle/Weapons/Meshes/Ka_Val/SM_KA_Val_X"));
        kaValMesh = kaValAsset.Object;
    
        static ConstructorHelpers::FObjectFinder<UStaticMesh> smg11Asset(TEXT("/Game/FPS_Weapon_Bundle/Weapons/Meshes/SMG11/SM_SMG11_X"));
        smg11Mesh = smg11Asset.Object;
    
        static ConstructorHelpers::FObjectFinder<UStaticMesh> ak47uAsset(TEXT("/Game/FPS_Weapon_Bundle/Weapons/Meshes/KA74U/SM_KA74U_X"));
        ak47uMesh = ak47uAsset.Object;
    }
    
    void ARiflePickup::Initialize(EWeapon weapon)
    {
        FVector meshOffset;
        UStaticMesh* weaponMesh;
    
        switch (weapon)
        {
        case EWeapon::EW_AK47:
        {
            weaponMesh = ak47Mesh;
            meshOffset = FVector(-15.f, 0.f, 0.f);
            break;
        }
        case EWeapon::EW_AR15:
        {
            weaponMesh = ar15Mesh;
            meshOffset = FVector(-15.f, 0.f, 0.f);
            break;
        }
        case EWeapon::EW_ASVAL:
        {
            weaponMesh = kaValMesh;
            meshOffset = FVector(-15.f, 0.f, 0.f);
            break;
        }
        case EWeapon::EW_SMG11:
        {
            weaponMesh = smg11Mesh;
            meshOffset = FVector(-15.f, 0.f, 0.f);
            break;
        }
        case EWeapon::EW_SUB:
        {
            weaponMesh = ak47uMesh;
            meshOffset = FVector(-15.f, 0.f, 0.f);
            break;
        }
        default:
        {
            weaponMesh = ak47Mesh;
            meshOffset = FVector(-15.f, 0.f, 0.f);
            break;
        }
    
        }
        if (weaponMeshComponent)
        {
            weaponMeshComponent->SetStaticMesh(weaponMesh);
            weaponMeshComponent->SetRelativeLocation(FVector(ForceInitToZero));
            weaponMeshComponent->SetRelativeLocation(meshOffset);
            weaponMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
        }
    }

    AShooterCPPGameMode.h

    Code:
    #include "CoreMinimal.h"
    #include "GameFramework/GameModeBase.h"
    #include "PickupBase.h"
    
    #include "ShooterCPPGameMode.generated.h"
    
    
    UCLASS(minimalapi)
    class AShooterCPPGameMode : public AGameModeBase
    {
        GENERATED_BODY()
    
    public:
        AShooterCPPGameMode();
        virtual void BeginPlay() override;
    
    protected:
    
        UFUNCTION(Server, Reliable, WithValidation)
        void SpawnWeaponToWorld(EWeapon weapon, FVector location, FRotator rotation);
    };

    AShooterCPPGameMode.cpp


    Code:
    #include "ShooterCPPGameMode.h"
    #include "ShooterCPPCharacter.h"
    #include "UObject/ConstructorHelpers.h"
    #include "PickupBase.h"
    #include "RiflePickup.h"
    #include "PickupBase.h"
    #include "Engine/World.h"
    
    
    
    AShooterCPPGameMode::AShooterCPPGameMode()
    {
        // set default pawn class to our Blueprinted character
        static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter"));
        if (PlayerPawnBPClass.Class != NULL)
        {
            DefaultPawnClass = PlayerPawnBPClass.Class;
        }
    }
    
    void AShooterCPPGameMode::BeginPlay()
    {
        Super::BeginPlay();
    
        UWorld* world = GetWorld();
    
        if (world)
        {
            FVector location = FVector(-1000.f, -1000.f, 200.f);
            FRotator rotation = FRotator::ZeroRotator;
    
            // Spawning a single weapon to the works works on the server, but is not displayed on the client
    
            SpawnWeaponToWorld(EWeapon::EW_AK47, location, rotation);
            ARiflePickup* temp =  (ARiflePickup*) GetWorld()->SpawnActor<APickupBase>(ARiflePickup::StaticClass(), location, rotation);
            temp->Initialize(EWeapon::EW_AK47);
    
        }
    }
    
    bool AShooterCPPGameMode::SpawnWeaponToWorld_Validate(EWeapon weapon, FVector location, FRotator rotation)
    {
        return true;    
    }
    
    void AShooterCPPGameMode::SpawnWeaponToWorld_Implementation(EWeapon weapon, FVector location, FRotator rotation)
    {
        ARiflePickup* temp = (ARiflePickup*)GetWorld()->SpawnActor<APickupBase>(ARiflePickup::StaticClass(), location, rotation);
        bool test = temp->HasAuthority();
        temp->Initialize(EWeapon::EW_AK47);
    }

    So, in my GameMode class, I'm spawning a single pickup. It works perfectly on the server, but will not propigate to the client.

    Can someone please tell me what I'm doing wrong?

    Thanks in advance!



    #2
    APickupItem::APickupItem(const FObjectInitializer & ObjectInitializer)
    : Super(ObjectInitializer)
    {
    Mesh = ObjectInitializer.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("Mesh"));
    RootComponent = Mesh;
    Mesh->SetCollisionProfileName(FName(TEXT("PhysicsActor")));
    Mesh->SetCollisionResponseToAllChannels(ECR_Ignore);
    Mesh->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);
    Mesh->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block);
    Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    Mesh->CanCharacterStepUpOn = ECanBeCharacterBase::ECB_No;
    Mesh->bEnablePhysicsOnDedicatedServer = true;
    Mesh->SetSimulatePhysics(true);
    Mesh->CanCharacterStepUpOn = ECanBeCharacterBase::ECB_No;

    WeaponComponent = ObjectInitializer.CreateOptionalDefaultSubobject<UPickupComponent>(this, TEXT("New_weapon"));
    WeapontComponent->SetupAttachment(Mesh, FName(TEXT("Gun_Root")));

    bReplicates = true;
    bAlwaysRelevant = true;
    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.TickInterval = 0.5f;

    Comment


      #3
      What I have on my pickup item. It replicates on both client and server. Maybe replicating location also

      void APickupItem::GetLifetimeReplicatedProps(TArray<FLifetimeProperty> & OutLifetimeProps) const
      {
      Super::GetLifetimeReplicatedProps(OutLifetimeProps);

      DOREPLIFETIME(APickupItem, Instigator);
      DOREPLIFETIME(APickupItem, ReplicatedLocation);

      Comment


        #4
        You need to add

        SetRemoteRoleForBackwardsCompat(ROLE_SimulatedProxy);

        To your constructor otherwise the default RemoteRole will be ROLE_None
        Joseph Wilcox
        [Technical Director] WisEngineering
        https://www.wisengineering.com
        https://www.digitalleprechaun.com

        Comment


          #5
          You don't seem to call the superclass. Might be something?

          Comment


            #6
            Thanks for all the suggestions. I've now tried them all but still don't have it working. The base constructor was automatically being called because it was the default constructor. However, I verified this was not the issue. I tried forcing the role in each constructor, but that did not make any difference.

            I'm running out of ideas. I'll probably remove the class hierarchy out of the equation and just create one class for testing purposes. Other than that, I have no idea.

            I'm stumped.

            Comment


              #7
              Is the root component ever set?

              I don't see it in your code, though thadkinsjr posted it in theirs.

              Without a valid root component on an actor that replicates, you would need to make sure bAlwaysRelevant is true.
              This is what the GameStateBase and similar actors do.

              A root component is also important to ensure the actors is replicated to the correct location.
              Otherwise, it will always end up at the origin of the world (0,0,0).

              You may notice that in various native C++ classes, a dummy transform component is created and set in the constructor.

              e.g. Controller.cpp

              Code:
              AController::AController(const FObjectInitializer& ObjectInitializer)
                  : Super(ObjectInitializer)
              {
              ...
                  TransformComponent = CreateDefaultSubobject<USceneComponent>(TEXT("TransformComponent0"));
                  RootComponent = TransformComponent;
              Rule#21: Be polite, be professional, but have a plan to kill everyone you meet.

              Comment


                #8
                Hi Kris,

                I modified my base class to set the RootComponent:

                Code:
                APickupBase::APickupBase()
                {
                    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
                    PrimaryActorTick.bCanEverTick = true;
                    bReplicates = true;
                    bReplicateMovement = true;
                
                    RootComponent = boxComponent;
                
                    SetRemoteRoleForBackwardsCompat(ROLE_SimulatedProxy);
                Unfortunately it made no difference. Any other ideas?

                Comment


                  #9
                  How about adding SetReplicates(true) in the PickupBase constructor
                  Code:
                   // Sets default values
                  APickupBase::APickupBase()
                  {    
                       //Whatever there is      
                        SetReplicates(true);    
                  }
                  If you see Actor class, there is one routine entirely dedicated for setting the replication environment. If that doesn't work, try browsing the UT Code https://github.com/EpicGames/UnrealT...e/UTPickup.cpp. Good luck!
                  Last edited by The-Cowboy; 06-02-2019, 11:22 PM.
                  https://ravimohan.net/
                  https://github.com/ravimohan1991

                  Comment


                    #10
                    Yeah, I tried SetReplicates(true) in my base and derived classes and it made no difference.

                    One thing I just noticed that is once all my pickups are spawned and visible by the server, if I Eject the camera from the player, all the assets in scene disappear (visibly). I'm guessing this is because the ejected camera just becomes a new client and can only see what is being replicated? Thought I would mention it in case it's a clue.

                    Comment


                      #11
                      Try taking the UTPickup class instead of your class APickupBase and inheriting that for testing purpose. That is the next natural step I can think of. If you still can't work it out, the problem is something else, because rest assured, I have played UT online (windows) and I certainly have picked the pickups!
                      Last edited by The-Cowboy; 06-03-2019, 01:00 AM.
                      https://ravimohan.net/
                      https://github.com/ravimohan1991

                      Comment


                        #12
                        A few pointers (heh):
                        • Your 'Initialize' function will not run on the Client, and it is terrible design to set weapon properties like this anyway. You should create blueprint sub-classes of the pickup, and set the default meshes there. You then spawn the subclasses, not class-default objects.
                        • Having a UFUNCTION marked with 'Server' and 'Reliable' in the Game Mode class makes no sense. The GameMode only exists on the Server so clients can't call it anyway, and the server is running that code locally. Just use a normal function to spawn the weapon. You *ONLY* spawn the actor on the Server.
                        • C-Style casts are dangerous and unneccesary. You're already casting the weapon with SpawnActor so the additional (ARiflePickup*) before it isn't needed.
                        • You do not need to replicate the actor components - they are part of the class-default object and the client knows what components an actor has. Marking components as replicated propertied in GetLifetimeReplicatedProps makes no sense and isn't needed.
                        • You're spawning an ARiflePickup::StaticClass() - so you're spawning the most basic C++ version of the actor with no meshes, or visible properties to speak of. How do you know where it is on the Client -OR- Server if you can't see it? The Initialize() function isn't going to run client side.
                        • Setting the RemoteRole will do absolutely nothing.
                        • You're spawning *Two* pickups on the Server in BeginPlay() - one from the Server function call, another immediatelly after it.
                        • Instigator and ReplicatedLocation also aren't needed - Instigator replicates anyway as part of the Super call, and ReplicatedLocation is pointless since ReplicatedMovement will override it anyway.
                        It's clear to me why you can't see the actor on the client - there's nothing to see because the Initialize() function won't run there how is the client supposed to know to run that, or what weapon class it is? Create blueprint sub-classes of the class and spawn those instead, and get rid of that Initialize() awfulness.

                        EDIT: Bullet-point formatting on this forum is awful.
                        Last edited by TheJamsh; 06-03-2019, 04:50 AM.

                        Comment


                          #13
                          TheJamsh I just implemented ever single bullet-point you listed and everything is working perfectly now. I guess I was trying to make it too complicated by doing EVERYTHING in C++. I now see the value in creating the final derived class as a blueprint and set the default mesh (and other vars) within the BP - then just spawn when needed. Seems simple now!

                          I also got rid of my Initialize() function that you were so fond of :-) since each weapon type now has its own blueprint to spawn - removed the need to set defaults like I was trying to do.

                          Thanks for the detailed response - you got me on the right track and I learned a lot while doing it.

                          Comment


                            #14
                            Do you mind sharing the right code!
                            https://ravimohan.net/
                            https://github.com/ravimohan1991

                            Comment


                              #15
                              No problem. It will take me a bit to get it all together, but I'll get it posted a bit later today.

                              Comment

                              Working...
                              X