언리얼 엔진/프로젝트

[언리얼 엔진] 팀 프로젝트 기록 05 - 무기 장착을 위한 WeaponComponent 구현

dhlee-dev 2026. 5. 18. 21:43

이전 글에서는 전투 시스템을 구현하기 전에, 전투 관련 컴포넌트를 어떤 책임으로 나눌지 정리했다.
이번에는 그중 첫 번째로 WeaponComponent를 구현한 내용을 정리해보려고 한다.

이번 작업의 핵심은 무기 자체와 무기 장착 관리를 분리하는 것이었다.

WeaponComponent는 직접 공격을 실행하는 컴포넌트가 아니라, 캐릭터가 현재 어떤 무기를 들고 있는지 관리하는 역할을 담당한다.


기존 템플릿 구조를 그대로 사용하지 않은 이유

처음에는 언리얼 1인칭 템플릿의 무기 구조를 참고했다.

템플릿에서는 무기 컴포넌트 하나가 총기 기능까지 함께 맡고 있었다.

발사체 클래스
발사 사운드
발사 몽타주
총구 위치
총기 전용 입력 매핑

간단한 1인칭 슈팅 게임이라면 이 구조도 괜찮다고 생각한다.

하지만 현재 프로젝트는 총기만 사용하는 구조가 아니다.
근접 무기와 원거리 무기를 함께 사용하고, 무기를 획득한 뒤 장착하는 흐름도 필요하다.

그래서 템플릿 구조를 그대로 가져오기보다는 다음처럼 역할을 나누기로 했다.

WeaponBase / GunBase / MeleeWeaponBase
- 무기 자체의 데이터와 기능 담당

WeaponComponent
- 무기 장착, 해제, 조회 담당

즉, 무기가 가진 데이터와 캐릭터가 무기를 관리하는 로직을 분리하는 방향으로 잡았다.


WeaponBase 만들기

먼저 모든 무기의 공통 부모 역할을 하는 WeaponBase를 만들었다.

WeaponBase에는 총기와 근접 무기가 공통으로 가질 수 있는 값들을 넣었다.

무기 메시
공격 사운드
공격 애니메이션
데미지 배율
스태미너 소모량
강화 레벨
장착 상태
장착 / 해제 함수

즉, WeaponBase는 “모든 무기가 공통으로 가지는 기본 정보”를 담당한다.

실제로 어떤 방식으로 공격하는지는 무기 종류에 따라 달라지기 때문에, 총기와 근접 무기는 각각 자식 클래스로 나누었다.


GunBase와 MeleeWeaponBase

총기 무기는 GunBase로 분리했다.

GunBase는 총기 전용 데이터와 상태를 가진다.

GunBase
- 탄약
- 남은 탄약
- 재장전
- 연사 속도
- 사거리
- 투사체 속도
- 총구 위치

근접 무기는 MeleeWeaponBase로 분리했다.

MeleeWeaponBase에는 콤보 공격과 공격 판정에 필요한 값을 넣었다.

근접 무기의 공격 판정은 무기 손잡이 쪽 소켓부터 무기 끝 소켓까지를 기준으로 Sphere Trace를 수행하는 방식으로 생각했다.
무기마다 길이와 두께가 다르기 때문에, Trace 시작 소켓, 끝 소켓, 반경 값은 MeleeWeaponBase가 가지도록 했다.

MeleeWeaponBase
- 약공격 몽타주
- 강공격 몽타주
- 콤보 섹션 이름
- Trace 시작 소켓
- Trace 끝 소켓
- Trace 반경

이렇게 나누면 총기와 근접 무기가 서로 다른 데이터를 가지면서도, 공통 무기 기능은 WeaponBase를 통해 공유할 수 있다.


WeaponComponent의 역할

WeaponComponent는 무기를 직접 공격에 사용하는 컴포넌트가 아니라, 장착된 무기를 관리하는 컴포넌트이다.

이번에 WeaponComponent가 담당하도록 한 역할은 다음과 같다.

WeaponComponent
- 무기 장착
- 무기 해제
- 현재 무기 조회
- 왼손 / 오른손 슬롯 관리
- 무기 입력 매핑 관리

처음에는 왼손 무기와 오른손 무기를 각각 변수로 둘 수도 있다고 생각했다.

LeftWeapon
RightWeapon

하지만 이렇게 하면 슬롯이 늘어나거나, 특정 슬롯을 공통적으로 처리해야 할 때 코드가 중복될 수 있다.

그래서 이전에 StatComponent에서 enum을 사용했던 것처럼 EWeaponSlot enum과 Weapons 배열을 이용하는 방식으로 정리했다.

EWeaponSlot
- LeftHand
- RightHand

Weapons[Slot]

이렇게 하면 왼손과 오른손을 enum 인덱스로 접근할 수 있고, 나중에 슬롯이 늘어나더라도 구조를 유지하기 쉽다.


무기 장착 흐름

무기 장착 흐름은 다음과 같이 정리했다.

1. 장착할 무기 클래스와 슬롯을 받는다.
2. 해당 슬롯에 이미 무기가 있다면 기존 무기를 해제한다.
3. 새 무기를 스폰한다.
4. 캐릭터 메시의 손 소켓에 무기를 붙인다.
5. Weapons 배열에 저장한다.
6. 무기 전용 입력 매핑을 추가한다.

장착 위치는 무기 자체가 아니라 슬롯을 기준으로 결정했다.

LeftHand  → LeftGrip
RightHand → RightGrip

처음에는 무기 자체가 장착 소켓 이름을 가지고 있어도 되지 않을까 생각했다.

하지만 같은 무기라도 왼손에 장착될 수도 있고, 오른손에 장착될 수도 있다.
그래서 장착 소켓은 무기 기준이 아니라 슬롯 기준으로 정하는 편이 더 자연스럽다고 판단했다.


무기 부착 위치 문제

무기를 손 소켓에 붙이는 과정에서 한 가지 문제가 있었다.

캐릭터 메시의 손 쪽에 LeftGrip, RightGrip 소켓을 만들고 무기를 부착했는데, 무기가 원하는 위치에 붙지 않았다.

이유는 무기가 소켓에 붙을 때 무기 메시의 Pivot을 기준으로 붙기 때문이다.

즉, 캐릭터 손 소켓 위치는 맞더라도 무기 메시의 Pivot 위치가 손잡이에 맞춰져 있지 않으면 무기가 손에 어색하게 붙어 보일 수 있다.

처음에는 무기 자체에도 Grip 소켓을 만들고, 캐릭터의 손 소켓과 무기의 Grip 소켓을 맞추는 방식을 생각했다.

Character RightGrip 소켓 ↔ Weapon Grip 소켓

이 방식이 더 정확할 수는 있지만, 무기마다 Grip 소켓을 만들고 소켓끼리 위치를 맞추는 과정이 생각보다 번거로웠다.

그래서 이번에는 무기별 Blueprint를 만들고,
그 안에서 StaticMesh의 상대 위치와 회전을 조절해서 손에 맞는 위치를 잡는 방식으로 해결했다.


Weapon Blueprint
- Root 기준으로 StaticMesh 위치 / 회전 조정
- 캐릭터 손 소켓에는 Weapon Actor를 부착

이렇게 하면 C++에서는 기존처럼 슬롯 기준 소켓에 무기를 붙이면 되고, 무기마다 손에 맞는 세부 위치는 Blueprint에서 조절할 수 있다.

결과적으로 무기 부착 로직은 단순하게 유지하면서, 무기별 위치 보정은 에디터에서 편하게 조정할 수 있었다.


무기 입력 매핑 관리

이번 프로젝트에서는 무기를 얻기 전에는 전투 입력이 필요하지 않다.

그래서 무기를 장착했을 때만 무기 관련 InputMappingContext를 추가하고, 양손 모두 무기가 없어지면 제거하는 방식으로 만들었다.

무기를 하나라도 장착함
→ Weapon InputMappingContext 추가

양손 모두 무기가 없음
→ Weapon InputMappingContext 제거

이렇게 하면 StartRoom에서 무기를 얻기 전에는 전투 입력이 활성화되지 않고,
무기를 획득한 뒤에 전투 입력이 활성화되는 구조를 만들 수 있다.


현재 무기 조회

WeaponComponent는 현재 장착된 무기를 찾아주는 함수도 제공한다.

GetCurrentGun()
- 현재 장착된 총기 반환

GetCurrentMeleeWeapon()
- 현재 장착된 근접 무기 반환

이 함수들은 이후 RangedCombatComponentMeleeCombatComponent에서 사용할 수 있다.

중요한 점은 WeaponComponent가 직접 공격을 처리하지 않는다는 것이다.

WeaponComponent는 현재 장착된 무기를 관리하고 찾아주는 역할에 집중하게 했다.


무기 장착 이벤트

무기를 장착했을 때 외부에서 반응할 수 있도록 장착 이벤트도 추가했다.

예를 들어 총기를 장착하면 UI에서 탄약 정보를 표시해야 할 수 있다.

이때 WeaponComponent가 직접 UI를 갱신하기보다는, 무기가 장착되었다는 사실만 이벤트로 알려주는 방식이 더 좋다고 생각했다.

무기 장착
→ OnWeaponEquipped 이벤트 발생
→ 필요한 곳에서 탄약 UI나 무기 UI 갱신

이렇게 하면 WeaponComponent는 UI를 직접 알 필요가 없고, 무기 장착 사실만 외부에 알려줄 수 있다.


실행 테스트


정리

이번에는 전투 컴포넌트 중 첫 번째로 WeaponComponent를 구현했다.
핵심은 무기 자체와 무기 장착 관리를 분리하는 것이었다.

무기 자체
- WeaponBase
- GunBase
- MeleeWeaponBase

무기 장착 관리
- WeaponComponent

WeaponBase는 공통 무기 정보를 담당하고, GunBaseMeleeWeaponBase는 각각 총기와 근접 무기의 전용 데이터를 담당한다.
WeaponComponent는 무기를 스폰하고, 왼손 또는 오른손 슬롯에 장착하고, 현재 장착된 무기를 반환한다.
무기를 장착하면 무기 전용 입력 매핑을 추가하고, 양손 모두 무기가 없어지면 입력 매핑을 제거한다.

이번 구조를 통해 무기 데이터, 무기 장착, 전투 실행을 서로 다른 책임으로 나눌 수 있었다.

다음 글에서는 이 WeaponComponent를 기반으로 근접 공격과 원거리 공격을 어떻게 구현했는지 정리해보려고 한다.