[리그오브워치개발일지] #18 캐릭터 장비 시스템
Git
레포지토리 주소 : https://github.com/kjinwoo12/UE5Game_LeagueOfWatch
기준 태그 : 18_WeaponSystem
LyraStarterGame 무기 관련 기능 분석
LyraStarterGame은
무기 기능을 만들기에 앞서, LyraStarterGame에서 무기 장착이 어떻게 이루어지는지 관련 기능과 함께 분석해보자. 무기와 관련된 코드가 너무 많아 요약해서 글을 작성했기 때문에 설명이 많이 부족할 수 있다. 좀 더 자세한 내용은 글에서 설명한 키워드를 바탕으로 직접 코드를 분석하는 것을 추천한다.
LyraStgarterGame에서 무기 관련 기능은 많이 복잡하게 만들어진 편이다. 무기를 획득하고 손에 무기를 들어올리는 것은 다음 순서를 따른다.
- 무기 생성기(
WeaponnSpawner
)에서 일정 주기마다 무기가 생성된다 - 캐릭터가 생성기에 다가가면 생성기 위에 표시된 무기를 획득할 수 있다.
- 캐릭터는 기본 권총을 제외한 최대 두 종류의 무기까지만 획득할 수 있다.
- 획득 순서에 따라 숫자 키(1~3)을 눌러서 캐릭터가 손에 쥐고있는 무기를 변경할 수 있다.
가장 먼저 플레이어가 무기를 획득할 수 있도록 만들어주는 액터 B_WeaponSpawner
에 플레이어가 근접하면 미리 설정된 데이터 에셋에 값을 참조해 플레이어에게 무기를 지급한다. 위 사진에서 보이는 액터의 경우에는 WeaponDefinition
에 값이 설정된 WeaponPickupData_Rifle
가 관련 데이터 에셋이다.
무기를 획득했을 때의 사운드, 획득 후 일정 시간이 지나 다시 무기가 생성되었을 때의 사운드, 생성기 위에 표시될 무기의 메시 등등 데이터를 확인할 수 있다. Equipment
탭에 있는 Inventory Item definition
은 UI에 표시할 아이콘같은 무기를 획득했을 때 필요한 정보나 장비할 무기의 데이터를 담는 오브젝트 블루프린트다. 라이플의 경우에는 Inventory Item definition
의 값이 ID_Rifle
로 설정되어있다.
실제로 플레이어가 장착하게 될 무기 액터의 정보는 Fragments-Index[0]-Equipment Definition
의 값으로 설정된 WID_Rifle
에서 확인할 수 있다. WID_Rifle
를 확인해보면 장착시킬 액터는 Actors to Spawn[0] - Actor to Spawn
에 B_Rifle
로 설정되어있고, 장착시킬 위치에 해당하는 스켈레톤 소켓의 값은 Actors to Spawn[0] - Attach Socket
에 weapon_r
이다. 배열로 되어있는 이유는 쌍권총같이 여러 액터가 필요한 경우를 대비한 것 같다.
void ULyraEquipmentInstance::SpawnEquipmentActors(const TArray<FLyraEquipmentActorToSpawn>& ActorsToSpawn)
{
if (APawn* OwningPawn = GetPawn())
{
USceneComponent* AttachTarget = OwningPawn->GetRootComponent();
if (ACharacter* Char = Cast<ACharacter>(OwningPawn))
{
AttachTarget = Char->GetMesh();
}
for (const FLyraEquipmentActorToSpawn& SpawnInfo : ActorsToSpawn)
{
/***** Equipment code ******/
AActor* NewActor = GetWorld()->SpawnActorDeferred<AActor>(SpawnInfo.ActorToSpawn, FTransform::Identity, OwningPawn);
NewActor->FinishSpawning(FTransform::Identity, /*bIsDefaultTransform=*/ true);
NewActor->SetActorRelativeTransform(SpawnInfo.AttachTransform);
NewActor->AttachToComponent(AttachTarget, FAttachmentTransformRules::KeepRelativeTransform, SpawnInfo.AttachSocket);
/***************************/
SpawnedActors.Add(NewActor);
}
}
}
위에 코드는 LyraStarterGame에서 무기 액터를 캐릭터에 장착하는 부분을 가져온 것이다. 액터를 생성하고 손에 쥐여주는 것에 직접적으로 관여하는 부분은 /***** Equipment code *****/
주석으로 둘러싸인 부분이 전부다. SpawnInfo
의 정보는 라이플의 경우 WID_Rifle
에서 Actors to Spawn
를 확인해 알 수 있다. B_Weapon
를 상속받는 B_Rifle
가 설정되어 있다.
Lyra에서는 무기를 장착할 때마다 새로운 액터를 생성하고 기존 액터를 삭제하는 방법을 사용한다. 이 방법은 무기별 남아있는 총알의 개수처럼 개별 무기의 데이터를 저장할 수 없는 문제가 있는데, B_Weapon
에 관련 데이터를 직접 저장하지 않고 ULyraInventoryItemFragment
와 인벤토리 시스템, 게임플레이 어빌리티 시스템 등을 사용해 데이터를 저장하는 방법으로 문제를 해결했다. 게임플레이 어빌리티 시스템의 경우에는 UE5 게임플레이 어빌리티 시스템 문서 참조
요약하면 Lyra의 무기 시스템은 *Definition
클래스로 무기 액터 생성 관련 데이터를 다루고, *Instance
클래스는 게임플레이 어빌리티 시스템을 활용해 실제 무기의 동작 및 데이터 관리를 담당한다.
요구사항 확인
LyraStarterGame에서 무기 관련 기능을 분석하자면 만들어야할 기능은 세 가지로 나눌 수 있다.
- 장비 탈부착
- 장비 부착 후 캐릭터 애니메이션
- 무기 자체 기능(데미지, 이펙트, 애니메이션 등)
구현
테스트용 무기 추가
LyraStarterGame에서 권총 매시와 관련 애니메이션을 가져왔다.
장비 탈부착
장비 탈부착을 위해 EquipmentLibrary
플러그인을 만들었다. EquipmentInstance
를 구현해 원하는 장착 아이템을 만들고, EquipmentComponent
를 캐릭터에 추가해서 EquipmentInstance
를 장비할 수 있다. 자세한 내용은 EquipmentLibrary 참고. 아래는 EquipmentLibrary
를 사용해 무기를 만든 코드다. 좌클릭을 누르면 권총을 탈부착하도록 만들었다.
LeagueEquipmentInstance.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "EquipmentInstance.h"
#include "Animation/AnimLayer.h"
#include "LeagueEquipmentInstance.generated.h"
/**
*
*/
UCLASS()
class LEAGUEOFWATCH_API ULeagueEquipmentInstance : public UEquipmentInstance
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FAnimLayer EquippedAnimLayer;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FAnimLayer UnequippedAnimLayer;
UFUNCTION(BlueprintCallable, Category=Animation)
TSubclassOf<UAnimInstance> GetEquippedAnimInstance(const FGameplayTagContainer& CosmeticTags) const;
UFUNCTION(BlueprintCallable, Category=Animation)
TSubclassOf<UAnimInstance> GetUnequippedAnimInstance(const FGameplayTagContainer& CosmeticTags) const;
};
LeagueEquipmentInstance.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Equipment/LeagueEquipmentInstance.h"
#include "EquipmentComponent.h"
TSubclassOf<UAnimInstance> ULeagueEquipmentInstance::GetEquippedAnimInstance(const FGameplayTagContainer& CosmeticTags) const
{
return EquippedAnimLayer.SelectBestLayer(CosmeticTags);
}
TSubclassOf<UAnimInstance> ULeagueEquipmentInstance::GetUnequippedAnimInstance(const FGameplayTagContainer& CosmeticTags) const
{
return UnequippedAnimLayer.SelectBestLayer(CosmeticTags);
}
BP_WeaponInstance Event
BP_WeaponInstance ActiveAnimLayerAndPlayPairedAnim
BP_PlayableCharacter
BP_PlayableCharacter_Tracer
1인칭 매시와 3인칭 매시 모두 아이템을 장착하기 때문에 FirstPersonEquipment
와 ThirdPersonEquipment
를 추가했다. BeginPlay
에 TargetMesh
를 지정하면 장비를 장착했을 때 액터를 매시 컴포넌트 소켓에 부착할 수 있다.