Announcement

Collapse
No announcement yet.

Replication Hell - the gift that keeps on giving

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

  • replied
    Originally posted by CashCache View Post
    I don't understand? I'm setting this here so the derived blueprint is already set by default. Is that what you are asking or am I missing something?
    I would use the Epic's routine SetReplicates(true) which has a special check for the Authority role. That way this setting doesn't get applied to the clients!

    I mean I trust
    Code:
    void AActor::SetReplicates(bool bInReplicates)
    {
        if (Role == ROLE_Authority)
        {
            // Only call into net driver if we actually changed
            if (bReplicates != bInReplicates)
            {
                // Update our settings before calling into net driver
                RemoteRole = bInReplicates ? ROLE_SimulatedProxy : ROLE_None;
                bReplicates = bInReplicates;
    
                // This actor should already be in the Network Actors List if it was already replicating.
                if (bReplicates)
                {
                    // GetWorld will return nullptr on CDO, FYI
                    if (UWorld* MyWorld = GetWorld())        
                    {
                        MyWorld->AddNetworkActor(this);
                        ForcePropertyCompare();
                    }
                }
    
            }
        }
        else
        {
            UE_LOG(LogActor, Warning, TEXT("SetReplicates called on actor '%s' that is not valid for having its role modified."), *GetName());
        }
    }
    and not reinventing the wheel (giving joys of OOP)!
    Last edited by The-Cowboy; 06-04-2019, 05:28 AM.

    Leave a comment:


  • replied
    Originally posted by CashCache View Post
    Thanks for the detailed response - you got me on the right track and I learned a lot while doing it.
    No worries, hope it helps If in doubt, study the content examples Epic have for C++ - they're quite good (if a little dated now!)

    Leave a comment:


  • replied
    Is this really necessary or you are just playing safe? Shouldn't you be calling super thingy!
    This is not needed and I have since removed it. As long as it's set in the base class it should work.

    As for calling the Base/Master/Super class, I don't need to explicitly call it since I'm using the default constructor. If I add a new constructor, or add parameters to the existing constructor, I would then need to explicitly call the super.


    Furthermore, you don't need to set the replication properties client side actually.
    I don't understand? I'm setting this here so the derived blueprint is already set by default. Is that what you are asking or am I missing something?

    Leave a comment:


  • replied
    Is this really necessary or you are just playing safe? Shouldn't you be calling super thingy!
    Originally posted by CashCache View Post
    Code:
    ARiflePickup::ARiflePickup()
    {
    bReplicates = true;
    bReplicateMovement = true;
    }

    Furthermore, you don't need to set the replication properties client side actually.
    Last edited by The-Cowboy; 06-03-2019, 10:19 PM.

    Leave a comment:


  • replied
    Here is the working and much simplified code. A few things of note:

    The RiflePickup class is basically empty now. All the default settings (collider and mesh) are still in the base class and I have no special features written yet for the rifle specifically. What is not shown below are 4 blueprint classes - one for each weapon in my game. This is where I set the default mesh and collider. In the GameMode class, I then spawn each BP and since the base class is marked for replication, it just works.

    PickupBase.h
    Code:
    UCLASS()
    class SHOOTERCPP_API APickupBase : public AActor
    {
        GENERATED_BODY()
    
    public:    
        // Sets default values for this actor's properties
        APickupBase();
    
        // the item to be spawned from this pickup
        UPROPERTY(EditDefaultsOnly, Category = WeaponToSpawn)
        TSubclassOf<AActualBase> weaponToSpawn;    
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;    
    
        UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
        UStaticMeshComponent* weaponMeshComponent;
    
        UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
        UBoxComponent* boxComponent;    
    
    public:    
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
    private:    
    
    };
    PickupBase.cpp
    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;    
    
        // Every pickup in the game will have a box collider, static mesh and an actual type to spawn after pickup (set in header)
        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));        
    }
    
    // Called when the game starts or when spawned
    void APickupBase::BeginPlay()
    {
        Super::BeginPlay();
    }
    
    // Called every frame
    void APickupBase::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
    }
    RiflePickup.h
    Code:
    UCLASS()
    class SHOOTERCPP_API ARiflePickup : public APickupBase
    {
        GENERATED_BODY()
    
    public:
        ARiflePickup();
    
    
    protected:    
    
    };
    RiflePickup.cpp
    Code:
    ARiflePickup::ARiflePickup()
    {
        bReplicates = true;
        bReplicateMovement = true;        
    }
    ShooterCPPGameMode.h
    Code:
    UCLASS(minimalapi)
    class AShooterCPPGameMode : public AGameModeBase
    {
        GENERATED_BODY()
    
    public:
        AShooterCPPGameMode();
        virtual void BeginPlay() override;
    
    protected:
    
        TSubclassOf<ARiflePickup> weaponAK47;
        TSubclassOf<ARiflePickup> weaponAR15;
        TSubclassOf<ARiflePickup> weaponSMG11;
        TSubclassOf<ARiflePickup> weaponSUB;
    };
    ShooterCPPGameMode.cpp
    Code:
    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;        
        }    
    
        static ConstructorHelpers::FClassFinder<ARiflePickup> AK47(TEXT("/Game/ThirdPersonCPP/Blueprints/WeaponPickup/Rifle_Pickup_AK47"));
        static ConstructorHelpers::FClassFinder<ARiflePickup> AR15(TEXT("/Game/ThirdPersonCPP/Blueprints/WeaponPickup/Rifle_Pickup_AR15"));
        static ConstructorHelpers::FClassFinder<ARiflePickup> SMG11(TEXT("/Game/ThirdPersonCPP/Blueprints/WeaponPickup/Rifle_Pickup_SMG11"));
        static ConstructorHelpers::FClassFinder<ARiflePickup> SUB(TEXT("/Game/ThirdPersonCPP/Blueprints/WeaponPickup/Rifle_Pickup_SUB"));    
    
        weaponAK47 = AK47.Class;
        weaponAR15 = AR15.Class;
        weaponSMG11 = SMG11.Class;
        weaponSUB = SUB.Class;    
    }
    
    void AShooterCPPGameMode::BeginPlay()
    {
        Super::BeginPlay();
    
        if (GetWorld())
        {
            FVector location = FVector(-1000.f, -1000.f, 200.f);
            FRotator rotation = FRotator::ZeroRotator;        
    
            ARiflePickup* temp = GetWorld()->SpawnActor<ARiflePickup>(weaponAK47, location, rotation);
    
            location += FVector(0.0f, 200.f, 0.f);
            ARiflePickup* temp2 = GetWorld()->SpawnActor<ARiflePickup>(weaponAR15, location, rotation);
    
            location += FVector(0.0f, 200.f, 0.f);
            ARiflePickup* temp3 = GetWorld()->SpawnActor<ARiflePickup>(weaponSMG11, location, rotation);
    
            location += FVector(0.0f, 200.f, 0.f);
            ARiflePickup* temp4 = GetWorld()->SpawnActor<ARiflePickup>(weaponSUB, location, rotation);        
    
        }
    }

    Leave a comment:


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

    Leave a comment:


  • replied
    Do you mind sharing the right code!

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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?

    Leave a comment:


  • replied
    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;

    Leave a comment:


  • replied
    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.

    Leave a comment:

Working...
X