Grab Object Replication on Server

Im trying to replicate a simple object grabbing feature. When I press a button, it grabs the object that Im looking at and it moves around with the player’s movement.

I have two base functions, one to grab the object (GrabObject()) and one to update the grabbed object’s location (GrabObjectUpdate()), and I have their Server functions as well.

The problem is that the Server function only executes on the Server, it doesnt run on the Client, so grabbing and updating only happens on the server correctly (it replicates there across all clients, but on the client side it doesnt).

I also have MultiCast function, which supposed to do the grab part, but that just crashes the game, so I just commented it out for now.

My question is, which parts of the code/function needs to run on the server, and on multicast to make it replicate on all clients when a client picks up the object?

I have followed this video, he does only a destroy actor action when the player presses a button, which doesnt need to update or to have a release button replicated, or anything like that. ← My setup looks similar to this.

For a simple object grabbing feature like the one you’re describing, the following steps are generally needed:

  1. On the client, when the grab button is pressed, call the GrabObject function on the server using a Remote Procedure Call (RPC).
  2. On the server, the GrabObject function should check if the player is looking at a valid object, and if so, it should set the object as the player’s “grabbed object” and call the GrabObjectUpdate function with a Multicast.
  3. The GrabObjectUpdate function should be Multicast to all clients, so that each client can update the location of the grabbed object to match the player’s movement.
  4. On the client, the GrabObjectUpdate function should update the location of the grabbed object based on the player’s movement.

It’s worth noting that the GrabObject function should not be multicast, as it’s only needed to be executed once, on the server-side.

Make sure that the object you are trying to grab has the net replicated property set to true, this will allow it to be replicated across all clients. Also, make sure that the object you are trying to grab is owned by the server, this can be done by checking the Net Authority property of the object.

It’s also important to note that the client-side should only be responsible for updating the location of the grabbed object based on the player’s movement. It should not be responsible for actually grabbing the object. The object grabbing should be done by the server.

It’s difficult to say for sure what’s causing your game to crash without more information about your code, but this should give you an idea of the steps that are typically needed to implement a simple object grabbing feature that replicates correctly across all clients.

I hope this is helpful! Please let me know if you need something clarified further.

1 Like

Thanks a LOT!

It is definetely helpful, Im going to set up things how you described them, and report back, and will post code too.

(The Function already checks if the player is looking at the correct object with LineTraceSingleByObject).
Since I couldnt find the exact properties that you described (Net Replicated and Net Authority) I guess it is Net Load On Client and Net Use Owner Relevancy, correct me if Im wrong! :grin:

netrepl
(The Image is the blueprint actor that is based on my C++ class which is based on Actor class and the one Im trying to pickup).

Also, Im calling the RPC (Server function) inside GrabObject function on the client, and it doesnt execute the RPC function. I read somewhere that is because the object is not owned by the client. What could I do about that?

Yes, sorry for the previous confusion about the properties that control replication.

Reading from documentation and from what I remember:

If an object is not owned by the client, then an RPC function called on that object from the client will not execute on the server.

One solution to this problem is to use the “Server” variant of the RPC function. These functions can be called from the client and will be executed on the server, regardless of whether the client owns the object or not.

For example, instead of calling “ClientRPCFunction” on the object, you would call “ServerRPCFunction” and that function would run on the server.

I hope this makes sense, if it doesn’t please let me know and we can continue discussing it! :slight_smile:

1 Like

Thanks again! No problem :grin:

I believe Im already using the Server variant of the RPC function, this is how it looks like:

	UFUNCTION(Server, Reliable, WithValidation)
	void ServerGrabObject();
	bool ServerGrabObject_Validate();
	void ServerGrabObject_Implementation();

	UFUNCTION(Server, Reliable, WithValidation)
	void ServerGrabObjectUpdate();
	bool ServerGrabObjectUpdate_Validate();
	void ServerGrabObjectUpdate_Implementation();

ServerGrabObject is called inside GrabObject function, and it doesnt execute.

GrabObject

void ACH_HideNSeek::GrabObject()
{
	FVector Start = GetMesh()->GetSocketLocation(FName("FPSSocket"));
	FVector End = Start + FPSCamera->GetForwardVector() * 500.0f;
	FHitResult Actor = CMP_PlayerAbilities->LTSByObject(Start, End, false);
	if (!bObjectGrabbed)
	{
		if (Actor.GetActor())
		{
			if (Actor.GetActor()->ActorHasTag(FName(TEXT("A_Item"))))
			{
				ServerGrabObject();
			}
		}
	}
	else if (bObjectGrabbed)
	{
		bObjectGrabbed = false;
		PhysicsHandle->ReleaseComponent();
	}
}

ServerGrabObject

void ACH_HideNSeek::ServerGrabObject_Implementation()
{
	if (GetLocalRole() == ROLE_Authority)
	{
		FVector Start = GetMesh()->GetSocketLocation(FName("FPSSocket"));
		FVector End = Start + FPSCamera->GetForwardVector() * 500.0f;
		FHitResult Actor = CMP_PlayerAbilities->LTSByObject(Start, End, false);
		if (!bObjectGrabbed)
		{
			if (Actor.GetActor())
			{
				if (Actor.GetActor()->ActorHasTag(FName(TEXT("A_Item"))))
				{
					UE_LOG(LogTemp, Warning, TEXT("ServerGrabObject()"));
					AItem->GrabObject(PhysicsHandle,Actor.GetComponent());
					PhysicsHandle->GrabComponentAtLocation(Actor.GetComponent(), NAME_None, Actor.GetComponent()->GetComponentLocation());
					bObjectGrabbed = true;
				}
			}
		}
		else if (bObjectGrabbed)
		{
			bObjectGrabbed = false;
			PhysicsHandle->ReleaseComponent();
		}
	}
}

A little update, I figured out that part of the ServerGrabObject function actually runs on the Client (called by the client). But it only executes to a certain part then it doesnt do anything.

void ACH_HideNSeek::ServerGrabObject_Implementation()
{
	if (GetLocalRole() == ROLE_Authority)
	{
		FVector Start = GetMesh()->GetSocketLocation(FName("FPSSocket"));
		FVector End = Start + FPSCamera->GetForwardVector() * 500.0f;
		FHitResult Actor = CMP_PlayerAbilities->LTSByObject(Start, End, false);
		if (!bObjectGrabbed)
		{

Reaches if (!bObjectGrabbed), then stops before if (Actor.GetActor()) (Tested with ifs) probably gives back false.

			if (Actor.GetActor())
			{
				if (Actor.GetActor()->ActorHasTag(FName(TEXT("A_Item"))))
				{
					UE_LOG(LogTemp, Warning, TEXT("ServerGrabObject()"));
					AItem->GrabObject(PhysicsHandle,Actor.GetComponent());
					PhysicsHandle->GrabComponentAtLocation(Actor.GetComponent(), NAME_None, Actor.GetComponent()->GetComponentLocation());
					bObjectGrabbed = true;
				}
			}
		}
		else if (bObjectGrabbed)
		{
			bObjectGrabbed = false;
			PhysicsHandle->ReleaseComponent();
		}
	}
}

If I comment the ifs, then the editor crashes because it doesnt find the Tag, the same code works in function GrabObject.

It seems that the issue is with the if statement if (Actor.GetActor()), which is checking if the hit result from the line FHitResult Actor = CMP_PlayerAbilities->LTSByObject(Start, End, false); has returned a valid actor. If the statement returns false, the rest of the code block inside it does not execute.

It could be that the CMP_PlayerAbilities->LTSByObject function is not returning a valid actor in this case. You may want to check the implementation of that function to see if there is any issue with how it is handling the hit result.

It could also be that the issue is with the line if (Actor.GetActor()->ActorHasTag(FName(TEXT("A_Item")))) where you’re checking if the actor returned has the tag “A_Item”. If the actor is not valid, this line of code would be trying to access the actor’s tag which would lead to a crash.

It would be best to check the value of Actor.GetActor() and Actor.GetComponent() to verify if they are valid before trying to use them.

I tried to verify if they are valid or nullptr, but it doesnt compile it, because I just cant use IsValid or nullptr on all types of variables.

This is fixed now, I had to pass the values as parameters from the client function to the server function, both on GrabObject and on GrabObjectUpdate, because it turns out that the server cant exactly run the same code as the client (LineTraceSingleByObject doesnt run on server therefore returning invalid HitResult (and probably a lot of other stuff doesnt run on server, just a reminder)), passing the values as parameters, solves the issue!

Thanks for your help!

Now I have another issue, since anyone could freely pickup Actors/Items/Objects with the tag “A_Item” (thats the class name of the Item), this would mean that an already picked up item can be picked up by multiple players at the same time. So I have added a variable inside the picked up item to check if it is false or true.

The idea is that a picked up item sets a variable inside of it to true, which would indicate that it is InUse, so other players would check if that is true or not, so they cant pick it up if it is InUse.

Code inside the picked up item:

//SERVER VALIDATION
bool AA_Item::ServerInUse_Validate(bool Value)
{
	return true;
}

//SERVER IMPLEMENTATION
void AA_Item::ServerInUse_Implementation(bool Value)
{
	bInUse = Value;
	if(bInUse)
	{
		GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Orange, TEXT("True!"));	
	}
	else if(!bInUse)
	{
		GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Orange, TEXT("False!"));	
	}
}

void AA_Item::InUse(bool Value)
{
	if(GetLocalRole() == ROLE_Authority)
	{	
		ServerInUse(Value);
	}
}

bool AA_Item::GetInUse()
{
	return bInUse;
}

Now, on the character, when the player picks it up, it runs the InUse and ServerInUse functions, but it doesnt set it for everyone, other players can still pick it up. Do I need to use multicast here? Client picks it up, Server crashes on pickup.

This is how I check it in the character class:
AItem->GetInUse() - if true it shouldnt be able to pick it up
AItem->InUse(true); - this sets it to true if the above state is false, and vice versa.

UPDATE: If I run Multicast or Server function on the item, then crashes on Server and works on Client.

Fixed!

Client1 grabbed an object

Server/ClientX tries to grab an already grabbed object

Solution was to down cast from the grabbed Actor to my Class that is based on Actor:
if(AA_Item* AItem = Cast<AA_Item>(Actor.GetActor()))

Then grab the object, since now AItem is definetely an AA_Item instance + definetely the grabbed object, then now we can call functions/variables inside of that class:
if(!AItem->GetInUse())

Then set it to true, so other players cant grab it:
AItem->InUse(true);

The variable is set with Multicast in AA_Item (the target item class).

void AA_Item::MultiInUse_Implementation(bool Value)
{
	if(GetLocalRole() == ROLE_Authority)
	{
		bInUse = Value;
	}
}

@EliasWick, thanks for your help once again! :grin:

No worries man! Awesome that you ended up solving it haha! I am super happy for you, and best of luck! :slight_smile:

1 Like

It means a lot that you helped me along the way, thank you :grin:

1 Like