Download

Subobject properties not replicating.

This is the error I get.
LogNetTraffic: Error: UActorChannel::ReadContentBlockHeader: Sub-object not in parent actor.

So I have a weapon class (skeletal mesh) that is owned by a specific character class. The specific character class is derived from a base character class which contains the subobject I’m trying to replicate. The subobject (a UDataAsset which derives from UObject) represents an attack move and it has a bunch of properties in it which I’m trying to replicate. When the weapon hits another character, I want to take that subobject’s properties and use it as parameters for a function on the hitactor from the hit result.The hitactor is another character derived from base character.

I did this in the UObject class and set the UPROPERTY as replicated for the 4 variables below.



bool UMove::IsSupportedForNetworking() const
{
    return true;
}

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

    DOREPLIFETIME(UMove, ImpactStunFrames);
    DOREPLIFETIME(UMove, ImpactForce);
    DOREPLIFETIME(UMove, AttackType);
    DOREPLIFETIME(UMove, bIsKnockDownMove);
}


Then in the basecharacter class, i have this at the bottom. CurrentMove is the subobject I’m trying to replicate with its relevant properties



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

    DOREPLIFETIME(ABaseCharacter, CurrentMove);
}

bool ABaseCharacter::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags)
{
    bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);

    if (CurrentMove)
    {
        WroteSomething |= Channel->ReplicateSubobject(CurrentMove, *Bunch, *RepFlags);
    }

    return WroteSomething;
}


This should all work but its not replicated. I’m getting that error. Does anyone know how to fix the issue?

Need to see how you’re constructing the UMove class on the character.



public:
    UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Category = "Fight")
        UMove* CurrentMove;




class MYGAME_API UMove : public UDataAsset


I don’t construct it. I just set it in the character derived blueprint class. My UMoves are a bunch of UDataAssets (right-click, miscellaneous, UDataAsset. Then I select UMove from the list). UDataAsset is derived from UObject.

Well as it says, you can’t replicate a subobject without the outer being actor. Simply assigning this UObject does not make its outer the character. In fact, any actor could reference the same move in this scheme. You need to replicate the subobject some other way. Like replicate the TSubclassOf<UFSMove> if you’re basically just assigning a default object. I haven’t worked with UDataAssets in a while though. If you’re editing it per character, then build a custom replicated struct for replicating the important data of the object.

Wait sorry, I do construct it using ConstructorHelpers. So CurrentMove is a variable in a class called BaseCharacter. I have a class called VampireCharacter that derives from BaseCharacter. In VampireCharacter’s constructor, I do this.



static ConstructorHelpers::FObjectFinder<UMove> FTest(TEXT("Move'/Game/Blueprints/Player/V_Idle.V_Idle'"));
CurrentMove = FTest.Object;


Shouldn’t that be fine then? Still doesn’t work.

That’s not you constructing it. You’re just getting a reference to a default object. Do you change CurrentMove’s properties during gameplay?

No I don’t yet. But I will in the future. I have a variable called damage in there which will change in the future depending on if the character gets a powerup. Wait, so those other variables I replicated up there don’t need to be replicated then? If they don’t change during gameplay? I’m passing those variables between characters which is why I thought they should be replicated.

I’m kinda confused what you’re doing. Are you trying to make a multiplayer game? The model you need to go for is:
Clent sends input to server.
Server acts on that input.
If Clients need to simulate whatever the server did, the Server replicates that info to relevant Clients (COND_OwnerOnly, COND_SkipOwner, <no condition>).

Yes its an online multiplayer. A fighting game. Anyways, I got my moves working on client and server screens now by doing the code below. So if I’m the server(authority), i call the function which is set as NetMulticast. If I’m the client (autonomousproxy) I call the function set as Server which calls a client NetMulticast in it.



UFUNCTION(Server, Reliable, WithValidation)
void Server_DoMove(UMove *NewMove);

UFUNCTION(NetMulticast, Reliable, WithValidation)
void Client_DoMove(UMove *NewMove);

void ABaseCharacter::PerformMove(UMove *NewMove)
{

if (Role == ENetRole::ROLE_Authority)
Client_DoMove(TheMove);
else if (Role == ENetRole::ROLE_AutonomousProxy)
Server_DoMove(TheMove);
}


The weird part is I unreplicated the variables in my first post and it magically works. So I don’t get that error since I just unreplicated everything. I guess now I’m just confused about knowing when to replicate a variable since it seems to work all fine without it.

I don’t know if this is actually working or it just seems like it is, but if it is it’s probably because your UMove reference is a default object. You could get the same effect with


<Role>_DoMove(TSubclassOf<UMove> NewMove)
{
  PerformMove(NewMove.GetDefaultObject());
}

This will not work if you start changing values of moves dynamically, which is not the correct way to do this anyhow.

You cannot replicate UObjects like you believe you are doing. You are not creating a UObject and sending it over the network. You are sending a reference to an object over the network that both the server and client agree exists. If you want to actually send more, dynamic data to the server you need to use UStructs. Make like a struct FRepMove that contains relevant data. It could also contain TSubclassOf to point to the type of move. Look at how FPointDamageEvent works. Its an FDamageEvent which points to a TSubclassOf. It contains additional, event specific values.

So say you want to replicate a move and maybe the speed it was performed at and the damage that was done, you would do:



USTRUCT()
struct FRepMove
{
  GENERATED_BODY()

  UPROPERTY() // properties dont need to be marked for replicated in a struct
  float MoveSpeed;

  UPROPERTY()
  float DamageDone;

  // this would contain maybe the base damage before some multiplier, the animation, the fx, the audio, etc. all that which can be loaded before the move was even performed
  UPROPERTY()
  TSubclassOf<UMove> Move;
}

class MYCharacter
{
  ...
  UPROPERTY(ReplicatedUsing=OnRep_Move)
  FRepMove ReplicatedMove;
}

void MYCharacter::OnRep_Move()
{
 // Do something with ReplicatedMove 
}


The client would send inputs to the server (and in a more advanced approach, also simulate the move on its end in order to predict the server accepting its move ahead of time). The server would then do the move and fill out the FRepMove on the character and clients would receive the FRepMove in like OnRep_Move. They would then be able to do something like display the damage done on the HUD and get the default object of the Move to playback the animation at MoveSpeed or do whatever you want with it (advanced: if the original client had already predicted this move, then they should not perform it again, just confirm it was performed - see how SavedMoves work in CharacterMovementComponent).

Yeah, the moves definitely work on both client and server when its not replicated. I even tried playing with my friend online with Hamachi (which simulates LAN) and they definitely work for both of us. The damage variable, AnimMontage, everything… which i’m pretty confused about. Feels like everything I learned doesn’t apply anymore.

Ok, so I understand what you mean. I constructed the object using NewObject instead and that error message is gone. I followed this guide (scroll to the bottom). I tried using TSubclassOf but it didn’t work. I can’t select the specific UDataAsset for some reason with that, just the base class UMove.

But then I have another issue. I tried constructing my UMove using the NewObject template and setting the parameters 1 by 1 but its not possible. I have an array of DataAssets called UMoveLinks in UMove. Within UMoveLinks, there are more UObjects like Input dataassets, etc. I am using a combo system that is a graph data structure (it is a modified version of the FightingPlugin from one of the live series by EpicGames).
https://docs.unrealengine.com/latest…f/R2V_D5Krdy4/

So I can’t construct everything through code. Its just way too much UObjects within UObjects. When I try using ConstructorHelpers from existing objects just for some of the inner UDataAssets, I get the same error. So in other words, I can’t really construct those variables. So I decided not to replicate it. Everything still works even if I don’t.

The damage variable and the Montage work because that object with those values exists on both the client and the server. They aren’t replicating. You wouldn’t want them replicating because that would be a lot of info that could be reduced to some object ID (like a UClass reference or a replicated Actor). Client and server are passing back and forth the ID of an object that they’ve agreed exists. It might actually be very slow the way its doing this if they have to send the whole object path back and forth.

You are wanting to replicate too much. I don’t know how to use an asset manager so I can’t give you advice on how to specifically deal with that, but you need to have owning client replicate input to the server, with the server then trying to determine what move that input corresponds to and if that move contributes to the combo. The server should then only replicate to clients what move was performed as well as other state information that is specific to that frame, like damage done or combo multiplier. Look at my FRepMove again.

Building a UObject that is replicated is clunky and you’ll end up replicating more than is necessary. You should never need to replicate more than a few bytes really related to this really. I can’t help you more without a more complete picture of your code or project, but I don’t know that I have time to solve these issues for you. My recommendation is to stop trying to replicate UObjects. Stick to Actor and Component property replication and RPCs. I’m working on a large game and haven’t once needed to replicate a UObject like you’re looking to do.