언리얼 에서 델리게이트는 C++ 객체에만 사용할 수 있는 델리게이트와 C++, 블루프린트 객체가 모두 사용할 수 있는 델리게이트로 나뉜다. 블루프린트 오브젝트는 멤버 함수에 대한 정보를 저장하고 로딩하는 직렬화 매커니즘이 들어있기 때문에 일반 C++ 언어가 관리하는 방법으로 멤버 함수를 관리 할수 없다. 

그래서 블루프린트와 관련된 C++함수는 모두 UFUNCTION 매크로를 사용해야 한다.
이렇게 블루프린트 객체와도 연동하는 델리게이트를 언리얼 엔진에서는 다이내믹 델리게이트라고 한다.

 

 

 

 


Delegate란?

함수를 바인딩하는 형태로 등록시켜 CallBack함수와 같이 활용 할 수 있습니다.

 

언리얼 C++에서 충돌감지 컴포넌트 개열에서 AddDynamic 메크로를 통해 출돌시 등록한 CallBack함수를 호출하는 것이 구현 되어 있습니다.

 

언리얼 C++에는 총 4가지 종류의 Delegate가 있습니다.

델리게이트(싱글 케스트) 가장 기본적인 Delegate로 함수 1개를 바인드하여 사용합니다.
멀티 케스트 싱글 케스트와 동일하지만 여러 함수를 바인드 할 수 있습니다.
이벤트 멀티 케스트와 동일하지만 전역으로 설정할 수 없어 외부 클래스에서 추가 델리게이트 선언이 불가능합니다.
다이나믹 멀티케스트(다이나믹) 다이나믹은 싱글과, 멀티 두개다 존재하며 다이나믹 델리게이트는 직렬화(Serialize)화 되어 블루프린트에서 사용 가능합니다.

 

바인딩(Bind)란 Delegate에 특정 바인드 함수를 통해 콜백함수를 등록하는 것을 의미합니다.

공식문서에는 여러 형태의 바인드함수가 있지만 이 포스팅에서는 함수 바인딩을 중심적으로 설명하겠습니다.

 

 

 


샘플 프로젝트 소개

샘플 프로젝트는 포스팅 상단에 업로드했습니다.

 

샘플 프로젝트는 폭탄 오브젝트가 하나 있으며 키보드 "B"키는 누르면 점화되어 3초 뒤에 폭발합니다.

프로젝트의 구현 형태는 폭탄 클래스에다 함수들을 바인드시켜놓고 폭탄이 터지면 바인드한 함수들을 호출하여

 

출력 로그에 해당 함수 내에서 로그 코드가 발동되게 구현했습니다.

 

 

 


기본적인 Delegate 세팅법은 먼저 싱글케스트를 예시로 먼저 설명하겠습니다.

 

헤더 파일 설명

//! ABoom.h

#include "Boom.generated.h"

//! SingleCast
DECLARE_DELEGATE(FDele_Single); 
DECLARE_DELEGATE_OneParam(FDele_Single_OneParam, int32);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()

........

public :
	FDele_Single Fuc_DeleSingle;
	FDele_Single_OneParam Fuc_DeleSingle_OneParam;
};

 

헤더 파일에서 Delegate함수를 만들고 DECLARE_DELEGATE()메크로를 통해 델리게이트화 시킵니다.

 

DECLARE_DELEGATE는 인자 값 없는 함수에 사용하고 DECLARE_DELEGATE_OneParam는 1개의 인자값이 있는 함수에 사용합니다, 만약 인자 값이 2개면 TwoParam을 사용합니다.

 

델리게이트는 자료형 이름 앞에 "F"를 붙여야합니다.

안 그러면 위 에러가 뜨게 됩니다.

 

 

Cpp 파일 설명

//! ABoom.cpp

void ABoom::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//! 델리게이트 해제
	Fuc_DeleSingle.Unbind();
	Fuc_DeleSingle_OneParam.Unbind();
}

void ABoom::Tick_Boom(float DeltaTime)
{
		//! Delegate호출하는 부분
		if(Fuc_DeleSingle.IsBound()==true)	Fuc_DeleSingle.Execute();
		if(Fuc_DeleSingle_OneParam.IsBound() == true) Fuc_DeleSingle_OneParam.Execute(123);
}

 

EndPlay함수에 Unbind()함수는 일종의 메모리 해제 함수입니다, 해당 델리게이트에 바인드된 함수를 제거합니다.

 

실제 델리게이트에 등록된 Callback 함수를 호출하는 부분입니다.

IsBound()를 통해 바인드가 되어있는지 확인하고 리턴값이 True이면 Execute()함수를 통해 호출합니다.

인자 값이 있는 함수는 Execute함수의 인자 값으로 입력합니다.

 

 

 


Delegate에 함수 등록 & 싱글케스트(Single cast)

이 챕터는 Delegate가 세팅된 ABoom 클래스에 ATestPlayer클래스의 함수를 등록하는 과정입니다.

 

//! ATestPlayer.h

UCLASS()
class DELEGATETEST_API ATestPlayer : public AActor
{
	GENERATED_BODY()
public:	
	//! Delegate에 의해 호출될 함수

	UFUNCTION()
		void CallDeleFunc_Single();
	UFUNCTION()
		void CallDeleFunc_Single_OneParam(int32 nValue);

먼저 ABoom클래스에 등록할 함수는 인자값에 맞춰 함수를 만듭니다.

Delegate에 등록할 함수는 UFUNCTION()메크로를 붙여야 합니다.

 

//! ATestPlayer.cpp

//! Delegate에 의해 호출될 함수
void ATestPlayer::CallDeleFunc_Single()
{
	UE_LOG(LogTemp, Warning, TEXT("CallDeleFunc_Single"));
}

void ATestPlayer::CallDeleFunc_Single_OneParam(int32 nValue)
{
	UE_LOG(LogTemp, Warning, TEXT("CallDeleFunc_Single_OneParam / %d"), nValue);
}

해당 함수에 단순하게 로그를 출력하는 코드를 작성했습니다.

 

//! ATestPlayer.cpp

void ATestPlayer::BeginPlay()
{
	m_pBoom->Fuc_DeleSingle.BindUFunction(this, FName("CallDeleFunc_Single"));
	m_pBoom->Fuc_DeleSingle_OneParam.BindUFunction(this, FName("CallDeleFunc_Single_OneParam"));
}

함수를 바인드하는 코드입니다, ABoom.h에서 만든 Delegate 변수에 접근하여 함수를 바인드하는 함수를 통해 함수를 등록합니다.

 

 

이로서 Delegate의 기본 형태인 싱글케스트 구현이 완료됬으며, 폭탄이 터지면 로그가 출력될 것입니다.

 

 


멀티 케스트(Multi cast)

멀티케스트가 델리게이트(싱글케스트)와 차이점은 함수를 여러개 바인드 할 수 있습니다.

 

//! ABoom.h

//! MultiCast
DECLARE_MULTICAST_DELEGATE(FDele_Multi);
DECLARE_MULTICAST_DELEGATE_OneParam(FDele_Multi_OneParam, int32);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	
	FDele_Multi Fuc_DeleMulti;
	FDele_Multi_OneParam Fuc_DeleMulti_OneParam;
};

멀티 케스트는 싱글케스트와 비슷하게 헤더파일에서 작성됩니다.

 

//! ABoom.cpp

void ABoom::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//! 델리게이트 해제
	Fuc_DeleMulti.Clear();
	Fuc_DeleMulti_OneParam.Clear();
}

void ABoom::Tick_Boom(float DeltaTime)
{
		if(Fuc_DeleMulti.IsBound() == true) Fuc_DeleMulti.Broadcast();
		if(Fuc_DeleMulti_OneParam.IsBound() == true) Fuc_DeleMulti_OneParam.Broadcast(456);
}

cpp파일입니다.

 

 

싱글케스트와 차이점은 Unbind가 Clear로 되고 Execute가 Broadcast로 바뀌었습니다.

 

 

//! ATestPlayer.cpp

void ATestPlayer::BeginPlay()
{
	Super::BeginPlay();

		//! SingleCast
		m_pBoom->Fuc_DeleSingle.BindUFunction(this, FName("CallDeleFunc_Single"));
		m_pBoom->Fuc_DeleSingle_OneParam.BindUFunction(this, FName("CallDeleFunc_Single_OneParam"));

		//! MultiCast
		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_1"));
		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_2"));
        m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_1"));
		m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_2"));

함수를 등록하는 부분입니다, 싱글케스트와 쓰임세는 비슷하지만 함수는 BindUFunction->AddUFunction으로 변경되었습니다.

 

결과를 확인하면 폭탄이 터질때 해당 델리게이트에 .BroadCast()함수를 한번 호출하면 안에 등록된 모든 함수가 호출이 되는 것을 확인 할 수 있습니다.

 

없으면 아무것도 실행 안됨


이벤트(Event)

이벤트는 멀티 케스트와 유사하지만 전역으로 설정할 수 없어 외부 클래스에서 추가 델리게이트 선언이 불가능합니다.

캡슐화 개념으로 사용하면 될것 같습니다.

 

//! ABoom.h

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	//! Event
	DECLARE_EVENT(ABoom, FDele_Event);
	DECLARE_EVENT_OneParam(ABoom, FDele_Event_OneParam, int32);
    
public :
	FDele_Event Fuc_DeleEvent;
	FDele_Event_OneParam Fuc_DeleEvent_OneParam;

전역에 작성했던 이전 Delegate와 달리 Event는 클래스 내부에 작성하고 인자값으로 주인클래스의 클래스를 입력합니다.

 

이외에는 이전 멀티케스트와 사용법이 동일합니다.

 

 

 


다이나믹 멀티캐스트 (다이나믹(Dynamic))

다이나믹 델리게이트는 직렬화(Serialize)화 되어 블루프린트에서 사용 가능합니다.

 

//! ABoom.h

//Dynamic
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDele_Dynamic);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDele_Dynamic_OneParam, int32, SomeParameter);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic Fuc_Dynamic;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic_OneParam Fuc_Dynamic_OneParam;

이전 델리게이트와 비슷하지만 변수 작성부분에서 BlueprintAssignable 등 UPROPERTY 메크로를 작성합니다.

 

해당 포스팅에서는 DYNAMIC_MULTICAST만 사용했습니다,

 

 

다이나믹 델리게이트는 직렬화(Serialize)화가 가능하고 UPROPERTY 메크로를 통해서 블루프린트에서 델리게이트 바인드가 가능합니다.

이 기능은 블루프린트 디스패처랑 비슷하게 사용이 가능합니다.

 

그리고 Cpp에서는 이전 멀티케스트와 동일하게 사용합니다.

 

 

//! ATestPlayer.cpp

void ATestPlayer::BeginPlay()
{
	Super::BeginPlay();
    
	m_pBoom->Fuc_Dynamic.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic);
	m_pBoom->Fuc_Dynamic_OneParam.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic_OneParam);

다이나믹 델리게이트는 이전과는 다르게 위 .AddDynamic 메크로함수로 바인드를 합니다.

 

 

폭탄이 터지고 로그를 확인하면 블루프린트에서 바인드한 Custom함수와, C++에서 바인드한 함수가 동시에 호출되는 것을 확인 할 수 있습니다.

 

 

 


핵심 코드

 

1. 델리게이트 세팅

//! SingleCast
DECLARE_DELEGATE(FDele_Single);
DECLARE_DELEGATE_OneParam(FDele_Single_OneParam, int32);

//! MultiCast
DECLARE_MULTICAST_DELEGATE(FDele_Multi);
DECLARE_MULTICAST_DELEGATE_OneParam(FDele_Multi_OneParam, int32);

//Dynamic
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDele_Dynamic);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDele_Dynamic_OneParam, int32, SomeParameter);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	//! Event
	DECLARE_EVENT(ABoom, FDele_Event);
	DECLARE_EVENT_OneParam(ABoom, FDele_Event_OneParam, int32);
    
    
 //---------------------------------------------------------------------
 
 public :
	FDele_Single Fuc_DeleSingle;
	FDele_Single_OneParam Fuc_DeleSingle_OneParam;

	FDele_Multi Fuc_DeleMulti;
	FDele_Multi_OneParam Fuc_DeleMulti_OneParam;

	FDele_Event Fuc_DeleEvent;
	FDele_Event_OneParam Fuc_DeleEvent_OneParam;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic Fuc_Dynamic;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic_OneParam Fuc_Dynamic_OneParam;

 

2. 델리게이트에 바인드된 함수 호출 & 해제

void ABoom::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//! 델리게이트 해제

	Fuc_DeleSingle.Unbind();
	Fuc_DeleSingle_OneParam.Unbind();

	Fuc_DeleMulti.Clear();
	Fuc_DeleMulti_OneParam.Clear();

	Fuc_DeleEvent.Clear();
	Fuc_DeleEvent_OneParam.Clear();

	Fuc_Dynamic.Clear();
	Fuc_Dynamic_OneParam.Clear();
}

void ABoom::Tick_Boom(float DeltaTime)
{
		//! Delegate호출하는 부분
		if(Fuc_DeleSingle.IsBound()==true)	Fuc_DeleSingle.Execute();
		if(Fuc_DeleSingle_OneParam.IsBound() == true) Fuc_DeleSingle_OneParam.Execute(123);
		if(Fuc_DeleMulti.IsBound() == true) Fuc_DeleMulti.Broadcast();
		if(Fuc_DeleMulti_OneParam.IsBound() == true) Fuc_DeleMulti_OneParam.Broadcast(456);
		if(Fuc_DeleEvent.IsBound() == true) Fuc_DeleEvent.Broadcast();
		if(Fuc_DeleEvent_OneParam.IsBound() == true) Fuc_DeleEvent_OneParam.Broadcast(789);
		if (Fuc_Dynamic.IsBound() == true) Fuc_Dynamic.Broadcast();
		if (Fuc_Dynamic_OneParam.IsBound() == true) Fuc_Dynamic_OneParam.Broadcast(999);
}

 

3. 델리게이트 함수 바인드

void ATestPlayer::BeginPlay()
{
	Super::BeginPlay();

	//! Delegate 등록하기
	if (m_pBoom != nullptr)
	{
		m_pBoom->Fuc_DeleSingle.BindUFunction(this, FName("CallDeleFunc_Single"));
		m_pBoom->Fuc_DeleSingle_OneParam.BindUFunction(this, FName("CallDeleFunc_Single_OneParam"));

		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_1"));
		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_2"));

		m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_1"));
		m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_2"));
		
		m_pBoom->Fuc_DeleEvent.AddUFunction(this, FName("CallDeleFunc_Event"));
		m_pBoom->Fuc_DeleEvent_OneParam.AddUFunction(this, FName("CallDeleFunc_Event_OneParam"));

		m_pBoom->Fuc_Dynamic.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic);
		m_pBoom->Fuc_Dynamic_OneParam.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic_OneParam);
	}
}

 

 

https://darkcatgame.tistory.com/66

반응형

언리얼엔진하면은 다룬다고 하면 알아야할 몇가지들이 있다. 그 중에서 언리얼엔진에서 베이스가 되는 프로퍼티 시스템인 리플렉션 이 있다.

 

 리플렉션이라는 것은 자바나 C#등에선 지원하지만, 언리얼엔진에서 사용되는 C++ 언어에서는 지원하지 않아 언리얼엔진에서 구현되어 있는 시스템이라고 했다.

 

하지만 난 리플렉션이라는게 뭐길래 엔진에서 구현해서 지원해주는것일까??  처음들어봐서 자바에서 어떻게 사용되는지 먼저 찾아보았다.

 

 

 

 

자바에서의 리플렉션 개념

자바에서의 리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다. 
(투영, 반사 라는 사전적인 의미를 지니고 있다.)

자바의 Reflection은 JVM에서 실행되는 애플리케이션의 런타임 동작을 검사하거나 수정할 수 있는 기능이 필요한 프로그램에서 사용됩니다. 쉽게 말하자면, 클래스의 구조를 개발자가 확인할 수 있고, 값을 가져오거나 메소드를 호출하는데 사용됩니다.

 

간단히 요약하자면, 컴파일 시간이 아니라 런타임시간에 동적으로 특정 클래스의 정보 객체화를 통해 분석 및 추출해낼수 있는 프로그래밍 기법이라고 표현할수 있다. (개발 과정에서 개발자에게 조금 더 도움이 되는 시스템이라고 다가왔다.)

 

 

 

 

 

언리얼 엔진에서의 리플렉션 

이런 좋은 시스템이 C++에는 지원하지 않아, 언리얼 엔진에서 구현되어 있는데, 우선  관련 설명을 찾아보면

리플렉션(Reflection)은 프로그램이 실행시간에 자기 자신을 조사하는 기능입니다.

이는 엄청나게 유용한 데다 언리얼 엔진 테크놀로지의 근간을 이루는 것으로, 에디터의 디테일 패널, 시리얼라이제이션, 가비지 콜렉션, 네트워크 리플리케이션, 블루프린트/C++ 커뮤니케이션 등 다수의 시스템에 탑재된 것입니다.


언리얼엔진 홈페이지 중...

위에서 '자기 자신'은 클래스, 구조체, 함수, 멤버 변수, 열거형 등을 의미하고 있다.

수집 된 정보는 UClass에 보관된다. 런타임에는 GetClass() 함수를 통해 접근하고, 컴파일 타임에서는 StaticClass()를 사용해 접근한다. 해당 함수들은 언리얼 헤더 툴에 의해 자동으로 생성된다.

 

 

 

 

 

잠깐, 왜 언리얼에서는 프로퍼티 시스템 이라고도 하는가?

전형적으로 이러한 리플렉션은 '프로퍼티 시스템'이라고 부르는데,그 이유로는 아마도 '리플렉션(Reflection/반사)'은 그래픽 용어라서 혼돈을 빚을 수 있기 때문인것 같다.(개인적인 추측)

 

 

리플렉션을 알려주는 방법?

 Unreal Header Tool (UHT)가 그 프로젝트를 컴파일할 때 해당 정보를 수집한다.

헤더(.h)에 리플렉션이 있는 유형으로 알려주려면, 헤더 파일 상단에 특수한 include 를 추가해 줘야 하야한다.

 그러면 리플렉션이 있는 유형은 이 파일을 고려해야 한다는것 그리고 시스템 구현에도 필요함을 UHT 에 알려줍니다.

그 #include는 아래와 같다.

#include "FileName.generated.h"

 

위와 같은 헤더가 추가 된다면, 이제 열겨형/UENUM(), 클래스/UCLASS(), 구조체/USTRUCT(), 함수/UFUNCTION(), 멤버변수/UPROPERTY() 를 사용하여 헤더의 다양한 유형과 멤버 변수 주석을 달 수 있다.

이 매크로 각각은 유형 및 멤버 선언 전에 오며, 추가적인 지정자 키워드를 담을 수 있습니다. 

 

 

 

 

 

리플렉션작동 원리

UBT(Unreal Build Tool) 는 그 역할을 위해 헤더를 스캔한 다음 리플렉션된 유형이 최소 하나 있는 헤더가 들어있는 모듈을 기억.

그 헤더 중 어떤 것이든 지난 번 컴파일 이후 변경되었다면, UHT 를 실행하여 리플렉션 데이터를 수집하고 업데이트.

UHT 는 헤더를 파싱하고, 리플렉션 데이터 세트를 빌드한 다음, (모듈별.generated.inl 에 기여하는) 리플렉션 데이터가 들어있는 C++ 코드를 생성할 뿐만 아니라, (헤더별 .generated.h 인) 다양한 헬퍼 및 thunk 함수도 생성.

 

요약 : 

1. UBT가 리플렉션 키워드 탐색

2. UHT가 해당 .cpp를 파싱

3. 리플렉션 데이터 정보를 수집

4. 수집된 정보는 별개의 C++ 코드 .generated.h / .cpp로 저장

5. 빌드 시 기존 코드에 .generated.h 코드를 추가해 컴파일

 

 

이렇게 UHT에 수집된 리플렉션 데이터는 별개의 코드로 저장되고 클래스이름.generated가 붙는다.

언리얼 실행환경은 이를 사용해 언리얼 오브젝트를 관리하고 에디터에서는 에디터에서 편집할 수 있는 인터페이스를 제공한다.

해당 파일들을 프로젝트 폴더에서 \Intermediate 폴더에 저장 된다. 클래스.h의 내용이 변경될때마다 자동 생성, 기존 파일을 덮어 쓴다.

 

 

 

generated 함수에는 StaticClass() / StaticStruct() 같은 것이 포함되어 있어, 유형에 대한 리플렉션 데이터를 구하는 것이 쉬워질 뿐만 아니라, 블루프린트나 네트워크 리플리케이션에서 C++ 함수를 호출하는 데 사용되는 thunk 를 구하는 것도 쉬워집니다.  이는 클래스나 구조체의 일부로 선언되어야 하며, GENERATED_UCLASS_BODY() 또는 GENERATED_USTRUCT_BODY() 매크로가 리플렉션된 유형에 포함되어야 하는지에 대한 이유가 됩니다.

이 매크로를 정의하는 #include 'TypeName.generated.h' 는 물론입니다.

 

 

 

ref : https://hyo-ue4study.tistory.com/182

 

반응형

 

 

Damage is a common concept in video games. Because of this, one of the default features in Unreal Engine is a framework for dealing and receiving damage.

This tutorial is the first in a series about damage, and aims to give you an introduction to the Damage system in blueprint and C++.

Damage in UnrealPermalink

To borrow a phrase from Alex Forsythe: With Unreal, damage comes standard. It’s a feature that is part of the Actor class, meaning that every actor class that you’ve created so far already natively supports dealing & receiving damage!

Let’s start this tutorial with a quick introduction to the concept of instigator. After that, we’ll look into how damage should be dealt and received, both in blueprint and C++.

InstigatorPermalink

If you’ve been working in Unreal, you might already have seen the term “instigator” a couple of times. Every actor has an Instigator property, it is a reference to a Pawn and represents the pawn/character responsible for any actions the spawned actor will do, like dealing damage. A very practical example is a cannonball, the instigator would be the pawn firing the cannon.

The instigator is usually set when you spawn the actor. In blueprints, this is done like this:

In C++, you can set the instigator pawn in the FActorSpawnParameters struct:

void AYourPawn::SpawnProjectile(const TSubclassOf<AActor> ProjectileClass)
{
    // FP_MuzzleLocation must be a valid component.
    check(FP_MuzzleLocation != nullptr);

    // Spawn a projectile at a specified transform, with instigator set to this pawn.
    const FTransform& SpawnTransform = FP_MuzzleLocation->GetComponentTransform();
    FActorSpawnParameters SpawnParams;
    SpawnParams.Instigator = this;
    World->SpawnActor<AActor>(ProjectileClass, SpawnTransform, SpawnParams);
}

It is also possible to change the instigator after spawning by simply calling SetInstigator.

The concept of instigators is incredibly useful for damage: It allows a damage receiver to act depending on who was responsible for dealing the damage. The classroom example for this is friendly fire: If you receive damage and the instigator of the damage is someone on your own team, you might decide to ignore the damage if friendly fire is disabled.

Dealing damagePermalink

To inflict damage on an actor with blueprints, simply call the ApplyDamage function:

Let’s briefly talk about some of the parameters:

  • Damaged Actor: The actor you’re dealing damage to.
  • Event Instigator: The controller that is responsible for dealing this damage. You can obtain the instigator controller using the GetInstigatorController node.
  • Damage Causer: The actor that actually dealt this damage (e.g. a projectile or grenade).
  • Damage Type Class: An object that can influence damage calculations. This deserves a tutorial on it’s own so we will just keep it empty for now.

In addition to ApplyDamage, there are also separate, specialized functions for dealing point and radial damage. Point damage is for damage at a particular point, coming from a particular direction (like being hit with a bullet). Radial damage is for damage in a wider area, possibily affecting multiple actors (like a grenade blast). More about point and radial damage in a future tutorial.

The C++ equivalent for dealing damage is AActor::TakeDamage. While the blueprint equivalent has different functions for each damage variant (i.e. point, radius, etc.), the TakeDamage function combines all of these into one with a FDamageEvent struct to differentiate between the variants.

Dealing damage in C++ is pretty simple though, here is the equivalent for the above blueprint example:

void AYourActor::DealDamageTo(class AActor* OtherActor)
{
    // Much like the blueprint example, we're not using DamageType yet.
    OtherActor->TakeDamage(4.2f, FDamageEvent(), GetInstigatorController(), this);
}

For dealing point and radial damage, you will need to supply a FPointDamageEvent or FRadialDamageEvent with the proper arguments, instead of the FDamageEvent. For applying radial damage, I recommend using the UGameplayStatics::ApplyRadialDamage helper function instead.

Responding to damagePermalink

Responding to damage in an actor is as simple as overriding the AnyDamage function in blueprint. For C++ things are a bit more tricky, we’ll get to that after the blueprint stuff.

In addition to the any damage function, there are also separate functions for receiving point and radial damage. Keep in mind that when you receive point or radial damage, AnyDamage will still get called!

The following is an example of responding to damage blueprint, simply override the AnyDamage function:

Most of the function arguments here are the same as the values you provided to the ApplyDamage function. The only exception is Damage Type, which is now an object reference rather than a class reference, more info on this in a future tutorial.

In addition to AnyDamage, there is also the OnTakeAnyDamage event dispatcher. This event is particularly useful if you have a component that takes care of dealing with damage, rather than the actor itself:

Unfortunately, the C++ function ReceiveAnyDamage in AActor was not made virtual by Epic, so it cannot be overridden. The next best way is to override the TakeDamage function instead (InternalTakeRadialDamage and InternalTakePointDamage exist for radial and point damage respectively):

float AYourActor::TakeDamage(const float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    // If you need the DamageType object like in blueprint, this is how you do it:
    UDamageType const* const DamageTypeCDO = DamageEvent.DamageTypeClass ? DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>() : GetDefault<UDamageType>();

    // Make sure to call Super so blueprint & event dispatchers still fire.
    return Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
}

Fortunately, the OnTakeAnyDamage delegate can simply be bound to in C++, just like with blueprint:

void UYourActorComponent::BeginPlay()
{
    Super::BeginPlay();

    // Subscribe to the owner receiving damage event.
    GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UYourActorComponent::OnTakeRadialDamage);
}

void UYourActorComponent::OnTakeRadialDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
{
    // Your damage handling code here..
}

Conclusion

This tutorial has covered the basics of damage: It introduced the concept instigators, how damage should be dealt and how it should be received, both in blueprint and C++.

 

 

 

 

 

 

https://jasperdelaat.com/unreal-engine/damage-1/

반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

[UE] 델리게이트 종류와 차이  (0) 2023.03.08
리플렉션  (0) 2023.03.07
애니메이션 디스턴스매칭  (0) 2023.02.20
TSharedPtr<.... , ESPMode::ThreadSafe> 스레드 안정성  (0) 2023.01.20
Widget for painting  (0) 2022.11.23

UE5 기능이긴한데 카테고리 만들기가 귀찮음으로..

 

로코모션 : 한 지점에서 다른 지점으로 이동하는 행위 또는 움직이는 방법의 정의다

로코모션의 퀄리티는 발이 미끄러지지 않게 처리하는 것이 핵심이다

게임 액터와 애니메이션 재생이 다르거나 블랜딩 할때 차이로 인해 미끄러지는 현상이 발생

 

루트모션이란 : 애니메이션 시퀀스의 루트본 움직임을 게임 앤터의 움직임으로 전환하는 기능이다

 

루트모션의 경우 애니메이션 블랜딩 할때 루트본도 같이 블랜딩 되기 때문에 미끄러짐 현상이 나타난다

 

이를 해결 하기 위해 대스턴스매칭 또는 포즈워핑이 사용된다

 

디스턴스매칭 : 거리를 기반으로 애니메이션 재생타이밍을 조절한다

 

 

디스턴스 매칭 미적용시 (속도가 감속 하는 경우인 경우)

5미터의 거리를 감속하면서 애니메이션을 멈출때 속도가 줄어들어도 3미터 거리 이동으로 이동하면서 정지한다고 하면 

상단 처럼 5미터 이동 발이 하단의 3미터 거리에서도 그대로 적용되어 발이 밀리게 된다

 

 

디스턴스 매칭 => 거리기반 디스턴스 매칭

거리기반 디스턴스 매칭의 경우

 

  1. 거리에 맞도록 애니메이션 시작 타이밍을 변경한다
  2. 5미터의 애니메이션 재생 중에 남은 거리가 3미터라면 3미터를 이동하는 애니메이션으로 재생을 하도록 하여 발이 밀리지 않도록 한다

좌 : 미적용, 우 : 적용

 

 

 

 

 

반복되는 루핑 애니메이션의 경우 거리를 이용할수 없기 때문에 프레임당 이동 거리를 기준으로 애니메이션 재생 속도를 조절하게 된다

 

속도기반 디스턴스 매칭

동일한 애니를 속도에 따라 재생 속도를 달리 한것

왼쪽은 초당 속도 200 미터, 오른쪽은 초당 속도 1000 미터 인 속도기반 디스턴스 매칭

 

 

  1. 디스턴스매칭을 위해 필요한 것은 특정 시점에서 캐릭터가 있을 위치를 예측하는것이 필요한데
    캐릭터가 정지할 위치를 알아야 현재 위치와 정지 위치 사이에 거리를 구하고 거리를 구해야 거리기반 디스턴스 매칭을 작동시킬수 있기 때문 
  2. 각 애니메이션에서 루트본의 이동거리 정보가 필요하다
    디스턴스 매칭에서 이 거리 정보를 기반으로 애니메이션의 시작지점을 결정하거나 애니메이션 재생속도를 결정하기 때문이다

 

 

[사용법 구현] 

우선 현재 캐릭터의 가속 여부를 구현다 HasAcceleration 변수에 매 프레임마다 저장

 

 

HasVelocity : 현재 캐릭터의 속도 여부도 저장한다

 

 

 

 

 

단방향용 로코모션용 애님 스테이트 머신 구성

 

캐릭터가 정지할 위치를 예측하기 위해 사용 되는 노드

입력들은 위에서 구한 것들을 입력해준다

위 출력을 아래 처럼 Vector LengthXY 를 통해 DistanceToTarget 변수에 저장

 

애니메이션이 끝나기 전에 캐릭터가 정지 위치에 도착하게 되면 애니메이션도 멈춰 버리기 때문에 자연스러운 정지를 위해서 DistanceToTarget 값이 0 보다 큰지 봐서 만약 크다면 아직 거리가 남아 있는 것이기 때문에 디스턴스 매칭을 작동시켜야 한다 아래처럼..

그런데 0 보다 작은 경우, 즉 이미 위치에 도달한 경우라면 AdvanceTime 을 이용해서 애니메이션을 원래의 속도로 플레이 시키도록 한다

 

 

 

 

 

스트라이드 워핑과 오리엔테이션 워핑을 조합하면 더 자연스러운 애니를 루트모션에서 만들 수 있다

 

스트라이드 워핑 : 이동 속도에 따라 포폭 조절, 디스턴스 매칭으로 우선 느려질때 애니메이션이 느린 걸로 교체되기 때문에 이때 스트라이드 워핑과 더 잘맞아 떨어짐

 

오리엔테이션 워핑  : 방향에 따라 하체 회전 , 만약 애니가 전,후 , 좌, 우 4방향 밖에 없다면 대각선일때 애니가 이상해지는데   

 

 적용 전 앞-우측 방향 진행시


적용 후 앞-우측 방향 진행

하체가 회전 된걸 볼 수 있다

 

 

ref : https://youtu.be/RFNGB4ZTusQ

ref : https://docs.unrealengine.com/5.0/ko/root-motion-in-unreal-engine/

반응형

스레드 안정성

기본적으로 스마트 포인터는 싱글 스레드가 접근하는 것이 안전합니다. 멀티 스레드가 접근해야 한다면 스마트 포인터 클래스의 스레드 세이프 버전을 사용하세요:

  • TSharedPtr<T, ESPMode::ThreadSafe>
  • TSharedRef<T, ESPMode::ThreadSafe>
  • TWeakPtr<T, ESPMode::ThreadSafe>
  • TSharedFromThis<T, ESPMode::ThreadSafe>

이러한 스레드 세이프 버전은 원자적(atomic) 참조 카운팅으로 인해 디폴트보다 다소 느리지만 그 비헤이비어는 일반 C++ 포인터와 같습니다:

  • 읽기와 복사본은 항상 스레드 세이프입니다.
  • 안전성을 위해 쓰기와 초기화는 반드시 동기화되어야 합니다.

하나 이상의 스레드가 포인터에 접근하지 않는다는 것이 확실하다면, 스레드 세이프 버전을 사용하지 않음으로써 퍼포먼스를 향상시킬 수 있습니다.

팁 및 제한사항

  • 가급적이면 함수에 데이터를 TSharedRef 또는 TSharedPtr 매개변수로 넣지 않는 것을 권장합니다. 이러한 데이터의 해제와 참조 카운팅으로 인해 오버헤드가 발생하게 됩니다. 그 대안으로, 레퍼런스된 오브젝트를 ‘const &'로 넣으세요. (즉 읽기로만 사용 할때를 말하는 것)
  • 쉐어드 포인터를 불완전한 타입/형식으로 미리 선언할 수 있습니
  • 쉐어드 포인터는 언리얼 오브젝트(UObject 와 이로부터 파생된 클래스)와 호환되지 않습니다. 언리얼 엔진은 ‘UObject' 관리를 위한 별도의 메모리 관리 시스템이 있으며 (언리얼 오브젝트 처리 문서를 참고하세요) 두 시스템은 완전히 다른 시스템입니다

 

 

ref : https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/SmartPointerLibrary/

반응형

 

 

In Unreal Engine blueprint widget, you can create different shapes through an array of points. There is no line drawing in the user widget with an array of points in C++, so we will use UWidgetBlueprintLibrary. In order to draw the shape, we must use UWidgetBlueprintLibrary::DrawLines. And then another trouble awaits us — we cann’t override OnPaint.

All animals are equal, but some animals are more equal than others.

So, we’ll just override NativePaint.

int32 UPaintWidget::NativePaint(const FPaintArgs &Args, 
const FGeometry &AllottedGeometry, const FSlateRect &MyCullingRect, FSlateWindowElementList &OutDrawElements, int32 LayerId, const FWidgetStyle &InWidgetStyle, bool bParentEnabled) const
{
    Super::NativePaint(Args, AllottedGeometry, MyCullingRect,  OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);    FPaintContext Context(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);    UWidgetBlueprintLibrary::DrawLines(Context, Points, FLinearColor::Blue, true, 10.0f);    return LayerId + 1;
}

We simply created a variable of the array of points in the class and by updating this array, our shape is also updated.

The complete code of this project is available on GitHub.

 

 

https://medium.com/@qerrant/gesture-recognizer-for-unreal-engine-24422d5868ba

반응형

 

javah 툴이라는 것이 자바의 코드중 cpp 에서 사용 할수 있도록 헤드를 만들어 주는 도구 역할을 한다

 

그리고 몇몇 네이밍 규칙들을 맞춰줘야 한다 _ 같은 것들

 

규칙들은 자바 코드 쪽에서

JNIEXPORT jintArray JNICALL Java_org_in_jnitest_JavaJNITest_OverloadedMethod___3II
   (JNIEnv *, jobject, jintArray, jint);


JNIEXPORT jdouble JNICALL Java_org_in_jnitest_JavaJNITest_OverloadedMethod__I_3Ljava_lang_String_2Ljava_lang_String_2
   (JNIEnv *, jobject, jint, jobjectArray, jstring);

 

JNIEnv *, jobject

이렇게 두개의 인자가 앞에 들어간다는 것이다, 그 이후엔 원하는 타입

 

 

 

 

The concept of function and data type is the basic building block of any programming language. When working with heterogeneous languages’ features, bridging their differences is crucial. JNI (Java Native Interface) provides a native interface through which Java communicates with other languages. In JNI, the first hurdle of establishing communication with languages other than Java is the distinctive feature of data type and function mapping between two unmatched programming languages. This communication difference begins with its data type and function representation because they are the basic structure of any code. Here we shall use C/C++ as the native language support for Java while working with JNI. The article tries to explicate function and data type mapping between Java and C/C++ with some appropriate examples.

Native Method Naming Conventions

Declaring a native method inside a Java code is as simple as adding just the native keyword. The complexity creeps in as soon as mapping occurs with the native language. The code looks mangled beyond recognition. Let’s get into the details and try to identify some of its common pattern.

As we know, C/C++ header files for the native method declared in the Java code are auto generated by the javah tool. The function naming scheme in the header file is mapped based on the following conventions:

  • the native method name always begins with ‘Java’ as the first word.
  • The package/sub package separator for the fully qualified name is separated by an underscore character.
  • For an overloaded method name, it begins with ‘Java’ as the first word, then fully qualified package/sub package name separated by underscore characters, and at the end, two underscore characters with the mangled method’s signature.

Undoubtedly, the native method signature is clumsy in appearance, yet the scheme has a logical pattern. Because they are auto generated with the javah tool, programmers need not worry about the naming convention. What we can do while implementing the methods in the C/C++ file is simply copy-paste the function prototype from the header file and concentrate on defining our logic rather than brooding over complexity. However, to get a glimpse of native method naming convention, observe the following Java file and its corresponding C/C++ header file generated through the javah tool.

 

 

package org.in.jnitest;

public class JavaJNITest {
   private native void method1();
   private native void method2(String str);
   private native int[] OverloadedMethod(int[] intArray,
      int intValue);
   private native double OverloadedMethod(int intValue,
      String strArray[], String str);
   private native double OverloadedMethod(double
      doubleValue, String str);
   private native double OverloadedMethod(short
      shortArray, String str);
}




/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_in_jnitest_JavaJNITest */

#ifndef _Included_org_in_jnitest_JavaJNITest
#define _Included_org_in_jnitest_JavaJNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_in_jnitest_JavaJNITest
 * Method:    method1
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_in_jnitest_JavaJNITest_method1
   (JNIEnv *, jobject);

/*
 * Class:      org_in_jnitest_JavaJNITest
 * Method:     method2
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_org_in_jnitest_JavaJNITest_method2
   (JNIEnv *, jobject, jstring);

/*
 * Class:     org_in_jnitest_JavaJNITest
 * Method:    OverloadedMethod
 * Signature: ([II)[I
 */
JNIEXPORT jintArray JNICALL Java_org_in_jnitest_JavaJNITest_OverloadedMethod___3II
   (JNIEnv *, jobject, jintArray, jint);

/*
 * Class:     org_in_jnitest_JavaJNITest
 * Method:    OverloadedMethod
 * Signature: (I[Ljava/lang/String;Ljava/lang/String;)D
 */
JNIEXPORT jdouble JNICALL Java_org_in_jnitest_JavaJNITest_OverloadedMethod__I_3Ljava_lang_String_2Ljava_lang_String_2
   (JNIEnv *, jobject, jint, jobjectArray, jstring);

/*
 * Class:     org_in_jnitest_JavaJNITest
 * Method:    OverloadedMethod
 * Signature: (DLjava/lang/String;)D
 */
JNIEXPORT jdouble JNICALL Java_org_in_jnitest_JavaJNITest_OverloadedMethod__DLjava_lang_String_2
   (JNIEnv *, jobject, jdouble, jstring);

/*
 * Class:     org_in_jnitest_JavaJNITest
 * Method:    OverloadedMethod
 * Signature: (SLjava/lang/String;)D
 */
JNIEXPORT jdouble JNICALL Java_org_in_jnitest_JavaJNITest_OverloadedMethod__SLjava_lang_String_2
   (JNIEnv *, jobject, jshort, jstring);

#ifdef __cplusplus
}
#endif
#endif

 

 

 

그리고 아래 데로 타입들을 맞춰 주면 된다

native 기본 데이터 타입은 j 로 시작하고 그 뒤를 잊는 소문자는 java 타입 명과 동일하다

 

Data Type Mapping

The mapping between Java data types and data types used in native code is pretty straightforward. The naming scheme remains the same: the native data type name is preceded by the character ‘j’, followed by all lowercase data type name equivalent to Java. JNI also includes another data type named jsize, which stores the length or an array or string.

Java primitive data type Native primitive data type Description
void void None
byte jbyte 8-bit signed. Range is -27 to 27 – 1
int jint 32-bit signed. Range is -231 to 231 – 1
float jfloat 32 bits. Represent a real number as small as 1.4 x 10-45 and as big as 3.4 x 1038 (approx.), positive or negative
double jdouble 64 bits. Represent a real number as small as 4.9 x 10-324 and as big as 1.7 x 10308 (approx.), positive or negative
char jchar 16-bit unsigned. Range is 0 to 65535
long jlong 64-bit signed. Range -263 to 263 – 1
short jshort 16-bit signed. Range is -215 to 215 – 1
boolean jboolean Unsigned 8 bits. true and false

In case of reference types, JNI defines a few of the most common references, such as string or class, and so forth. Other references are mapped to the JNI jobject. String, Class, and Throwable are just exceptional reference types that otherwise can be handled by jobject.

Java Reference type JNI type Description
java.lang.object jobject Any Java object
java.lang.String jstring String representation
java.lang.Class jclass Java class object
java.lang.Throwable jthrowable Java throwable object

JNI uses jarray for a generic array type that represents the Java array type. Another data type, named jvalue, is nothing but a union type defined in C/C++.

Conclusion

These are some of the primary yet very vital concepts to begin coding in JNI. Although data types of most languages are more or less similar, when it comes to mapping between two different languages, the question always arises about how they are mapped. Observe that the classic C/C++ data type naming is not followed in JNI. The representation may be the same, yet the naming scheme differs. Here, we have refreshed the concept, thus paving way for more hands on work in the next set of articles.

 

 

 

 

ref : https://www.developer.com/microsoft/c-sharp/jni-data-type-mapping-to-c-c/

 

 

 

반응형

자바에서 AAA.java 에서 

아래 함수를 선언만 해놓는다


public class AAA{
@Keep
public static native void onImageReady(byte[] buffer, int width, int height);

 

그리고 cpp 에서 onImageReady 아래 처럼 만들어 놓으면
java 에서 cpp 호출 할때 이벤트 처럼 호출이 가능해진다

언리얼에서 자바를 호출 할수 있게 가능하게 만들어줌

 

w자바에서 onImageReady 호출

_activity.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					Bitmap bm = BitmapFactory.decodeFile(path);
					Bitmap bitmap = rotate(bm, path);

					onImageReady(getBitmapBytes(bitmap), bitmap.getWidth(), bitmap.getHeight());

 




UE4 C++ 코드

#if PLATFORM_ANDROID

JNI_METHOD void Java_com_회사명_androidgoodies_AAA_onImageReady(JNIEnv* env, jclass clazz, jbyteArray buffer, int width, int height)
{
UE_LOG(LogTemp, Warning, TEXT("C++!"));

JNIEnv* Env = FAndroidApplication::GetJavaEnv();
bufferRef = static_cast<jbyteArray>(Env->NewGlobalRef(buffer));

Env->DeleteLocalRef(buffer);
AsyncTask(ENamedThreads::GameThread, [=]() {
TArray<uint8> byteArray = AGArrayConvertor::ConvertToByteArray(bufferRef);
UTexture2D* result = AGMediaUtils::TextureFromByteArray(byteArray, width, height);

UAGPickersBPL::OnImageReady(result);

byteArray.Empty();
});
}

 

 

본 코드는 AndroidGoodies 플러그인이 필요하다

 

 

https://www.unrealengine.com/marketplace/ko/product/android-native-goodies

반응형

 

RPC (Remote Procedure Call) 는 로컬에서 호출되지만 (호출하는 머신과는) 다른 머신에서 원격 실행되는 함수를 말합니다.

RPC 함수는 매우 유용하게 사용될 수 있으며, 네트워크 연결을 통해 클라이언트와 서버 사이에 메시지를 전송할 수 있습니다.

이 기능의 주요 용도는 속성상 장식이나 휘발성인 비신뢰성 게임플레이 이벤트를 위한 것입니다. 이는 사운드 재생, 파티클 스폰, 액터의 핵심적인 기능과는 무관한 일시적 효과와 같은 작업을 하는 이벤트를 포함합니다. 기존에 이러한 유형의 이벤트는 종종 액터 프로퍼티를 통해 리플리케이트되고는 했습니다.

RPC 사용시 오너십 작동 방식 을 이해해 두는 것이 중요합니다. 대부분의 RPC 실행 장소를 결정하기 때문입니다.

RPC 사용하기

함수를 RPC 로 선언하려면 UFUNCTION 선언에 Server, Client, NetMulticast 키워드를 붙여주기만 하면 됩니다.

예를 들어 함수를 서버에서 호출되지만 클라이언트에서 실행되는 RPC 로 선언하려면, 이렇게 합니다:

UFUNCTION( Client )
void ClientRPCFunction();

함수를 클라이언트에서 호출되지만 서버에서 실행되는 RPC 로 선언하는 것은 Server 키워드를 사용한다는 것 빼고는 매우 비슷합니다:

UFUNCTION( Server )
void ServerRPCFunction();

Multicast 라 불리는 특수 유형 RPC 함수가 하나 더 있습니다. Multicast RPC 는 서버에서 호출된 다음 서버는 물론 현재 연결된 모든 클라이언트에서도 실행되도록 고안된 것입니다. 멀티캐스트 함수를 선언하려면 그냥 NetMulticast 키워드를 사용하면 됩니다:

UFUNCTION( NetMulticast )
void MulticastRPCFunction();

멀티캐스트 RPC 는 클라이언트에서도 호출 가능하지만, 이 경우 로컬에서만 실행됩니다.

간단 팁

함수 초반에 Client, Server, Multicast 키워드를 앞쪽에 어떻게 붙였는지 보세요. 저희 내부적으로 합의한 규칙으로, 이 함수를 사용하는 프로그래머들에게 이 함수가 각각 클라이언트, 서버, 모든 클라이언트에서 호출된다는 것을 알리기 위한 것입니다.

멀티플레이어 세션 도중 이 함수가 어느 머신에서 호출되는지 한 눈에 알아볼 수 있기에 매우 유용합니다.

요건 및 주의사항

RPC 의 정상 작동을 위해 충족시켜야 하는 요건이 몇 가지 있습니다:

  1. Actor 에서 호출되어야 합니다.
  2. Actor 는 빈드시 replicated 여야 합니다.
  3. 서버에서 호출되고 클라이언트에서 실행되는 RPC 의 경우, 해당 Actor 를 실제 소유하고 있는 클라이언트에서만 함수가 실행됩니다. (client)
  4. 클라이언트에서 호출되고 서버에서 실행되는 RPC 의 경우, 클라이언트는 RPC 가 호출되는 Actor 를 소유해야 합니다.
  5. Multicast RPC 는 예외입니다:
    • 서버에서 호출되는 경우, 서버에서는 로컬에서 실행될 뿐만 아니라 현재 연결된 모든 클라이언트에서도 실행됩니다.
    • 클라이언트에서 호출되는 경우, 로컬에서만 실행되며, 서버에서는 실행되지 않습니다.
    • 현재 멀티캐스트 이벤트에 대해 단순한 스로틀 조절 메카니즘이 있습니다. 멀티캐스트 함수는 주어진 액터의 네트워크 업데이트 기간동안 두 번 이상 리플리케이트되지 않습니다. 장기적으로 크로스 채널 트래픽 관리 및 스로틀 조절 지원을 개선시킬 계획입니다.

 

Autority

Role_Autority : 서버상에 존재하는 복제된 액터, 서버만이 액터의 변수와 상태에 대한 권한을 갖고 있음,
Role_AutonomousProxy : 소유한 클라이언트에 존재하는 액터의 버전으로 간주함, 클라와 서버간의 직접적인 RPC 송수신 등의 작업을 수행함
Role_SimulatedProsy : 다른 모든 클라이언트에 있는 액터 버전으로 간주한다, 순전히 시뮬레이터이며 액터 상태에 대한 아무런 권한이 없음(조정할수 없는 다른 캐릭터들)의 역할

 

(멀티 플레이 접속시) 서버에선 플레이어 컨트롤러 2개(플레이어 수만큼), 클라이언트에선 플레이어 컨트롤러가 1개이다

 

아래는 리슨 서버로 실행하여 서버 pc 에서 본 화면

 

  • 플레이어 컨트롤러는 소유 클라이언트에서 생성되며 플레이어 컨트롤러는 서버와의 모든 통신이 이뤄지는 채널이다
    (이것이 비 소유 클라이언트와의 차이임)
  • (멀티 플레이 접속시) 서버에선 플레이어 컨트롤러 2개(플레이어 수만큼)
    클라이언트에선 플레이어 컨트롤러가 1개이다

    각 컨트롤러는 서버와 소유 클라이언트 간에 복제된다, 같은 맥락으로 게임 모드는 네트워크 권한에서만 생성된다(이경우는 대게 서버임),  클라이언트와 서버 간의 게임 플레이어 정보를 전달하는 방법은 GameState 와 PlayerState 오브젝트를 통해 이뤄지는데 이 두 오브젝트도 연결된 모든 클라이언트에 복제된다

 

 

서버와 클라이언트 통신 할때 GameState 와 PlayerState 오브젝트를 통해서 정보 전달이 이뤄진다

그리고 이 두 오브젝트는 연결된 모든 클라이언트에 복제된다

 

Player state : 플레이어 상태 : 모든 플레이어는 서버(혹은 스탠드 얼론 게임)에 각자의 PlayerState를 가집니다. 

PlayerState는 모든 클라이언트에게 리플리케이트 되며

플레이어의 네트워크와 관련 정보를 지니게 된다, 플레이어 이름, 점수 등등

 

 

다음 표는 호출 액터의 소유권(가장 왼쪽 열)에 따라 주어진 유형의 RPC 실행 위치를 나타냅니다.

서버에서 호출된 RPC

액터 소유권리플리케이트 안됨NetMulticast서버클라이언트

 

RPC 는 호출 PC 에서 액터의 소유권을 보고 호출되는 대상이 1차적으로 정해지고, 그다음 프래그를 보고 어떻게 호출할지 2차적으로 결정 된다

 

Multicast 는 소유권과 관계 없이 동작하는 특성을 갖음

리플리케이트 안됨 = 리플리케이트 없는 상태일때를 말함

서버소유 액터 일때 NetMulticast 도 서버와 모든 클라이언트에서 실행이다

 

 

 

 

클라이언트에서 호출된 RPC

액터 소유권리플리케이트 안됨NetMulticast서버클라이언트

 

 


위 표를 기준으로 Multicast 예를 들자면

[호출 PC 가 서버에서 호출인 경우 ]

Multicast RPC 는 서버에서 호출되는 경우서버와 현재 연결된 모든 클라이언트에서 실행된다

 

[호출 PC 가 클라이언트에서 호출하는 경우 ]

클라이언트에서 호출되는 경우 소유권에 관계 없이 로컬에서만 실행되며 서버에서 실행되지 않는다

 

 

 

신뢰성

기본적으로 RPC 는 비신뢰성입니다. RPC 호출이 원격 머신에서 확실히 실행되도록 하기 위해서는 Reliable 키워드를 붙이면 됩니다:

UFUNCTION( Client, Reliable )
void ClientRPCFunction();

블루프린트

RPC 로 마킹된 함수는 블루프린트에서 호출해도 리플리케이트됩니다. 이 경우 마치 C++ 에서 호출된 것과 같은 규칙을 따릅니다. 현재 블루프린트에서 동적으로 함수를 RPC 마킹하는 것은 가능하지 않습니다.

하지만 Custom event 는 블루프린트 에디터 안에서 replicated 마킹 가능합니다.

이 기능을 사용하기 위해서는, 이벤트 그래프에서 custom event 를 새로 만듭니다. custom event 에 클릭한 다음 디테일 뷰에서 Replication 세팅을 편집합니다:

인증

최근, 악성 데이터/입력 감지를 위한 관문 역할을 위해 RPC 에 인증(validation) 함수를 추가하는 기능이 생겼습니다. RPC 에 대한 인증 함수가 악성 파라미터를 감지한 경우, 해당 RPC 를 호출한 클라이언트/서버 연결을 끊도록 시스템에 알린다는 개념입니다.

RPC 에 대해 인증 함수를 선언하려면, UFUNCTION 선언문에 WithValidation 키워드를 추가해 주기만 하면 됩니다:

UFUNCTION( Server, WithValidation )
void SomeRPCFunction( int32 AddHealth );

그런 다음 Implementation 함수 옆 어딘가에 Validate 함수를 넣어주면 됩니다.

bool SomeRPCFunction_Validate( int32 AddHealth )
{
    if ( AddHealth > MAX_ADD_HEALTH )
    {
        return false;                       // This will disconnect the caller
    }
    return true;                              // This will allow the RPC to be called
}

void SomeRPCFunction_Implementation( int32 AddHealth )
{
    Health += AddHealth;
}

좀 더 최근에, 클라이언트->서버 RPC 의 경우 _Validation 함수를 포함하도록 UHT 를 변경했습니다. 서버 RPC 함수의 안전성 확보를 위해, 알려진 모든 입력 제한에 대해 각각의 모든 파라미터의 유효성 검사를 위한 코드를 쉽게 추가할 수 있도록 하기 위한 것입니다.

 

 

ref : https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Actors/RPCs/

반응형

 

 

I have data endpoint which returns a json like [{"Name": "Alex", "Age": 24}]

now if i Deserialize this as an JsonObject It fails as it's an array with no name how can I get the data inside the array?

 

 

Here is an snippet from my code. Should help.

TSharedRef<TJsonReader<>> reader = TJsonReaderFactory<>::Create(data);

TArray< TSharedPtr<FJsonValue> > ue4ObjectArray;

successful = FJsonSerializer::Deserialize(reader, ue4ObjectArray);

 

 

TArray<TSharedPtr<FJsonValue>> objArray = JsonObject->GetArrayField(TEXT("AwesomeStructs"));

for (int32 i = 0; i < objArray.Num(); i++)
	{
		TSharedPtr<FJsonValue> value = objArray[i];
		TSharedPtr<FJsonObject> json = value->AsObject();

		FVector  AwesomeVector  = ParseAsVector(json, FString("AwesomeVector"));
		bool     AwesomeBoolean = json->GetBoolField(TEXT("AwesomeBoolean"));
		float    AwesomeFloat   = json->GetNumberField(TEXT("AwesomeFloat"));
		int32    AwesomeInteger = json->GetNumberField(TEXT("AwesomeInteger"));
		FRotator AwesomeRotator = ParseAsRotator(json, FString("AwesomeRotator"));
		
		FAwesomeStruct AwesomeStruct = FAwesomeStruct::BuildAwesomeStruct(
			AwesomeVector,AwesomeBoolean,AwesomeFloat,AwesomeInteger,AwesomeRotator
		);

		AwesomeStructs.Push(AwesomeStruct);
	}
}

 

 

Array 읽기

TArray<TSharedPtr<FJsonValue>> Array = JsonObject->GetArrayField(TEXT("FieldName"));

또는

const TArray<TSharedPtr<FJsonValue>>* Value;
if(JsonObject->TryGetArrayField(TEXT("FieldName"), Value)) {
	// When Parsing Success.
} else {
	// When Parsing Failed.
}

 

ref : https://ballbot.tistory.com/45

ref : https://www.reddit.com/r/unrealengine/comments/shpgbw/json_array_to_unreal_engine_c_or_varest/

ref : https://gist.github.com/hanzochang/07eba255ce0d1695582a92e98e973200

반응형
2. Abstract
 
해당 클래스가 추상 클래스임을 나타내는 지정자이다.
당연하게도 인스턴스를 생성하지 못하며, 에디터 상에 그 자체로 추가될 수 없다.
 
엔진에 미리 정의된 클래스 중에선, 대표적으로 AActor 클래스가 추상 클래스이다.
 
  1. UCLASS(abstract)
  2. class ENGINE_API AActor : public UObject
  3. {
  4.     ...
  5. }

 

 

Hi, I’m wondering why if writing pure virtual methods in my abstract base classes is supported? Currently I’ve got a class that inherits from actor and I’ve marked it as UCLASS(abstract) with a pure virtual method.

I then have a class that inherits from this class, and implements the pure virtual method. When I compile this I get the following error.

Only functions in the second interface class can be declared abstract.

Does that mean if I want to use pure virtual methods in my abstract class that I have to use a separate interface? If that’s the case, then what is the purpose of declaring my class abstract? Aren’t pure virtual methods what make classes abstract?

UCLASS(abstract)
class ASpawnVolume : public AActor
{
	GENERATED_UCLASS_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "Spawning")
	virtual void SpawnUnitWithTransform(TSubclassOf<AActor> unitType, FTransform trans);

	UFUNCTION(BlueprintCallable, Category = "Spawning")
	virtual void SpawnUnit(TSubclassOf<AActor> unitType);

	UFUNCTION(BlueprintCallable, Category = "Spawning")
	virtual void SpawnUnits(TSubclassOf<AActor> unitType, uint32 count);

	UFUNCTION(BlueprintCallable, Category = "Spawning")
	virtual FTransform GetRandomTransformInVolume() = 0;
};

 

 

 

UClasses cannot really be abstract in the C++ sense, because the UObject sub-system requires that each class can be instantiated (it creates at least one instance of each class as a so called Class Default Object [CDO] that holds the default properties of that class). Therefore, every class method must have an implementation, even if it does nothing.

That being said, you can get similar behavior by decorating your inline method implementations with the PURE_VIRTUAL macro. This will tell the UObject sub-system that your intent is to declare a pure virtual method. So even though the method is not pure virtual in the C++ sense - it has a (possibly empty) function body - the compiler can still ensure that all child classes do supply an actual implementation.

For example, you could do:

virtual void SpawnUnit(TSubclassOf<AActor> unitType) PURE_VIRTUAL(ASpawnVolume::CanRedo,);

You can find many more examples in the code base, including methods with return values by searching for PURE_VIRTUAL.

 

 

 

I’d like to add to this that the definition of the PURE_VIRTUAL macro is defined as

#define PURE_VIRTUAL(func,extra) { LowLevelFatalError(TEXT("Pure virtual not implemented (%s)"), TEXT(#func)); extra }.

 

 

In case you want a pure virtual with return type, the return must be part of the PURE_VIRTUAL macro. as it must be a complete function in the CPP sense.

	virtual bool IsValid() PURE_VIRTUAL(MyClass::IsValid, return false;);

 

ref : http://egloos.zum.com/sweeper/v/3205742

ref : https://forums.unrealengine.com/t/how-do-i-implement-pure-virtual-methods/280323

반응형

This Repository shows a Code Example of how to use UE4 Online Subsystem Sessions in C++.

If you need some additional information, consider heading over to the matching Blog Post: https://cedric-neukirchen.net/2021/06/27/ue4-multiplayer-sessions-in-c/

Project was created in Unreal Engine 4.26.2.

Please report any Issues with GitHubs Issues page.

Please utilize Pull Requests if you wish to suggest changes, improvements or updates.

 

 

ref : https://github.com/eXifreXi/CppSessions

 

GitHub - eXifreXi/CppSessions: Repository showing how to use UE4 OnlineSubsystem Sessions in C++.

Repository showing how to use UE4 OnlineSubsystem Sessions in C++. - GitHub - eXifreXi/CppSessions: Repository showing how to use UE4 OnlineSubsystem Sessions in C++.

github.com

반응형

 

Q :

Hello all!

I feel like this is a pretty simple one, but I can’t wrap my head around it.

In a multiplayer game with a dedicated server and multiple players in the level, what will GetPlayerController(0) return?

What if it’s not a dedicated server game?

Is the answer different when the code runs on the server or on each of the clients?

My first guess is that the first player spawned into the level has the index 0. My second guess is that the index 0 is always with the “local” player for clients; not sure what it would be for the server. I’m edging towards guess 1 at the moment.

 

 

A :

Player 0 is the first player created on the machine that is running the code.

So if you have 2 players on the Server and 2 players on a client and 2 players on another client for a total of 6 players in the game over 3 computers,

then the server’s 2 players will be at index 0 and 1,
and client A’s 2 players will be at index 0 and 1 on their machine. they will also be on the server’s machine but who knows what index they’ll be at there, and they’ll have IDs of -1 because they dont belong to the server machine.

client B, exact same situation.

So server will see 6 player controllers but only its own will have IDs >= 0 the others have IDs == -1

Each client will only see its own playercontrollers.

 

 

ref : https://forums.unrealengine.com/t/what-does-get-player-controller-player-index-0-return-in-multiplayer/431805

반응형
FString UMobileUtility::GetIPAddress()
{
	FString IpAddr("NONE");
	bool canBind = false;
	TSharedRef<FInternetAddr>LocalIp = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalHostAddr(*GLog, canBind);
	if (LocalIp->IsValid())
	{
		//If you want to add a port, write ture
		IpAddr = LocalIp->ToString(false); 
	}

	return IpAddr;
}
반응형
UFUNCTION()

이전 UPROPERTY()는 C++의 변수를 블루프린트와 연동하는 내용이였으면 이번엔 함수에 대해 포스팅을 합니다.

Category나 Meta키워드는 UPROPERTY와 중복되는 내용이니 이전 포스팅을 참고해 주시길 바랍니다.

 

 


BlueprintCallable

가장 기본적으로 C++에서 작성한 함수를 블루프린트에서 사용할 수 있게 하는 BlueprintCallable키워드 입니다,

간단한 기능이라서 블루프린트 내에서 따로 재정의 할 수 없습니다.

 

 


BlueprintPure

매우 간단하게 값 하나 반환하는 형태에 사용하는 BlueprintPure키워드입니다.

 

인자값을 추가로 넣는 것도 가능합니다.

 

 

UPARAM 메크로 - 레퍼런스를 인자로 받고 싶을때

인자값으로 레퍼런스 주소를 입력하고 싶어서 위 처럼 코드를 작성했으나 우측 처럼 레퍼런스 변수가 반환값으로 설정 되어 있습니다.

정확한 원인은 모르겠으나 UPARAM메크로를 사용해야 해결이 가능합니다.

 

레퍼런스 인자값 앞에 UPARAM(ref) 메크로를 붙임으로서 의도한대로 블루프린트에서 인자값을 받을 수 있게 되었습니다.

UPARAM의 정확한 기능은 모르겠지만 또 다른 사용 용도가 있습니다.

 

인자값에 DisplayName기능을 사용 할 수 있습니다.

 

 

 


BlueprintImplementableEvent

이전에 BlueprintCallable은 블루프린트가 C++ 함수를 호출하는 형태면 BlueprintImplementableEvent 는 C++이 블루프린트 함수를 호출하는 형태 입니다, 다만 C++에서 블루프린트에서 사용할 함수 원형을 작성해야 합니다.

위 처럼 코드를 작성하면 블루프린트에서 해당 함수를 사용 할 수 있으며

 

 

블루프린트에서 위 처럼 함수의 기능을 작성하고 

 

C++코드에서 해당 함수를 호출하면 위 처럼 동작을 하게 됩니다.

 

 

블루프린트 Custom 함수 호출하기

BlueprintImplementableEvent 는 C++에서 함수 원형을 만들어서 호출하는 방식이 였고 이번엔 블루프린트에서 자체적으로 만든 Custom Event 함수를 C++에서 호출해 보겠습니다.

 

 

결과가 잘 나옵니다.

 

인자값을 넣고 싶으면 위와 같이 FString::Printf함수를 이용해 "함수 이름 %d" 형식으로 구현할 수 있습니다.

 

 

 


BlueprintNativeEvent

BlueprintImplementableEvent 와 비슷하게 C++에서 함수 원형을 만드는 것은 동일하지만 BlueprintNativeEvent는 C++에서 가상함수를 만드는 개념입니다.

 

그래서 "void CallNativeFuction"이라는 함수를 만들고 C++에서 가상함수로 "void CallNativeFuction_Implementation()"함수를 추가로 만들었습니다.

 

부모 함수 호출하는 방법은 이벤트 노드에 우측마우스 클릭해서 위 메뉴로 생성할 수 있습니다.

 

 

 

ref : https://darkcatgame.tistory.com/64

반응형

언리얼 엔진 4 는 다수의 멀티플레이어 함수성이 내장되어 있어, 네트워크를 통해 플레이 가능한 기본적인 블루프린트 게임을 쉽게 구성할 수 있습니다. 간단히 뛰어들어 멀티플레이어 게임 플레이를 시작해 볼 수 있습니다. 기본적인 멀티플레이어 기능이 지원되는 로직 대부분은 Character 클래스 및 삼인칭 템플릿에서 사용하는 CharacterMovementComponent 에 내장된 네트워킹 기능 지원 덕입니다.

게임플레이 프레임워크 검토

게임에 멀티플레이어 함수성을 추가하려면, 엔진에 제공되는 주요 게임플레이 클래스의 역할과 서로, 특히나 멀티플레이어 컨텍스트에서 어떻게 작동하는지를 이해하는 것이 중요합니다:

  • GameInstance 게임 인스턴스
  • GameMode 게임 모드
  • GameState 게임 스테이트
  • Pawn (및 거기서 상속되는 Character), 폰과 캐릭터
  • PlayerController 플레이어 컨트롤러
  • PlayerState 플레이어 스테이트

자세한 정보는 게임플레이 프레임워크 문서를 참고하시고, 멀티플레이어 게임을 디자인할 때는 최소한 다음의 팁 정도는 유념해 주시기 바랍니다:

  • GameInstance 는 엔진 세션 도중에만 존재합니다. 즉 엔진 시작 시 생성되며, 종료될 때까지 소멸 또는 대체되지 않습니다. 서버와 각 클라이언트마다 별도의 GameInstance 가 존재하며, 이 인스턴스는 서로 통신하지 않습니다. GameInstance 는 게임 세션 외부에 존재하므로, 특정 유형의 지속성 데이터를 저장하기에 좋은 곳입니다. 이를테면 플레이어 전반적인 통계 (지금까지 이긴 게임 횟수), 계정 정보 (특수 아이템 잠김/해제 상태), 언리얼 토너먼트같은 경쟁형 게임에서 돌아가며 플레이할 맵 목록 등을 말합니다.
  • GameMode 오브젝트는 서버에서만 존재합니다. 일반적으로 클라이언트가 명시적으로 알 필요가 없는 게임 관련 정보를 저장합니다. 예를 들어 "로켓 런처만" 나오는 특수 규칙 게임에서, 클라이언트는 이 정보를 알 필요가 없지만, 맵에 무기를 무작위로 스폰할 때 서버는 "로켓 런처" 카테고리에서만 선택해야한다 알아야 할 것입니다.
  • GameState 는 서버와 클라이언트에 존재하며, 서버는 GameState 상의 리플리케이티드 변수를 사용하여 모든 클라이언트에서 게임에 대한 데이터를 최신 상태로 유지할 수 있습니다. 예로, 야구 게임에서 GameState 를 통해 각 팀의 점수와 현재 이닝을 리플리케이트할 수 있습니다.
  • 각 클라이언트에는 그 머신의 플레이어마다 하나의 PlayerController 가 존재합니다. 이는 서버와 연결된 클라이언트 사이에 리플리케이트되지만, 다른 클라이언트에는 리플리케이트되지 않으므로, 서버는 모든 플레이어에 대한 PlayerController 를 갖고 있고, 로컬 클라이언트는 로컬 플레이어에 대한 PlayerController 만 갖습니다. 클라이언트가 접속되어 있는 도중에는 PlayerController 가 존재하여 Pawn 에 할당되지만, Pawn 과는 달리 소멸되거나 리스폰되지는 않습니다. 특정 플레이어에게만 감지된 게임 이벤트에 반응하여 미니맵에 핑을 찍도록 서버가 클라이언트에게 알리는 등의 작업처럼, 다른 클라이언트에 리플리케이트할 필요 없는 클라이언트와 서버 사이 통신에 적합합니다.
  • PlayerState 는 게임에 접속된 모든 플레이어에 대해 서버와 클라이언트 양쪽에 존재합니다. 이 클래스는 소유중인 클라이언트만이 아닌 모든 클라이언트가 알아야 하는, 이를테면 프리-포-올 게임에서 각 플레이어의 현재 점수와 같은 리플리케이티드 프로퍼티에 사용할 수 있습니다. PlayerController 처럼 개별 Pawn 에 할당되지만, Pawn 과는 달리 소멸 및 리스폰되지 않습니다.
  • Pawn (및 Character 포함) 역시 서버와 모든 클라이언트에 존재하며, 리플리케이티드 변수 및 이벤트를 갖고 있습니다. 특정 변수나 이벤트에 대해 PlayeyController 를 쓸 것이냐, PlayerState 를 쓸 것이냐, Pawn 을 쓸 것이냐 하는 것은 상황에 따라 다르지만, 주로 고려할 것은 PlayerController 와 PlayerState 는 플레이어가 접속된 시간동안 그리고 게임이 새 레벨을 로드하지 않는 한 내도록 유지되는 반면, Pawn 은 그렇지 않을 수 있습니다. 예를 들어 Pawn 이 게임플레이 도중 죽는 경우, 보통 소멸되고 새로운 Pawn 으로 대체되는 반면, PlayerController 와 PlayerState 는 계속해서 존재하며 새로운 Pawn 의 스폰이 끝나면 거기에 할당됩니다. Pawn 의 생명력은 그래서 Pawn 자체에 저장되는데, 실제 그 Pawn 인스턴스에만 해당되며, 새로운 Pawn 으로 대체되면 리셋시켜야 하기 때문됩니다.

액터 리플리케이션

UE4 의 네트워킹 기술의 핵심은 액터 리플리케이션입니다. "Replicates" (리플리케이트) 옵션이 True 로 설정된 액터는 서버에서 그 서버에 연결된 클라이언트로 자동 동기화됩니다. 꼭 이해해야 할 점은, 액터는 서버에서 클라이언트로만 리플리케이트되지, 클라이언트에서 서버로 액터를 리플리케이트시키는 것은 불가능하다는 점입니다. 물론, 클라이언트에서 서버로 데이터를 전송하는 것이 가능은 하며, 리플리케이트되는 "Run on server" 이벤트를 통해 이루어집니다.

블루프린트에서 액터 리플리케이트 에서 구체적인 예제를 확인해 보시고, 액터 리플리케이션 도 참고해 보시기 바랍니다.

오소리티

월드의 모든 액터에 대해서, 접속된 플레이어 중 하나는 해당 액터에 대해 오소리티가 있는 것으로 간주됩니다. 서버에 존재하는 모든 액터에 대해서, 서버는 모든 리플리케이티드 액터를 포함해서 해당 액터에 대해 오소리티를 갖습니다. 결과적으로 클라이언트에서 Has Authority 함수가 실행되고, 타깃이 그에게 리플리케이트된 액터인 경우, False 를 반환하게 됩니다. 또한 Switch Has Authority 편의 매크로를 사용하여 리플리케이트되는 액터에서 서버와 클라이언트에 따라 다른 동작을 하도록 하는 분기를 빠르게 만들 수도 있습니다.

변수

액터상의 변수에 대한 디테일 패널에 보면 리플리케이션 드롭다운이 있어 변수 리플리케이션 방법을 조정할 수 있습니다.

옵션설명

None 없음 - 새 변수의 기본값으로, 이 값을 네트워크를 통해 클라이언트에 전송하지 않는다는 뜻입니다.
Replicated 리플리케이트됨 - 서버가 이 액터를 리플리케이트하면, 이 변수를 클라이언트에 전송합니다. 받는 클라이언트의 변수 값은 자동으로 업데이트되어, 다음 번 접근할 때 서버상에 있던 값을 반영합니다. 물론 실시간 네트워크를 통해 플레이할 때는, 네트워크 지연시간만큼 업데이트에 걸리는 시간이 지연됩니다. 기억할 것은 리플리케이티드 변수는 한 방향, 즉 서버에서 클라이언트로만 갑니다! 클라이언트에서 서버로 데이터를 전송하는 방법은 "이벤트" 부분을 확인하세요.
RepNotify 리플 알림 - Replicated 옵션처럼 변수 리플리케이션은 하지만, 추가로 블루프린트에 OnRep_<변수명> 함수가 생성됩니다. 이 함수는 이 변수의 값이 변할 때마다 서버와 클라이언트에서 엔진에 의해 자동 호출됩니다. 이 함수는 게임의 필요에 따라 원하는 대로 자유롭게 구현해도 됩니다. 

엔진의 내장 클래스 내 변수 다수에는 이미 리플리케이션이 켜져 있어서, 여러가지 기능이 멀티플레이어 상황에서도 자동으로 작동합니다.

블루프린트에서 변수 리플리케이트 에서 변수 리플리케이션 관련 구체적인 예제 안내 및 프로퍼티 리플리케이션 문서도 참고해 보시기 바랍니다.

스폰 및 소멸

리플리케이트되는 액터가 서버에 스폰되면, 클라이언트에 통신하여 거기서도 자동으로 그 액터 사본을 스폰합니다. 하지만 일반적으로 리플리케이션은 클라이언트에서 서버로 이루어지지 않기에, 리플리케이트되는 액터가 클라이언트에 스폰되는 경우 그 액터는 해당 클라이언트에만 존재하게 됩니다. 서버도 다른 클라이언트도 그 액터 사본을 받지 않습니다. 하지만 스폰하는 클라이언트는 그 액터에 대한 오소리티를 갖게 됩니다. 게임플레이에 영향을 끼치지 않는 장식성 액터같은 것에는 유용할 수 있지만, 게임플레이에 영향을 끼치고 리플리케이트시켜야 하는 액터의 경우 서버에서 스폰되도록 하는 것이 가장 좋습니다.

리플리케이트되는 액터를 소멸(destroy)시키는 상황도 비슷합니다: 서버가 하나를 소멸시키면, 모든 클라이언트에서도 각자의 사본을 소멸시킵니다. 클라이언트는 오소리티를 가진, 즉 직접 스폰시킨 액터를 자유롭게 소멸시킬 수 있는데, 다른 플레이어에 리플리케이트되지도 않았고 영향을 끼치지도 않을 것이기 때문입니다. 클라이언트가 오소리티를 갖지 않은 액터를 소멸 시도하는 경우, 그 소멸 요청은 무시됩니다. 여기서의 핵심 요점은 액터 스폰에도 마찬가지입니다: 리플리케이트되는 액터를 소멸시켜야 하는 경우, 서버에서 소멸시켜 주세요.

이벤트 리플리케이션

블루프린트에서 액터와 그 변수 리플리케이트에 추가로, 클라이언트와 서버 너머로 이벤트를 실행시킬 수도 있습니다.

블루프린트에서 함수 리플리케이트 에서 구체적인 예제에 대한 안내 및 RPC 문서도 참고해 보시기 바랍니다.

RPC (Remote Procedure Call) 이라는 용어가 보이는데, 블루프린트에서 리플리케이트되는 이벤트는 본질적으로 엔진 내에서 RPC 로 (보통 C++ 에서는 그렇게 불립니다) 컴파일된다는 점만 알아두시면 됩니다.

오너십

멀티플레이어 작업시 이해해야 할 중요한 개념, 특히나 리플리케이트되는 이벤트 관련해서는, 어떤 접속을 특정 액터 또는 컴포넌트의 오너로 간주할 것인지 입니다. 우리 용도상 "Run on server" 이벤트는 클라이언트가 소유하는 액터 (또는 그 컴포넌트)에서만 호출 가능하다는 것을 압니다. 이게 무슨 뜻이냐면, 다음 액터 또는 그 액터 중 하나의 컴포넌트에서 "Run on server" 이벤트를 전송할 수만 있다는 뜻입니다:

  • 클라이언트의 PlayerController 자체,
  • 클라이언트의 PlayerController 가 빙의된 Pawn, 또는
  • 클라이언트의 PlayerState.

마찬가지로 "Run on owning client" 이벤트를 전송하는 서버의 경우, 그 이벤트 역시 이 액터 중 하나에서 호출되어야 합니다. 그렇지 않으면 서버는 이벤트를 전송할 클라이언트를 알지 못하게 되어, 서버에서만 실행될 것입니다!

이벤트

커스텀 이벤트의 디테일 패널에서, 이벤트 리플리케이션 방식을 설정할 수 있습니다.

옵션설명

Not Replicated 리플리케이트 안됨 - 기본값으로, 이 이벤트에 대한 리플리케이션이 없다는 뜻입니다. 클라이언트에서 호출되는 경우 해당 클라이언트에서만 실행되고, 서버에서 호출되는 경우 서버에서만 실행됩니다.
Multicast 멀티캐스트 - 서버에서 멀티캐스트 이벤트가 호출되면, 타깃 오브젝트를 어느 접속에서 소유했는지와 무관하게 접속된 모든 클라이언트에 리플리케이트됩니다. 클라이언트가 멀티캐스트 이벤트를 호출하는 경우, 리플리케이트되지 않은 것으로 간주, 호출한 클라이언트에서만 실행합니다.
Run on Server 서버에서 실행 - 이 이벤트가 서버에서 실행된 경우, 서버에서만 실행됩니다. 클라이언트에서 클라이언트가 소유한 타깃으로 실행된 경우, 서버에 리플리케이트되어 실행됩니다. "Run on Server" 이벤트는 클라이언트가 서버에 데이터를 전송하기 위한 주요 메서드입니다.
Run on Owning Client 소유 클라이언트에서 실행 - 서버에서 호출된 경우, 이 이벤트는 타깃 액터를 소유한 클라이언트에서 실행됩니다. 서버는 액터 자체를 소유할 수 있으므로, "Run on Owning Client" 이벤트는 그 이름과 무관하게 서버에서 실행 가능합니다. 클라이언트에서 호출된 경우, 이벤트는 리플리케이트되지 않은 것으로 간주, 호출한 클라이언트에서만 실행됩니다.

다음 표는 이벤트 호출 방법에 따라 여러가지 리플리케이션 모드가 이벤트 실행 위치에 어떠한 영향을 끼치는지 나타냅니다.

서버에서 이벤트가 호출된 경우, 타깃이 왼쪽 열이라 한다면, 실행되는 곳은...

리플리케이트 안됨멀티캐스트서버에서 실행소유 클라이언트에서 실행

클라이언트 소유 타깃 서버 서버와 모든 클라이언트 서버 타깃의 소유 클라이언트
서버 소유 타깃 서버 서버와 모든 클라이언트 서버 서버
미소유 타깃 서버 서버와 모든 클라이언트 서버 서버

이벤트가 클라이언트에서 호출된 경우, 타깃이 왼쪽 열이라 한다면, 실행되는 곳은...

리플리케이트 안됨멀티캐스트서버에서 실행소유 클라이언트에서 실행

호출 클라이언트에 소유된 타깃 호출 클라이언트 호출 클라이언트 서버 호출 클라이언트
다른 클라이언트에 소유된 타깃 호출 클라이언트 호출 클라이언트 버림 호출 클라이언트
서버 소유 타깃 호출 클라이언트 호출 클라이언트 버림 호출 클라이언트
미소유 타깃 호출 클라이언트 호출 클라이언트 버림 호출 클라이언트

위 표에서 볼 수 있듯이, 클라이언트에서 호출되면서 서버에서 실행 설정되지 않은 이벤트는 리플리케이트되지 않는 것으로 간주됩니다.

 

클라이언트에서 서버로 리플리케이트되는 이벤트를 전송하는 유일한 방법은 클라이언트에서 서버로 정보를 통신하는 방법뿐인데, 일반적인 액터 리플리케이션은 서버에서 클라이언트로만 이루어지도록 디자인되었기 때문입니다.

 

또한 멀티캐스트 이벤트는 서버에서만 전송 가능하다는 점도 눈여겨 봅시다. 언리얼의 클라리언트-서버 모델로 인해, 클라이언트는 다른 클라이언트와 직접 접속되지 않으며, 서버에만 접속됩니다. 그러므로 클라이언트에서 다른 클라이언트로 멀티캐스트 이벤트를 직접 전송하는 것은 불가능하고, 서버와의 통신이 반드시 있어야만 합니다.

 

리플리케이트되는 이벤트를 서버에서 실행 이벤트 하나, 멀티캐스트 이벤트 하나, 총 두 개를 사용하여 이러한 작동방식을 흉내낼 수는 있습니다. 서버에서 실행 이벤트의 구현은 필요하다면 인증을 거친 후 멀티캐스트 이벤트를 호출합니다.

그러면 멀티캐스트 이벤트 구현은 접속된 모든 플레이어에게 실행시키고자 하는 로직을 수행합니다.

인증을 전혀 하지 않는 예제의 경우, 다음 그림을 참고하세요:

 

 

소유중인 클라에서 서버로 ForwardMulticast 를 콜하고 서버에선 전체 클라로 Server Multicast 를 보내는 예제이다

 

 

 

참가중 고려사항

게임 상태 변화 통신시 리플리케이티드 이벤트 사용시 염두에 둬야 할 점 한 가지는, 참가중 상태를 지원하는 게임을 어떻게 처리할 것인지 입니다. 플레이어가 진행중인 게임에 참가할 때, 참가 전에 일어난 리플리케이트되는 이벤트는 새 플레이어에게 실행되지 않을 것입니다. 여기서 한 가지 중요한 점은, 게임이 참가중 상태 처리를 제대로 하도록 하기 위해서는, 리플리케이트되는 변수를 통해 중요 게임플레이 데이터를 동기화시키는 것이 최선이라는 점입니다. 꽤 자주 겪게 되는 패턴은 클라이언트가 월드에서 어떤 동작을 하고, 서버는 "서버에서 실행" 이벤트를 통해 그 동작을 알린 뒤, 그 이벤트의 구현에서 서버는 그 동작에 따른 몇 가지 리플리케이트되는 변수를 업데이트합니다. 그러면 그 동작을 수행하지 않은 다른 클라이언트에서도 리플리케이트되는 변수를 통해 동작의 결과를 확인할 수 있게 됩니다. 추가로 동작이 벌어진 이후 참가중 상태였던 클라이언트도 월드 상태를 제대로 확인할 수 있게 되는데, 서버에서 리플리케이트되는 변수의 가장 최신 값을 받게 되기 때문입니다. 서버가 이벤트 전송만 했다면, 참가중 상태의 플레이어는 그런 동작이 있었는지 알지 못할 것입니다!

신뢰성

리플리케이트되는 이벤트에 대해 Reliable (신뢰성)인지 Unreliable (비신뢰성)인지 선택할 수 있습니다.

신뢰성 이벤트는 (위의 오너십 규칙을 따른다는 가정하에) 목적지에 반드시 도달한다는 보장이 있습니다만, 그로 인해 보다 많은 대역폭을 필요로 하고, 지연시간이 길어질 수 있습니다. 신뢰성 이벤트를 너무 자주, 이를테면 매 틱마다 전송하지 않도록 하세요. 신뢰성 이벤트에 대한 엔진의 내부 버퍼가 넘칠(overflow) 수가 있으며, 그러한 현상이 발생하면 관련된 플레이어는 접속이 끊어질 것입니다!

비신뢰성 이벤트는 이름이 암시하는 바대로 목적지에 도달하지 못할 수 있는데, 예를 들면 네트워크에서의 패킷 손실이라든가 엔진에서 우선권이 높은 부분을 먼저 전송하기 위해 트래픽을 차지하기로 결정한 경우입니다. 그에 따라 비신뢰성 이벤트는 신뢰성보다 대역폭을 적게 차지하며, 자주 호출해도 보다 안전합니다.

 

 

 

ref : https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Networking/Blueprints/

반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

UE4 Get IP Adress C++  (0) 2022.07.22
UFUNCTION BlueprintCallable, BlueprintImplementableEvent, ..  (0) 2022.07.19
UE5 특징  (0) 2022.06.16
lambda with timers  (1) 2022.04.24
비히클 셋업  (0) 2022.03.16

루멘 글로벌 일루미네이션 및 리플렉션

루멘은 씬과 라이트 변화에 즉각적으로 반응하는 완전 다이내믹 글로벌 일루미네이션 및 리플렉션 솔루션입니다. 아티스트와 디자이너는 루멘을 활용하여 더욱 다이내믹한 씬을 제작할 수 있습니다. 태양의 각도를 바꾸거나, 손전등을 켜거나, 외부의 문을 열거나, 벽에 구멍을 내면 상황에 따라 간접광과 리플렉션이 적용됩니다.

이 시스템은 밀리미터 단위의 디테일한 환경에서 킬로미터 단위의 광활한 환경까지 무한한 바운스와 간접 스펙큘러 리플렉션으로 디퓨즈 인터리플렉션을 렌더링합니다.

 

이제 아티스트와 디자이너는 사전 연산된 라이팅이 텍스처에 베이크되어 있는 스태틱 씬에 얽매이지 않습니다. 개별 스태틱 메시에 대한 라이트 맵 UV를 구성하거나 라이팅을 다시 빌드할 필요 없이 에디터 내에서 변화를 바로 확인할 수 있어 시간이 크게 절약됩니다.

 

루멘은 효율적인 소프트웨어 레이 트레이싱을 구현하여 글로벌 일루미네이션 및 리플렉션이 다양한 그래픽 카드에서 실행되도록 지원하는 동시에 고급 비주얼을 위한 하드웨어 레이 트레이싱도 지원합니다.

 

 

 

나나이트 가상화 지오메트리

나나이트의 가상화된 마이크로폴리곤 지오메트리 시스템을 사용하면 방대한 양의 지오메트릭 디테일이 포함된 게임을 만들 수 있습니다. ZBrush 스컬프트에서 사진측량 스캔까지 수백만 개의 폴리곤으로 구성된 영화 수준의 소스 아트를 바로 임포트하고, 리얼타임 프레임 레이트를 유지한 채로 눈에 띄는 퀄리티 손실 없이 수백만 번 배치할 수 있습니다.

나나이트는 인식 가능한 디테일만 스마트하게 스트리밍 및 처리하여 트라이앵글 수와 드로 콜의 제약을 크게 줄입니다. 노멀 맵에 디테일을 굽거나 디테일 레벨을 직접 작성하는 등 시간이 걸리는 작업을 제거하여 창작에만 집중할 수 있습니다.

 

 

 

 

언리얼 엔진 5 얼리 액세스 출시 이후로 나나이트에서 향상된 사항은 다음과 같습니다.

  • 디스크에서 나나이트 메시의 크기를 줄이는 최적화 컨트롤
    • 가장 중요도가 낮은 데이터를 제거함으로써 손실 압축에 가깝게 만들어 개발 과정에서 메시를 최적화할 수 있습니다. 예를 들어 테크 데모 ‘나나이트 세계의 루멘'에서는 조각상 중 하나의 가슴 부분에서 눈에 띄는 차이 없이 트라이앵글의 88%(및 디스크 크기)를 줄일 수 있었습니다.
  • 압축 기능을 향상해 동일한 퀄리티의 나나이트 메시를 사용할 때 차지하는 공간을 20%까지 줄일 수 있었습니다.
  • UE4에서 UE5로 프로젝트 변환 시 나나이트 검사 툴(Nanite Auditing Tool) 이 나나이트를 활용할 수 있도록 기존 콘텐츠의 대량 이동을 보조하고, 지원되지 않는 기능이 활성화되는 것 처럼 개발 과정에서 깨지게 되는 나나이트 콘텐츠를 진단합니다.

 

 

 

버추얼 섀도 맵(베타)

버추얼 섀도 맵은 언리얼 엔진 5의 나나이트, 월드 파티션 기능을 활용해 차세대 다이내믹 섀도잉을 실시간으로 제공합니다. 영화 수준의 에셋과 대규모 오픈 월드를 지원하는 데 필요한 높은 퀄리티의 섀도를 일관되게 제공합니다.

기존 다이내믹 섀도잉 기법은 소규모 또는 중간 규모의 월드로 제한되는 경우가 많고 디자이너와 아티스트가 퍼포먼스를 위해 퀄리티를 희생해야 했습니다. 반면 버추얼 섀도 맵은 가장 필요한 곳의 퀄리티를 자동으로 높여 주는 단일 통합 섀도잉 메서드를 제공합니다. 이제 사실적이고 부드러운 반그림자 및 컨택트 하드닝을 통해 먼 거리에 걸쳐 작은 오브젝트 및 큰 오브젝트에 일관된 퀄리티의 섀도를 적용할 수 있습니다.

 

 

 

템포럴 슈퍼 해상도

나나이트 마이크로폴리곤 지오메트리와 차세대 게임의 퀄리티에 대한 기대로 화면에 표시할 수 있는 디테일이 그 어느 때보다 높아졌습니다. 이 기대감을 충족시키기 위해, 언리얼 엔진 4의 하이엔드 플랫폼용 템포럴 안티 에일리어싱(Temporal Anti-Aliasing, TAA)의 대안으로 템포럴 슈퍼 해상도 알고리즘을 새로 개발했습니다.

템포럴 슈퍼 해상도는 언리얼 엔진에 기본 내장되어 높은 퀄리티의 업샘플링을 제공하고 폭넓은 하드웨어를 지원합니다. 템포럴 슈퍼 해상도는 디폴트 안티 에일리어싱 메서드이며 모든 프로젝트에서 활성화되어 있습니다.

 

 

 

DirectX 12

이제 언리얼 엔진 5의 기능을 사용해 Windows PC의 RHI를 구현할 때는 Microsoft의 DirectX 12(DX12)를 권장합니다. 에픽에서는 엔진에서 DX12 지원을 강화하는 동안 메모리 관리, 기능 무결성, 안전성을 최우선으로 고려할 예정입니다. 언리얼 엔진 5에서 새로 생성한 프로젝트는 기본적으로 DX12를 사용합니다.

 

 

Vulkan

언리얼 엔진 5의 Vulkan RHI는 여러 번의 안정성 및 기능 업데이트를 통해 데스크톱 및 모바일에서의 효율이 향상되었습니다. Linux에 나나이트, 루멘(소프트웨어 레이 트레이싱 전용) 등 언리얼 엔진 5 기능이 지원됩니다.

사양에 대한 자세한 정보는 하드웨어 및 소프트웨어 사양 문서를 참조하세요.

 

 

 

액터당 한 개의 파일

새로운 액터당 한 개의 파일(One File Per Actor) 시스템은 월드 파티션과 함께 작동하여 대규모 월드에서 공동 편집을 더욱 쉽게 만들어 줍니다. 레벨 에디터는 개별 액터를 단일 모놀리식 레벨 파일로 그룹화하지 않고 자체적인 파일로 저장합니다. 즉, 전체 레벨이 아닌 소스 컨트롤에서 필요한 액터만 체크아웃하면 됩니다.

이 기능은 월드 파티션을 사용할 때 기본적으로 활성화되어 있습니다.

자세한 정보는 액터당 한 개의 파일 문서를 참조하세요.

 

 

 

대규모 월드 좌표

이제 대규모 월드 좌표(Large World Coordinates, LWC)가 언리얼 엔진 5에서 더블 정밀도 데이터 베리언트 타입을 지원합니다. 부동 소수점 정밀도를 향상하기 위해 UE5의 모든 엔진 시스템 전반에서 커다란 변화가 이루어졌습니다. 새로운 엔진 시스템에는 건축 시각화, 시뮬레이션, 렌더링 (나이아가라 및 HLSL 코드), 대규모 월드 스케일 프로젝트 등이 있습니다. 대규모 월드 좌표를 UE의 모든 곳에서 실행하고 노출하도록 많은 개발 작업을 진행했지만, 원래 지점에서 멀리 떨어진 일부 시스템의 경우 아직 해결하지 않은 정밀도 문제 때문에 몇 가지 제한이 존재합니다.

라이선스 이용자는 코드를 바로 이 신규 데이터 타입으로 옮기기 시작할 수 있으나, 엔진의 전 시스템이 대규모에서도 지금처럼 효율적으로 작동하는지 확인한 후, 5.1 버전에서 대규모 월드에 대한 생성 및 배치를 정식으로 지원할 예정입니다. 이후 최대 월드 크기의 디폴트 값을 매우 큰 수치로 설정할 것입니다.

언리얼 엔진 4에서는 32비트 플로트 정밀도 타입으로 인해 월드의 크기가 제한되었습니다. LWC는 코어 데이터 타입으로 64비트 더블을 제공하여 프로젝트 크기를 크게 향상시킵니다. 이 변화로 대규모 월드를 구축할 수 있게 되었으며, 액터 배치 정확도 및 오리엔테이션 정밀도가 크게 향상되었습니다. 대규모 월드 좌표는 더블 타입을 사용하기 때문에, PhysX 피직스 시스템 은 UE5와 호환되지 않습니다.

대규모 월드 실험에 관련된 자세한 정보는 대규모 월드 좌표 문서를 참조하세요.

 

 

 

 

PhysX 및 카오스 피직스 시스템

UE5는 피직스 시뮬레이션에 대해 카오스 피직스 엔진을 사용하며, 기본값인 PhysX 를 대체합니다. PhysX가 여전히 UE5에 있지만 향후 버전에서 제거될 예정입니다. 카오스 피직스의 피직스 시뮬레이션은 PhysX와 다르게 작동하며, 일관적인 물리 동작을 위해선 개발자의 조정이 필요합니다.

피직스 틱 속도는 기본적으로 새로 생성된 프로젝트에 따라 변경됩니다. 틱 속도 변화는 프로젝트 세팅(메뉴: 수정(Edit) > 프로젝트 세팅(Project Settings) ) 내에 있는 틱 비동기 피직스에서 액세스할 수 있습니다. 이 신기능은 게임 스레드 대신 자체 스레드에서 피직스를 시뮬레이션합니다.

 

PhysX는 UE5에서 제거될 예정이나, 필요한 경우 PhysX가 활성화된 소스에서 컴파일할 수 있습니다.

 

 

 

 

게임플레이 프레임워크

제거

블루프린트 네이티브화(Blueprint Nativization) 는 UE5에 없습니다. 이 기능을 활용한 프로젝트에는 변경사항이 없습니다. 성능에 영향을 미칠 수 있으나 적절하게 작동하려면 수정이 필요합니다. 이 경우 개발자는 다른 최적화 접근 방식을 수행해야 합니다.

네트워킹

지원 중단

AES, RSA, RSA 키 AES 암호화 핸들러(AES, RSA, and RSA Key AES Encryption Handler) 는 UE5 5.0에서 지원 중단되고, UE5의 향후 버전에서 제거될 예정입니다. 현재는 DTLS만 사용됩니다.

  • AES GCM은 UE5 5.0 이전에는 사용되지만 UE5의 후속 릴리스에서는 제거됩니다. 사용자는 이 변경사항으로 인해 프로젝트를 수정할 필요가 없습니다.

코어

제거

젠 로더(Zen Loader)  이벤트 중심 로더(Event-Driven Loader) 를 대체합니다. 대부분의 사용자는 이벤트 주도형 로더와 직접 접속하지 않으므로 이 변경사항에 따라 프로젝트 마이그레이션 도중 작업을 수행할 필요가 없습니다.

지원 중단

언리얼 인사이트(Unreal Insights) 시스템은 UE5 5.0 정식 출시 이후 통계 시스템(Stats System) 을 대체합니다. 통계 시스템은 UE5 5.0에 계속 존재하지만 언리얼 인사이트를 위해 제거될 예정입니다.

반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

UFUNCTION BlueprintCallable, BlueprintImplementableEvent, ..  (0) 2022.07.19
블루프린트에서의 멀티플레이어  (0) 2022.07.13
lambda with timers  (1) 2022.04.24
비히클 셋업  (0) 2022.03.16
check(표현식);  (0) 2022.03.03

 

It just that i have to write a long line and then declare a function in the header and implement it in the cpp file only to make a simple delay.
In javascript for example you can do something like that:

setTimeout(function()
{
...
},300);

But when i tried to use lambda within the timer function it says it couldn’t convert what he needs to lambda.
Is it really have to be such a mess only to make a simple delay ?

 

 

 

FTimerDelegate TimerCallback;
TimerCallback.BindLambda([]
{
	// callback;
});

FTimerHandle Handle;
GetWorld()->GetTimerManager().SetTimer(Handle, TimerCallback, 5.0f, false);

 

Inrate 에 대한 설명  == /** Time between set and fire, or repeat frequency if looping. */

 

 

 

Is this what you’re looking for?

 

 

 

 

If you want an inline callback, you can do the following:

GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([] { /* callback; */ }), 5.0f, false);

 

 

 

ref : https://forums.unrealengine.com/t/can-i-use-lambda-with-timers/30620

 

Can i use lambda with timers?

It just that i have to write a long line and then declare a function in the header and implement it in the cpp file only to make a simple delay. In javascript for example you can do something like that: setTimeout(function() { ... },300); But when i tried

forums.unrealengine.com

 

 

33님 댓글 

GetWorldTimerManager().SetTimer(FTimerhandle,[this]{ bla bla bal },InRate,Loop);



반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

블루프린트에서의 멀티플레이어  (0) 2022.07.13
UE5 특징  (0) 2022.06.16
비히클 셋업  (0) 2022.03.16
check(표현식);  (0) 2022.03.03
문자 변환  (0) 2022.02.10

자동차 바퀴는 Kinematic 으로 설정하고 콜리전 같은 효과를 주기위해서 대신 Ray cast 를 사용하여 월드와 상호작용하도록 처리한다

 

 

 

 

비히클용으로 아주 기본적인 최소한의 아트 셋업은, 스켈레탈 메시 입니다. 비히클 유형에 따라 얼마나 복잡한 아트 셋업이 필요할 것인지가 결정되며, 서스펜션에 특별한 관심을 쏟아야 할 수 있습니다. 탱크라면 서스펜션이 거의 항상 보이지 않을 테니 별달리 신경쓸 필요가 없겠지만, 비히클 게임 에서 볼 수 있는 것과 같은 모래언덕용 소형차라면 노출된 부품이 좀 더 그럴싸하게 움직이도록 하기 위해서는 조인트가 추가로 필요할 것입니다.

기본

비히클 메시는 양수 X 를 아래로 향하고 있어야 합니다.

언리얼 엔진 4 에서 사용할 휠의 반경은 센티미터 단위로 측정해야 합니다.

위 그림에서는 Maya의 Distance Measurement (거리 측정) 툴을 사용하여 휠 서로 반대편의 두 버텍스 사이의 거리에 따라 휠의 지름을 측정합니다. 휠의 반경은 이 값의 절반이 됩니다.

3DsMax 의 경우 Helpers 섹션에도 비슷한 툴이 있습니다.

조인트

4륜 비히클에 필요한 최소한의 조인트 갯수는 루트에 하나, 휠마다 하나씩 총 5 개입니다. 휠과 루트 조인트는 X 가 앞쪽, Z 가 위쪽으로 맞춰져 있어야 합니다:

그래야 Y 축 중심으로 회전하면서 Z 축으로 스티어링됩니다.

다른 모든 조인트는 원하는 대로 배치 가능하지만, 애니메이션 블루프린트  Look At 노드같은 것들은 X 가 앞쪽이라 가정한다는 점 참고하시기 바랍니다.

시각적으로 어색해 보이는 것을 방지하기 위해, 휠의 조인트는 중앙을 정확히 맞춰야 합니다. 콜리전 감지에 시각적인 메시가 사용되지는 않을 것이지만, 휠 메시가 중앙을 벗어난 경우 휠이 망가진 것처럼 보일 것이기에, 모션 블러로 인해 정말 눈에 띨 것입니다.

바인딩

Maya의 경우 표준 스무드 바인드고, 3DS Max 의 경우 스킨 모디파이어 입니다. 휠은 하나의 조인트에만 웨이팅이 있어야 이상한 변형 없이 자유로운 스핀이 가능합니다. 쇼크 앱소버와 스트럿 서스펜션의 경우 복잡한 스키닝으로 해도 가능하지만, 언리얼 에디터 측에서는 좀 더 생각을 많이 해야 할 것입니다.

익스포트

비히클은 단순히 스켈레탈 메시 로 익스포트되며, 별달리 고려할 것은 없습니다.

임포트

스켈레탈 메시에 대한 표준 FBX 임포트입니다. 임포터가 피직스 애셋 을 생성하도록 하는 것이 좋을 것입니다.

피직스 애셋

피직스 애셋 은 비히클에 엄청나게 중요하므로, 간과하거나 생략할 수 없습니다. 처음 언리얼 엔진 4 로 피직스 애셋 을 생성할 때, 아마도 거의 이와 같아 보일 것입니다:

그 이유는 피직스 애셋 툴 (PhAT) 이 조인트에 스키닝된 버텍스를 가능한 만큼 래핑 시도하기 때문입니다. 그리고 솔직히, 그 전부 지우고 다시 만들게 될 것입니다. 왜냐구요? 모든 피직스 바디를 결합시킬 컨스트레인트 재생성을 제대로 처리할 수 있는 방법이, 현재 PhAT 에는 없기 때문입니다. 즉 중간 피직스 바디를 삭제하면 상위 계층구조에 새로운 컨스트레인트를 만들지 않습니다. 그러나 모든 피직스 바디를 삭제하고 루트 조인트부터 쌓아가기 시작하면 모든 컨스트레인트가 제대로 생성될 것입니다.

계층구조 패널에서 모든 것을 Shift 선택한 다음 Delete 키를 누릅니다. 그러면 애셋에서 모든 피직스 바디가 제거됩니다.

이제 루트 조인트부터 시작해서 비히클 조인트에 피직스 바디를 만들기 시작합니다. 염두에 둘 것은, 물리 시뮬레이션을 하거나 비히클의 바운드에 영향을 끼칠 필요가 있는 조인트에만 피직스 바디가 필요하다는 것입니다.

여기 우리 버기의 경우, 루트에 대한 박스나 휠에 대한 구체가 시작하기 딱 좋을 것이며, 원하는 동작을 내기 위해서는 여러가지 다른 피직스 바디가 필요할 것입니다.

바디 콜리전 생성

비히클 루트에 대한 박스를 생성하려면:

  1. 계층구조 패널에서 루트 조인트에 우클릭합니다.
  2. 바디 추가 를 선택합니다.
  3. 콜리전 지오메트리  박스 로 설정합니다.
  4. "OK" 를 클릭합니다.
  5. 그 후 새로운 피직스 바디 를 이동, 회전, 스케일 조절하여 비히클의 모양을 더욱 잘 추정해 낼 수 있습니다.

휠 콜리전 생성

휠에 쓸 구체를 생성하려면:

  1. 계층구조 패널에서 휠 조인트에 우클릭합니다.
  2. 바디 추가 를 선택합니다.
  3. 콜리전 지오메트리  구체 로 설정합니다.
  4. "OK" 를 클릭합니다.
  5. 디테일 패널에서 Physics Type 프로퍼티를 찾아 Kinematic 으로 설정해 줍니다.비히클의 바운드에 영향을 끼치려는 휠에는 필수인데, 그림자와 컬링이 정상 작동하도록, 또 게임이 시작되면 비히클에서 휠이 떨어져 나오지 않도록 하기 위해서입니다.
  6.  피직스 바디 는 절대 실제 콜리전에 사용되지 않습니다.
    현재 휠은 레이 캐스트를 사용하여 월드와의 상호작용을 처리합니다.

더 나아가서

간단한 셋업은 이렇습니다. '비히클 게임' 내 '비히클' 의 '피직스 애셋' 을 살펴보면 이렇습니다:

휠 외에 모든 콜리전 바디는 비히클의 루트 조인트상에 존재합니다. 휠이 특정 게임 요소에 걸리지 않도록 하고, 휠의 메시가 벽이나 철책에 잘리지 않도록 하기도 합니다.

 

ref : https://docs.unrealengine.com/4.27/ko/InteractiveExperiences/Vehicles/VehicleContentCreation/

반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

UE5 특징  (0) 2022.06.16
lambda with timers  (1) 2022.04.24
check(표현식);  (0) 2022.03.03
문자 변환  (0) 2022.02.10
언리얼4 Android용 Visual Studio 디버깅 가능해지다!!  (0) 2021.08.20

이 매크로는 표현식을 실행한 뒤, 어서트 결과가 false 이면 실행을 중지시킵니다. 표면식은 매크로가 빌드에 컴파일되는 경우에만 실행됩니다 (DO_CHECK=1). 가장 간단한 형태의 check() 매크로입니다.

예:

check(Mesh != nullptr);
check(bWasInitialized && "Did you forget to call Init()?");

 

checkf(표현식, ...);

checkf() 매크로는 표현식이 true 가 아니면 디버깅에 도움이 되는 추가 정보를 출력하는 것이 가능합니다. 컴파일 면에 있어서는 check() 와 똑같습니다.

예:

checkf(WasDestroyed, TEXT( "Failed to destroy Actor %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel());
checkf( TCString<ANSICHAR>::Strlen( Key ) >= KEYLENGTH( AES_KEYBITS ), TEXT( "AES_KEY needs to be at least %d characters" ), KEYLENGTH( AES_KEYBITS ) );

 

 

 

ref : https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/Assertions/

반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

lambda with timers  (1) 2022.04.24
비히클 셋업  (0) 2022.03.16
문자 변환  (0) 2022.02.10
언리얼4 Android용 Visual Studio 디버깅 가능해지다!!  (0) 2021.08.20
모바일 Joystick 을 PC 에서도 입력되도록  (0) 2021.06.28

FName

콘텐츠 브라우저에서 새 애셋 이름을 지을 때, 다이내믹 머티리얼 인스턴스의 파라미터를 변경할 때, 스켈레탈 메시에서 본에 접근할 때, 모두 FName 을 사용합니다. FName 은 문자열 사용에 있어서 초경량 시스템을 제공하는데, 주어진 문자열이 재사용된다 해도 데이터 테이블에 한 번만 저장되는 것입니다. FName 은 대소문자를 구분하지 않습니다. 변경도 불가능하여, 조작할 수 없습니다. 이처럼 FName 의 정적인 속성과 저장 시스템 덕에 찾기나 키로 FName 에 접근하는 속도가 빠릅니다. FName 서브시스템의 또다른 특징은 스트링에서 FName 변환이 해시 테이블을 사용해서 빠르다는 점입니다.

FText

In Unreal Engine 4 (UE4) the primary component for text localization is the FText class. All user-facing text should use this class, as it supports text localization by providing the following features:

FText also features the AsCultureInvariant function (or the INVTEXT macro), which creates non-localized, or "culture invariant" text. This is useful for things like converting a player name from an external API into something you can display in your user interface.

You can create a blank FText using either FText::GetEmpty(), or by using just FText().

FString

FName 이나 FText 와는 달리, FString 은 조작이 가능한 유일한 스트링 클래스입니다. 대소문자 변환, 부분문자열 발췌, 역순 등 사용가능한 메서드는 많습니다. FString 은 검색, 변경에 다른 스트링과의 비교도 가능합니다. 그러나 바로 그것이 FString 이 다른 불변의 스트링 클래스보다 비싸지는 이유입니다.

변환

에서로예제

FName FString TestHUDString = TestHUDName.ToString();
FName FText TestHUDText = FText::FromName(TestHUDName);
FName -> FText 는 가능한 경우도 있지만, FName 의 내용이 FText 의 "자동 현지화" 혜택을 받지 못할 수 있음에 유념해 주시기 바랍니다.

FString FName TestHUDName = FName(*TestHUDString);
FString -> FName 은 손실성 변환이라 위험합니다. FName 은 대소문자를 구분하지 않기 때문입니다.

FString FText TestHUDText = FText::FromString(TestHUDString);
FString -> FText 은 가능한 경우도 있지만, FString 의 내용이 FText 의 "자동 현지화" 혜택을 받지 못할 수 있음에 유념해 주시기 바랍니다.

FText FString TestHUDString = TestHUDText.ToString();
FText -> FString 은 안전하지 않습니다. 일부 언어에서는 변환시 손실될 위험이 있습니다.

FText FName FText 에서 FName 으로의 직접 변환은 없습니다. 대신, FString 을 거친 다음 FName 으로 변환하는 방법이 있습니다.
FText -> FString -> FName 은 손실성 변환이라 위험합니다. FName 은 대소문자를 구분하지 않기 때문입니다.

인코딩

일반적으로 스트링 변수 리터럴 설정시에는 TEXT() 매크로를 사용해야 합니다. TEXT() 매크로를 지정하지 않으면, 리터럴은 ANSI 를 사용해서 인코딩되기에, 지원되는 글자가 크게 제한됩니다. FString 에 전달되는 ANSI 리터럴은 TCHAR (네이티브 유니코드 인코딩)으로의 변환을 겪어야 하기에, TEXT() 를 사용하는 편이 보다 효율적입니다.

인코딩 관련 상세 정보는 캐릭터 인코딩 문서를 참고해 주시기 바랍니다.

 

ref : https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/StringHandling/

반응형

아주 갱장한 소식입니다

이제 언리얼4 에서도 안드로이드 디버깅이 가능해졌다고 하네요!!

 

 

Android용 Visual Studio 디버깅

이제 Android 프로젝트에서도 Visual Studio 디버깅 워크플로를 사용할 수 있습니다. Google의 Visual Studio용 AGDE 플러그인을 설치하면, Android 프로젝트 생성 시 언리얼 엔진에서 해당 플러그인을 내부적으로 활성화합니다. 이를 통해 Visual Studio에서 직접 프로젝트를 디플로이 및 디버그할 수 있습니다.

 

 

 

 

https://docs.unrealengine.com/4.27/ko/WhatsNew/Builds/ReleaseNotes/4_27/

반응형

아래 옵션들을 체크해주면 된다

반응형

 

캐릭터 BP 의 컴포넌트들 에서 CharacterMovement 가 아래 처럼 존재할 수있다

 

ABaseCharacter::ABaseCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UTDCharacterMovementComponent>(CharacterMovementComponentName))
{

 

 

ObjectInitializer 파라메터로 받는 생성자에서 상위클래스 초기화될 때 파라메터로 커스텀하게 만든 캐릭터 무브먼트 컴포넌트를 생성해주면됨.

다만 기본 Getter 인 GetCharacterMovement() 는 기본 클래스인 UCharacterMovementComponent를 리턴하므로 부를때마다 캐스팅을 하던지, 아니면 그냥 아래와 같은 래퍼를 하나 쓰는것도 간단해서 괜찮아보이긴 함.

 

FORCEINLINE UTDCharacterMovementComponent* GetTDCharacterMovement() { return Cast<UTDCharacterMovementComponent>(GetCharacterMovement()); }

 

 

 

ref : https://bbagwang.com/unreal-engine/character-movement-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95/

반응형

 

나나이트 : 공수가 적어져 게임 창작에만 집중할 수 있는 렌더링을 말합니다

 

CTO 인 KIM LIBRERI 는 나나이트를 자유라고 표현합니다

 

 

그도 그럴 것이  초고해상도를 모델을 쉽게말해 그냥 UE5 에 올리면 됩니다 아래 처럼요

이 전의 작업 공수에 비하면 굉장히 비약적인 발전이라 볼 수 있을것입니다

 

 

 

 

제약 사항과 물론 시간차는 있겠지만 더이상 저해상도 노우폴이나 노멀을 따로 만들지 않아도 되는 시기가 다가왔습니다

 

EPIC 에서 이런 저런 시도를 많이 했었지만 나나이트 뿐만 아니라 관련된것들이 이 전과는 상당히 의미 있는 변화라고 볼 수가 있습니다

시간이 갈 수록 그 변화의 폭은 클것으로 예상 되됩니다

 

 

 

 

퀵셀 메가스캔 : epic 에서 만든 실사 모델 스캔ㅊ라이브러리

 

사실 이것은 놀랍다기 보단 epic 에서 훨씬 오래전부터 계속 작업을 해왔던 것입니다

 

 

실제 스캔 장비들을 다각도에서 스캔하여 실제 모델을 만듭니다

이런 스캐닝된 모델 형태들과 같은것을 epic 에서 이미 제공을 하고 있었지요

 

 

이제 점점 이것이 빛을 발하는 날이 다가오고 있는것 같네요

실시간으로 UE 에서 초하이폴을 끌어다 배치 하는 하는 모습인데

 

이제 커스터마이징(LOD 기타 최적화 등등, 조립되어 있는 모델) 할 필요도 없이 UE 에서 알아서 처리해줍니다

 

즉 그냥 붙여 넣기만 하면 되는 것이죠 

 

지형 오브젝트 회전 배치

 

 

이런 식으로배치된 지형이 이미 잘 알려진 바로 이것입니다

 

 

각 개별 에셋인데 어떤각 개별 에셋에는 수천만개의 폴리곤(트라이앵글)로 이루어져 있기도 합니다

 

그래서 전체 씬의 개수는 셀수가 없게 된다 또는 세는것이 의미 없게 됩니다

 

하지만 이것이 이제 거의 바로 앞으로 다가왔다는 것이죠

 

 

이제 임포트하기만 하면 디테일은 UE 가 알아서 해주게 됩니다

 

 

 

 

 

 

작업 후..

 

 

 

 

ref : https://www.youtube.com/watch?v=OkfLh-laEww

반응형

언리얼 도큐먼트에 보면 게임모드가 리플리케이트 되지 않는 다는 내용을 볼수 있긴 하지만

실행을 하다보면 게임모드가 클라에 있는 경우를 볼 수 가있는데 왜 그런것인지 한번 파악을 해보자

 

 

용어 정리

새 에디터 창(PIE) : 새로운 창에서 실행 화면을 띄움 (Client, Server 를 구분하면서 보기에 유용하다)

스탠드 얼론(Stand alone) : 독립형 게임을 말한다

리슨서버 : 서버와 클라 동시에 실행되는 서버를 말한다

데디케이트 서버 : 렌더링 관련기능 등이 언리얼에서 빠진 오직 서버 기능만이 있는 서버를 말한다

 

 

 

멀티 환경 설정

보다 정확한 멀티 플레이 세팅을 하려면 아래 단일 프로세스 하 실행 옵션을 해제해야한다

옵션이 체크되면 하나의 프로세스에서 실행 되다보니 값이 중복되는 등 제대로된 상태를 보기 어렵게 된다

 

 

 

테스크

 

월드에 큐브를 하나 배치하고 이 큐브에 대한 메터리얼 세팅하는 테스트를 하기위해 다음과 같이 세팅한다

 

큐브를 레벨에 올리고 다음과 같이 블루프린트를 만들어준다

 

 

큐브의 리플리케이션 설정은 다음과 같다

Net Load On Client : 클라이언트에 맵이 로드되면서 동시에 이 항목이 체크된 해당 액터도 맵과 함께 클라이언트에게 보여지게 된다

 

 

 

 

게임모드의 실행여부를 확인하기 위해 게임모드 클래스를 상속받는다

 

void ANSGameMode::BeginPlay()
{
Super::BeginPlay();

GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Red, FString::Printf(TEXT("AGameMode::BeginPlay")));

 

 

 

 

Play Offiline 로 실행할경우 게임모드의 존재 여부를 살펴보면 

 

넷 모드를 Play Offline 으로 세팅하고 실행해보면

 

 

Play Offiline 으로 할 경우

 

Role 이 Autority 이고 게임모드의 BeginPlay 또한 호출 되는 것을 볼 수가 있다

이때에는 게임 모드가 클라이언트에서도 실행이 되며 존재한다(실행 자체가 클라임으로)

 

 

 

아래와 같이 클라이언트에서 실행 되었지만 게임모드는 클라에서 만들어졌기 때문에 Role 이 Authority 이며

게임모드 BeginPlay 또한 실행 된것을 볼 수 있으며 메터리얼 또한

노란색 무늬가 있는 메터리얼로 씌워진 것을 볼 수 있다

(멀티 플레이어 옵션에서 -> 플레이어 수 를 늘려도 마찬가지)

 

 

 

Play As Listen Server 로 플레이 하는 경우

 

 

 

 

서버에서는 GameMode 의 Role 가 Authority 이며 GameMode::BeginPlay 또한 호출 된것을 볼수 있으며 게임 모드가 존재 한다는 것을 알수 있다

 

 

 

하지만 클라이언트에서는 GameMode 의 Role 가 None 이며GameMode::BeginPlay 또한 호출 되지 않았다는 것을 알 수 있다(존재하지 않음으로) 

 

 

 

 

 

 

Play As Client 로 테스트를 하기 위해선 아래 과정들이 필요하다

 

 

 

먼저 서버를 위한 빌드를 하나 한다 

 

 

프로젝트 세팅에서 다음 처럼 세팅한다음 빌드를 한 후

 

 

아래 처럼 서버바로 가기를 세팅 한다

 

 

 ?Listen -Rex=500 -ReY=350 -WINDOWED

 

 

서버를 실행 .exe

서버(리슨서버)를 띄웠으니 당연히 Authority 에 큐브 또한 노란색 띄 메터리얼이 입혀졌다

게임모드의 BeginPlay 도 호출된것을 볼 수 있다

 

 

서버를 띄운 다음

 

또한 콘솔 명령으로 Open 1278.0.0.1 로 클라이언트에서 서버쪽으로 붙을 수가 있다

 

 

멀티 플레이 환경 세팅이 되었다는 가정 하에 게임 모드가 존재하는 상황이 있는데

 

서버 역할을 하는 경우와 

 

 

 ?Listen -Rex=500 -ReY=350 -WINDOWED

 

 

 

에디터에서 Play As Client 로 실행하면 

 

에디터에서 실행한 화면이 Server 가 아닌 Client 라는 것을 타이틀창에서 알수 있으며

게임모드의 Role 또한 None 이며 Beginplay 또한 호출되지 않은 즉 게임모드가 존재 하지 않는 다는 거을 알 수가 있다

 

 

 

추가로 붙은 클라이언트에서도 마찬가지인 상황을 알 수가 있다

 

 

 

 

 

게임 모드의 존재 유무 정리

 

Play as Offline 으로 실행 될때 게임 모드가 존재하며 Role 이 Authority 인것을 알수 있고

서버로 실행 되었을때에도 마찬가지로 Authority 이면서 BeginPlay 가 호출된것을 볼 수가 있다

 

 

멀티 환경에서 게임 모드는 서버에서 클라로  Replicated 되지 않고 서버에만 존재하지만

 

Play as Offline 인 클라이언트 단독으로만 실행 할 경우에는 게임모드가 클라이언트에서 생성 되고 삭제 되고 다른곳에 복제 시킬 일도 없음으로 클라이언트 자체가 GameMode 의 Authority 가 된다는 것을 알 수가 있다

 

 

Role_Authority 란 서버만을 얘기 하는 것이 아니라(보통은 서버) 해당 오브젝트가 생성되어 실질적으로 관리를 하고 있는 대상에 따라 Authority 가 된다

 

 

ex) 

1. 멀티 환경에서 서버에서 게임모드는 서버가 관리 함으로 서버에서 GameMode 는 Role_Authority 

하지만 클라에서는 존재자체를 하지 않음

 

2. Play As Offline 으로 서버 없이 실행할 경우 클라에 게임모드가 생성이 되며 이때 생성된 게임 모드는 Role 은 Role_Authority 가 된다, 여기서 관리 되고 삭제 됨으로

 

 

언리얼 도큐먼트 설명에 나와 있는 클라로 Replicated 하지 않는다,

즉 클라이언트중 게임모드가 존재하지 않는다는 것은

멀티 환경 구성이 갖추어 진상태에서 서버가 존재하지만 Play As Client 로 실행 하여 클라로 붙었을때 게임 모드가 존재하지 않는 것이다라는 것을 알 수 있다

반응형

버텍스 애니메이션 툴

2D 텍스처나 메시의 UV 에 복잡한 애니메이션 데이터를 저장하면, 애니메이션 부하를 줄이면서도 애니메이션에 필요한 모양과 느낌을 유지시키는 데 좋습니다. 과거에는 그렇게 하기 위해 모프 타깃 을 사용했겠지만, 이 새로운 방법에는 전에 없던 장점이 몇 가지 있습니다. 그 중 하나는, 기존에 캐스케이드 파티클 에디터에서 모프 타깃을 활용하거나 하지 않으면 불가능했을 복잡한 시스템 내 애니메이션 데이터를 사용할 수 있다는 점입니다. 여기서는 이 스크립트를 사용해서 UE4 프로젝트에 쓸 콘텐츠를 생성하는 법에 대해 다루도록 하겠습니다.

 

스크립트 위치

이 스크립트는 언리얼 엔진 4 (UE4) 4.8 이상 버전에서만 사용가능합니다.

버텍스 애니메이션 스크립트의 이름은 VertexAnimationTools.ms 이고, 위치는 다음과 같습니다:

Engine\Extras\3dsMaxScripts\VertexAnimationTools.ms

3Ds Max 2014 이상 버전에서의 이미지 감마 처리 방식 보정을 위해 버텍스 애니메이션 스크립트 최신 버전이 업데이트되었습니다.

툴 주의사항

이 툴은 복잡한 애니메이션 데이터를 텍스처에 저장하여 애니메이션 부하를 줄이는 데 매우 좋지만, 툴 사용시 생기는 문제점이 몇 가지 있습니다. 무엇보다 이 툴은 하나의 2D 텍스처 내 최대 8192 버텍스 에만 영향을 끼칠 수 있습니다. 그 이유는, DirectX 11 에서 최대 텍스처 크기는 X 나 Y 어느 한 쪽이 8192 픽셀 이내여야 하기 때문입니다. 이 툴은 다음 공식을 사용하여 텍스처 내 데이터를 생성합니다.

최종 텍스처 해상도: X = 메시의 버텍스 수, Y = 캡처된 프레임 수.

이러한 한계로 인해 이 툴은 비주얼 이펙트나 배경 스태틱 메시처럼 애니메이션이 필요는 하지만 복잡한 애니메이션 리깅까지 쓰기에는 조금 무리인 경우에 최적입니다. 이 툴은 스켈레탈 메시 애니메이션과도 작동하지 않는데 본 트랜스폼은 머티리얼 에디터에서 사용할 수 없기 때문입니다. 즉 스켈레탈 메시의 버텍스에 비슷한 방식으로 영향을 끼치려면, 모프 타깃 을 사용해야 한다는 뜻입니다.

버텍스 애니메이션 툴 분석

버텍스 애니메이션 툴 안에는 스태틱 메시의 버텍스에 영향을 끼치는 완전히 다른 메서드가 둘 있습니다. 다음 부분에서는 이 두 메서드에 대해 그 차이점을 포함해서 다루도록 하겠습니다.

  • 버텍스 애니메이션 툴: 버텍스 애니메이션 툴 상단의 Vertex Animation Tools 부분은 모프 타깃 버텍스 위치와 노멀을 저장하는 2D 텍스처 생성용입니다.

    프로퍼티 이름설명

    Animation Options:

    애니메이션 옵션 - 3dx Max 의 타임라인을 사용해서 제작된 애니메이션을 사용하거나, 3Ds Max 나 Maya, Blender 같은 다른 3D 패키지에서 제작된 개별 키프레임을 사용하도록 선택한 다음, 그 패키지에서 프레임별로 익스포트하여 3Ds Max 에서 애니메이션을 재구성할 수 있도록 합니다.

    Process Animated Meshes:

    애니메이티드 메시 처리 - 이 버튼은 3Ds Max 씬에 있는 애니메이티드 메시를 처리하고, 필요한 텍스처를 생성한 뒤 익스포트합니다.

    Anim Start:

    애님 시작 - 애니메이션 시작 프레임을 지정하는 옵션입니다.

    Anim End:

    애님 끝 - 애니메이션 끝 프레임을 지정하는 옵션입니다.

    Frame Skip:

    프레임 스킵 - 프레임을 건너뛰어 텍스처 공간 절약을 시도해 볼 수 있는 옵션입니다.

    Process Selected Meshes:

    선택된 메시 처리 - 키 프레임 메시가 활성화된 경우에만 사용할 수 있는 옵션으로, 키 프레임 메시를 선택한 순서대로 처리합니다.

  • Sequence Painter: 시퀀스 페인터 - 버텍스 애니메이션 툴과 비슷하지만 한 가지 핵심적인 차이점이 있는데, 버텍스 위치 정보가 2D 텍스처가 아닌 메시의 UV 에 저장됩니다.

    프로퍼티 이름설명

    Paint Selection Sequence:

    선택 시퀀스 페인트 - 메시 버텍스에 대한 정보를 2D 텍스처가 아닌 메시 UV 안에 저장합니다.

3Ds Max 버전 & 스크립트 설치

이 툴은 3Ds Max 2015 에서만 테스트되었습니다. 다른 버전에서도 작동이 가능할 수는 있지만, 테스트를 거치진 않았으므로 다른 버전을 사용하는 경우 직접 위험을 감수하셔야 합니다. 스크립트 설치는 간단히 4.8\Engine\Extras\3dsMaxScripts 에서 3Ds Max 뷰포트에 끌어놓으면 스크립트가 저절로 실행됩니다.

이 스크립트를 자주 사용하시는 경우, 언제든 툴바나 쿼드 메뉴에 스크립트를 추가하실 수 있습니다. 자세한 방법은 Autodesk site 에 그 방법이 매우 자세히 소개되어 있습니다.

3Ds Max 유닛 설정

툴 사용 시작 전 3Ds Max 에서 사용하는 측정단위가 UE4 에서 사용하는 측정단위와 제대로 맞는지 확인해 줘야 합니다. 이런 식으로 툴이 3Ds Max 에서 익스포트하는 데이터가 UE4 에서도 동일한 방식으로 작동하도록 만들 수 있습니다. UE4 는 센티미터를 기본 측정단위로 사용하므로 3Ds Max 에서도 동일한 단위를 사용하는지 확인해 줘야 하며, 변경하는 방법은 다음과 같습니다.

  1. 먼저 3Ds Max 2015 를 열고 로드되면 메인 툴바에서 Customize > Unit Setup 을 선택합니다.

  2. 다음 System Unit Setup 을 클릭한 다음 Inches 에서 Centimeters 로 변경한 뒤 OK 버튼을 누릅니다.

  3. 마지막으로 Display Unit Scale  Generic Units 으로 변경한 다음 OK 버튼을 누릅니다.

이 단계는 매우 중요하므로 건너뛰시면 안됩니다. 이 단계를 건너뛰면 UE4 와 3Ds Max 의 단위가 달라 콘텐츠 임포트시 렌더링 오류가 생길 수 있습니다.

툴 선택

버텍스 애니메이션 3Ds Max 스크립트는 버텍스 애니메이션 데이터 저장을 위한 메서드를 두 가지 제공합니다. 하나는 버텍스 위치를 2D 텍스처에 저장하는 반면, 다른 하나는 메시의 UV 에 버텍스 위치 데이터를 저장합니다. 두 메서드 구성 및 사용법 링크는 아래에서 찾을 수 있습니다.

키 프레이밍 메서드 - 다른 3D 패키지에서 익스포트한 뒤 3Ds Max 로 임포트할 수 있는 개별 키 프레임을 사용하는 메서드입니다. 그 정보는 메시의 UV 에 저장됩니다.

애니메이션 타임라인 메서드 - 3Ds Max 애니메이션 타임라인을 사용하여 그 결과를 2D 텍스처에 인코딩하는 메서드입니다.

팁 & 정보

이 기법을 최대한 활용하기 위한 팁과 정보가 몇 가지 있는데, 아래와 같습니다.

애니메이션 재생 속도 높이기

애니메이션 재생 속도가 너무 느리다면, TimeWithSpeedVariable 머티리얼 함수를 사용하여 재생 속도를 높이면 됩니다. 그 방법은 TimeWithSpeedVariable 출력을 MS_SequencePainter_SequenceFlibook 머티리얼 함수를 사용하는 경우 0-1 Animation 입력에 연결하기만 하면 됩니다. MS_VertexAnimationTools_MorphTargets 머티리얼 함수를 사용한다면 TimeWithSpeedVariable 출력을 Morph Animations 입력에 연결하면 됩니다.

다중 애니메이티드 메시

한 번에 여러 개의 애니메이티드 메시를 선택하면 스크립트는 그 데이터 전부를 하나의 메시와 한 세트의 텍스처로 구워줍니다. 여러가지 파트로 이루어진 캐릭터 작업시 매우 유용한 기능인데요. 평소처럼 사용하고자 하는 파트를 선택한 다음 스크립트를 실행해 주기만 하면 됩니다. 그러면 스크립트가 선택한 조각들을 합쳐 필수 2D 텍스처와 함께 메시를 새로 생성해 줍니다.

프레임 스킵

스크립트의 Vertex Animation Tools 섹션 아래 Frame Skip 옵션을 사용하면 특정 프레임을 건너뛸 수 있습니다. 원본 애니메이션의 모양과 느낌을 유지하면서도 최종 텍스처 크기를 줄일 수 있는 매우 유용한 옵션입니다.

아래 비디오에서 프레임 스킵 옵션의 실전 적용 예제를 확인할 수 있습니다. Original 이라 적힌 처음 차주전자를 보면, 전체 프레임 범위의 애니메이션이 보입니다. 2 라고 적힌 다음 차주전자를 보면, 짝수 프레임을 건너뛴 애니메이션이 보입니다. 마지막 예제에서 볼 수 있듯이, 10 프레임을 건너뛴대도 모양과 느낌이 그대로 유지됩니다.

메시 이름 /번호텍스처 크기메모리 절약

Original

175 KB

N/A

2

59 KB

116 KB

5

30 KB

145 KB

10

21 KB

154 KB

기술적 정보

버텍스 애니메이션 스크립트 작동방식에 대한 기술적인 정보입니다. 참고로 이 부분은 스크립트를 변경하기 위해 그 작동 원리를 더욱 자세히 알고자 하는 분들을 위한 것입니다.

한계

버텍스 위치 모프 타깃 정보는 16 비트 부호화 부동 소수점 파일 포맷으로 저장됩니다. 32 비트 이미지면 정밀도가 조금 더 높겠지만, 대부분의 FX 작업에는 16 으로 충분할 것입니다. 말이 나온 김에, 오프셋 버텍스 위치는 원래 놓이는 위치에서 멀리 이동할 수록 정밀도가 떨어질 것입니다.

또 참고로 스크립트 텍스처는 가장 가까운 인접 메서드를 사용하여 샘플링해야 합니다.

메모리 사용량

버텍스당 각 프레임에 쓰이는 메모리 양은 다음과 같습니다:

  • 버텍스 오프셋 텍스처: 프레임당 버텍스마다 8 바이트 (각 픽셀)

  • 노멀 텍스처: 프레임당 버텍스마다 4 바이트 (각 픽셀)

 

 

 

 

반응형

다이내믹 섀도잉

다이내믹 오브젝트(, 이를테면 모빌리티가 무버블로 설정된 스태틱 메시 컴포넌트 및 스켈레탈 메시 컴포넌트)는, 디스턴스 필드 섀도맵에서 월드의 스태틱 섀도잉에 통합시켜야 합니다. 이는 Per Object 섀도로 가능합니다. 각 이동 오브젝트는 하나의 스테이셔너리 라이트에서 다이내믹 섀도를 두 개 만듭니다: 오브젝트에 드리워지는 정적인 월드에 대한 그림자와, 월드에 드리워지는 오브젝트에 대한 그림자입니다. 이런 구성에서 스테이셔너리 라이트에 드는 섀도잉 비용은, 영향을 끼치는 다이내믹 오브젝트에서만 발생합니다. 즉 다이내믹 오브젝트의 수에 따라 비용이 매우 조금 들 수도 많이 들 수도 있습니다. 다이내믹 오브젝트의 수가 일정 이상이라면, 무버블 라이트의 효율이 더 좋습니다.

 

 


섀도 맵 캐시

 

 

Movable Lights (무버블 라이트)는 완벽히 동적인 빛과 그림자를 드리우며, 위치나 방향, 색, 밝기, 감쇠, 반경 등 그 모든 프로퍼티를 변경할 수 있습니다. 여기서 나오는 라이트는 라이트 맵에 구워넣지 않으며, 현재 간접광을 받을 수 없습니다.

섀도잉

그림자를 드리우도록 설정된 무버블 라이트는 전체 씬 다이내믹 섀도를 사용하기에 퍼포먼스 비용이 엄청납니다. 퍼포먼스에 가장 큰 영향을 끼치는 부분은 라이트에 영향받는 메시의 갯수와, 해당 메시의 트라이앵글 수 입니다. 즉 커다란 반경에 그림자를 드리우는 무버블 라이트는 반경이 작은 무버블 라이트보다 비용이 몇 배가 될 수 있다는 뜻입니다.

사용법

어느 라이트든지 Transform (트랜스폼) 카테고리 아래 보면 Mobility (모빌리티) 이라는 풀다운 프로퍼티가 있습니다. 이 옵션을 Movable (무버블)로 바꿉니다. 이 프로퍼티는 블루프린트에 추가된 라이트 컴포넌트에도 나타납니다.

섀도 맵 캐시

포인트 / 스포트 라이트가 움직이지 않을 때, 그 라이트에 대한 섀도 맵을 저장한 뒤 다음 프레임에 재사용할 수 있습니다. 그러면 배경이 잘 움직이지 않는 게임에서 무버블 포인트 / 스포트 라이트의 그림자 비용을 크게 줄일 수 있습니다. 여기서는 언리얼 엔진 4 (UE4) 프로젝트에서 그 기능을 사용하는 방법을 알아보겠습니다.

섀도 맵 캐시 & 퍼포먼스

Shadow Map Caching (섀도 맵 캐시) 기능은 어느 UE4 프로젝트에서든 자동 활성화 가능합니다. 섀도 맵 캐시를 사용했을 때의 퍼포먼스를 확인해 보려면, 다음과 같은 방법으로 섀도 맵 캐시 기능을 껐다켰다 하면 됩니다:

다음 섀도 맵 캐시 데모에서는 Sun Temple 프로젝트가 사용되었습니다. 에픽 게임스 런처의 학습 탭에서 이 맵을 받으실 수 있습니다.

  1. 프로젝트의 레벨에서 다이내믹 섀도를 드리우도록 하고픈 라이트를 전부 선택합니다.

  2. 라이트의 모빌리티  무버블 로 설정하고 그림자 드리우기 옵션이 켜졌는지 확인합니다.

  3. 물결표 (`) 키를 눌러 콘솔 창을 열고 Stat Shadowrendering 이라 입력하면 현재 다이내믹 섀도 비용을 볼 수 있습니다.

  4. 다시 물결표 (\) 키를 눌러 <strong>콘솔</strong> 창을 열고 r.Shadow.CacheWholeSceneShadows 0` 이라 입력하여 다이내믹 섀도 캐시 기능을 끕니다.

    CallCount  InclusiveAug 부분의 숫자를 유심히 봐주세요.

  5. 이제 물결표 키를 한 번 더 눌러 콘솔 창을 열고 r.Shadow.CacheWholeSceneShadows 1 이라 입력하여 섀도 캐시 기능을 다시 켭니다. CallCount  InclusiveAug 숫자를 비교해 보면 이 기능이 다이내믹 섀도 퍼포먼스에 끼치는 영향을 확인할 수 있습니다.

    Shadow Caching On

    Shadow Caching Off

     

     

퍼포먼스

섀도 맵 캐시 기능은 퍼포먼스에 엄청난 영향을 끼칩니다. NVIDIA 970 GTX GPU 에서 1920x1200 해상도로 이 최적화를 테스트해 본 결과는 다음과 같습니다.

  • 이 기능을 켜기 전, 그림자를 드리우는 포인트 라이트가 셋 있는 환경에서 캐시가 없는 경우 섀도 뎁스를 렌더링하는 데 걸린 시간은 14.89ms 입니다.

  • Cached Shadow Maps (섀도 맵 캐시) 기능을 켠 상태에서, 똑같은 렌더링 작업에 걸린 시간은 .9ms, 약 16 배 빠른 것입니다!

    참고로 포인트 라이트 33 개의 기여 부분을 렌더링하는 데는 여전히 2ms 가 걸리는데, 이 부분은 다른 식으로 최적화시킬 수는 있지만 이러한 변화에는 영향받지 않습니다.

  • 섀도 맵 캐시에 사용되는 최대 메모리 양은 r.Shadow.WholeSceneShadowCacheMb 로 조절할 수 있습니다.

한계

섀도 맵 캐시 기능이 UE4 프로젝트의 다이내믹 섀도 비용을 낮춰주기는 하지만, 지원되지 않는 기능과 함께 썼을 때 렌더링 부작용을 일으키게 되는 한계점이 몇 가지 있습니다. 여기서는 섀도 맵 캐시의 한계는 무엇이고 그와 관련해서 무엇을 할 수 있는지 알아보겠습니다.

  • 기본적으로 오브젝트가 다음 요건을 충족했을 때만 캐시가 가능합니다:

    • 프리미티브의 모빌리티  스태틱 또는 스테이셔너리 여야 합니다.

    • 레벨에 사용되는 머티리얼이 World Position Offset 을 사용하지 않아야 합니다.

    • 라이트는 포인트 또는 스포트 라이트여야 하고, 그 모빌리티  무버블 로 설정되어 있으며, 그림자 드리우기 옵션이 켜져 있어야 합니다.

    • 라이트가 한 곳에 있어야 합니다.

    • 애니메이팅 Tessellation 또는 Pixel Depth Offset 을 사용하는 머티리얼은 그림자의 뎁스를 캐시에 저장할 때 부작용이 생길 수 있습니다.

 

 

 

 

 

 

ref : docs.unrealengine.com/ko/BuildingWorlds/LightingAndShadows/LightMobility/StationaryLights/index.html

반응형

/** * Gets the list of support fullscreen resolutions. * @return true if successfully queried the device for available resolutions. */

UFUNCTION(BlueprintCallable, Category="Rendering")

static bool GetSupportedFullscreenResolutions(TArray<FIntPoint>& Resolutions);

 

 

 

 

you can Use Bluerpinnt to change resolution

 

 

and Desktop resolution

 

 

 

FullScreen

 

 

Full screen C++ code

 

 

 

ref : www.youtube.com/watch?v=yc28rvQ6SNk

 

 

 

 

 

 

 

반응형

Hello, I try to make resolution settings, but when change resolution with console command with custom resolution ( r.setRes 600 x 400 ) it change the window size and if is full screen it doesn't make anything. How to make resolutions settings with this or other method?

 

 

The r.SetRes command is the right way to go, but you are missing one little feature to make it work with full screen:
Usage of r.SetRes r.SetRes 600x400, where n can either be "f" for fullscreen or "w" for windowed, so if you want to apply 600x400 at fullscreen, you do r.SetRes 600x400f

 

ConsoleCommand

 

 

forums.unrealengine.com/development-discussion/blueprint-visual-scripting/107699-resolution-change

반응형

+ Recent posts