아래 글은 “언리얼 엔진에서 무기를 장착하는 로직”을 체계적으로 구현하기 위해,
**Combat Component(전투 구성 요소)**라는 UActorComponent를 만들어 장착 로직을 옮기는 과정을
천천히 살펴본 강의 내용을 정리한 것입니다.
목차
- 왜 전투 로직을 분리해야 할까?
- Combat Component 생성하기
- 장비(Equip) 입력 바인딩하기
- Combat Component에서 무기 장착하기
- Skeleton Socket 설정 및 무기 부착
- 맺음말 & 다음 단계
1. 왜 전투 로직을 분리해야 할까?
게임이 복잡해질수록, 캐릭터 클래스(BlasterCharacter)에 모든 전투 관련 코드(무기 장착, 사격 로직, 재장전, 발사 효과 등)가 쌓이게 됩니다.
이렇게 한 클래스가 너무 많은 책임을 지면 코드 유지보수가 어려워지고, 멀티플레이 로직 등을 추가할 때 혼란이 커집니다.
따라서 UActorComponent(전투 전용 컴포넌트)를 만들어, 전투에 관련된 로직만 따로 관리하면 구조가 한결 깔끔해집니다.
- BlasterCharacter: 캐릭터의 기본 이동, UI, 기타 전반적인 동작 담당
- CombatComponent: 전투 로직 전담 (무기 장착, 발사, 재장전 등)
2. Combat Component 생성하기
언리얼 에디터에서 C++ 클래스 추가 버튼을 통해, 부모 클래스로 ActorComponent를 선택해 새 클래스를 만듭니다.
예시:
- Add New → New C++ Class → Actor Component
- 이름을 CombatComponent로 지정
- 원하는 폴더(예: Blaster/BlasterComponents)에 저장
// CombatComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CombatComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class YOURGAME_API UCombatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UCombatComponent();
protected:
virtual void BeginPlay() override;
private:
// 이후에 전투 관련 변수들을 여기서 관리
};
- PrimaryComponentTick.bCanEverTick = false; 등을 통해,
당장 매 프레임 처리가 필요 없으면 Tick을 꺼두는 것도 좋습니다.
BlasterCharacter에 CombatComponent 등록
// BlasterCharacter.h (일부)
private:
UPROPERTY(VisibleAnywhere, Category="Combat")
class UCombatComponent* Combat;
// BlasterCharacter.cpp (생성자)
ABlasterCharacter::ABlasterCharacter()
{
Combat = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));
// ...
}
- 이렇게 하면 BlasterCharacter가 생성될 때 CombatComponent도 자동으로 함께 생성되고,
전투 관련 모든 기능은 Combat 객체를 통해 접근하게 됩니다.
3. 장비(Equip) 입력 바인딩하기
무기를 주울 때(장착) 사용할 액션 매핑을 만들어줍니다.
- Edit → Project Settings → Input
- Action Mappings에서 새 항목 추가 → 이름: Equip (키: E 등 원하는 키)
이후, 캐릭터 코드에서 이를 바인딩합니다:
// BlasterCharacter.h (일부)
protected:
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
void EquipButtonPressed(); // E 키를 눌렀을 때 호출될 함수
// BlasterCharacter.cpp
void ABlasterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// "Equip"이라는 액션 매핑을 E 키에 바인딩
PlayerInputComponent->BindAction("Equip", IE_Pressed, this, &ABlasterCharacter::EquipButtonPressed);
}
void ABlasterCharacter::EquipButtonPressed()
{
if (!Combat) return;
// 여기서 "CombatComponent"의 로직을 호출할 것
// 예) Combat->EquipWeapon(...);
}
- 이제 게임 실행 중 E 키를 누르면 EquipButtonPressed() 함수가 불리고,
그 안에서 전투 컴포넌트의 EquipWeapon 함수를 호출해 무기를 장착하도록 만듭니다.
4. Combat Component에서 무기 장착하기
4.1 CombatComponent에 메서드 정의
CombatComponent에 실제 무기 장착을 수행할 함수를 작성합니다.
무기를 장착하려면, 그 무기에 대한 포인터(AWeapon*)가 필요합니다.
// CombatComponent.h
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class YOURGAME_API UCombatComponent : public UActorComponent
{
GENERATED_BODY()
public:
void EquipWeapon(class AWeapon* WeaponToEquip);
private:
// 현재 장착 중인 무기
class AWeapon* EquippedWeapon;
// CombatComponent는 어느 캐릭터에 속해있는지 알아야 함
class ABlasterCharacter* Character;
protected:
virtual void BeginPlay() override;
};
// CombatComponent.cpp
#include "CombatComponent.h"
#include "BlasterCharacter.h"
#include "Weapon.h"
#include "Engine/SkeletalMeshSocket.h"
UCombatComponent::UCombatComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UCombatComponent::BeginPlay()
{
Super::BeginPlay();
// 소유 캐릭터 설정 등 필요 시
// Character = Cast<ABlasterCharacter>(GetOwner());
}
void UCombatComponent::EquipWeapon(AWeapon* WeaponToEquip)
{
if(!Character || !WeaponToEquip) return;
// (1) EquippedWeapon 변수 설정
EquippedWeapon = WeaponToEquip;
EquippedWeapon->SetWeaponState(EWeaponState::EWS_Equipped);
// (2) 소유자 설정 (무기의 Owner를 캐릭터로)
EquippedWeapon->SetOwner(Character);
// (3) 소켓에 무기 부착
const USkeletalMeshSocket* HandSocket =
Character->GetMesh()->GetSocketByName(FName("RightHandSocket"));
if(HandSocket)
{
HandSocket->AttachActor(EquippedWeapon, Character->GetMesh());
}
// (4) 픽업 위젯은 더이상 필요 없으므로 숨김
EquippedWeapon->ShowPickupWidget(false);
}
- SetWeaponState(EWeaponState::EWS_Equipped): 무기 상태를 “Equipped”로 전환
- SetOwner(Character): 언리얼 액터(Weapon)에는 Owner를 지정할 수 있음
- AttachActor(...): 실제로 무기를 캐릭터의 소켓에 부착(카메라 기반 FPS라면 다른 소켓 설정 가능)
4.2 캐릭터에서 사용 예시
이제 ABlasterCharacter::EquipButtonPressed()에서 CombatComponent에 무기를 전달하기만 하면 됩니다(물론 어떤 무기를 장착할지 로직은 추가 필요).
void ABlasterCharacter::EquipButtonPressed()
{
if(!HasAuthority())
{
// 서버 권한이 아닌 클라이언트라면,
// RPC(서버로 호출) 형태로 Equip 요청을 해야 할 수도 있음
return;
}
if(!Combat) return;
if(!OverlappingWeapon) return; // 예: 캐릭터가 겹치고 있는 무기 포인터
Combat->EquipWeapon(OverlappingWeapon);
}
- 중요:
- 실제 멀티플레이 환경에선 클라이언트가 단순히 로컬에서 EquipWeapon을 호출할 수 없음(서버 권한 필요).
- 따라서 “서버 RPC”로 호출하거나, “서버에서 Overlap 이벤트 발생 시 자동 장착”처럼 서버가 결정하도록 해야 합니다.
- 여기서는 간단히 “서버에서만 EquipWeapon이 동작”하도록 예시 코드를 작성했습니다.
5. Skeleton Socket 설정 및 무기 부착
5.1 소켓 생성
- 콘텐츠 브라우저에서 캐릭터 스켈레톤(예: SK_Character 등)을 더블 클릭
- Skeleton Tree에서 hand_r(오른손 뼈) 우클릭 → Add Socket
- 만들어진 소켓 이름을 RightHandSocket 등으로 지정
- 우클릭 → Add Preview Asset 등으로 미리 무기를 배치해보고 위치/회전 값을 미세 조정
5.2 위치/회전 조정
무기를 실제 착용 상태로 보이도록, 소켓의 Relative Location, Relative Rotation 등을 알맞게 설정합니다.
- 왼손 위치가 어긋나면, IK(역운동학)나 Hand IK 기능을 이용해 왼손 위치를 제어하는 방법도 있습니다.
- 일단 오른손 소켓만 정확하게 잡아주어도 간단한 프로토타이핑엔 충분합니다.
6. 맺음말 & 다음 단계
이번 강의에서 한 일은 다음과 같습니다.
- CombatComponent를 새로 만들어, 캐릭터의 전투 로직을 분리
- Equip 액션 매핑(E 키)을 설정해, EquipWeapon 함수를 호출
- 무기 부착: 무기를 RightHandSocket에 붙이고, 소유자/위젯 상태를 변경
이제 캐릭터는 서버 권한이 있을 때만 무기를 장착할 수 있습니다.
하지만 클라이언트에서 무기를 주우려 하면 “권한이 없다”는 문제가 남아있습니다.
이를 해결하려면, 서버 RPC(Remote Procedure Call)나 Replication(변수 복제)를 통해
“클라이언트가 E 키를 누름 → 서버가 확인 후 실제 장착” 과정을 거쳐야 합니다.
앞으로 살펴볼 주제
- 클라이언트에서 무기를 집는 로직: 서버 RPC를 통해 무기 장착을 요청하는 구조
- 무기 드롭 & 재장전: 마찬가지로 서버에 로직이 있으며, 상태를 Replication으로 동기화
- 왼손 IK(역운동학): 무기를 오른손에 붙이고, 왼손 위치를 자동으로 보정
위 단계들을 차근차근 구현해 나가면, 좀 더 완성도 높은 멀티플레이 전투 시스템을 갖출 수 있을 것입니다.
계속 학습해 나가며, CombatComponent를 중심으로 전투 시스템을 확장해 보세요!
결론
전투 로직은 게임에서 가장 복잡해질 수 있는 부분입니다.
**CombatComponent**라는 별도 액터 컴포넌트로 분리하면, “캐릭터 이동”과 “전투”가 깔끔하게 구분되어 코드 유지보수와 확장에 큰 도움을 줍니다.
이번 강의의 핵심은 “무기 장착 로직을 CombatComponent::EquipWeapon()로 이동시키고, Skeleton Socket에 무기를 부착”하는 흐름이었습니다.
다음에는 클라이언트-서버 간 무기 장착 처리를 어떻게 통합하는지, 실제 멀티플레이 상황을 가정해 구현해 보겠습니다.
궁금한 점이 있거나, 더 알고 싶은 주제가 있다면 댓글이나 문의로 남겨주세요!
'UnraealEngine > Multiplayer Shooter' 카테고리의 다른 글
| 48. Remote Procedure Calls (0) | 2025.03.13 |
|---|---|
| 46.Variable Replication (0) | 2025.03.13 |
| 44~46 (0) | 2025.03.13 |
| 41.Seamless Travel and Lobby (0) | 2025.03.12 |
| 42.Network Role (0) | 2025.03.12 |