Hi everybody.
I use UE 5.4.4 in net mode as a Listen server so i won’t need a dedicated server to play the game.
I’m trying to develop a game with a puzzle. So i created a puzzlePiece actor that you can interact with to take it in your hand (snapping it to a skeletal mesh socket).
If you are on the client who control the pawn which interacted with the puzzle piece i want to attach the piece to the Mesh1P skeletal mesh. However if you are not on the client who control the pawn i want to attach the piece to the skeletal mesh named Mesh (aka the 3rd person Mesh).
My problem is that if i interact with the actor, from the Listen server then all the client see it attached to the Mesh1P. Conversely if i interact with the actor from a client then it attaches for every other client on the Mesh, even for the one controlling this pawn.
I think my problem comes from the replication logic. Because if i interact from the client the Listen server attaches it on the Mesh. But if i interact from the Server then the server attaches it to the Mesh1P.
And that make me think that the behavior of the server is replicated to the client even if don’t want to.
I don’t know how i should manage this so not every client copy the server behavior because i still need to replicate the action to every client but also to adapt it based on which client it is.
PuzzlePiece.h ( the actor you interact with) and InteractiveInterface
UCLASS()
class ESCAPEGAME_API APuzzlePiece : public AActor, public IInteractiveInterface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category=PuzzlePiece)
USphereComponent* SphereCollisionComp;
UPROPERTY(EditAnywhere, Category=PuzzlePiece)
UStaticMeshComponent* Mesh;
public:
// Sets default values for this actor's properties
APuzzlePiece();
UFUNCTION()
virtual void OnInteract(APawn* Sender) override;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UFUNCTION(NetMulticast, Reliable)
void PickedUpBy(AEscapeGameCharacter* player);
void PickedUpBy_Implementation(AEscapeGameCharacter* player);
UPROPERTY(EditAnywhere, Category=PuzzlePiece)
FName Socket_Name;
};
UINTERFACE(MinimalAPI)
class UInteractiveInterface : public UInterface
{
GENERATED_BODY()
};
class IInteractiveInterface
{
GENERATED_BODY()
public:
// Interact function
virtual void OnInteract(APawn* Sender);
bool ShouldExecOnServer() const { return bShouldExecOnServer; }
void setShouldExecOnServer(bool value) { bShouldExecOnServer = value; }
private:
bool bShouldExecOnServer = true;
};
PuzzlePiece.cpp
void APuzzlePiece::OnInteract(APawn* Sender)
{
IInteractiveInterface::OnInteract(Sender);
UE_LOG(LogTemp, Warning, TEXT("OnInteract Has Authority %s"), HasAuthority() ? TEXT("True") : TEXT("False"));
if (AEscapeGameCharacter* player = Cast<AEscapeGameCharacter>(Sender))
{
SetOwner(player);
PickedUpBy(player);
}
}
void APuzzlePiece::PickedUpBy_Implementation(AEscapeGameCharacter* player)
{
FAttachmentTransformRules attachRules = FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true);
attachRules.ScaleRule = EAttachmentRule::KeepWorld;
// The local player only see the arms but the other player see the whole body so we need to adapt wich mesh we use.
UE_LOG(LogTemp, Warning, TEXT("Is locally controleld: %s"), player->IsLocallyControlled() ? TEXT("True") : TEXT("False"));
UE_LOG(LogTemp, Warning, TEXT("Is locally controleld: %d"), player->GetNetPushId());
if (player->IsLocallyControlled())
{
AttachToComponent( player->GetMesh1P(), attachRules, Socket_Name);
}
else
{
AttachToComponent( player->GetMesh(), attachRules, Socket_Name);
}
Mesh->SetSimulatePhysics(false); // Disable physique to prevent strange comportement
SetActorEnableCollision(false);
}
EscapeGameCharacter.h
UCLASS(config=Game)
class AEscapeGameCharacter : public ACharacter
{
GENERATED_BODY()
/** Pawn mesh: 1st person view (arms; seen only by self) */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Mesh, meta = (AllowPrivateAccess = "true"))
USkeletalMeshComponent* Mesh1P;
/** First person camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
UCameraComponent* FirstPersonCameraComponent;
/** Jump Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))
UInputAction* JumpAction;
/** Move Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))
UInputAction* MoveAction;
/** Interact Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))
UInputAction* InteractAction;
public:
AEscapeGameCharacter();
virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;
/** Returns Mesh1P subobject **/
USkeletalMeshComponent* GetMesh1P() const { return Mesh1P; }
/** Returns FirstPersonCameraComponent subobject **/
UCameraComponent* GetFirstPersonCameraComponent() const { return FirstPersonCameraComponent; }
/** Look Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* LookAction;
protected:
virtual void BeginPlay();
/** Called for movement input */
void Move(const FInputActionValue& Value);
/** Called for looking input */
void Look(const FInputActionValue& Value);
// Handle all interact action from player
void Interact();
UFUNCTION(Server, Reliable)
void ServerInteract(const TScriptInterface<IInteractiveInterface>& Target);
void ServerInteract_Implementation(const TScriptInterface<IInteractiveInterface>& Target);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input)
int InteractDistance;
};
EscapeGameCharacter.cpp
void AEscapeGameCharacter::Interact()
{
UE_LOG(LogTemp, Warning, TEXT("AEscapeGameCharacter::Interact"));
FVector StartLocation = FirstPersonCameraComponent->GetComponentLocation();
FVector EndLocation = FirstPersonCameraComponent->GetForwardVector() * InteractDistance + StartLocation;
FHitResult Hit;
GetWorld()->LineTraceSingleByChannel(Hit, StartLocation, EndLocation, ECC_Visibility);
DrawDebugLine(GetWorld(), StartLocation, EndLocation, FColor::Red, false, 5, 0, 5);
if (!Hit.bBlockingHit) { return; }
if (Hit.GetActor()->Implements<UInteractiveInterface>())
{
TScriptInterface<IInteractiveInterface> InteractiveActor = TScriptInterface<IInteractiveInterface>(Hit.GetActor());
UE_LOG(LogTemp, Warning, TEXT("Net Rep Responsible Owner: %s"), (HasNetOwner() ? TEXT("Yes") : TEXT("No")));
if (InteractiveActor->ShouldExecOnServer())
{
ServerInteract(InteractiveActor);
} else
{
InteractiveActor->OnInteract(this);
}
} else
{
UE_LOG(LogTemp, Warning, TEXT("Hitted something"));
}
}
void AEscapeGameCharacter::ServerInteract_Implementation(const TScriptInterface<IInteractiveInterface>& Target)
{
if (Target)
{
Target.GetInterface()->OnInteract(this);
}
}