객체 지향적 설계 (책임 주도 설계)

협력에 필요한 책임을 결정하고 객체에게 책임을 할당하자 
( <-> 협력 대상 )

 

스킬 컴포넌트

ㄴ 캐릭터가 가진 스킬 목록 보유 ( 새로운 스킬을 넣었다 뺐다 할 것이 아니기 때문에 컨테이너가 아닌 프로퍼티로 )

ㄴ 키보드 입력 관련 처리 ( <-> 캐릭터의 InputComponent를 참조해서 동작과 Bind 해줘야 한다.  )

ㄴ 키보드 입력, 활성화 상태에 따라 스킬 시전 ( <-> Skill )

 

스킬

스킬이 가져야 하는 공통 속성

ㄴ 활성화 상태 (스킬이 비활성화 상태인 경우에는 입력을 받아도 사용이 되지 않아야 함)

ㄴ 쿨타임 

ㄴ 지속시간

 

스킬이 가져야 하는 공통 행동

ㄴ 스킬 시전하기

ㄴ 스킬 멈추기

 

만들고자 하는 스킬의 능력 (스킬 상속 받기)

1. 슬로우

ㄴ 카메라 효과 조정 (<-> Camera 혹은 CineCamera 흑백 효과)

ㄴ 게임 시간 느리게? 만들기 (<-> Timer Manager 모든 플레이어에게 적용)

 

2. 이동기

ㄴ 캐릭터가 움직이는 방향으로 순간적으로 도약 ( ? )

ㄴ 캐릭터 이펙트 적용 (잔상 효과를 주고 싶다) ( 메시 컴포넌트 -> 머터리얼 ? )

 

캐릭터 컴포넌트
ㄴ 캡슐 컴포넌트 (루트 컴포넌트)
  ㄴ 무기 컴포넌트
ㄴ 스킬 컴포넌트

 

스킬 컴포넌트 클래스는 보유한 스킬들을 단순히 관리하는 컴포넌트이다. 기능만 필요할 뿐 계층 구조에 따른 위치 정보가 필요하지 않기 때문에 Actor Component 클래스를 상속하도록 한다.

 

계획 수정..

내가 만들고자 하는 기능 제작을 도와주는 템플릿을 Gameplay Ability System 플러그인에서 제공하기 때문에 새로 Skill Component를 만들지 않고 플러그인을 활용하도록 한다. 플러그인 사용 방법을 익히면서 내가 생각한 설계 방법과 다른 점이 있는지 살펴 보는 것이 더 낫다고 판단했다.

 

게임플레이 어빌리티 시스템

https://docs.unrealengine.com/5.0/en-US/gameplay-ability-system-for-unreal-engine/

 

Gameplay Ability System

High-level view of the Gameplay Ability System

docs.unrealengine.com

https://www.youtube.com/watch?v=YvXvWa6vbAA 

언리얼 공식 유튜브 채널에서 소개하는 어빌리티 시스템

https://www.youtube.com/watch?app=desktop&v=dLbWVQajnfk 

언리얼 공식 영상은 아니지만 설명이 잘 되어 있음

 

https://github.com/tranek/GASDocumentation#concepts-a

 

GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer s

My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project. - GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's Gamepl...

github.com

 

GameplayAbility System과 interact하고 싶은 액터는 스스로 Ability System 컴포넌트를 가지거나 그 컴포넌트를 가진 다른 액터에 접근하면 된다.

 

이 액터는 캐릭터, 플레이어 스테이트, 플레이어 컨트롤러가 될 수 있는데, 내가 만드는 게임에서는 캐릭터가 죽고 리스폰하는 상황이 발생하기 때문에 캐릭터에 이 컴포넌트를 추가하게 된다면 죽을 때마다 능력에 대한 정보가 사라지게 된다. 캐릭터가 아닌 액터를 사용해야 한다.

 

기존에 BullettimePlayerState에서 각 플레이어의 킬/데스 정보를 기록했으므로 이 액터에 Ability System을 상속받도록 하고, 킬/데스 정보 관리를 통합시킬 생각이다. 또한 Gameplay Ability는 비동기적으로 캐릭터 애니메이션이나 파티클/사운드 이펙트 처리, 사용자 입력, Replication 기능도 제공한다고 하니, 여기저기에 만들어 놓은 기능들을 천천히 옮겨야겠다.

 

1. IAbilitySystemInterface 인터페이스를 상속하여 GetAbilitySystemComponent를 오버라이드 해야 한다.

ㄴ Getter 함수를 만들어 줘야 한다는 뜻이다.

 

2. 액터가 Ability를 사용하기 위해서는 Ability System 컴포넌트가 해당 Ability를 승인해야 한다.

ㄴ 어빌리티 시스템은 액터에게 승인해준 어빌리티의 리스트를 들고 있다.

ㄴ 어빌리티 시스템은 Attribute도 들고 있다.

 

3. 어빌리티는 FGameplayAbilitySpec 구조체로 정의되는데, 어빌리티 시스템 컴포넌트의 GiveAbility함수 호출을 통해 승인된다. 이 함수는 FGameplayAbilitySpecHandle을 반환하는데 이 구조체를 통해 어빌리티 기능을 취소시킬 수 있다.

어빌리티의 실행 주기

ㄴ CanActivateAbility : 실행 전에 실행 가능한 상태인지 확인한다.

ㄴ CallActivateAbility : 실행 가능한 상태인지 확인하지 않고 어빌리티 코드를 실행한다.ActivateAbility를 오버라이드 하여 커스텀 기능을 추가해야 한다.

Actor나 컴포넌트에서 사용하는 tick 함수 같은 처리 방식이 아닌 task를 사용하여 작업을 처리한다. Task들의 결과를 핸들링하기 위해 C++에서는 델리게이트를 사용하고, 블루프린트에서는 노드를 output execution pin에 연결하여 처리한다.

CommitAbility 함수는 Ability를 실행하는데 필요한 비용(마나, 쿨타임 적용 등)을 적용하는 함수이다.CancelAbility 함수는 Ability를 취소하는 함수이다. CommitAbility와 다르게 외부에서 호출가능하고 OnGamePlayAbilityCancelled를 오버라이딩하여 캔슬되었을 경우에 처리할 로직을 추가할 수 있다.

ㄴ TryActivateAbilityCanActivateAbility + CallActivateAbility

어빌리티 실행이 끝난 후에는 EndAbility를 호출해주어야 한다. (그렇지 않으면 어빌리티가 계속 동작하는 것으로 인식해서 미래에 이 어빌리티를 사용하지 못하거나 이 어빌리티가 막고있는 다른 어빌리티를 사용하지 못하게 된다.) 

 

4. 태그

ㄴ 어빌리티 사이에 어떻게 상호작용할지 결정하는데 사용된다.

ㄴ 각각의 어빌리티는 태그의 집합을 가진다.

ㄴ 태그는 계층 구조로 이뤄져 있다.

Cancel : 이 어빌리티가 실행되는 동안 이미 실행되고 있던 다른 어빌리티의 실행을 취소한다.

Block : 이 어빌리티가 실행되는 동안 태그가 매칭되는 다른 어빌리티들의 실행을 막는다.

 

5. 어빌리티 시스템 컴포넌트는 다양한 방법으로 어빌리티를 활성화할 수 있도록 다양한 함수들을 갖고 있다.

Input code

tag

AbilitySpecHandles

 

6. AttributeSet, Attribute

ㄴ UAttributeSet을 상속받는 커스텀 AttributeSet클래스를 정의한다. 그리고 어빌리티 시스템 컴포넌트를 가진 액터의 프로퍼티로써 위치하도록 한다.

ㄴ 액터의 BeginPlay에서 어빌리티 시스템 컴포넌트->GetSet<커스텀AttributeSet> 함수를 호출하여 AttributeSet을 초기화한다.

ㄴ 각각의 Attribute는 FGameplayAttributeData 구조체로 정의된다. (UPROPERTY로 정의해야 리플렉션 시스템에 보인다)

ㄴ 각각의 Attribute들에 대해 Getter와 Setter를 빠르게 만들 수 있는 매크로가 있다. +레퍼런스를 반환하는 Property Getter

ㄴ AttributeSet은 Attribute이 바뀌었을때 실행되는 PostGameplayEffectExecute 콜백 함수를 오버라이딩하면 편하다. 내부에서 어떤 속성이 바뀌었는지 확인하여 상황에 맞는 로직을 실행하도록 만들 수 있다.

ㄴ 체력이 최소 체력과 최대 체력을 벗어나지 않도록 하는 로직을 사용한다면 여기에 구현할 수 있다.

ㄴ 인게임 리액션을 발동시키는 로직을 사용한다면 여기에 구현할 수 있다.(캐릭터의 체력이 0이 되었을 때 캐릭터를 죽게하도록)

ㄴ 속성의 Default 값들을 설정하는데 데이터 테이블 에셋을 사용할 수 있다. 액터 블루프린트의 어빌리티 시스템 컴포넌트에서 설정 가능하다. 데이터 테이블은 AttributeMetaData 구조체로 만들어야 한다.

ㄴ 데이터 테이블에서 Row Name을 [AttributeSet 클래스 이름].속성 으로 지정한다.

ㄴ Attribute는 기준 값과 현재 값을 주시한다.

 

+ 주의할 점

AttributeSet과 Attribute는 블루프린트에서 생성될 수 없다 C++에서만 가능하다.

다른 Attribute에서 SetAttribute를 호출하지 말고 Gameplay effect System을 사용하자. (캡슐화)

어빌리티 시스템 컴포넌트는 최대한 높은 레벨의 액터에 붙여주자 (내 프로젝트의 경우 캐릭터 < 플레이어 스테이트)

AttributeSet을 아무데나 붙이지 말고 AbilitySystemComponent를 가진 액터에만 붙이자

 

7. GameplayEffects

ㄴ Attribute를 수정하는 역할을 한다.

ㄴ 지속시간 (즉발, 무한, 유한)을 갖는다.

ㄴ 블루프린트 에디터에서 지속시간, 적용 값 등을 조정할 수 있다.

ㄴ UGameplayEffectExecutionCalculation 클래스를 상속받는 커스텀 클래스 (예를 들면 DamageExecution 클래스)를 정의하고 Execute_Implementation 함수를 구현해주어야 한다.

ㄴ 이 함수의 내부에서는 인자를 통해 이펙트의 source와 target에 대한 액터, 태그 정보를 얻을 수 있고 그 정보들로 값을 계산한다.

ㄴ 계산한 값을 AddOutputModifer를 호출함으로써 타깃의 속성 값에 연산한다.

 

블루프린트에서 이펙트를 적용하는 방법

ApplyGameEffectToTarget에 소스, 타겟 어빌리티 시스템 컴포넌트를 넘겨주어 양쪽에 효과를 적용시킨다

ApplyGameEffectToSelf는 소스 어빌리티 시스템 컴포넌트를 넘겨주어 한 쪽에만 효과를 적용시킨다.

 

Effect의 주요 프로퍼티

ㄴ Duration

지속기간

ㄴ Modifiers and Executions

attribute를 어떻게 수정할 것인지, 어떤 계산식을 사용할 것인지

ㄴ Application Requirements

이펙트를 적용하는 조건 (어떤 태그가 있거나, 없거나, 무작위)을 정한다.

ㄴ Granted Abilities

액터에게 어빌리티를 부여

ㄴ Stacking

이미 버프나 디버프를 가진 타깃에 다시 적용할때와 같은 상황에서 어떤 정책을 사용하여 처리할 것인지

ㄴ Gameplay Cue Display

비주얼 이펙트 네트워크 친화적으로 다룰 수 있게 해줌

 

8. Gameplaycue

ㄴ 각각의 비주얼 이펙트가 Cue (GameplayCueNotifyStatic 혹은 GameplayCueNotifyActor 상속) 에 대응됨, 각각 태그를 설정함 (Static은 한번 실행되고 사라지는 이펙트에, Actor는 실제로 월드에 생성되어 액터처럼 다뤄짐)

ㄴ 액터큐의 경우 캐릭터에 attatch 하거나 인스턴스 복제관련 속성이 추가적으로 존재한다.

ㄴ 이펙트가 진행되는 동안 visual effect를 트리거하기 위해 사용

ㄴ Effect의 Display 속성에서 GameplayCue 태그를 설정해 두면 이펙트가 적용될 때 해당 태그를 가진 큐를 적용시킨다.

 

9. Ability 블루프린트와 GameplayAbilityTask

ㄴ 여러가지 종류의 Task가 존재한다. (Move to Location, Wait Gameplay Event, Wait for Confirm Input, Wait Delay 등)

ㄴ GameplayAbility Task는 Ability를 실행시키는 가장 대중적인 방법이다.

ㄴ Abiltiy task는 ability 블루프린트에서 노드로 표현된다. 

ㄴ Task 노드는 여러 개의 실행 경로를 가진다.

ㄴ 어빌리티가 활성화 되어 있는 동안 특정 유형의 태그가 있는 다른 능력이 발생하지 않도록 차단할 수 있다.

ㄴ 커스텀 task도 만들 수 있다.

ㄴ CommitAbility 노드는 AbilitySystemComponent에게 ability의 비용을 적용하라고 알려주는 역할이다.

ㄴ Wait Gameplay Event 노드를 생성하면 태그가 일치하는 이벤트를 기다린다 (listen). Event가 Received 되었을 때 이펙트 적용되도록 연결하여 이펙트를 처리할 수 있다. 

ㄴ 이벤트는 캐릭터나 플레이어 스테이트의 Send Gameplay Event to Actor 노드를 실행할 경우 전달된다.

ㄴ Apply Root Motion Jump Force 노드는 캐릭터 무브먼트 컴포넌트를 멈추고 이 노드를 대신 실행하게 한다. ability 실행 도중 캐릭터의 움직임을 제어할 수 있다.

ㄴ ApplyGameEffectToTarget 노드는 타겟을 Target Data라는 액터 배열을 인자로 받아 여러 타겟에 대해 이펙트를 일으킬 수 있다.

 

10. 레플리케이션

ㄴ 어빌리티의 내부 상태와 Gameplay Event의 레플리케이션을 지원한다.

ㄴ 어빌리티에는 멀티 플레이어 게임에서 어빌리티를 어떻게 레플리케이트 할 것인지에 대한 정책이 있다.

Local Predicted

ㄴ 서버가 클라이언트를 믿어줄 것이라 생각하고 클라이언트가 즉시 어빌리티를 실행, 빠른 반응성과 정확도

Local Only

ㄴ 클라이언트에서만 어빌리티가 실행됨

Server Initiated

ㄴ 서버에서 어빌리티가 실행되고 클라이언트로 전파되는 방식, 빠른 액션의 어빌리티는 Local Predicted 만큼은 부드럽지 않을 수 있다.

Server Only

ㄴ 서버에서만 실행되고 레플리케이트되지 않는 어빌리티, 어빌리티가 server-authoritative data에만 영향을 주도록 하고, 이 data들은 client로 replicate 되도록 한다.

 

11. Instancing Policy

ㄴ 어빌리티가 실행될 때마다 새로운 객체가 생성되기 떄문에, 어빌리티의 실행이 잦은 경우에 성능적인 문제가 생길 수 있다. 사용자가 기능성과 성능 사이를 타협할 수 있도록 생성 정책이 있다.

Instanced per Execution

ㄴ 실행할 때마다 객체 스폰, 블루프린트 그래프 사용 가능, 모든 멤버 변수를 기본값으로 초기화 & 접근 가능, 오버헤드가 크기 때문에 자주 사용하지 않는 어빌리티에 적합함

ㄴ 롤로 치면 평타는 부적합, 궁극기는 적합

Instanced per Actor

ㄴ 어빌리티가 처음 실행될 때 액터가 어빌리티의 인스턴스를 하나 생성하고 미래의 실행에서는 이것을 재활용

ㄴ 실행 사이에 멤버 변수의 값 정리가 요구된다.

ㄴ 레플리케이션에 이상적인 방법

Non-Instanced

ㄴ 가장 효율적으로 동작하는 정책

ㄴ 인스턴스를 새로 생성하지 않고 CDO를 사용한다.

ㄴ 레플리케이션, RPC 안됨

ㄴ 어빌리티가 C++로 작성되어야 한다.

ㄴ 어빌리티 실행 도중 멤버 변수 값을 변경할 수 없다.

ㄴ 자주 사용되고 많은 캐릭터에 의해 사용되는 어빌리티에 적합하다.

 

+ Recent posts