이번 글의 주제는 인벤토리에 가지고 있는 총기와 현재 장착하고 있는 총기를 연동하는 방식에 대한 내용이다.
1. 전체적인 로직
UI의 동작
- 왼쪽은 무기를 획득하기 전에 모습이다. 이때는 UI의 버튼을 클릭하여도 아무 일도 발생하지 않는다.
- 오른쪽은 무기를 획득한 후의 모습이다.
- 무기는 각자 고유한 int32 변수를 가지고 있어, 어떤 무기인지 식별할 수 있게 하였다.
- 무기를 획득하게 되면, GameInstance가 가지고 있는 TArray<int32> WeaponInventoryItem; 에 해당 인덱스를 저장하게 된다.
- UI 에서는 (GameState) 해당 인덱스를 체크해서, 존재하는 인덱스라면 오른쪽 처럼 UI에 존재하는 이미지의 투명도를 1로 만들어 보이게 해준다.
- 이후 투명도가 1인 버튼은 클릭이 가능하게 되고, 클릭하는 순간 EquipWeaponBack 함수를 call하게 된다.
캐릭터의 동작
- 캐릭터는 CurrentWeapon이라는 현재 장착한 무기에 대한 정보와 TempWeapon이라는 임시 무기를 가지고 있다.
EquipWeaponBack 의 동작
- 위에서 버튼이 클릭된 경우 해당 함수가 call되게 되는데 이때 캐릭터의 변수인 TempWeapon에 해당하는 인덱스의 무기를 spawn하게 된다.
- 이후 CurrentWeapon이 존재하면 swap을 해주고
- 없다면 CurrentWeapon = TempWeapon을 통해 로직을 구성했다.
2. 문제점
이 방식은 계속해서 무기를 spawn하기 때문에 아래와 같을 시점에서 버그가 생기게 된다.
- 무기(A)를 10발 쏘고 장전이 안된 상태로 다른 무기(B)로 교체
- 다른 무기(B)를 쏘고 다시 무기(A)로 교체하면, 무기(A)의 총알이 모두 장전된 상태가 된다.
- 이유는 무기 자체적으로 Total Ammo와 Current Ammo가 있는데, Current Ammo는 Total Ammo인 상태로 만들었기 때문이다.
- 즉, 기존의 무기가 가지고 있는 정보를 저장하는 것이 아니라 새롭게 생성하기 때문이다.
(글을 쓰면서 갑자기 든 생각은 우리가 무기를 획득했을 때 장전이 안되어 있는 것이 맞는 것 같기도 하다..)
3. 해결책
캐릭터에 새로운 변수인 TMap<int32, AWeapon*> PlayerWeaponInventory; 를 가지게 하였다.
이 변수가 무기 포인터를 가지고 있게 해서 Current Weapon과 Swap하도록 만들기 위해서 였다.
EquipWeaponBack 수정 버전
- 만약 해당 인덱스에 해당하는 무기가 없다면 새롭게 spawn해서 PlayerWeaponInventory에 넣어주기
- 장착된 무기가 없는 경우 Current Weapon을 바로 새로 스폰한 무기로 교체
- 장착된 무기가 있는 경우 기존에 장착한 무기의 정보를 가져와서 현재 게임에서 보이지 않게 만들고, 장착하고자 하는 무기를 PlayerWeaponInventory 의 [ ] 연산을 통해 가져와서 게임에서 보이도록 교체하기
더보기
void APlayerCharacter::EquipWeaponBack(int32 WeaponIdx)
{
//attack animation -> can't change weapon
if (bIsWeaponEquipped)
return;
//timer reset
GetWorldTimerManager().ClearTimer(FireRateTimerHandle);
//no weapon exist(check by idx) -> spawn
if (PlayerWeaponInventory[WeaponIdx] == nullptr)
{
if (WeaponIdx == 1)
{
PlayerWeaponInventory[WeaponIdx] = GetWorld()->SpawnActor<AWeaponAR1>();
}
else if (WeaponIdx == 2)
{
PlayerWeaponInventory[WeaponIdx] = GetWorld()->SpawnActor<AWeaponAR2>();
}
else if (WeaponIdx == 3)
{
PlayerWeaponInventory[WeaponIdx] = GetWorld()->SpawnActor<AWeaponSR>();
}
else if (WeaponIdx == 4)
{
}
}
if (IsValid(CurrentWeapon))
{
TObjectPtr<AWeapon> Temp = PlayerWeaponInventory[WeaponIdx];
int32 Idx = CurrentWeapon->GetWeaponNumber();
PlayerWeaponInventory[Idx] = CurrentWeapon;
PlayerWeaponInventory[Idx]->SetActorHiddenInGame(true);
CurrentWeapon = Temp;
CurrentWeapon->SetActorHiddenInGame(false);
}
else
{
CurrentWeapon = PlayerWeaponInventory[WeaponIdx];
}
CurrentWeapon->SetActorEnableCollision(false);
FName WeaponSocket(TEXT("back_socket"));
CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocket);
bIsWeaponEquippedBack = true;
}
4. 추가
처음에 TMap<int32, AWeapon*> PlayerWeaponInventory 타입을 사용하게 된 이유는 CurrentWeapon이라는 변수와 바꿔가면서 구현하면 될 것이라고 생각했지만, 이 방식은 이전의 무기가 계속해서 보이는 문제점이 있었다.
이전의 무기에 대한 정보를 저장하면서 무기는 보이지 않게 하는 방법은 SetActorHiddenInGame 함수를 쓰는 방법이 최선일 것 같다고 생각하여, 해당 방식을 사용해서 구현했다.
'Unreal Engine' 카테고리의 다른 글
Unreal Engine - TSubclassOf & StaticClass (0) | 2025.02.27 |
---|---|
Unreal Engine - 총기 반동, 데칼, 탄피 효과 (0) | 2025.02.27 |
UE5 Issues : 인벤토리 UI (0) | 2025.02.26 |
UE5 Issues : 캐릭터 아이템 획득 로직 구현 (0) | 2025.02.24 |
UE5 Issues : Enhanced Input (Triggered & Started) (0) | 2025.02.23 |