아래 글은 “언리얼 엔진에서 무기를 장착하는 로직”을 체계적으로 구현하기 위해,
**Combat Component(전투 구성 요소)**라는 UActorComponent를 만들어 장착 로직을 옮기는 과정을
천천히 살펴본 강의 내용을 정리한 것입니다.


목차

  1. 왜 전투 로직을 분리해야 할까?
  2. Combat Component 생성하기
  3. 장비(Equip) 입력 바인딩하기
  4. Combat Component에서 무기 장착하기
  5. Skeleton Socket 설정 및 무기 부착
  6. 맺음말 & 다음 단계

1. 왜 전투 로직을 분리해야 할까?

게임이 복잡해질수록, 캐릭터 클래스(BlasterCharacter)에 모든 전투 관련 코드(무기 장착, 사격 로직, 재장전, 발사 효과 등)가 쌓이게 됩니다.
이렇게 한 클래스가 너무 많은 책임을 지면 코드 유지보수가 어려워지고, 멀티플레이 로직 등을 추가할 때 혼란이 커집니다.

따라서 UActorComponent(전투 전용 컴포넌트)를 만들어, 전투에 관련된 로직만 따로 관리하면 구조가 한결 깔끔해집니다.

  • BlasterCharacter: 캐릭터의 기본 이동, UI, 기타 전반적인 동작 담당
  • CombatComponent: 전투 로직 전담 (무기 장착, 발사, 재장전 등)

2. Combat Component 생성하기

언리얼 에디터에서 C++ 클래스 추가 버튼을 통해, 부모 클래스로 ActorComponent를 선택해 새 클래스를 만듭니다.

예시:

  1. Add New → New C++ Class → Actor Component
  2. 이름을 CombatComponent로 지정
  3. 원하는 폴더(예: 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) 입력 바인딩하기

무기를 주울 때(장착) 사용할 액션 매핑을 만들어줍니다.

  1. Edit → Project Settings → Input
  2. 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 소켓 생성

  1. 콘텐츠 브라우저에서 캐릭터 스켈레톤(예: SK_Character 등)을 더블 클릭
  2. Skeleton Tree에서 hand_r(오른손 뼈) 우클릭 → Add Socket
  3. 만들어진 소켓 이름을 RightHandSocket 등으로 지정
  4. 우클릭 → Add Preview Asset 등으로 미리 무기를 배치해보고 위치/회전 값을 미세 조정

5.2 위치/회전 조정

무기를 실제 착용 상태로 보이도록, 소켓의 Relative Location, Relative Rotation 등을 알맞게 설정합니다.

  • 왼손 위치가 어긋나면, IK(역운동학)나 Hand IK 기능을 이용해 왼손 위치를 제어하는 방법도 있습니다.
  • 일단 오른손 소켓만 정확하게 잡아주어도 간단한 프로토타이핑엔 충분합니다.

6. 맺음말 & 다음 단계

이번 강의에서 한 일은 다음과 같습니다.

  1. CombatComponent를 새로 만들어, 캐릭터의 전투 로직을 분리
  2. Equip 액션 매핑(E 키)을 설정해, EquipWeapon 함수를 호출
  3. 무기 부착: 무기를 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

+ Recent posts