오늘은 언리얼의 RPC와 Replication에 대해서 알아보았다.

 

 

RPC란?

 

RPC는 서버와 클라이언트 간에 특정 함수를 실행하는 방식이다. 호출하는 PC와 실행되는 PC가 따로 있는 것이다.

RPC의 주요 종류에는 Client -> Server, Server -> Client, Multicast가 있다.

 

1. Client -> Server

  • 클라이언트에서 함수를 호출하면 서버에서 실행한다.
  • ex) 플레이어가 총을 쏠 때 서버에서 검증

2. Server -> Client

  • 서버에서 클라이언트로 특정 함수를 실행하도록 요청한다.
  • ex) 특정 이펙트 이벤트 발생 시 사용

3. Multicast

  • 서버에서 모든 클라이언트에게 실행 명령을 보낸다.
  • ex) 모든 플레이어에게 특정 애니메이션 또는 효과 동기화

 

위의 Client, Server, NetMulticast는 UFUNCTION() 매크로와 함께 사용되는 키워드이다.

 

UFUNCTION(Client) -> 클라이언트에서 해당 RPC를 실행하라

UFUNCTION(Server) -> 서버에서 해당 RPC를 실행하라

UFUNCTION(NetMulticast) -> 서버를 포함한 모든 클라이언트에서 해당 RPC를 실행하라

 

Client, Server, NetMulticast와 같이 UFUNCTION() 매크로와 함께 사용 되는 키워드에는 WithValidation, Unreliable, Reliable이 있다.

 

1. WithValidation

  • 서버 실행 RPC 경우에 사용되는 키워드
  • 키워드가 붙은 RPC는 _Implementation() 함수와 _Validate() 함수로 나뉜다.
  • _Validate 함수는 해당 RPC가 실제로 실행될지 말지 결정한다.
  • 해킹 등의 비정상적인 호출을 방지하기 위해 사용한다.

2. Unreliable

  • UDP 통신을 이용한다.
  • 속도가 빠르지만 무조건 실행될 것이란 보장이 없다.
  • 이펙트, 사운드 같이 게임에 큰 영향 없는 로직에 사용한다.

3. Reliable

  • TCP 통신을 이용한다.
  • 속도가 느리지만 무조건 실행된다.
  • 충돌, 데미지 ,스폰과 같이 게임에 큰 영향을 끼치는 로직에 사용한다.

 

 

Replication이란?

 

Replication이란 생성된 객체 또는 변수의 값을 서버와 클라이언트 간에 동기화 하는 기능이다.

 

1. 변수 (Properties)

  • UPROPERTY(Replicated) 를 사용하여 변수 동기화 가능하다.

2. 액터 (Actors)

  • bReplicates = true; 로 설정하면 서버가 해당 액터를 모든 클라이언트와 동기화한다.

 

리플리케이트 변수 선언

UPROPERTY(Replicated)
int32 Health;

 

 

리플리케이트 활성화

void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(AMyCharacter, Health); // Health 변수 동기화
}

 

RepNotify (변경될 때 실행되는 함수)

UPROPERTY(ReplicatedUsing=OnRep_Health)
int32 Health;

UFUNCTION()
void OnRep_Health();
void AMyCharacter::OnRep_Health()
{
    // 클라이언트에서 실행됨 (UI 업데이트 등)
}

 

 

RPC, Replication 비교

  RPC Replication
주요 목적 함수 실행 요청 데이터 동기화
방향 특정 클라이언트 / 서버로 호출 서버 -> 클라이언트
주요 사용 사례 공격, 점프, 상호작용 체력, 위치, 상태 값 유지
적용 대상 함수 변수 및 액터
실행 방식 직접 호출 자동 동기화

오늘은 어제 만든 AimOffset을 활용하여 캐릭터가 움직이면서 Pitch, Yaw 값이 변할 때

애니메이션이 재생 되도록 만들어봤습니다.

 

 

 저번에 만든 AimOffset 활용하기

  • FRotator AimDirection을 선언해줍니다.

 

  • 캐릭터의 마우스 움직임을 감지하는 함수에 AimDirection의 변화를 담는 코드를 추가합니다.

 

  • ABP의 AnimGraph로 돌아와 Idle 스테이트에 들어가서 적용을 해줍니다.

 

PalyerCharcter는 BP Player Character를 Cast한 값입니다.

 

 

 이렇게 Jog 스테이트에도 적용 해주면 끝

 

Idle 상태이거나 Jog 상태일 때 Yaw와 Pitch 값에 따라 캐릭터의 애니메이션이 변합니다.

오늘은 저번에 리타게팅한 애니메이션을 바탕으로 AimOffset을 만들어 보았다.

애니메이션이 AimOffset에 등록이 안돼서 애를 먹었는데,

리타게팅한 애니메이션은 Additive Setting이 안돼 있어서 그랬던 것이었다.

 

 

1. AimOffset 만들기

 

 

 

2. Axis settings를 통해 Horizontal과 Vertical에 원하는 값 주기

설정한 모습

 

 

3. Additive Settings의 Preview Base Pose에 Idle 애니메이션 넣기

 

 

4. 애니메이션을 넣기전에 원하는 애니메이션의 Additive Settings 해주기

  • Additive Anim Type -> Mesh Space
  • Base Pose Type -> Selected animation frame
  • Base Pose Animation -> 3번에서 넣은 애니메이션 넣어주기

 

 

5. 에셋 브라우저에서 원하는 애니메이션 넣기

 

 

이렇게 해서 AimOffset을 만들어봤다.

오늘은 내가 사용하고 싶은 파라곤 에셋의 애니메이션 노드 중 Leg IK가 작동을 하지 않아

Control Rig 노드를 이용했다.

 

 먼저 파라곤 에셋에는 Control Rig Class가 존재하지 않는다. Leg IK 노드를 이용하여 제어를 하기 때문인 것 같다.

왜인지 모르겠지만 Leg IK 노드를 에셋과 동일하게 이용해도 제어가 불가능 했기 때문에 Control Rig 노드를 사용하게끔 선회를 했다... 그치만 Control Rig Class가 존재하지 않기 때문에 만들어줘야했다..

 

 

 

1. 본 제공되는 CR_Mannequin_BasicFootIK를 복붙하기

 

 

2. 우측 상단 Preview 패널의 Preview Mesh를 내가 원하는 Mesh로 설정하기

 

3. 좌측 하단의 Rig Hierarchy 패널의 root bone을 우클릭 하여 Refresh 하기 -> Select Mesh는 위 Preview Mesh와 동일

 

 

3번까지의 모든 과정을 마치고 컴파일이 된다면 Control Rig 노드의 Control Rig Class에 할당이 가능해진다.

이렇게 해서 Control Rig 노드의 사용이 가능해졌다.

오늘은 애니메이션 리타겟팅을 해봤습니다.

 

 

언리얼 애니메이션 리타겟팅이란?

 

 한 스켈레톤의 애니메이션을 다른 스켈레톤에서도 사용할 수 있도록 변환하는 과정이다.

 

 

리타기팅 하는 법

 

1. 본인이 원하는 애니메이션을 프로젝트로 갖고 온다.

2. 애니메이션 시퀀스 파일을 우클릭하여 리타겟 애니메이션을 누른다

 

3. 타겟 스켈레탈 메시를 선택한다.

4. 리타겟팅 할 애니메이션 파일들을 선택한다.

5. Export Animation을 누른다

 

6. 원하는 폴더에 리타겟팅한 애니메이션 시퀀스를 넣는다.

 

리타겟팅한 애니메이션 시퀀스가 생겼다.

오늘은 캐릭터 에셋에 붙어 있는 무기를 제거하는 꼼수(??)를 배웠다

 

 

 보다시피 gun이 스켈레톤에 붙어 있어 내가 원하는 무기를 부착 할 수 없다.

방법은 2가지가 있다.

 

1. 스켈레톤의 스케일을 0으로 변경하는 것

2. 무기와 관련된 머티리얼을 OpacityMask가 0인 머티리얼로 변경하는 것

 

 

스켈레톤의 스케일을 0으로 변경하기

 

1. 원하는 스켈레톤을 선택한다.

2. Edit Skeleton을 누른다.

3. Scale 값을 0으로 조절한다.

 

0으로 변경한 모습

 

 

 무기와 관련된 머티리얼을 OpacityMask가 0인 머티리얼로 변경하기

 

 

무기에 적용된 머티리얼을 찾는다.

 

 

Blend Mode를 Masked로 변경한다.

Opacity Mask 값을 0으로 변경하고 머티리얼을 적용하면 끝이다.

 

머티리얼을 적용한 모습

 

1번 방법으로는 특정 애니메이션에서 스케일을 0으로 줄인 스켈레톤이 깨져서 허공에 돌아다닐 수 있다.

애니메이션에서 스케일을 재조정 할 수 있기 때문...

오늘은 코드카타를 하던 중 C++ 연결 리스트에 대해 궁금해져서 공부해봤습니다.

 

연결 리스트(Linked List)란?

 

 연결 리스트는 각 요소(노드)가 포인터를 사용하여 다음 요소와 연결되는 자료구조이다. 배열과 달리 메모리의 연속성이 보장되지 않으며, 동적으로 크기를 조절할 수 있다.

 

연결 리스트의 구조

 

1. 데이터(Data): 실제 저장할 값

2. 포인터(Next): 다음 노드의 주소

[10 | *] -> [20 | *] -> [30 | *] -> nullptr
  • 10이 첫 번째 노드(Head), 다음 노드 20을 가리킨다.
  • 마지막 노드는 nullptr을 가리켜 리스트의 끝을 나타낸다.

 

연결 리스트의 종류

 

1. 단일 연결 리스트

  • 한 방향으로만 연결
  • 각 노드는 다음 노드의 주소만 저장
#include <iostream>

struct Node {
    int data;
    Node* next;
};

void printList(Node* head) {
    Node* temp = head;
    while (temp != nullptr) {
        std::cout << temp->data << " -> ";
        temp = temp->next;
    }
    std::cout << "nullptr" << std::endl;
}

int main() {
    // 노드 생성
    Node* head = new Node{10, nullptr};
    Node* second = new Node{20, nullptr};
    Node* third = new Node{30, nullptr};
    
    // 노드 연결
    head->next = second;
    second->next = third;
    
    // 리스트 출력
    printList(head);
    
    // 메모리 해제
    delete head;
    delete second;
    delete third;
    
    return 0;
}

 

2. 이중 연결 리스트

  • 양방향 연결
  • 각 노드는 이전 노드의 다음 노드의 주소를 저장
#include <iostream>

struct Node {
    int data;
    Node* prev; // 이전 노드를 가리킴
    Node* next;
};

void printList(Node* head) {
    Node* temp = head;
    while (temp != nullptr) {
        std::cout << temp->data << " <-> ";
        temp = temp->next;
    }
    std::cout << "nullptr" << std::endl;
}

int main() {
    Node* head = new Node{10, nullptr, nullptr};
    Node* second = new Node{20, head, nullptr};
    Node* third =  new Node{30, second, nullptr};
    
    head->next = second;
    second->next = third;
    
    printList(head);
    
    delete head;
    delete second;
    delete third;
    
    return 0;
}

 

3. 원형 연결 리스트

  • 마지막 노드가 다시 첫 번째 노드를 가리킨다.
  • 단일 또는 이중 연결 리스트 형태 가능

 

연결 리스트 삽입/삭제

 

  • 노드 삽입
void InsertFront(Node*& head, int value) {
    Node* newNode = new Node{value, nullptr};
    newNode->next = head;
    head = newNode;
}

 

  • 노드 삭제
void deleteNode(Node*& head, int key) {
    Node* temp = head;
    Node* prev = nullptr;
    
    while (temp != nullptr && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }
    
    if (temp == nullptr) return;
    
    if (prev == nullptr) {
        head = temp->next;
    }
    else {
        prev->next = temp->next;
    }
    
    delete temp;
}

 

 

STL List

 

  • std::list 클래스를 사용하여 이중 연결 리스트를 쉽게 구현할 수 있다.
  • 이중 연결 리스트는 앞 뒤 자유롭게 탐색이 가능하고 삭제가 효율적이다.
  • 순차적 데이터 추가 및 삭제에 적합하다.
#include <iostream>
#include <list>

int main() {
    std::list<int> myList = {10, 20, 30};  // 초기화 리스트로 생성

    myList.push_front(5);   // 맨 앞에 삽입
    myList.push_back(40);   // 맨 뒤에 삽입

    myList.pop_front();     // 맨 앞 요소 삭제
    myList.pop_back();      // 맨 뒤 요소 삭제

    std::cout << "List Elements: ";
    for (int val : myList) {
        std::cout << val << " ";
    }

    return 0;
}

///
List Elements: 10 20 30

 

 

std::list 주요 함수 및 프로퍼티

 

함수/프로퍼티 설명
push_front(value) 맨 앞에 요소 추가
push_back(value) 맨 뒤에 요소 추가
pop_front() 맨 앞 요소 삭제
pop_back() 맨 뒤 요소 삭제
front() 첫 번째 요소 반환
back() 마지막 요소 반환
size() 리스트 크기 반환
empty() 리스트가 비었는지 확인
insert(iterator, value) 특정 위치에 요소 삽입
erase(iterator) 특정 위치 요소 삭제
remove(value) 특정 값과 일치하는 요소 모두 삭제
sort() 리스트 정렬
reverse() 리스트 순서 뒤집기

오늘은 UE5에서 Git LFS를 사용하는법을 공부했습니다.

 

 

Git LFS란?

 

 Git LFS(Git Large File Storage)는 Git에서 대용량 파일(ex: 고해상도 이미지, 오디오, 비디오, 게임 에셋 등)을 효율적으로 관리하기 위한 확장 기능이다.

GitHub에서는 단일 파일 100 MB 제한을 하기 때문에 언리얼 엔진을 사용할 때는 Git LFS를 사용해야 한다.

 

 

Git LFS의 주요 특징

  • Git 저장소에는 실제 파일이 아닌 파일의 포인터(경로 및 메타데이터)가 저장된다.
  • 실제 파일은 별도의 원격 저장소(LFS 서버)에 저장된다.
  • 필요할 때만 실제 파일을 다운로드하기 때문에 작업 속도가 빨라진다.

 

Git LFS 사용 방법

 

1. Git LFS 설치

 

 

2. Git LFS 설치 확인

  • git lfs install 입력 시 하단 처럼 나오면 정상적으로 설치 완료

 

 

3. 언리얼 프로젝트에 Git LFS 적용 하는법

 

gitignore.io

Create useful .gitignore files for your project

www.toptal.com

  • 위 사이트에서 UnrealEngine 넣고 생성을 누른다.
  • 나온 텍스트를 .gitignore 파일에 붙여 넣는다.
  • 관리할 파일 지정

 

 이러한 방식으로 언리얼 엔진에서 자주 사용하는 파일 유형인 "*.uasset" , "*.umap", "*.png", "*.wav", "*.bk2"를 추가한다.

git lfs track을 하면 자동으로 .gitattributes 파일이 생성되며 자동 반영된다.

그 후 커밋을 하고 원격 저장소에 push를 하면 된다.

오늘은 범위 지정 연산자에 대해서 알아봤습니다.

 

:: (범위 지정 연산자)란?

 

:: 는 범위 지정 연산자로, 특정 네임스페이스, 쿨래스, 또는 전역 범위에 있는 변수나 함수를 참조할 때  사용된다.

 

 

C++ 에서 :: 의 사용 사례

 

1. 네임스페이스 내의 멤버 접근

namespace MyNamespace{
    int Value = 10;
}

int main() {
    int x = MyNamespace::Value; // MyNamespace 내부의 Value 사용
}

 

 

2. 클래스의 정적(Static) 멤버 접근

class MyClass{
public:
    static int StaticValue;
};

int MyClass::StaticValue = 42; // 정적 멤버 변수 정의

int main(){
    int x = MyClass::StaticValue; // 정적 멤버 변수 접근
}

 

 

3. 클래스 외부에서 멤버 함수 정의

class MyClass {
public:
    void Print();
};

void MyClass::Print() { // 클래스 외부에서 Print() 함수를 정의할 때 사용
    std::cout << "Hello from MyClass" << std::endl;
}

 

 

4. 전역 범위 연산 (:: 단독 사용)

int Value = 5;

namespace MyNamespace {
    int Value = 10;
}

int main() {
    int x = ::Value; // 전역 범위의 Value 사용
}

오늘은 강의를 듣다 TSubclassOf<>와 TSoftclassPtr<>에 대해 궁금해서 알아봤습니다.

 

 

TSubclassOf<>란?

 

 TSubclassOf<>는 특정 클래스 또는 그 하위 클래스를 저장할 수 있는 템플릿이다.

  • 컴파일 타임에 UCLASS()로 선언된 클래스 타입을 강제할 수 있다.
  • 런타임 시점에서도 GetDefalutObject() 등을 통해 클래스 정보를 가져올 수 있다.
  • 하드 래퍼런스를 가지므로 클래스가 항상 메모리에 로드된다.

 

예시 코드

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    // 특정 AActor를 상속받은 클래스만 할당 가능
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Config")
    TSubclassOf<AActor> ActorClass; // AActor의 하위 클래스만 저장 가능(UClass* 형태)

    void SpawnMyActor(UWorld* World)
    {
        if (ActorClass) // 유효한 클래스인지 확인
        {
            World->SpawnActor<AActor>(ActorClass, FVector(0, 0, 100), FRotator::ZeroRotator);
            // ActorClass의 인스턴스 생성
        }
    }
};

 

 

TSoftClassPtr<>이란?

 

 TSoftClassPtr<>은 클래스를 경로로 저장하는 소프트 래퍼런스 포인터이다.

  • 언리얼의 ASset Registryt를 이용하여 클래스를 경로 기반으로 참조한다.
  • 객체를 직접 참조하지 않으므로 메모리를 절약할 수 있다.
  • LoadSynchronous() 또는 AsyncLoadClass()를 호출하여 명시적으로 로드해야 한다.

 

예제 코드

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    // TSoftClassPtr을 사용하여 클래스 경로만 저장 (메모리 절약)
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Config")
    TSoftClassPtr<AActor> ActorSoftClass; // 실제 객체가 아니라 경로만 저장

    void LoadAndSpawnActor(UWorld* World)
    {
        if (ActorSoftClass.IsValid()) // 경로가 유효한지 확인
        {
            // 동기적으로 로드 (런타임 성능 고려 필요)
            UClass* LoadedClass = ActorSoftClass.LoadSynchronous();
            if (LoadedClass)
            {
                World->SpawnActor<AActor>(LoadedClass, FVector(0, 0, 100), FRotator::ZeroRotator);
            }
        }
    }
};

 

 

TSubclassOf<> vs TSoftClassPtr<> 비교

 

비교 항목 TSubclassOf<> TSoftClassPtr<>
메모리 사용 하드 래퍼런스 (항상 로드) 소프트 래퍼런스 (지연 로딩 가능)
블루프린트 지원 가능 가능
런타임 접근 즉시 사용 가능 LoadSynchronous() 필요
GC 자동 관리 로딩 시 직접 관리 필요
주 용도 런타임에 즉시 사용할 클래스 저장 클래스의 경로만 저장하여 메모리 절약

 

 

TSubclassOf<> 와 TSoftClassPtr<> 사용 시기

 

TSubclassOf<> 사용 시기

  • 클래스가 항상 메모리에 로드되어 있어야 할 때
  • 즉시 액세스 해야 하며 성능이 중요한 경우 (ex: 스폰 시스템)
  • 컴파일 타임에 타입 안전성을 보장해야 할 때

TSoftClassPtr<> 사용 시기

  • 메모리를 절약해야 하며 클래스를 필요할 때만 로드할 때
  • 특정 클래스가 필요하지 않을 수도 있는 경우 (ex: DLC, 선택 캐릭터)
  • 에셋이 많이지면서 패키지 로딩  최적화가 필요한 경우

+ Recent posts