전송 지연 시간 (Latency)

  • 두 기기 간에 데이터를 최소량 전송할 때 걸리는 시간 (ms)
  • 반응을 시작하기까지의 지연 시간
  • 네트워크 품질을 결정하는 기준 3가지 중 하나
    • Packet Loss : 패킷을 잃어버린 것 (데이터 유실)
    • 전송 속도 : 두 기기간에 초당 전송될 수 있는 최대 데이터 양 (BitPerSecond, Bytes/Second)
    • 전송 지연 시간 (Latency)

Latency Vs Throughput

  • Lateny : 1개의 요청/작업에 걸리는 시간 (ms)
  • Throughput : 일정 시간동안 처리 가능한 요청의 수
    • 초당 100개의 요청 처리 가능

Latency Vs Response Time vs Processing Time

  • Lateny : 반응이 시작되기까지의 지연
  • Response Time : 요청 전체가 완료될 때까지의 시간 (Latency 포함)
  • Processing Time : 실제 로직 수행에 걸린 시간 (I/O 제외)

Latency 종류

  • Network : 네트워크를 통해 데이터가 왕복에 걸리는 시간
    • 클라이언트 ↔ 서버간의 Ping
  • Input : 사용자 입력이 시스템 반응으로 이어지기까지의 시간
    • 마우스 클릭 후 반응 시간
  • Render : GPU가 렌더링을 시작하고 끝낼 때까지의 걸리는 시간
    • 프레임 렌더링 시간
  • Application : 어플리케이션에서 내부 로직 수행에 걸리는 시간
    • DB 쿼리, 알고리즘 실행 시간
  • Disk : 디스크에서 데이터를 읽고 쓰는데 걸리는 시간
    • SSD vs HDD 응답 시간
  • Audio : 입력 음성이 스피커로 출력되는데까지의 걸리는 시간
    • 보이스 채팅 딜레이

Latency 발생 원인

  • 네트워크 : 거리, 라우터/스위치 수, DNS 지연, 방화벽 검사
  • 하드웨어 : 디스크 접근, CPU 컨텍스트 스위칭, 캐시 미스
  • 소프트웨어 : 알고리즘 복잡도, 동기화, 불필요한 I/O
  • 스레드/락 : Mutex, Semaphore, Deadlock 대기 시간
  • GC : 언어 런타임의 메모리 정리로 인한 멈춤]

측정 방법

  • End - to - End
    • 클라이언트로부터 응답까지 전체 흐름 측정
    • Start Timer → 요청 전송 → 응답 수신 → Stop Timer
    • Latency - Stop - Start
  • BreakDown (구간별)
    • 네트워크 전송 시간
      • Ping, Traceroute
    • 서버 처리 시간
      • 백엔드 로그, APM 툴 (New Relic, Datadog 등)
    • DB 지연
      • 쿼리 프로파일링 (EXPLAIN, QueryTime)
    • 렌더링 지연
      • GPU 프로파일러, 프레임 분석

메트릭 지표

  • Average : 전체 요청의 평균 지연 시간
  • P95/P99 : 95%, 99% 요청이 이 시간 이내에 응답됨
  • Max : 가장 오래 걸린 요청 시간
  • Jitter(지터) : Latency의 변동 폭 (불규칙성)

Latency 최적화 전략

  • 네트워크 : CDN, 지역별 서버 배치, 압축
  • 서버 처리 : 비동기 처리, 캐싱, 경량화
  • DB : 쿼리 튜닝, 인덱싱, 메모리 캐시 (Redis)
  • 멀티 스레딩 : 락 최소화, 스레드 풀 사용
  • 게임/렌더링 : Prediction, Interpolation, Frame Delay 최소화
  • 분산 시스템 : RPC 최적화, 데이터 파티셔닝, Circuit Breaker 패턴 등

Latency 공식

  • L = λ × W
  • L : 시스템 내 평균 요청 수 (Queue 길이)
  • λ : 요청 도착률 (Throughput)
  • W : 평균 응답 시간 (Latency)

Latency 측정법 : 라운드 트립 레이턴시 (Round Trip Latency)

  1. 기기 A에서 기기 B에 패킷 전송
  2. 기기 B는 이를 수신, A에 패킷 전송
  3. A에서 1번과정의 시간과 현재 시간의 차이를 구하여 2로 나눈다.
    1. 현재시간 - (A →B전송시간 / B→A 전송시간)

IOCP 내 패킷 유실시 UDP 및 TPC에서의 현상

  • UDP : 패킷 유실 발생시 데이터 그램 자체가 유실
    • Ex) 1만 바이트의 데이터그램을 UDP로 전송시 1300바이트의 패킷으로 분할하여 전송
    • 해당 패킷 1개를 유실시 1만바이트 데이터그램 전체 유실
    • 레이턴시 : 네트워크 기기의 레이턴시
  • TCP 패킷 유실 발생시 유실된 패킷에 대한 Ack가 도착하지 않는다.
    • Ack가 올때까지 대기 후 오지않으면 다시 패킷을 전송
    • 3Hand - 4Hand Shake
    • 지연시간이 발생하지만, 데이터를 송/수신 처리한다. (신뢰성)
    • 레이턴시 : 네트워크 기기(라우터,스위치 등) 레이턴시 + 패킷 유실률 * 재전송 대기 시간

언리얼내의 추측항법을 통한 레이턴시

  • 추측항법
    • 다른 캐릭터의 위치 정보를 이미 지난 약간의 시간만큼 예측 하는 것
    • 상대방 움직임을 어느정도 예상해서 그 위치로 갈 수 있게 보정시키는 방법
    • 두 기기간의 레이턴시를 파악하는 것
    • 언리얼의 CharacterMovementComponent는 이동 예측 + 보정을 자동으로 수행한다. (추측 항법)
      • 높은 핑 환경에서의 예측이 어긋나고 서버 보정시 순간이동처럼 보이는 현상

RPC (Remote Procedure Call)의 레이턴시 특성

  • Client → Server (RPC Server)
    • 입력 전달, 행동 요청
    • 클라이언트 → 서버 RTT만큼 지연
  • Server → Client (RPC Client)
    • 피드백, 애니메이션 트리거
    • 지연 + 클라이언트 처리 순서 문제
  • Multicast (RPC Multicast)
    • 모든 클라이언트에 일괄 동기화
    • RTT * N, 부하 높음

레이턴시 최소화 방법

  1. 불필요한 TCP 대신 UDP로 통신하기
    1. 언리얼 엔진은 이미 다른 방식의 IOCP 형태로 통신중 (엔진 내부적으로 최적화가 진행되어 있음)
    2. 언리얼의 Reliable → UnReliable 리팩토링
  2. 가급적 적은 수의 패킷 전송
  3. 클라이언트와 서버 간 통신과 클라이언트끼리 직접 통신을 섞어쓰기
    1. C/S네트워킹 + P2P 네트워킹

 


출처 및 참고내역

 

멀티플레이어 게임 프로그래밍(도서) - https://www.yes24.com/product/goods/38868446

게임 서버 프로그래밍 교과서(도서) - https://www.yes24.com/Product/Goods/71768958

 

개요

코딩테스트 집중 시간 중 튜터님께서 언급하셨던 네트워크 효율적인 트래픽 관리를 위해 사용하는 알고리즘 중 하나입니다.

Rate Limiting 방식을 사용하여 클라이언트의 요청에 대한 서버의 트래픽 제어 방법이 있으며,

이번 포스팅에서는 Rate Limiting 관련 알고리즘 중 Token Bucket 알고리즘에 대해 알아보겠습니다.

 

Rate Limiting / Limiter

  • Rate Limit
    • 일정 시간동안 허용되는 요청의 수 (1분에 100개 요청 가능 등.)
  • Rate Limiter
    • 실제로 적용하고 감시하는 소프트웨어 또는 기능 (알고리즘 등)
  • Rate Limiting
    • 클라이언트의 요청 빈도를 제한하는 행위.
    • 개념, 정책 및 적용 등까지 포함된 언어

정의

  • API의 요청 제한 혹은 트래픽 제어를 위해 나온 개념
  • 네트워크 또는 서비스의 자원을 보호, 안정성을 유지하기위한 메커니즘

특징

  • 서버 API가 과도한 요청으로부터 다운되거나 성능이 저하되는것을 방지
  • 악의적인 공격 (DDos, API Abuse) 차단
  • 서비스 자원을 공평하게 분배
  • 사용량 기반 요금제 또는 제한을 관리하기 위함 (OpenAI API, TwitterAPI 등)

장점

  • 백엔드 서버의 부하를 방지함으로써 자원을 보호한다.
  • DDos 또는 Abuse User등에 대한 차단 가능. 보안 강화
  • 고정 사용량 유저의 성능 안정화 (Qos 유지)
  • 유료/무료 사용자 제한 구현 가능 (요금제 차별화)
  • Token Bucket 등으로 일시적인 과부화 흡수 가능

단점

  • 한계 도달시 정당한 사용자도 차단될 가능성이 있음.
  • 슬라이딩 윈도우, 토큰 버킷등의 구현복잡성 존재
  • 다중 서버 환경에서는 상태 공유가 필요하다.
    • 분산 환경에서의 동기화 어려움
  • IP변경, API Key 재발급 등을 통한 우회
    • 별도의 IP 또는 API Key재발급시 검증을 통한 시간당 요청 횟수 제한 설정 필요

게임 서버에서의 Rate Limit 적용 예시

  • 로그인 서버
    • DDos 및 접속 폭주 방지
    • 우선순위 기반 대기열 (Queue)를 사용
    • IP/계정별 로그인 시도 횟수 제한
    • 토큰 발급 지연
      • 요청이 허용된 경우에만 세션 토큰 발급
      • Ex) ID, PW등의 DB내 데이터 비교를 통한 요청
  • 매치메이킹 요청
    • 유저가 자주 매칭을 요청하거나 취소하는 경우
    • 일정시간 내 매칭 요청/취소 횟수 제한시 패널티 부여
    • 매칭 서버 부하 감소 및 매칭 시스템 안정화 가능

 

 

Token Bocket

https://www.geeksforgeeks.org/token-bucket-algorithm/

정의

  • 일정한 속도로 토큰을 생성하여 버킷에 저장
  • 요청마다 해당 토큰을 소비하여 요청을 허용하거나 제한하는 방식
    • 토큰이 있는 경우 - 응답시 요청 수락 응답
    • 토큰이 없는 경우 - 응답시 요청 거절 또는 지연 응답

Bucket (Structure)

  • 일정 개수의 토큰을 담는 저장소 (Bucket)
  • 버킷이 담을 수 있는 최대 토큰수 보유 (BucketCapacity)
  • 일정 주기로 토큰이 생성되는 속도 보유 (TokenRate)
  • 요청을 처리할 수 있는 권한 단위 (Token)

동작 원리

  1. 시스템이 실행되는 경우, 토큰이 일정 속도로 버킷에 추가된다.
  2. 요청이 들어오는 경우
    1. 토큰이 있는 경우 - 토큰을 1개 소비, 요청 허용
    2. 토큰이 없는 경우 - 요청을 거절 또는 큐에 삽입하여 지연 처리
  3. 버킷은 최대 용량 이상의 토큰을 저장하지 못한다.
    1. 넘치는 토큰은 버려진다.
  4. 토큰은 시간에 따라 누적이 가능하여 버스트 트래픽에도 일정량 허용이 가능하다.

특징

  • 시간 기반 제어 : 초당 몇개 요청 허용 등 시간 단위로 요청 제어
  • 버스트 허용 가능 : 짧은 시간동안 요청이 들어와도 처리 가능
  • 유연성 : 속도제한 + 버스트 처리 모두 지원 가능
  • 간단한 구조 : 상태는 현재 토큰수를 통한 관리 가능

장점

  • 버스트 처리 지원 : 갑작스러운 트래픽에도 탄력적
  • 정상 유저 보호 : 어뷰징 유저 요청차단 가능
  • Qos 제어 : 초당 요청 수 제한 설정으로 서비스 품질 유지
  • 간단한 구현 : 알고리즘 자체가 직관적
  • 유연한 정책 설정 : 시간, 사용자, IP 단위 등 적용 가능

단점

  • 정확한 간격 제어 어려움
    • 일정 요청 간격 유지에는 부적합하다.
    • 이러한 경우에는 Leaky Bucket이 적합하다.
  • 상태 관리 필요
    • 사용자별 토큰 상태 저장이 필요하다. (Redis, 메모리 등)
  • 분산 시스템에서 복잡성 증가
    • 여러서버 간 토큰 상태 동기화 필요
  • 토큰 생성 타이밍 중요
    • 정확한 생성 주기 관리가 되어있지 않은 경우 예상과 다른 결과 발생

기존 구조 - Login Sequence

  1. 사용자가 로그인 요청 전송
  2. API 서버는 요청을 바로 Auth Service로 전달
  3. 인증 성공 여부를 클라이언트에 응답
  • 요청이 몰릴 경우 서버가 모두 처리 시도 → CPU/RAM 과부화
  • 특정 유저가 수백번 로그인 요청(ABuse)시 서버 자원 소모
  • DDos 및 정상 사용자 구분 불가
  • 비동기 큐 처리 시도시 모든 요청을 다 수신해야함

토큰 버킷 적용 - Login Sequence

  1. 유저 로그인 요청
  2. Rate Limiter 적용 - 유저 IP 기준 토큰 존재 여부 확인
    1. 있다 → 토큰 소비 → API 서버 전달
    2. 없다 → 대기열 안내
  3. 로그인 성공/실패 응답

기존에 알고있던 Bearer Token과는 다름.

Bearer Token

  • 인증 토큰
  • 사용자 인정/인가 처리
  • 클라이언트 → 서버 요청 헤더에서 사용
  • Auth 서버 로그인 성공시 발급
  • 클라이언트에서 보관
  • 이 요청을 누가 보낸것인가? → Bearer Token

Token Bucket

  • 요청 수 제어시 사용
  • 요청 횟수 제한을 통한 서버 안정화
  • 서버에 저장
  • 요청을 지금 받아도 되는가? → Token Bucket

 


 

출처 및 참고내역

https://www.geeksforgeeks.org/token-bucket-algorithm/

A* Algorithm

  • 다익스트라를 확장해 만든 경로 탐색 알고리즘
  • 휴리스틱(Heuristic) 기반의 경로 탐색 알고리즘
  • F(n) = H(n) + G(n) (최종 가중치 = 목적지까지의 거리 + 이동 비용)
    • F(n) : 출발 노드에서 도착 노드까지 경로의 전체 가중치 합
    • H(n) : 현재 노드에서 목적지 노드까지의 추정 경로 가중치 (휴리스틱 기반)
    • G(n) : 출발 노드에서 현재 노드까지의 경로 가중치
    • F(n)이 가장 최소가 되는 노드를 다음 탐색 노드로 지정한다. (우선순위 큐를 사용)
  • 휴리스틱 함수를 통해 어떻게 설계하는지에 따라 알고리즘의 성능이 달라진다.
    • 유클리드 거리 (Euclidean Distance): 대각선 포함, 자유롭게 모든 방향으로 이동 가능
    • 맨해튼 거리 (Manhattan Distance) : 가로/세로 방향으로만 이동 가능
  • 언리얼 에서는 NavMesh를 사용하지 못하는 경우 (점프, 사다리 등의 거리 계산)에 사용할 수 있다.
  • 또는 NavMesh는 런타임 환경에서의 길찾기 거리 갱신이 어려운 경우 등에 사용할 수 있다.
  • 게임 내 자동 길찾기의 경우 미니맵 UI에 보여지는 최단거리 표시 등

※ 휴리스틱 : 최선의 방향을 추정하는 방식 또는 기준.

 

유클리드 VS 맨해튼 VS BFS

https://www.researchgate.net/figure/Green-line-Manhattan-distance-Red-line-euclidean-distance-Blue-line-equivalent-to-8_fig4_331302550

 

 

BFS : 파란 선

맨해튼 : 녹색 선

유클리드 : 빨간 선

 

다익스트라(Dijkstra) Vs A* (A Star)

https://devforum.roblox.com/t/how-does-apathfinding-work/271356/2

 

다익스트라 :

목표 지점까지의 모든 방향을 균일하게 탐색하며, 최단 경로를 보장하지만 비 효율적인 탐색 범위가 발생할 수 있습니다.

 

A*

목표까지의 거리를 계산하고, 그 거리를 노드의 전체 가중치에 더하여 F(n) = H(n) + G(n)으로 바꾸는 추가적인 휴리스틱 방식을 통해 이를 수행합니다.

실제 비용과 경험적 추측을 혼합하여 가장 낮은 F(n)값을 기준으로 노드를 탐색합니다.

 

 

 


출처 및 참고내역

https://devforum.roblox.com/t/how-does-apathfinding-work/271356/2

https://www.geeksforgeeks.org/a-search-algorithm/

 

 

ESlateVisiblity

  • 위젯을 모니터상의 출력여부에 대한 상태를 제어할 때 사용하는 열거형
  • SetVisibility(ESlateVisibility InVisibility) : 현재 위젯의 상태를 변경할 때
  • GetVisibility() : 현재 위젯의 상태를 확인할 때 (Return ESlateVisibility Visibility)
  • 위젯 애니메이션 블렌딩의 경우
  • 마우스 입력 또는 키보드 입력값을 받을 수 있는지에 대한 여부 결정시

 

 

ESlateVisibility - SlateWrapperTypes.h

/** Is an entity visible? */
UENUM(BlueprintType)
enum class ESlateVisibility : uint8
{
	/** Visible and hit-testable (can interact with cursor). Default value. */
	Visible,
	/** Not visible and takes up no space in the layout (obviously not hit-testable). */
	Collapsed,
	/** Not visible but occupies layout space (obviously not hit-testable). */
	Hidden,
	/** Visible but not hit-testable (cannot interact with cursor) and children in the hierarchy (if any) are also not hit-testable. */
	HitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self & All Children)"),
	/** Visible but not hit-testable (cannot interact with cursor) and doesn't affect hit-testing on children (if any). */
	SelfHitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self Only)")
};

 

  • Visible
    • SetVisibility 설정하지 않을 경우의 기본값
    • 화면에 출력되며 마우스 클릭 및 터치 등의 인터렉션이 가능하다.
    • 일반적으로 위젯이 열려있으며, 버튼, 텍스트 등의 인터렉션이 필요하 경우 (우편함, 로비 플레이 버튼 등)
  • Collapsed
    • 화면에 출력되지 않으며 레이아웃에서 공간을 차지하지 않는 형태
    • 라운드 시작시 상호작용하지 못하는 위젯들 (상점 등)
  • Hidden
    • 화면에 출력되지 않으며 레이아웃에서 공간을 차지하는 형태
    • 플레이어 이름표 애니메이션 처리 또는 레이아웃 위치를 유지해야하는 경우
  • HitTestInvisible
    • 화면에 출력되지만 인터렉션이 불가능하며, Child에게도 적용된다.
    • 화면 전체에 덮인 오버레이의 경우 (대쉬 이펙트, 블러드 이펙트 등)
  • SelfHitTestInvisible
    • 화면에 출력되지만 인터렉션이 불가능한 형태. Child 위젯들에게는 적용되지 않는다.
    • 듀토리얼에서의 특정 버튼의 활성화의 경우

 

ContextSwitching

 

ContextSwitching의 정의

CPU에서 실행할 프로세스 혹은 쓰레드를 다른 프로세스 혹은 쓰레드로 전환하는 방법을 의미합니다.

프로세스에서 쓰레드, 쓰레드에서 프로세스로 전환하는것은 불가능하며 각각의 동일한 구조끼리 실행중인 상태를 전환 하는것을 의미합니다.

 

ContextSwitching을 하는 과정에 대해 알아보기 전에, 몇가지 미리 알아야할 내용을 설명드리겠습니다.

 

 

스케줄러(Scheduler) 

  • 어떤 프로세스나 스레드가 CPU를 사용할지 작업을 결정하는 담당

컨텍스트(Context)

  • CPU가 프로세스나 스레드를 실행하기 위한 모든 정보를 포함 하는것.

오버헤드(OverHead)

  • A라는 행동을 처리할 시 10의 시간이 걸렸는데, 안전성을 고려하여 B라는 처리를 하였을 시 총 20의 시간이 소요되었다. 여기서 증가한 10의 시간을 오버헤드라고 한다.
  • 오버헤드가 점점 누적될 시 시스템이 정지되거나 느려지는 등 심각한 오류가 생길 수 있다.

Process State 

프로세스가 컨텍스트 스위칭을 하기위해 가지는 5가지의 상태가 있습니다..

  • New : 프로세스가 생성되고 시작한다.
  • Running : 프로세스가 동작중인 상태. 프로세스 명령어를 로딩해서 CPU에 옮기고있다.
  • Waiting : Running상태의 프로세스가 동작되는동안 대기중인 상태
  • Ready : 프로세스가 Ready Queue에 들어가서, 차례를 기다리고 있는 상태.
  • Terminated : 모든걸 끝낸 프로세스 상태

PCB(Process Control Block)

  • 컨텍스트 스위칭은 PCB라는 메모리의 공간에 Process 상태값들을 저장하는 블록.
  • 프로세스는 여러개가 실행되는 방식으로 보이지만, TimeSharing을 통해 빠른 속도로 이동하며 프로세스들을 처리해준다.

 

 

ContextSwitching의 전환 과정

예시) 프로세스 P0의 상태값이 Running이다.

  1. 운영체제에서 스케쥴러에 의해 Interrupt 또는 SystemCall이 걸린 상태.
  2. 현재 Running이던 P0의 정보를 OS가 저장한다.
  3. P0의 상태값이 Waiting이 된다. 그 후 Queue구조(FIFO) P1의 상태값이 Running으로 변한다.
  4. 동시에 PCB1 Register에 저장되어있던 값이 P1과 관련된 정보로 변한다.
  5. 이때 CPU에서 실행되던 P0과 관련된 레지스터 값들이 PCB0에 저장된다.
  6. PCB1이 실행된다.

해당 컨텍스트 스위칭을 하는 과정에서 일정한 값의 오버헤드가 발생하며, 실행중인 프로세스가 전환이 됩니다.

 

정리 및 느낀점

정리

컨텍스트 스위칭이란, 프로세스 또는 쓰레드가 각각 다른 프로세스 또는 쓰레드에게 전환화는 것을 말한다.

프로세스는 5개의 상태값을 가질 수 있으며, 컨텍스트 스위칭을 하는 과정에서 상태에 따라 전환을 한다.

PCB는 프로세스의 정보를 저장하는 영역이며, 컨텍스트 스위칭시에 데이터를 옮기거나 저정하기 위해 사용한다.

 

 

 

참고내역 및 출처

https://resilient-923.tistory.com/217

https://inpa.tistory.com/entry/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%E2%9A%94%EF%B8%8F-%EC%93%B0%EB%A0%88%EB%93%9C-%EC%B0%A8%EC%9D%B4?category=890836

발생한 문제

프로젝트 및 월드 세팅을 언리얼 엔진에서 제공하는 GameState 클래스의 경우 컨트롤러에 빙의한 캐릭터 내
Movement Component가 자동으로 동기화가 됨.

 

게임 내 공통적으로 필요한 기능들을 구현하기 위해 GameStateBase 기반의 커스텀 GS를 생성, 게임모드 생성자에서

GameStateClass = MyGS::StatcClass() 적용, 프로젝트 및 월드세팅 확인 후 결과

다른 컴포넌트들은 정상적으로 동기화가 되었으나, 언리얼에서 제공하는 Movement Component는 동기화가 안됨.

 

해결 과정

AMyGameMode.cpp

AMyGameState::AMyGameState()
{
	bReplicates = true;
	bAlwaysRelevant = true;
	bNetLoadOnClient = true;
}

 

리플리케이티드 및 네트워크 동기화를 위한 변수 true 적용해보기

언리얼에서 제공하는 기본 게임 스테이트 클래스에서는 적용이 되어있었으나,

커스텀 게임 스테이트 클래스에서는 명시적으로 처리해야 하는 것.

  • bReplicates : 클라이언트에 해당 GameState 복제에 대한 여부
  • bAlwaysRelevant : 모든 클라이언트가 항상 리플리케이션 되도록 설정
  • bNetLoadOnClient : 클라이언트가 레벨 로드시 GameState를 생성하도록 설정

 

BP_MyCharacter

 

무브먼트 컴포넌트 내 리플리케이트 설정

  • C++ : GetMovementComponent()->SetIsReplicated(true);

 

두가지의 적용 결과 언리얼에서 제공하는 기본적인 기능들이 리플리케이션이 적용되지 않았습니다.

- Weapon관련 RPC, Health관련 RPC의 경우는 정상적으로 동작됨을 확인

 

 

AGameStateBase -> AGameState 클래스 상속 변경

 

기반 상속구조 변경 후 정상적으로 동기화가 됨.

 

 

멀티플레이 내 GameState VS GameStateBase

  • override 하지 않은 함수들의 경우, 기본적으로 GameStateBase에서 동일한 로직을 구현할 것이라 생각.
  • 생성자 관련 부분 먼저 확인해보기

GameStateBase.cpp - Constructor
GameState.cpp - Constructor

 

  • 생성자 관련 부분에서 State에서는 추가적인 멀티플레이 로직이 없는것을 확인 할 수 있었음.
  • 즉, 생성자 외 오버라이드된 함수 내에서 추가기능을 통해 Possess 관련 동기화과 있을것이라 생각.

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/game-mode-and-game-state-in-unreal-engine

 

공식문서 설명을 보면, 해당 함수 및 변수들이 멀티플레이 관련 역할을 한다고 알 수 있다.

 

// AGameStateBase.cpp
void AGameStateBase::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	UWorld* World = GetWorld();
	World->SetGameState(this);

	FTimerManager& TimerManager = GetWorldTimerManager();
	if (World->IsGameWorld() && GetLocalRole() == ROLE_Authority)
	{
		UpdateServerTimeSeconds();
		if (ServerWorldTimeSecondsUpdateFrequency > 0.f)
		{
			TimerManager.SetTimer(TimerHandle_UpdateServerTimeSeconds, this, &AGameStateBase::UpdateServerTimeSeconds, ServerWorldTimeSecondsUpdateFrequency, true);
		}
	}

	for (TActorIterator<APlayerState> It(World); It; ++It)
	{
		AddPlayerState(*It);
	}
}

// AGameState.cpp
void AGameState::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	FTimerManager& TimerManager = GetWorldTimerManager();
	TimerManager.SetTimer(TimerHandle_DefaultTimer, this, &AGameState::DefaultTimer, GetWorldSettings()->GetEffectiveTimeDilation() / GetWorldSettings()->DemoPlayTimeDilation, true);
}

 

  • 주요 문제는 StateBase 상속 시 Component가 동기화 되지않음
  • 즉, Component 관련 설정이 문제다. -> PlayerArray에 추가하는 로직이 오버라이드 되어있다.
  • 언리얼은 서버 동기화시 실시간성을 동시 지원하기위해 예측 및 Reconciliation 방법을 사용.
  • 동기화시 서버 월드와 클라이언트의 월드는 서로 다르기때문에 월드 서버를 기준으로 동작해야된다.
    • TimeManager를 통해 서버의 월드를 Simulate해서 클라이언트의 월드를 복제, 시간값 관련 동기화

 

 

 

 

 

아키텍처 (Architecture)

  • 하나의 시스템(어플리케이션,서비스) 등의 구성 및 동작들을 나타내는 설계도 
  • 시스템의 구성 및 동작원리를 나타내는 것
  • 구성 요소간의 관계도 또는 시스템 외부 환경과의 관계도

 

컴포넌트 기반 아키텍처 (Component Architecture)

https://www.mendix.com/ko/blog/what-is-component-based-architecture/

 

  • 모듈화가 가능하다
    • 여러 액터들이 같은 컴포넌트를 공유할 수 있다. (재사용성이 높음)
    • 기능별 독립적으로 실행할 수 있다.
  • 확장성이 높다.
    • 새로운 기능 추가시 Actor의 수정을 최소화 할 수 있다. (유지보수성이 높다)
  • 객체지향 원칙의 특징을 활용하기 적합하다.
    • Actor가 여러 역할을 할 경우 SRP를 위반하게된다.
    • 기능 추가/제거 시 수정이 최소화된다. (OCP)
  • 필요한 기능들을 개별적으로 설정할 수 있다.
    • Actor -> bReplicated시 Actor 관련 전체 네트워크 동기화
    • ActorComponent -> SetIsReplicated()를 사용하여 특정 컴포넌트만 동기화
  • 블루프린트 연동 가능
    • 비개발자들과의 원활한 협업 가능

 

컴포넌트 기반 아키텍처를 설계한 이유

 

컴포넌트 기반의 아키텍처는 위의 설명과 같이 모듈화를 함으로써 유지보수성 및 재사용성이 높습니다.

또한 하나의 컴포넌트마다 각자 하나의 책임만을 가지게 할 수 있음으로써 객체지향의 원칙을 유지할 수 있습니다.

 

LyraCharacter - Actor Components

Ex) TakeDamage를 사용한 상자 / 캐릭터 -> HealthComponent를 사용하여 동일 로직 처리

 - 하나의 컴포넌트로 다양한 액터에 동일한 로직 적용함으로써 코드 중복 제거, 재사용성 증가

 - 각 액터마다 추가 기능들이 있을 경우 HealthComponent를 상속하여 다형성을 통한 높은 확장성 보장

 

 

 

 

출처 및 참고내역

https://www.mendix.com/ko/blog/what-is-component-based-architecture/

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/lyra-sample-game-in-unreal-engine

데이터 테이블 (DataTable)

  • CSV 또는 Json과 같은 데이터 파일을 사용할 수 있다.
    • CSV/Json같은 외부 파일을 이용해 데이터를 저장/불러오기 가능
    • 에디터 내부에서도 데이터 입력 가능
  • UStruct 기반의 데이터 구조를 고정해서 사용하다
    • struct FDataTable : public FTableRowBase
  • 확장이 어려우며, 다른 데이터 구조와 연결하기가 어렵다.
  • 원하는 정보를 찾기 쉽다
  • 런타임 환경에서 데이터테이블 Row들에 대한 수정이 불가능하다.
  • TMap, TArray와 같은 컨테이너 타입의 사용이 불가능하다.
    • TArray는 기본 자료형은 가능하다. (FText, float, FVector 등)
  • 대화 / 퀘스트 시스템, 언어 시트, 게임 전역 설정, 경험치 요구량 또는 능력치 증가표 등에 적합하다.

사용 예시

1. NormalQuestDataTable.h

// 테이블 Row에 들어갈 값을 정의하는 구조체.
// FTableRowBase기반으로 구현한다.

USTRUCT(BlueprintType)
struct FQuestDataTable : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName QuestID; 

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText QuestName; 

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString QuestDescription; 

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 RewardXP; 
	
	//...
};

 

 

2. 언리얼 엔진 내 데이터테이블 생성 (Row Select)

정의한 구조체 기반의 데이터테이블을 생성할 수 있다.

 

3. 정의한 데이터테이블 Row관련 Column 정의

생성한 데이터 테이블을 에디터 레벨에서도 정의할 수 있다.

또한, 정의한 데이터 테이블을 CSV,Json 타입으로 변환이 가능하다.

 

4. DataTable 조회

// DataTable 조회 방법

	for (const FName& RowName : RowNames)
	{
		FUIObjectBaseData* RowData = UICardInfoDataTable->FindRow<FUIObjectBaseData>(RowName, ContextString);

		if (RowData && RowData->CardType == CardType)
		{
			UpdateCardUI(*RowData);
			ApplyCardType = CardType;
			ApplyValue = RowData->IncreaseAmount * GradeValue;
			return;
		}
	}

어떤 Row를 기준으로 조회할 것인지, 그 후 Contaions, IsA와 같은 방법을 통해 원하는 Column을 가져올 수 있다.

 

 

데이터 에셋 (Data Asset)

  • 에디터를 통해 데이터를 입력할 수 있다
  • 작은 규모의 정보 저장에 적합하다
    • 데이터 중첩 가능
    • 개별 아이템 등을 위한 구조에 적합
  • 확장성이 좋다. 재사용성 및 계층구조에 적합하다.
  • TArray, FStruct, TMap등 컨테이너 구조를 활용한 데이터 관리가 가능하다.
  • 데이터 테이블에 비해 로딩 속도가 빠르다
  • Input Action, Weapon Data, Item Data 등 에디터 레벨에서 설정하는 것들
    • DataPrimaryAsset을 통해 런타임 환경에서도 변경할 수 있다.

 

사용 예시

1. WeaponDataAsset.h

// 1. 무기 데이터를 리스트 형태로 하나의 데이터에셋에 관리하는 경우

USTRUCT(BlueprintType)
struct FWeaponInfo
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName WeaponID;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Damage;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float Range;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    UTexture2D* Icon;
};

// WeaponDataAsset_List.h
UCLASS(BlueprintType)
class UWeaponDataAsset_List : public UDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FWeaponInfo> WeaponList;
};

// 2. 단일 무기에 대한 데이터 에셋의 경우 
UCLASS(BlueprintType)
class UWeaponDataAsset : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName WeaponID;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Damage;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float Range;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    UStaticMesh* WeaponMesh;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    USoundBase* FireSound;
};

 

 

 

 

2. WeaponDataAsset VS WeaponDataAsset_List 

DataAsset 내 데이터들을 선언이 가능하며, 해당 데이터들을 구조체를 활용하여 컨테이너를 사용한 데이터 관리가 가능하다.

 

3. DataTable_List 조회

// DataAsset 조회 방법

	for (const auto& WeaponData : WeaponDataAssets->WeaponTextures)
	{
		if (WeaponData.WeaponClass == CurrentWeaponClass)
		{
			if (WeaponTexture && WeaponData.WeaponTexture)
			{
				WeaponTexture->SetBrushFromTexture(WeaponData.WeaponTexture);
			}
			return;
		}
	}

또는 IsA, Contains를 사용한 조회 가능.

 

 

 

출처 및 참고내역

 

https://dev.epicgames.com/community/learning/tutorials/jO8J/unreal-engine-vs

Thread Pool

정의

Thread Pool 이란 쓰레드도 결국 하나의 객체이며 데이터 처리를 위해 들어오는 요청마다 쓰레드 객체를 계속 생성하면

쓰레드 생성에 소요되는 시간에 의해 요청 처리가 더 오래걸리며 또한 오버헤드의 누적, 메모리가 고갈되는 문제점이 있습니다.

이러한 문제를 방지하기위해 쓰레드 객체를 재사용하기위해 미리 생성된 쓰레드의 집합으로, 작업 처리를 효율적으로 관리할 수 있는 기법입니다.

Thread Pool의 특징

쓰레드 풀은 미리 생성된 쓰레드들의 집합이며, 미리 생성된 쓰레드들을 재사용 하는 방식으로 구성되어있습니다.

또한 쓰레드 풀은 들어오는 요청(Task)들을 쓰레드 풀 내부에 선입선출(FIFO) 구조로 받아오는 TaskQueue가 있으며, 해당 Queue에서 먼저 들어온 요청을 각각 쓰레드풀내부의 Run상태가 아닌 쓰레드들에게 할당하는 방식입니다. 

이러한 기법으로 인해 스레드가 무제한으로 생성되는 것을 방지하여 재사용을 통한 메모리 관리, 보다 적은 오버헤딩이라는 장점이 있습니다.

Thread Pool외에도 TCP Connection을 할 시에 미리 Collection을 만들어 미리 초대할수있도록 준비하는 Collection Pool,

프로세스를 여러개 만들어서 사용하는 Process Pool 같은 방식이 있습니다.

 

또한 Thread Pool에 사용할 스레드들의 개수를 설정할 수 있는데, 설정 하는 도중 가장 최적의 스레드의 개수를 

알 수 있는 수학적 공식이 있습니다.

 

Thread Pool에 생성할 스레드 개수에 대한 수학적 공식

  • 적정 스레드 개수 = CPU 코어의 수 * ( 1 + 대기시간 / 작업시간 )
  •  

리틀의 법칙

정의

스레드의 개수가 지연시간이나 처리량(시스템이 처리 가능한 처리량)에 미치는 영향을 계산하기 위해 만든 수식입니다.

 

 

L = λ * W

 

L : 동시에 처리되는 요청의 개수

λ  : 시스템이 처리 가능한 평균 처리량 (스레드 풀의 할당된 스레드의개수)

W : 평균 요청 처리 시간

 

Ex) 평균 처리량(스레드)는 15개, 평균 요청 처리시간은 66ms다

-> 15(평균 처리 스레드) / 0.066(1초) = 227(동시에 처리 될 수 있는 요청의 개수)

즉, 동시에 처리할 수 있는 요청의 개수는 227인것을 알 수 있습니다.

 

리틀의 법칙은 스레드 풀을 사용하여 처리를 하면 처리 시간마다 요청이 처리되는 평균 개수를 알 수 있으며,

리틀의 법칙을 통해 내부 큐에 들어올 수 있는 요청의 개수를 제한 할 수 있습니다.

 

암달의 법칙(Amdahl's Law)

정의

Thread Pool에서 적정 스레드의 개수, 암달의 법칙을 사용하여 스레드가 평균 요청 시간동안 처리할 수 있는 요청의 개수를 알 수 있었습니다. 암달의법칙은 병렬화 작업 성능의 향상 가능성을 예측하며, 작업의 한계를 설명하게 위해 만들어진

수학적 수식입니다. 

 

해당 그래프를 빗대어 알 수 있듯이, 프로세서가 2^2 형식으로 늘어나며, 병렬화의 비율이 높아져도 처리속도의 증가량이 점점 줄어드는것을 볼 수 있습니다.

 

이것을 수학적 수식으로 표현 할 수있습니다.

 

수식 : S(n) = 1 / [(1 - P) + (P / n)]

  • S(n) = n개의 프로세서를 사용할 때 전체 시스템의 성능 향상 비율.
  • P = 병렬화 가능한 작업의 비율
  • n = 사용되는 프로세서의 수
  • (1 - P) = 병렬처리가 불가능한 부분

 

이러한 그래프와 수학적 수식을 통해 병렬화 처리 및 프로세서를 최대한으로 사용하는것이 효율적인 방법은 아니며, 

작업의 한계를 알 수 있습니다.

 

정리 및 느낀점

정리

병렬처리는 데이터의 처리 속도를 줄여주지만, 병렬화를 통한 작업의 한계가 있다.

그것을 암달의 법칙을 통해 알 수 있으며, CPU의 코어의 개수에 따른 스레드의 개수에 대한 적정관계를

Thread Pool을 통해 알 수 있으며, 또한 쓰레드 풀 내부 스레드가 받아올 수 있는 요청의 개수는 리틀의 법칙을 통해 알 수 있다. 

빅 데이터일수록 요청들이 많아지는데, 해당 요청을 스레드와 1+1 맵핑하여 스레드를 생성 시 생성 시간이 요청시간보다 오래걸리는 경우가 있는데, 이것을 방지하기위해 스레드를 재사용하여 생성시간을 줄여 메모리를 절약하는 방법이

Thread Pool이다.

 

느낀점

스레드 풀 생성시 CPU 코어에 따른 적정 스레드의 개수를 나타내는 방법.

스레드 풀 내부에서 평균 요청처리 시간과 쓰레드의 개수에 따라 동시에 처리되는 요청의 개수.

병렬화 처리시 병렬화 비율과 프로세서개수의 적정 비율을 나타내어 작업의 한계를 알려주는 수식.

3개의 수식이 나왔지만, 서로 다른의미를 알려줍니다.

이러한 수식을 통하여 CPU코어에 따라 만들어야할 스레드의 적정 개수를 알 수있으며,

람다식이나 내부클래스같이 코드의 간결성이라는 장점이 있지만 남용하면 오히려 코드의 구독성이 줄어드는 단점이 있듯습니다.

스레드 또한 10개만으로도 처리할 수 있는일을 100개를 생성하여 처리를 시키면 90개의 스레드들의 상태가 대기중인것을 느낄 수 있는데, 이것은 물리적 메모리(RAM)의 자원이 낭비되는것을 알 수 있게 되었습니다.

 

 

참고내역 및 출처

Thread Pool -  https://www.youtube.com/watch?v=B4Of4UgLfWc&t=548s

Thread Pool -  https://www.baeldung.com/thread-pool-java-and-guava

Little, Amdahl - https://velog.io/@ckstn0777/%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0-%EC%95%94%EB%8B%AC%EC%9D%98-%EB%B2%95%EC%B9%99

 

개인 프로젝트를 진행하던 중 애니메이션 구조에 대해서 확장적인 설계에 대해 고민하게 되었으며

UE5 프레임워크의 이해를 돕기 위한 샘플 게임 프로젝트로 설계된 학습 리소스 "Lyra Starter Game"의 구조를 학습하며

이를 기반으로 애니메이션을 설계 및 리팩토링을 하고자 합니다.

이번 포스팅에서는 개인 프로젝트의 확장적 구조 설계 및 오버헤드 관련 최적화와 관련되어 학습한 Lyra 프로젝트 내
애니메이션에 대해 알아보겠습니다.

 

※ Lyra는 GAS(GameAbilitySystem)를 사용하여 설계되어있으며, 진행중인 프로젝트에서는 GAS를 사용하지 않습니다.
이로인해 GAS 관련된 학습 내용은 해당 포스팅에서 최소한의 부분만 작성하였습니다.

 

Lyra Animation 구조

 

ABP_Mannequin_Base

ABP_Mannequin_Base

C++ 클래스 LyraAnimInstance를 기반으로 생성된 애니메이션 블루프린트입니다.

 

LyraAnimInstance.h

UCLASS(Config = Game)
class ULyraAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

public:

	ULyraAnimInstance(const FObjectInitializer& ObjectInitializer);

	virtual void InitializeWithAbilitySystem(UAbilitySystemComponent* ASC);

protected:

#if WITH_EDITOR
	virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif // WITH_EDITOR

	virtual void NativeInitializeAnimation() override;
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

protected:

	// Gameplay tags that can be mapped to blueprint variables. The variables will automatically update as the tags are added or removed.
	// These should be used instead of manually querying for the gameplay tags.
	UPROPERTY(EditDefaultsOnly, Category = "GameplayTags")
	FGameplayTagBlueprintPropertyMap GameplayTagPropertyMap;

	UPROPERTY(BlueprintReadOnly, Category = "Character State Data")
	float GroundDistance = -1.0f;
};
  • FDataValidationContext
    • 데이터 검증을 위한 구조체
    • #if WITH_EDITOR를 사용하여 에디터 레벨에서만 컴파일되게 제한, 유효성검사를 하여 런타임 내 최적화
    • 데이터 검증에 따른 결과를 EDataValidationResult를 사용하여 반환한다.
      (Data Validation Plugin / UObjectGlobal.h)
  • FGameplayTagBlueprintPropertyMap

 

 

 

ABP_Mannequin_Base - AnimGraph

해당 애니메이션 그래프는 애니메이션을 직접 참조하는것이 아닌, 몽타주 및 LinkedAnimation Layer의 진입점을 제공하여 UpperBody / Lower Body 포즈를 혼합하여 재생할 수 있게 도와줍니다.

예를들어, Weapon들은 필요한 Montage 및 애니메이션 레이어에 대한 참조를 보유하고 있으며, 해당 Weapon이 로드될
경우 그에 맞는 애니메이션이 같이 로드되도록 합니다.

 

 

Blueprint ThreadSafe Update Animation

Anim Graph 로직 - Multi Thread 전 / 후

Multi Thread 전

ABP에 추가된 Blend, IK, Physics Simulation 등과 같은 런타임환경에서 값 계산하여 재생되는 애니메이션들은 추가될수록 성능에 대한 부정적인 영향을 줄 수 있습니다. 

이벤트 그래프 로직은 NativeUpdateAnimation 함수에서 매 틱마다 계산되며 일반적으로 Tick마다 호출되는 작업 중 이벤트 그래프 로직이 가장 큰 비중을 차지하게 됩니다.

매 틱마다 호출되는 이벤트 그래프 계산에 대한 성능 최적화를 위해 Lyra에서는 멀티스레드를 사용한 퍼포먼스 최적화 작업을 수행합니다.

 

Multi Thread 후

최적의 퍼포먼스로 실행하기위해 이벤트 그래프 대신 멀티스레드 애니메이션을 사용하여 애니메이션 값을 계산합니다.

애니메이션 최적화를 위한 기법 중 하나이며, Thread Safe 함수를사용하여 동시에 작업을 수행하여 각 Tick마다 필요한 시간을 줄이며 애니메이션 시스템 성능을 향상시킬 수 있습니다.

Project Settings - General Settings - Anim Blueprints - Allow MultiThreaded Animation Update

상단 이미지의 설정을 활성화하여 사용 할 수 있습니다.

 

ABP_Mannequin_Base - BlueprintThreadSafeUpdateAnimation

스레드 세이프는 이벤트 그래프에서처럼 게임 객체의 데이터에 직접 액세스 할 수 없습니다.

멀티스레드 환경에서 다른 스레드가 동시에 실행되어 해당 데이터를 변경할 수 있기 때문입니다.

Blueprint Update Animation에서는 Property Access를 사용하여 데이터 안정성이 보장될 경우(Thrad Safe할 경우) 자동으로 복사합니다.

Lyra에서는 MovementComponent, Controller, Owner 등의 값들을 AnimInstance 내부 변수로 캐싱하는데 사용하고있습니다.

 

※Property Access : 게임 스레드에서 실행되는 컴포넌트 또는 변수를 엑세스 하는 방법.
애니메이션 시스템의 퍼포먼스를 높이는 방법 중 하나이며, 스레드 세이프와 호환되도록 변환하는 방식으로 작동한다.

 

Animation Graph

ABP_Mannequin_Base - AnimGraph - Locomotion State Machine

ABP_Base내에서 스테이트 머신을 사용한 상태를 정의하며 상태의 동작은 ItemAnimLayerBase 계층에서 처리됩니다. 

 

Locomotion SM - Idle

 

Base 애니메이션 블루프린트 내에서 노드가 업데이트가 될 경우 UpdateIdleState관련 함수가 호출되도록 매핑이 되어있습니다. 해당 함수는 Idle 스테이트 로코모션 노드의 Output Pose를 계산하는데 사용되는 로직을 볼 수 있습니다.

출력되는 애니메이션은 Anim_ItemAnimLayers 기반의 애님 블루프린트 및 애니메이션 블루프린트 링크
(Animation Blueprint Linking)에서 결정하며 애니메이션 블루프린트 일부를 재사용 할 수 있습니다.

Locomotion을 재사용하기위해서 해당 애니메이션 블루프린트에 접근할 수 있도록 하는 방식은 Sub Anim Instance을 사용합니다.

 

 

애니메이션 블루프린트 링크 (Animation Blueprint Linking)

서브 애님 인스턴스 방식을 확장한 구조입니다.

애니메이션 그래프의 서브섹션을 동적으로 전환 할 수 있도록 지원합니다.

링크된 레이어 애니메이션 블루프린트를 사용하여 애니메이션 블루프린트의 여러곳을 오버라이드하여 사용 할 수 있습니다.

Lyra에서는 플레이어가 들고 있는 무기에 따라 Locomotion SM의 애니메이션 에셋, 포즈가 동적으로 전환될 수 있습니다.

 

애니메이션 레이어 인터페이스 (Animation Layer Interface)

ABP에서 애니메이션을 오버라이드할 수 있는 곳을 지정하는 역할을 합니다.

애니메이션 간 연결을 상속구조없이 재사용할 수 있게 설계된 모듈형 애니메이션 구조입니다.

Lyra에서는 모든 WeaponType이 사용하는 Aim Offset의 Yaw,Pitch와 같은 공통적으로 필요한 함수 및 프로퍼티들을 설정하고있습니다.

 

※ Sub Anim Instance - https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-sub-anim-instances-in-unreal-engine?application_version=5.0

※ Animation Blueprint Linking - https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-animation-blueprint-linking-in-unreal-engine

 

ABP_ItemAnimLayerBase

ABP_ItemAnimLayersBase - FullBody_StartState (Animation Layer)

PropertyAccess를 사용하여 값이 업데이트 될 경우 또는, 바인딩된 경우 설정한 함수를 실행합니다.

해당 함수를 실행하여 현재 애니메이션이 어떤것을 출력해야할지 동적으로 계산합니다.

Lyra에서 Anim Node Function을 사용하여 관련성이 있을 경우(바인딩 된 경우) 재생할 애니메이션을 선택하며,

또다른 시퀀스에서는 애니메이션의 재생속도를 관리하고 있습니다.

 

ABP_LocomotionLayers(Pistol, Rifle,  Shoutgun, Unarmed...)

ABP_RifleAnimLayers - Detail

ABP_ItemAnimLayersBase를 상속받은 Locomotion 폴더 내 ABP의 설정된 애니메이션을 실행합니다.

 

 

구조 정리

ABP_Mannequin_Base

  • 캐릭터에 연결된 메인 ABP
  • 애니메이션 그래프 내 Locomotion State Machine, Linked Animation Layer를 사용한 애니메이션 Entry Point 제공
  • Blend Pose를 위한 FullBody -> Upper/Lower Body Split
  • 런타임 환경에서 최종 출력될 애니메이션에 대한 값 계산
    • 애니메이션의 전체적인 흐름 제어 및 레이어 라우팅 담당

Linked Anim Layer (ALI_ItemAnimLayers)

  • ABP_Base의 Linked Animation 함수 실행
  • ALI_ItemAnimLayers에 정의된 함수들이며, 상속구조없이 바인딩가능, 유연성 및 재사용성 증가

 

ABP_ItemAnimLayerBase

  • ALI_ItemAnimLayers 내 함수 구현
  • 어떤 애니메이션을 쓸 지 결정하는 역할
    • 실제 출력되는 애니메이션은 하위 클래스 ABP_Rifle/Pistol ... 등의 설정한 애니메이션 시퀀스 출력
  • WeaponType, IsJump, bCrouch 등 바인딩된 AnimInstance의 Character Movement Component을 사용한
    최종 애니메이션 결정

ABP_Locomotion

  • 무기 타입에 따라 분기된 실제 애니메이션 그래프
  • 무기타입 변경시 해당 클래스가 애니메이션 스타일을 시각적으로 변경한다.

Lyra 성능 최적화 방법

  • 이벤트 그래프 -> 멀티스레드 애니메이션 (Blueprint ThreadSafe UpdateAnimation)을 사용한 멀티스레드
  • 동기화를 데이터 Process Access를 사용한 ThradSafe 방식 사용
  • 재사용 및 유연성 고려한 Animation Layer Interface를 사용한 함수 정의

 

 

 


출처 및 참조링크

 

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/lyra-sample-game-in-unreal-engine

https://www.youtube.com/watch?v=ys_kSOKpTtg&list=PLNBX4kIrA68lSY6Pj3zDVH6kGDIMgwOvr&index=3

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/animation-blueprint-nodes-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/animation-optimization-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/data-validation-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/property-access-in-unreal-engine

https://daekyoulibrary.tistory.com/entry/UE5-Render-thread%EC%99%80-Animation-thread%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4

+ Recent posts