트러블 슈팅

PickUp 리플리케이션 및 콜리전 경고 메세지 문제 해결

Turtle_Jun 2025. 11. 11. 23:44

2025.11.10 - [트러블 슈팅] - PickUp 기능

 

PickUp 기능

트러블 슈팅픽업 시 들고있는 오브젝트가 리플리케이션 안되는 문제콜리전 프리셋을 에디터에서 수정시 픽업이 되지 않는 문제해결 방안 모색 콜리전 프로파일 채널을 추가하고 리플리케이션

unrealstudyhome.tistory.com

 

 

 

// LNCharacter.h

#pragma region PickupSystem
	
protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pickup")
	UPhysicsHandleComponent* PhysicsHandle;

	UPROPERTY(ReplicatedUsing = OnRep_CarriedObject)
	TObjectPtr<AActor> CarriedObject;	// 잡고있는 오브젝트

	// 이전에 들고 있던 물체 추적용
	TObjectPtr<AActor> LastCarriedObject;

	UPROPERTY(EditDefaultsOnly, Category = "Pickup")
	FName PickupSocketName = TEXT("PickupSocket");
	
	UPROPERTY(EditDefaultsOnly, Category = "Pickup")
	float PickupDistance = 70.f;

	UPROPERTY(EditDefaultsOnly, Category = "Pickup")
	FVector PickupOffset = FVector(0.f, 0.f, 0.f);

	UPROPERTY(EditDefaultsOnly, Category = "Pickup")
	float PickupTraceRadius = 25.f;

	ECollisionEnabled::Type HeldItemCollisionType;	// 잡고있는 오브젝트의 콜리전 타입 저장
	
	void PickupObject(AActor* TargetActor);
	void ReleasePickupObject();

	// 실제 물리적 Attach/Detach 로직(서버와 클라 모두 실행) 
	void AttachCarriedObject();
	void DetachCarriedObject();
	
	FTimerHandle PickupUpdateTimerHandle;
	
#pragma endregion PickupSystem
// LNCharacter.cpp

void ALNCharacter::HandlePickup(const FInputActionValue& Value)
{
	UE_LOG(LogTemplateCharacter, Warning, TEXT("PickupHandle"));

	// 들고있다면 내려놓기
	if (IsValid(CarriedObject))
	{
		if (!HasAuthority())
		{
			ServerRPC_HandlePickup(CarriedObject, false);
		}
		else
		{
			ReleasePickupObject();
		}

		return;	// 내려놓고 종료
	}

	// 들고 있지 않다면 -> 전방 트레이스로 물체 찾기
	const FVector Start = GetActorLocation();
	const FVector End = Start + GetActorForwardVector() * PickupDistance;
	
	FHitResult Hit;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this);
	float PicupTraceCapsuleHalfHeight = this->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();

	
	bool bHit = GetWorld()->SweepSingleByObjectType(
		Hit,
		Start,
		End,
		FQuat::Identity,
		ECC_PhysicsBody,
		FCollisionShape::MakeCapsule(PickupTraceRadius, PicupTraceCapsuleHalfHeight),
		Params
	);

	DrawDebugCapsule(GetWorld(), (Start + End) * 0.5f, PicupTraceCapsuleHalfHeight ,PickupTraceRadius, FQuat::Identity,
		bHit ? FColor::Green : FColor::Red, false, 1.0f, 0, 1.5f);
	
	if (bHit && Hit.GetComponent() && Hit.GetComponent()->IsSimulatingPhysics())
	{
		AActor* HitActor = Hit.GetActor();
		if (HitActor)
		{
			if (!HasAuthority())
			{
				ServerRPC_HandlePickup(HitActor, true);
			}
			else
			{
				PickupObject(HitActor);
			}
		}
	}
}

void ALNCharacter::PickupObject(AActor* TargetActor)
{
	if (!TargetActor || !HasAuthority())	return;
	
	// 서버에서만 CarriedObject 설정 (이것이 복제되면서 OnRep이 호출됨)
	CarriedObject = TargetActor;
	bIsCarry = true;

	// 서버에서도 실제 Attach 수행
	AttachCarriedObject();
	
	UE_LOG(LogTemplateCharacter, Log, TEXT("[Server] PickupObject: %s"), *TargetActor->GetName());
}

void ALNCharacter::ReleasePickupObject()
{
	if (!CarriedObject || !HasAuthority())	return;

	// 서버에서도 실제 Detach 수행
	DetachCarriedObject();

	// CarriedObject를 nullptr로 설정 (이것이 복제되면서 OnRep이 호출됨)
	CarriedObject = nullptr;
	bIsCarry = false;

	UE_LOG(LogTemplateCharacter, Log, TEXT("[Server] ReleasePickupObject"));
}

// OnRep 함수: CarriedObject가 변경될 때 클라이언트에서 호출됨
void ALNCharacter::OnRep_CarriedObject()
{
	// 이전에 들고 있던 물체가 있으면 먼저 Detach
	if (LastCarriedObject && !CarriedObject)
	{
		DetachCarriedObject();
		UE_LOG(LogTemplateCharacter, Log, TEXT("[Client] OnRep_CarriedObject: Detached %s"), *LastCarriedObject->GetName());
	}
	
	// 새로 들 물체가 있으면 Attach
	if (CarriedObject)
	{
		AttachCarriedObject();
		UE_LOG(LogTemplateCharacter, Log, TEXT("[Client] OnRep_CarriedObject: Attached %s"), *CarriedObject->GetName());
	}
	
	// 현재 값을 LastCarriedObject에 저장
	LastCarriedObject = CarriedObject;
}

// 실제 물리적 Attach 로직 (서버와 클라이언트 모두에서 실행)
void ALNCharacter::AttachCarriedObject()
{
	if (!CarriedObject)	return;

	UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(CarriedObject->GetRootComponent());
	
	if (PrimComp && PrimComp->IsSimulatingPhysics())
	{
		// 캐릭터와 충돌 방지
		HeldItemCollisionType = PrimComp->GetCollisionEnabled();
		PrimComp->IgnoreActorWhenMoving(this, true);
		PrimComp->SetSimulatePhysics(false);
		PrimComp->SetEnableGravity(false);
        PrimComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);

		// 들고있을 오브젝트 크기 기반 거리 계산
		FVector Origin, Extent;
		CarriedObject->GetActorBounds(true, Origin, Extent);
		float ObjectSize = Extent.Size();
		float Distance = FMath::Clamp(ObjectSize * 0.5f, 40.f, 120.f);

		FVector SocketLocation = GetMesh()->GetSocketLocation("PickupSocket");
		FVector AttachLocation = SocketLocation + GetActorForwardVector() * Distance;

		CarriedObject->SetActorLocation(AttachLocation);
		CarriedObject->AttachToComponent(
			GetMesh(),
			FAttachmentTransformRules::KeepWorldTransform,
			TEXT("PickupSocket")
		);
		
		UE_LOG(LogTemplateCharacter, Log, TEXT("[%s] AttachCarriedObject: %s"), 
			HasAuthority() ? TEXT("Server") : TEXT("Client"), 
			*CarriedObject->GetName());
	}
}

// 실제 물리적 Detach 로직 (서버와 클라이언트 모두에서 실행)
void ALNCharacter::DetachCarriedObject()
{
	// LastCarriedObject를 사용 (OnRep에서 설정됨)
	AActor* ObjectToDetach = LastCarriedObject ? LastCarriedObject : CarriedObject;

	if (!ObjectToDetach)	return;

	if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(ObjectToDetach->GetRootComponent()))
	{
		PrimComp->SetCollisionEnabled(HeldItemCollisionType);
		PrimComp->IgnoreActorWhenMoving(this, false);
		PrimComp->SetSimulatePhysics(true);
		PrimComp->SetEnableGravity(true);
	}

	ObjectToDetach->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
	ObjectToDetach->SetOwner(nullptr);

	UE_LOG(LogTemplateCharacter, Log, TEXT("[%s] DetachCarriedObject: %s"), 
		HasAuthority() ? TEXT("Server") : TEXT("Client"),
		*ObjectToDetach->GetName());
}

 

 

 

 

오브젝트가 Pickup 되고난 후 클라이언트에 복제가 되지 않은 이유론 클라이언트에서 CarriedObject 가 복제는 되나 Attach/Detach가 서버에서만 실행되는 문제가 있었음.

따라서 OnRep 함수를 위처럼 구현하여 클라이언트에서도 CarriedObject가 Attach/Detach되도록 구현하였다.

 

 

콜리전 설정 순서를 Nocollision 으로 설정 후 Physics 설정을 수정하여서 위처럼 경고 메세지가 출력되었었다.

Nocollision 설정하는 순서를 위처럼 변경하여주면 해결된다!.