본문 바로가기

게임플레이 능력 시스템 구현

Ability·Effect·AttributeSet과 서버 권한·예측 흐름을 이해하고 비용·쿨다운 적용과 기본 공격의 연결을 구성합니다.

공격 하나는 이벤트 그래프에 몽타주 재생과 피해 처리를 이어 붙여 만들 수 있습니다. 그런데 스킬이 열 개로 늘고, 각 스킬에 마나 비용과 쿨다운(재사용 대기시간)이 붙고, 기절한 동안에는 어떤 스킬도 쓸 수 없어야 하고, 이 모든 것이 멀티플레이어에서 맞아야 한다면 이야기가 달라집니다. 스킬마다 비슷한 검사와 예외 처리가 반복되고, 어느 스킬이 어느 상태를 막는지 그래프만 봐서는 알기 어려워집니다.

언리얼 엔진은 이런 문제를 위해 게임플레이 능력 시스템(Gameplay Ability System, GAS)을 제공합니다. 이 글에서는 GAS의 구성 요소와 멀티플레이어에서의 실행 흐름을 살펴보고, 근접 기본 공격 하나를 능력과 이펙트 에셋으로 연결해 봅니다. GAS는 여러 스킬의 비용, 쿨다운, 상태 효과를 함께 관리해야 할 때 검토할 선택 심화 과정이며, 간단한 공격 하나를 만드는 데 꼭 필요한 것은 아닙니다.

한 가지 전제가 있습니다. 아래 기본 공격 예제는 C++로 작성한 GAS 기반 클래스, 곧 능력 시스템 컴포넌트와 속성 세트를 갖춘 캐릭터 기반 클래스가 준비되어 있어야 따라 할 수 있습니다. 이 글은 그 기반 클래스의 전체 코드를 다루지 않으므로, 아직 준비하지 않았다면 구성 요소와 실행 흐름을 먼저 읽고 기반 클래스가 마련된 뒤 예제를 진행하세요. C++ 클래스를 만들고 그 클래스를 부모로 하는 블루프린트를 만드는 방법은 언리얼 C++ 교재의 C++로 Blueprint 클래스 확장에서 다룹니다.


GAS가 해결하는 문제

GAS는 캐릭터의 스킬, 마법, 공격, 방어, 버프와 디버프, 아이템 사용처럼 여러 형태의 능력을 모듈로 나누어 구현하고 관리하는 고수준 프레임워크입니다. 능력과 스탯, 상태 이상이 많은 RPG, AOS, FPS 같은 장르에 특히 잘 맞습니다.

GAS를 쓰면 얻는 것은 다음과 같습니다. 각 능력을 독립된 에셋(Gameplay Ability)으로 정의하므로 여러 캐릭터가 같은 능력을 재사용하기 쉽습니다. 대부분의 GAS 구성 요소가 네트워크 복제를 지원하므로 멀티플레이어 개발 부담이 줄어듭니다. 체력, 마나, 공격력 같은 스탯(attribute)을 한곳에서 관리하고 그 변경을 효율적으로 복제하며, 기절, 중독, 속도 증가 같은 상태 효과(Gameplay Effect)를 유연하게 적용하고 해제할 수 있습니다. 입력과 능력 실행을 연결하는 방법도 정해져 있고, 많은 설정을 데이터나 블루프린트 에셋으로 관리하므로 디자이너가 직접 값을 조정하며 반복 작업을 빠르게 할 수 있습니다.


GAS를 이루는 구성 요소

GAS는 몇 개의 핵심 클래스와 개념이 함께 동작합니다.

능력 시스템 컴포넌트(UAbilitySystemComponent, ASC)는 능력 실행과 상태 적용을 맡는 중심 컴포넌트입니다. 능력의 부여와 제거, 실행과 취소, 속성 변경, 이펙트의 적용과 해제가 모두 이 컴포넌트를 거치며, 서버와 클라이언트 사이에서 능력과 속성을 동기화합니다. ASC는 폰이나 캐릭터에 둘 수도 있고 플레이어 스테이트 같은 다른 액터에 둘 수도 있습니다. 그래서 ASC를 가진 소유자 액터(Owner Actor)와, 실제로 월드에서 능력을 수행하는 아바타 액터(Avatar Actor)가 다를 수 있습니다.

게임플레이 능력(UGameplayAbility)은 능력 하나의 로직을 정의하는 에셋으로, GA_Fireball, GA_MeleeAttack, GA_Heal처럼 만듭니다. 블루프린트로 구현하며, 어떤 조건에서 활성화되는지, 무엇을 실행하는지, 쿨다운과 소모 비용, 취소 조건 등을 담습니다. 능력에 붙이는 태그와 활성화 조건은 능력 안에 정의하지만, 입력이 들어왔을 때 ASC에 활성화를 요청하는 연결은 따로 만들어야 합니다.

속성 세트(UAttributeSet)는 체력, 마나, 공격력, 방어력, 속도 같은 캐릭터의 스탯을 정의하는 그릇입니다. ASC에 등록해 쓰며, 각 속성은 FGameplayAttributeData 타입으로 기본값(base value)과 현재값(current value)을 가집니다. 현재값은 지금 적용 중인 이펙트를 반영한 값이고, 기본값은 그보다 드물게 바뀝니다. 최대 체력이 필요하다면 MaxHealth라는 별도 속성과 제한 규칙을 정의해야 합니다. 공식 문서의 예제는 속성 세트를 C++로 정의하며, 멀티플레이어에서의 속성 복제와 OnRep 처리도 C++ 기반 클래스에서 구성합니다.

게임플레이 이펙트(UGameplayEffect)는 속성을 바꾸거나 태그를 붙이고 떼어 캐릭터의 상태를 바꾸는 에셋입니다. 체력을 계속 깎는 GE_Poison, 속도를 올리는 GE_SpeedBoost, 기절 태그를 붙이는 GE_Stun 같은 식입니다. 이펙트의 지속 방식(Duration Policy)은 세 가지입니다. 즉시 적용되고 끝나는 Instant, 정해진 시간 동안 이어지는 Has Duration, 명시적으로 제거할 때까지 이어지는 Infinite입니다. 독처럼 일정 간격으로 반복 적용하는 주기 적용(periodic)은 별도의 지속 방식이 아니라, Has Duration이나 Infinite 이펙트에 Period를 설정해 만듭니다. 속성 값을 실제로 바꾸는 부분은 수정자(Modifier)로, 덧셈이나 곱셈 같은 연산과 바꿀 속성을 지정하며, 수정자의 Attribute에서는 등록된 속성 세트의 속성을 고릅니다.

게임플레이 태그(Gameplay Tag)는 능력, 이펙트, 캐릭터의 상태를 식별하는 계층적인 이름입니다. Ability.Melee, State.Stunned, Buff.Speed처럼 점으로 계층을 나누고, 여러 태그를 FGameplayTagContainer에 묶어 다룹니다. 태그를 쓰면 어떤 능력이 다른 능력에 막히게 하거나, 특정 태그를 가진 대상에게만 이펙트를 적용하는 규칙을 만들 수 있습니다. 쓸 태그는 프로젝트 설정의 Gameplay Tags에서 정의합니다.

능력, 이펙트, 속성 세트를 나누는 기준

세 구성 요소의 관계를 한 줄로 줄이면, 능력은 실행 흐름을, 이펙트는 값과 태그를 바꾸는 규칙을, 속성 세트는 값을 보관하고 변경을 처리하는 일을 맡습니다. 그래서 실무에서는 세 가지 규칙을 지키는 편이 좋습니다. 첫째, 능력 안에 피해 같은 수치 계산을 직접 써 넣기보다 이펙트와 태그 규칙으로 분리합니다. 그래야 같은 능력을 다른 수치로 재사용할 수 있습니다. 둘째, 속성 세트는 값의 보관과 변경 이벤트 처리에 집중하고, UI 갱신은 별도의 위젯이나 컨트롤러에서 처리합니다. 셋째, 쿨다운과 비용은 가능하면 게임플레이 이펙트로 통일해, 여러 능력이 같은 방식으로 다루게 합니다.


멀티플레이어에서 능력이 실행되는 순서

GAS의 능력은 멀티플레이어에서 다섯 단계로 실행됩니다.

첫째는 초기화와 부여입니다. 능력은 서버에서만 부여할 수 있으며, 서버와 소유 클라이언트에서 ASC의 액터 정보(Actor Info), 곧 소유자와 아바타가 누구인지가 올바르게 초기화되어 있어야 합니다. 둘째는 활성화 요청으로, 입력이 들어오면 ASC에 능력 활성화를 요청합니다. 능력의 네트워크 실행 정책(Net Execution Policy)에 따라 서버에서 실행하거나 클라이언트에서 먼저 예측해 실행하며, 예측을 쓴다고 모든 효과가 예측되는 것은 아닙니다.

셋째는 커밋입니다. 능력 안에서 Commit Ability를 호출하면 비용과 쿨다운을 검사하고 적용하며, 성공 여부를 Boolean으로 돌려줍니다. 실패하면 그 자리에서 실행을 끝냅니다. 넷째는 능력의 실제 실행으로, 애니메이션과 타격 판정을 진행하되 최종 피해와 대상 검증은 서버에서 합니다. 다섯째는 종료와 보정입니다. 정상 완료든 취소든 모든 경로가 End Ability에 도달해야 하며, 공식 문서에 따르면 능력을 제대로 끝내지 않으면 시스템이 그 능력을 아직 실행 중으로 여겨 다시 쓸 수 없게 됩니다. 예측한 상태에 대한 서버의 응답도 이 단계에서 반영합니다. 쿨다운을 능력이 끝날 때 적용하고 싶다면, 기본 커밋이 비용과 쿨다운을 함께 적용한다는 점을 고려해 별도로 설계해야 합니다.

블루프린트로 구현할 때는 능력 실행의 최종 판단을 서버에 맡기고, 클라이언트는 반응이 빠르게 느껴지도록 하는 예측 연출에 집중합니다. 비용이나 쿨다운 때문에 실패했다면 아이콘 깜빡임, 소리, 문구 같은 피드백을 바로 보여 줍니다. 네트워크 규칙은 블루프린트에서의 리플리케이션에서 본 서버 권한과 소유권의 기준을 그대로 따릅니다.


기본 공격을 능력으로 만들기

플레이어 캐릭터의 근접 공격을 GAS로 구성해 보겠습니다. 이 예제는 C++ GAS 기반 클래스가 준비되어 있다는 전제에서, 블루프린트로 만든 능력과 이펙트 에셋을 연결합니다. 속성 세트를 블루프린트의 Float 변수로 만드는 절차는 아닙니다.

자신의 비용과 적의 피해는 서로 다른 ASC에 적용합니다

자기 ASC 활성 요청→Commit 성공 비용/쿨다운→접촉 서버 대상 검증→적 ASC Health에 -20→종료 정리의 실행 경계이다. Commit 실패·중단·취소 경로와 자기/적 효과를 보존한다.

ABILITY · EFFECT · TARGET ATTRIBUTES

자신의 비용과 적의 피해는 서로 다른 ASC에 적용합니다

본문의 기본 공격 예제는 캐릭터가 ASC를 소유하고 같은 캐릭터가 Avatar입니다. 네이티브 기반 클래스와 Actor Info 초기화가 준비되어 있다는 전제입니다.

자기ASC와적ASC의다른적용경로공격자ASC에서능력활성화를요청한다. 커밋성공시에구성한자기비용과쿨다운을자기ASC에적용한다. 성공분기의접촉시서버가대상을검증하고GE_Damage를검증한적ASC의Health에적용한다. 커밋실패는공격종료이며도식은모든효과의예측을보장하지않는다.공격자 · 자신의 ASC 영역서버의 대상 확정자신의 ASCOwner = AvatarGA_MeleeAttackCommit 결과 분기요청비용성공 후 접촉대상 검증대상 · 거리 · 권한GE_DamageInstant · Additive -20적의 ASCAttributeSet Health
자기ASC와적ASC의다른적용경로공격자ASC에서능력활성화를요청한다. 커밋성공시에구성한자기비용과쿨다운을자기ASC에적용한다. 성공분기의접촉시서버가대상을검증하고GE_Damage를검증한적ASC의Health에적용한다. 커밋실패는공격종료이며도식은모든효과의예측을보장하지않는다.자신의 ASCOwner = AvatarGA_MeleeAttackCommit 결과 분기활성 요청자기 적용서버 확정성공 후 접촉대상 검증대상 · 거리 · 권한GE_DamageInstant · Additive -20적의 ASCAttributeSet Health

점선 자기 적용은 성공한 커밋에서 구성한 비용·쿨다운을 자신의 ASC에 적용하는 관계입니다. 비용 없음인 연습에서는 이 경로에 적용할 Effect가 없습니다. 커밋 실패면 공격 실행을 종료합니다. 적의 피해는 접촉 이벤트에서 서버가 검증한 대상 ASC로 전달하며 Apply Gameplay Effect to Owner와 구별합니다.

접촉 이벤트와 종료 이벤트는 역할이 다릅니다

Wait Gameplay Event가 Event.AttackHit를 받는 경로에서 피해를 처리합니다. Montage Completed/Blend Out을 접촉 순간으로 쓰지 않습니다. 정상 완료·중단·취소 모두 End Ability와 필요한 정리에 연결합니다.

다른 설계에서는 ASC Owner와 Avatar가 다를 수 있습니다. 로컬 예측이 모든 효과의 예측을 보장하지 않으며 최종 피해는 서버가 검증합니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목자기 ASC와 적 ASC의 다른 적용 경로
관계·조건 설명공격자 ASC에서 능력 활성화를 요청한다. 커밋 성공 시에 구성한 자기 비용과 쿨다운을 자기 ASC에 적용한다. 성공 분기에서 접촉하면 서버가 대상을 검증하고 GE_Damage를 검증한 적 ASC의 Health에 적용한다. 커밋 실패 시에는 공격을 종료하며, 이 도식은 모든 효과의 예측을 보장하지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
자기 ASC Commit

자기 능력의 활성·비용·쿨다운을 검사한다.

  • 소유자: 자기 ASC
  • 조건: Commit 성공
  • Commit 실패는 대상 피해 경로로 진행하지 않는다.
대상 검증

접촉 이벤트에서 서버가 적 대상을 확인한다.

  • 권한: 서버
  • 대상: 적 ASC
  • 자기 비용 효과와 적의 피해 효과를 같은 소유 상태로 묶지 않는다.
효과·종료

적 Health에 -20을 적용하고 능력 자원을 정리한다.

  • 적 Health 변화: -20
  • 종료: 능력 정리
  • 중단·취소는 정상 접촉 피해와 독립 경로로 보존한다.

자신의 비용과 적의 피해는 서로 다른 ASC에 적용합니다

자기 ASC 활성 요청→Commit 성공 비용/쿨다운→접촉 서버 대상 검증→적 ASC Health에 -20→종료 정리의 실행 경계이다. Commit 실패·중단·취소 경로와 자기/적 효과를 보존한다.

ABILITY · EFFECT · TARGET ATTRIBUTES

자신의 비용과 적의 피해는 서로 다른 ASC에 적용합니다

본문의 기본 공격 예제는 캐릭터가 ASC를 소유하고 같은 캐릭터가 Avatar입니다. 네이티브 기반 클래스와 Actor Info 초기화가 준비되어 있다는 전제입니다.

자기ASC와적ASC의다른적용경로공격자ASC에서능력활성화를요청한다. 커밋성공시에구성한자기비용과쿨다운을자기ASC에적용한다. 성공분기의접촉시서버가대상을검증하고GE_Damage를검증한적ASC의Health에적용한다. 커밋실패는공격종료이며도식은모든효과의예측을보장하지않는다.공격자 · 자신의 ASC 영역서버의 대상 확정자신의 ASCOwner = AvatarGA_MeleeAttackCommit 결과 분기요청비용성공 후 접촉대상 검증대상 · 거리 · 권한GE_DamageInstant · Additive -20적의 ASCAttributeSet Health
자기ASC와적ASC의다른적용경로공격자ASC에서능력활성화를요청한다. 커밋성공시에구성한자기비용과쿨다운을자기ASC에적용한다. 성공분기의접촉시서버가대상을검증하고GE_Damage를검증한적ASC의Health에적용한다. 커밋실패는공격종료이며도식은모든효과의예측을보장하지않는다.자신의 ASCOwner = AvatarGA_MeleeAttackCommit 결과 분기활성 요청자기 적용서버 확정성공 후 접촉대상 검증대상 · 거리 · 권한GE_DamageInstant · Additive -20적의 ASCAttributeSet Health

점선 자기 적용은 성공한 커밋에서 구성한 비용·쿨다운을 자신의 ASC에 적용하는 관계입니다. 비용 없음인 연습에서는 이 경로에 적용할 Effect가 없습니다. 커밋 실패면 공격 실행을 종료합니다. 적의 피해는 접촉 이벤트에서 서버가 검증한 대상 ASC로 전달하며 Apply Gameplay Effect to Owner와 구별합니다.

접촉 이벤트와 종료 이벤트는 역할이 다릅니다

Wait Gameplay Event가 Event.AttackHit를 받는 경로에서 피해를 처리합니다. Montage Completed/Blend Out을 접촉 순간으로 쓰지 않습니다. 정상 완료·중단·취소 모두 End Ability와 필요한 정리에 연결합니다.

다른 설계에서는 ASC Owner와 Avatar가 다를 수 있습니다. 로컬 예측이 모든 효과의 예측을 보장하지 않으며 최종 피해는 서버가 검증합니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목자기 ASC와 적 ASC의 다른 적용 경로
관계·조건 설명공격자 ASC에서 능력 활성화를 요청한다. 커밋 성공 시에 구성한 자기 비용과 쿨다운을 자기 ASC에 적용한다. 성공 분기에서 접촉하면 서버가 대상을 검증하고 GE_Damage를 검증한 적 ASC의 Health에 적용한다. 커밋 실패 시에는 공격을 종료하며, 이 도식은 모든 효과의 예측을 보장하지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
자기 ASC Commit

자기 능력의 활성·비용·쿨다운을 검사한다.

  • 소유자: 자기 ASC
  • 조건: Commit 성공
  • Commit 실패는 대상 피해 경로로 진행하지 않는다.
대상 검증

접촉 이벤트에서 서버가 적 대상을 확인한다.

  • 권한: 서버
  • 대상: 적 ASC
  • 자기 비용 효과와 적의 피해 효과를 같은 소유 상태로 묶지 않는다.
효과·종료

적 Health에 -20을 적용하고 능력 자원을 정리한다.

  • 적 Health 변화: -20
  • 종료: 능력 정리
  • 중단·취소는 정상 접촉 피해와 독립 경로로 보존한다.

위 그림은 이 예제의 기본 공격에서 자신의 비용과 적의 피해가 서로 다른 ASC에 적용된다는 점을 보여 줍니다. 이 예제에서는 캐릭터가 ASC를 소유하고 같은 캐릭터가 아바타이므로, 그림의 공격자 영역에 Owner = Avatar라고 적혀 있습니다. 공격자의 ASC에서 GA_MeleeAttack의 활성화를 요청하고, 커밋이 성공하면 구성해 둔 자신의 비용과 쿨다운이 점선을 따라 자신의 ASC에 적용됩니다. 커밋이 성공한 뒤 접촉이 일어나면 서버가 대상, 거리, 권한을 검증하고, GE_Damage(Instant, Additive -20)를 검증한 적 ASC의 Health에 적용합니다. 그림의 Additive는 언리얼 엔진 5.8 에디터에서 Add (Base)로 표시되는 더하기 연산입니다.

그림의 주석은 세 가지를 짚습니다. 비용이 없는 연습에서는 자기 적용 경로에 적용할 이펙트가 없고, 커밋이 실패하면 공격 실행이 끝납니다. 적의 피해는 접촉 이벤트에서 서버가 검증한 대상 ASC로 보내며, 자기 자신에게 적용하는 Apply Gameplay Effect to Owner와 구별합니다. 또 접촉 이벤트와 종료 이벤트는 역할이 달라서, 피해는 Wait Gameplay Event가 Event.AttackHit를 받는 경로에서 처리하고 몽타주의 Completed나 Blend Out을 접촉 순간으로 쓰지 않습니다. 다른 설계에서는 Owner와 Avatar가 다를 수 있으며, 로컬 예측이 모든 효과의 예측을 뜻하지 않는다는 점도 함께 적혀 있습니다.

단계 그림은 이 흐름을 세 단계로 따라갑니다. 자기 ASC 커밋 단계에서 자신의 능력 활성화와 비용, 쿨다운을 검사하고, 대상 검증 단계에서 접촉 이벤트가 오면 서버가 적 대상을 확인하며, 효과와 종료 단계에서 적의 Health에 -20을 적용하고 능력의 자원을 정리합니다.

플러그인과 태그 준비하기

먼저 Edit > Plugins에서 Gameplay Abilities 플러그인을 찾아 활성화하고 에디터를 재시작합니다. C++ 기반 코드에서는 GameplayAbilities, GameplayTags, GameplayTasks 모듈의 의존성도 설정합니다. 그다음 Edit > Project Settings > Gameplay Tags에서 세 태그를 추가합니다. 능력을 식별할 Ability.Melee, 공격 중임을 나타낼 State.Attacking, 그리고 공격이 닿는 순간을 알리려고 직접 보낼 이벤트 태그 Event.AttackHit입니다.

C++ 기반 클래스가 갖춰야 할 것

BP_PlayerCharacter의 부모가 되는 C++ 기반 클래스에는 AbilitySystemComponent와, UAttributeSet을 상속한 AS_MyCharacterAttributes가 갖춰져 있어야 합니다. 이 속성 세트는 Health와 MaxHealth를 FGameplayAttributeData로 선언하고 둘 다 초깃값을 100으로 둡니다. 블루프린트의 일반 Float 변수는 게임플레이 이펙트의 Attribute로 고를 수 없다는 점이 이 전제가 필요한 이유입니다. 속성의 복제와 OnRep, ASC 등록, 그리고 체력이 0과 MaxHealth 사이를 벗어나지 않게 하는 범위 제한(PreAttributeChange, PostGameplayEffectExecute)도 C++에서 구현하고, UI는 이 값의 변경을 구독합니다. 속성 세트는 액터가 아닌 UObject이므로 Spawn Actor of Class로 만들지 않습니다.

액터 정보도 준비되어야 합니다. 이 예제는 캐릭터가 ASC를 소유하므로 소유자와 아바타를 모두 그 캐릭터로 초기화합니다. 서버에서 빙의할 때와 클라이언트에서 소유 정보가 준비될 때 C++ 기반 코드가 InitAbilityActorInfo를 호출하도록 하며, 이 글에 나오는 초기화 함수가 모두 블루프린트 노드로 노출되어 있다고 가정하지 않습니다. 또 IAbilitySystemInterface를 구현하거나 ASC 참조를 명시적으로 제공해, 능력을 쓸 캐릭터의 ASC를 찾을 수 있게 하고, 다시 빙의할 때 아바타 정보도 갱신합니다.

GA_MeleeAttack 만들기

콘텐츠 브라우저에서 Blueprint Class를 고르고 All Classes에서 GameplayAbility를 검색해 선택한 뒤, 이름을 GA_MeleeAttack으로 짓습니다. GA_MeleeAttack을 열어 Class Defaults에서 능력의 태그 항목에 Ability.Melee를 추가합니다. 언리얼 엔진 5.8에서 이 항목은 AssetTags (Default AbilityTags)라는 이름으로 표시되며, 이전 버전에서 Ability Tags라고 부르던 항목입니다.

비용과 쿨다운이 필요하다면 Cost Gameplay Effect Class와 Cooldown Gameplay Effect Class에 따로 만든 이펙트를 지정합니다. 비용 이펙트는 미리 준비한 마나 같은 자원 속성을 깎고, 쿨다운 이펙트는 지속 시간과, 그동안 능력을 막는 데 쓸 쿨다운 태그를 갖도록 구성합니다. 이 기본 공격에서는 둘 다 선택 사항으로 둡니다. 시간 숫자 하나를 적거나 입력 이름을 맞춘다고 비용과 쿨다운이 저절로 연결되지는 않습니다.

이제 이벤트 그래프에서 Activate Ability 이벤트를 구현합니다. 먼저 Commit Ability의 Boolean 결과로 분기해, 실패하면 취소로 끝내고 성공한 경우에만 공격을 진행합니다. 성공 경로에서는 Wait Gameplay Event로 Event.AttackHit 태그를 한 번 기다리도록 등록한 뒤, Play Montage and Wait에 공격 몽타주 AM_PlayerAttack을 연결합니다. 기본 Play Montage and Wait 노드에는 임의의 이벤트 이름을 받는 입력이 없으므로, 몽타주의 타격 노티파이를 처리하는 곳에서 Send Gameplay Event to Actor로 아바타에 Event.AttackHit를 직접 보내도록 구성합니다. 몽타주가 정상적으로 끝나면 End Ability를, 중단되거나 취소되면 취소 종료를 연결하고, 필요한 정리는 On End Ability에서 합니다.

타격 순간에 피해 적용하기

Wait Gameplay Event가 이벤트를 받는 경로에서 서버가 트레이스나 콜리전으로 유효한 적과 거리를 검증하고 대상 데이터를 만듭니다. 노티파이가 왔다는 사실이나 클라이언트가 보낸 대상만으로 피해를 확정하지 않습니다. 검증한 대상의 ASC에는 GE_Damage를 한 번 적용합니다. 대상 데이터가 필요하다면 Ability Target Data from Actor 같은 노드로 만들어 그 능력의 대상 적용 노드에 넘깁니다.

여기서 흔한 실수가 두 가지 있습니다. 몽타주의 On Completed나 On Blend Out은 애니메이션이 끝나거나 섞여 빠지는 시점이지 공격이 닿는 순간이 아니므로, 피해는 앞의 이벤트 경로에 두고 완료 경로는 능력 종료에만 씁니다. 또 Apply Gameplay Effect to Owner는 능력을 가진 자기 자신에게 적용하는 노드이므로, 적의 피해에 쓰면 공격자가 피해를 입습니다. 적에게 주는 피해와 자신에게 적용하는 비용 이펙트를 혼동하지 않습니다.

GE_Damage 만들기

Blueprint Class의 All Classes에서 GameplayEffect를 검색해 선택하고, 이름을 GE_Damage로 짓습니다. GE_Damage의 Class Defaults에서 Duration Policy를 Instant로 두어 즉시 적용되게 합니다. Modifiers의 + 버튼으로 수정자를 하나 추가하고, Attribute는 AS_MyCharacterAttributes의 Health, Modifier Op는 더하기 연산인 Add (Base)(열거값 AddBase, 예전 버전의 Additive)로 정합니다. 고정 피해 예제라면 Modifier Magnitude를 Scalable Float로 두고 -20.0을 입력합니다.

피해량을 능력에서 넘겨주려고 Modifier Magnitude를 Set by Caller로 바꾸는 경우에도, 지정한 데이터 태그로 음수 값 -20.0을 넘겨야 Add (Base) 연산이 체력을 줄입니다. 양수 20.0을 넘기면 체력이 20 늘어납니다. 설정을 마치면 컴파일하고 저장합니다.

캐릭터에 능력 부여하기

BP_PlayerCharacter를 열고, 서버에서 초기화가 끝나는 경로에서 GA_MeleeAttack을 레벨 1로 한 번 부여합니다. 준비한 C++ 기반 코드를 쓰거나, ASC에 노출된 Give Ability 노드를 씁니다. 공식 문서대로 능력은 서버만 부여하거나 회수할 수 있으므로 클라이언트에서 부여하지 않고, 다시 빙의할 때 같은 능력을 중복으로 부여하지 않게 합니다. 이 예제는 태그로 능력을 활성화하므로, 부여할 때 입력 태그 같은 별도 핀을 설정하지 않습니다. 프로젝트마다 입력 태그로 능력을 묶는 방식은 별도로 만드는 시스템입니다.

공격 입력 연결하기

언리얼 엔진 5.8에서는 향상된 입력(Enhanced Input)이 기본으로 켜져 있으므로, 공격 입력도 프로젝트 설정의 예전 Action Mappings 대신 이 방식으로 연결합니다. 콘텐츠 브라우저의 Input 분류에서 bool 값 타입의 입력 액션 IA_Attack을 만들고, 입력 매핑 컨텍스트 IMC_Default에 IA_Attack을 추가해 Left Mouse Button을 묶습니다. 이 매핑 컨텍스트는 플레이어 컨트롤러가 시작할 때 Enhanced Input Local Player Subsystem의 Add Mapping Context로 등록합니다.

그다음 플레이어 컨트롤러 PC_MyPlayer의 이벤트 그래프에 IA_Attack 입력 액션 이벤트를 만듭니다. 누르는 순간을 나타내는 Started 핀에서 Get Controlled Pawn으로 빙의한 캐릭터를 찾고, 유효한 캐릭터에서 ASC를 가져옵니다. 이 예제에서 ASC는 캐릭터에 있으므로 플레이어 컨트롤러 자신에게 ASC가 있다고 가정하지 않습니다. 그리고 ASC의 Try Activate Abilities By Tag를 호출하며, Gameplay Tag Container에 Ability.Melee를 넣고 Allow Remote Activation은 능력의 실행 정책에 맞게 정합니다. 이 노드에는 비용이나 쿨다운을 무시하는 입력이 없습니다. 반환값은 활성화를 시도했다는 뜻일 뿐, 그 뒤의 커밋이나 타격이 성공했다는 뜻은 아닙니다. 마지막으로 컴파일하고 저장합니다.


시험하고 디버깅하기

모든 블루프린트를 컴파일하고 저장한 뒤 게임을 플레이해, 왼쪽 마우스 버튼을 누르면 GA_MeleeAttack이 활성화되고 공격 애니메이션이 재생되는지 확인합니다. 콘솔에 ShowDebug AbilitySystem을 입력하면 GAS 관련 디버그 정보가 화면에 표시됩니다. 여기서 현재 ASC의 소유자와 아바타, 부여된 능력, 현재 태그, Health 값, 커밋의 성공과 실패를 구분해 확인하고, 단독 실행과 서버, 클라이언트 실행의 결과를 따로 기록합니다.

멀티플레이어에서 GAS를 쓸 때 자주 생기는 실수는 네 가지입니다. 클라이언트에서만 능력 성공을 처리하면 부정행위와 화면 불일치가 생깁니다. 비용과 쿨다운의 적용 순서를 혼동하면 비용이 부족해 실패했는데 쿨다운이 걸리거나, 반대로 쿨다운만 빠지는 버그가 생깁니다. 클라이언트의 예측 결과와 서버의 확정 결과가 다를 때 보정하지 않으면 순간이동이나 중복 타격처럼 보입니다. State.Stunned 같은 제약 태그를 한곳에서 관리하지 않으면 함께 쓰면 안 되는 스킬이 동시에 실행되는 버그가 잦아집니다.

그래서 점검은 순서를 정해 진행합니다. 먼저 단독 플레이에서 능력, 이펙트, 속성 세트의 흐름이 의도대로 동작하는지 확인합니다. 다음으로 리슨 서버 2인 테스트에서 입력, 실행, 복제의 로그를 확인하고, 네트워크 에뮬레이션으로 패킷 지연을 준 환경에서 예측과 보정이 자연스러운지 점검합니다. 마지막으로 ShowDebug AbilitySystem과 stat net을 함께 켜고 병목이나 불일치가 생기는 구간을 추적합니다.


GAS를 도입할지 판단하기

GAS의 장점은 분명합니다. 각 능력을 독립적으로 설계하고 시험할 수 있고, 복제 로직이 프레임워크 안에 통합되어 있어 멀티플레이어 개발이 수월하며, 속성과 이펙트로 복잡한 스탯과 버프, 디버프를 일관되게 관리하고, 많은 설정을 데이터로 다루어 개발 효율을 높입니다.

반면 배우기 어렵고 초기 설정이 복잡합니다. 이 글의 예제만 해도 C++ 기반 클래스와 속성 세트가 먼저 있어야 했습니다. 소규모 프로젝트나 능력이 몇 개뿐인 게임이라면 이 비용이 얻는 것보다 클 수 있습니다. 여러 능력이 같은 비용, 쿨다운, 상태 규칙을 공유하고 이를 멀티플레이어에서도 관리해야 한다면 GAS 도입을 검토하고, 그렇지 않다면 2장과 4장의 함수, 이벤트, 인터페이스로 만드는 편이 나을 수 있습니다.


정리

  • GAS는 능력, 이펙트, 속성, 태그로 스킬과 상태를 모듈화하는 프레임워크이며, 여러 능력의 비용, 쿨다운, 상태 규칙을 함께 관리할 때 검토합니다.
  • ASC는 능력과 이펙트를 처리하는 중심 컴포넌트이고, 소유자 액터와 아바타 액터는 다를 수 있습니다. 속성 세트는 공식 문서의 예처럼 C++로 정의합니다.
  • 이펙트의 지속 방식은 Instant, Has Duration, Infinite이며, 주기 적용은 Period 설정으로 만듭니다.
  • 능력은 서버에서 부여하고, Commit Ability의 결과로 분기하며, 모든 경로가 End Ability에 도달해야 합니다.
  • 피해는 접촉 이벤트에서 서버가 검증한 적의 ASC에 적용하고, 몽타주 완료 시점이나 Apply Gameplay Effect to Owner와 혼동하지 않습니다.
  • 언리얼 엔진 5.8에서는 향상된 입력으로 공격 입력을 만들고, Try Activate Abilities By Tag로 태그에 맞는 능력을 활성화합니다.

연습 문제

1. 공격 뒤의 두 Health

준비된 C++ 기반에서 공격자의 Health는 70, 적의 Health는 95입니다. 이 경우 비용과 쿨다운 이펙트는 지정하지 않았고, 다른 효과나 피해 경로도 없습니다. Commit Ability가 성공하고, 서버가 검증한 적의 ASC에 본문의 Instant, Add (Base) -20 이펙트를 한 번 적용했다면 두 Health는 각각 얼마일까요? 다른 시도에서 Commit Ability가 실패했다면 몽타주와 피해를 계속 진행할까요?

해설 보기

적의 Health는 95 + (-20) = 75가 되고, 공격자는 70 그대로입니다. 이 연습에는 비용과 다른 효과가 없으므로 공격자의 값이 바뀔 이유가 없습니다. 만약 공격자의 Health가 줄었다면 GE_Damage를 자신의 ASC에 적용한 것이므로 대상이 잘못된 것입니다.

커밋이 실패하면 공격을 끝내는 경로로 가므로 몽타주와 피해를 실행하지 않습니다. 또 활성화 요청의 반환값이 참이었거나 애니메이션이 화면에 보였다는 사실만으로, 서버에서 적의 Health가 줄었다고 확정할 수는 없습니다. 실제 게임에서는 대상 속성 세트의 범위 제한과, 복제로 받은 값을 확인합니다.

2. 체력이 오히려 늘어나는 피해

GE_Damage의 Modifier Magnitude를 Set by Caller로 바꾸고, 능력에서 데이터 태그로 피해량 20.0을 넘겼습니다. 적의 Health가 95였다면 이펙트를 적용한 뒤 얼마가 될까요? 왜 그럴까요?

해설 보기

115가 됩니다. 수정자의 연산이 Add (Base)이므로 넘겨받은 값을 그대로 더하기 때문에, 양수 20을 넘기면 체력이 20 늘어납니다. 피해를 주려면 -20.0을 넘겨야 95 + (-20) = 75가 됩니다.

다만 MaxHealth가 100이고 속성 세트에서 범위를 제한하도록 구현했다면 115가 아니라 100에서 멈출 수 있습니다. 어느 쪽이든 이 결과는 피해가 아니라 회복이므로, 넘기는 값의 부호를 확인해야 합니다.

3. 몽타주 완료에 붙인 피해

어떤 개발자가 Play Montage and Wait의 On Completed 핀에 피해 적용을 연결했습니다. 공격 몽타주가 1초 길이이고, 칼이 닿는 장면은 0.4초 지점입니다. 어떤 문제가 생길까요? 공격 도중 플레이어가 맞아 몽타주가 중단된다면 어떻게 될까요?

해설 보기

피해가 칼이 닿는 0.4초가 아니라 몽타주가 끝나는 1초 무렵에 들어가므로, 화면의 타격과 피해 시점이 어긋납니다. 그 사이에 적이 사거리 밖으로 벗어나도 피해가 들어갈 수 있습니다.

몽타주가 중단되면 On Completed가 아니라 중단이나 취소 쪽 핀이 실행되므로 피해가 아예 들어가지 않습니다. 반대로 개발자가 이를 고치려고 중단 핀에도 피해를 붙이면, 칼이 닿지도 않았는데 피해가 들어갑니다. 피해는 0.4초 지점의 노티파이에서 Send Gameplay Event to Actor로 보낸 Event.AttackHit를 Wait Gameplay Event가 받는 경로에서 서버가 검증한 뒤 적용하고, 몽타주의 완료와 중단 핀은 능력 종료에만 씁니다.

능력, 이펙트, 속성 세트의 자세한 설정은 언리얼 엔진 공식 문서의 게임플레이 능력 시스템과 그 하위 문서인 게임플레이 능력 사용, 속성과 속성 세트에서 확인할 수 있습니다.

이것으로 9장을 마칩니다. 9장에서는 게임 모드와 두 스테이트로 규칙과 상태를 나누고, 플레이어 컨트롤러와 폰으로 입력과 행동을 나눈 뒤, 리플리케이션으로 서버가 판단한 결과를 클라이언트에 맞추는 방법을 익혔습니다. 이어 체크포인트와 세이브로 진행 상황을 레벨과 실행을 넘어 이어 가게 하고, 게임플레이 능력 시스템으로 비용과 쿨다운이 있는 능력을 구성했습니다. 다음 장의 물리 시뮬레이션 기본 제어부터는 물리 시뮬레이션과 콜리전, 트레이싱, 날씨와 시간, 동적인 환경 상호작용을 다루고, 마지막으로 블루프린트의 성능을 측정하고 최적화하는 방법을 살펴봅니다.