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 설정하는 순서를 위처럼 변경하여주면 해결된다!.
'트러블 슈팅' 카테고리의 다른 글
| PickUp 기능 (0) | 2025.11.10 |
|---|---|
| 최종 팀 프로젝트 Pickup 기능 구현 (0) | 2025.11.06 |
| 멀티플레이 (데디서버) Hit 이펙트 첫 스폰 깨지는 문제 (0) | 2025.10.03 |
| 멀티플레이 (데디서버) 캐릭터 수영 로직 적용 안되는 문제 (0) | 2025.10.03 |
| 멀티플레이 (데디서버) 캐릭터 슬라이딩 동작 중복 입력되는 문제 (0) | 2025.10.03 |