이번 구현의 목적은 캐릭터가 화면의 중심으로 원하는 아이템을 하나만 선택해서 획득할 수 있도록 하는 것이다.
그림으로 나타내면 아래와 같은 느낌이다.
말로 정리해 보자면,
- 아이템을 랜덤하게 스폰해주는 상자가 존재
- 이 상자는 collision을 가지고 있어서 해당 collision 안으로 들어가게 되면, 캐릭터의 시점으로 line trace 시작
- collision 밖으로 나오면 line trace 꺼주기
- line trace를 통해서 아이템을 peeking 하게되면, 해당 아이템에 UI가 뜨게 되고 획득 가능한 상태가 된다.
- 만약 아이템 peeking이 끝나면, UI도 꺼지고 아이템 획득도 불가능한 상태
- 이후 F키를 누르면 아이템 획득이 가능한 시스템
1. 상자 로직
상자의 기본 로직은 다른 조원분이 구현해 주셨고, 나는 collision에 overlap된 순간 캐릭터가 linetrace를 시작할 수 있도록 함수를 추가해 주었다.
- 아래 코드에서 overlap 시작 시 call되는 StartPeek()와 overlap이 끝날 때 call되는 StopPeek() 함수이다.
- 각 함수는 캐릭터에서 linetrace가능 여부를 판단하는 bool 값 변수를 바꿔주는 함수이다.
void AChest::OnPlayerOverlapBegin(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult)
{
if (OtherActor && OtherActor->IsA(APlayerCharacter::StaticClass()))
{
bPlayerInRange = true;
bCanOpenChest = true;
OpenChest();
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
if (PlayerCharacter)
{
PlayerCharacter->StartPeek();
}
}
}
void AChest::OnPlayerOverlapEnd(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex)
{
if (OtherActor && OtherActor->IsA(APlayerCharacter::StaticClass()))
{
bPlayerInRange = false;
bCanOpenChest = false;
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
if (PlayerCharacter)
{
PlayerCharacter->StopPeek();
}
}
}
- StopPeek 함수의 경우 추가적인 구현이 되어있는데, 이것은 UI 관련 예외처리를 위해서이다.
- PeekingItem이 있는 경우 상자의 Collision을 벗어났을 때 linetrace는 끝나서 PeekingItem이 없어져야 하지만
- if(PeekingItem) 아래 코드가 없는 경우 PeekingItem이 유지되는 버그를 수정한 내용이다.
void APlayerCharacter::StartPeek()
{
bCanTraceForItemPeeking = true;
}
void APlayerCharacter::StopPeek()
{
bCanTraceForItemPeeking = false;
if (PeekingItem)
{
if (Cast<AWeapon>(PeekingItem))
{
Cast<AWeapon>(PeekingItem)->bIsPeeking = false;
}
if (Cast<ABaseItem>(PeekingItem))
{
Cast<ABaseItem>(PeekingItem)->bIsPeeking = false;
}
}
}
2. 캐릭터 로직
캐릭터는 Tick 에서 위에서 말한 bool 변수에 따라 line trace를 하게 된다.
void APlayerCharacter::Tick(float DeltaTime)
{
if (bCanTraceForItemPeeking)
BeginTraceForPickItem();
....
}
line trace를 하는 함수는 아래와 같다. 이 함수는 화면의 중심점에서 시야 방향으로 linetrace를 쏘는 기능을 한다.
DeprojectScreenPositionToWorld() 와 LineTraceSingleByChannel()를 사용해서 구현하였다.
- 해당 함수는 화면 중심에서 line trace를 쏴서 hit 되는 경우
- PeekingItem 이라는 포인터 변수에 해당 Actor를 저장하게 된다.
- 원래 if(bHit) 인 경우 각 아이템이 가지고 있는 ShowUI라는 함수를 통해 UI가 보이도록 구현하였다.
- 그러나 이렇게 구현된 경우 peeking이 끝났을 때 UI가 꺼지지 않아 ShowUI를 각 아이템 클래스의Tick에서 Call하도록 수정하였다. (아래 코드가 수정된 버전의 코드)
void APlayerCharacter::BeginTraceForPickItem()
{
//trace 하기
APlayerCharacterController* PlayerController = Cast<APlayerCharacterController>(GetController());
if (PlayerController)
{
int32 ScreenWidth;
int32 ScreenHeight;
PlayerController->GetViewportSize(ScreenWidth, ScreenHeight);
const FVector2D ScreenCenter(ScreenWidth * 0.5f, ScreenHeight * 0.5f);
FVector WorldLocation;
FVector WorldDirection;
if (PlayerController->DeprojectScreenPositionToWorld(ScreenCenter.X, ScreenCenter.Y, WorldLocation, WorldDirection))
{
FVector TraceStart = WorldLocation + WorldDirection * 100.f;
FVector TraceEnd = TraceStart + (WorldDirection * 10000.f);
FHitResult HitResult;
FCollisionQueryParams TraceParams;
TraceParams.AddIgnoredActor(this);
bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
/*FColor LineColor = bHit ? FColor::Red : FColor::Blue;
DrawDebugLine(GetWorld(), TraceStart, TraceEnd, LineColor, false, 2.0f, 0, 2.0f);*/
if (bHit)
{
PeekingItem = HitResult.GetActor();
}
}
}
}
어려움을 겪었던 부분이 캐릭터가 peeking을 하지 않는 아이템은 UI를 꺼주는 부분인데 이 부분은 아이템의 Tick을 활용해서 구현하였다.
3. 아이템 로직
아이템에서는 Tick함수를 통해 ShowUI와 StopUI를 사용하게 된다.
- 주의점은 계속해서 bIsPeeking = false 를 해주어야 한다는 점이다.
void AWeapon::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bIsPeeking)
ShowUI();
else
StopUI();
bIsPeeking = false;
}
- bIsPeeking 변수는 캐릭터에서 Tick을 통해 변경해준다. (아래 캐릭터 Tick 함수 코드 참고)
void APlayerCharacter::Tick(float DeltaTime)
{
if (bCanTraceForItemPeeking)
BeginTraceForPickItem();
if (PeekingItem && bCanTraceForItemPeeking)
{
if (PeekingItem->ActorHasTag("Weapon"))
Cast<AWeapon>(PeekingItem)->bIsPeeking = true;
if (PeekingItem->ActorHasTag("Item"))
Cast<ABaseItem>(PeekingItem)->bIsPeeking = true;
}
}
4. 최종 로직
마지막으로 캐릭터가 PeekingItem이 있는 경우 캐릭터의 Input Action과 연동되어있는 PickUp 함수를 call해주면 된다.
void APlayerCharacter::PickUp(const FInputActionValue& value)
{
if (PeekingItem != nullptr)
{
if (PeekingItem->ActorHasTag("Weapon"))
{
EquipWeaponBack();
PeekingItem->Destroy();
}
else if(PeekingItem->ActorHasTag("Item"))
{
PickUpItem();
PeekingItem->Destroy();
}
}
}
정리하자면,
- 캐릭터는 상자 Collision과 overlap 된 상황에 따라 bCanTraceForItemPeeking 값이 변하게 되고
- bCanTraceForItemPeeking 값에 따라 BeginTraceForPickItem() 함수를 실행한다.
- 만약 BeginTraceForPickItem 함수 안에서 hit된 물체가 있는 경우 PeekingItem 이라는 변수에 해당 Actor값을 저장한다.
- 이후 캐릭터 Tick에서 PeekingItem이 존재하고, BeginTraceForPickItem함수가 실행되는 도중이라면, 아이템이 가지고 있는 bIsPeeking 변수를 true로 만들어 준다.
- 아이템의 Tick에서는 bIsPeeking 변수가 true인 경우 ShowUI 함수를 통해 UI를 켜주고, 바로 bIsPeeking을 false로 바꿔준다.
- 캐릭터의 경우 PeekingItem이 존재하고, F 키를 눌러 PickUp함수가 실행된 경우 아이템
- 캐릭터에 부착하거나 -> EquipWeaponback() 함수
- 획득한다. -> PickUpItem() 함수
최종 동작 모습
'Unreal Engine' 카테고리의 다른 글
UE5 Issues : 총기 스폰 및 포인터 (0) | 2025.02.27 |
---|---|
UE5 Issues : 인벤토리 UI (0) | 2025.02.26 |
UE5 Issues : Enhanced Input (Triggered & Started) (0) | 2025.02.23 |
UE5 Issues : 애니메이션 (Motion Matching, Blend Pose) (0) | 2025.02.21 |
UE5 Issues : 이모저모 (0) | 2025.02.13 |