Game Develop

[UE5] 언리얼에서의 생성자는 여러번 호출된다. 본문

UnrealEngine5/이것저것

[UE5] 언리얼에서의 생성자는 여러번 호출된다.

MaxLevel 2024. 3. 19. 07:05

필요없는 잡소리를 듣고싶지 않다면

 

https://forums.unrealengine.com/t/when-is-the-constructor-called/480067

 

When is the Constructor called?

Hey guys, first of all, i am sorry for my English. So i need to say that i am in the beginning of GameDev. I know the basics of c++, and so on, and therefore i wanted to look into Unreal Engine. My Problem is something like this: why is it not possible to

forums.unrealengine.com

 

이 글을 읽는걸 추천. 언리얼입문자라면 반드시 보는걸 추천한다.

언리얼엔진이라는 거대한 프레임워크가 어떻게 돌아가는지에 대한 출발점이라고도 볼 수 있기 때문이다.

 

일단 나같은경우, 작업을 하다가 문제가 발생했었는데 특정 액터컴포넌트의 생성자에서 외부csv파일을 불러오는 코드를 수행한다. 해당 코드를 수행하기위해서 서브시스템을 불러와야하기 때문에

 

{
    const auto dataManager = GetWorld()->GetGameInstance()->GetSubsystem<UDataManager>();
    dataManager->InitAttackInformations("DataTable'/Game/DataAsset/AttackInformation_Player.AttackInformation_Player'", m_AttackInformations);
}

 

이런 식으로 코드를 작성했다. 

그런데 이렇게 하고 빌드한다음 에디터를 켰더니, 켜지지도 않았다. GetWorld부분에서 터졌는데, 대충 로그를 찍어보니 GetWorld가 nullptr로 되어있었다. 즉 World가 정의 안되어있었다는 것.

 

그래서 일단 아래와 같이 수정 후, 빌드하고 에디터를 켰다.

if (GetWorld() != nullptr)
{
    const auto dataManager = GetWorld()->GetGameInstance()->GetSubsystem<UDataManager>();
    dataManager->InitAttackInformations("DataTable'/Game/DataAsset/AttackInformation_Player.AttackInformation_Player'", m_AttackInformations);
}

 

일단 다시 에디터를 키려고 작성한 코드라서, 데이터들은 못불러왔다고 생각했다.

GetWorld가 nullptr이라 생각했으니까..

근데 막상 게임시작을 하니까 잘됐다. 즉, 데이터가 잘 불러와졌다는 것이다;;;

 

생각해보니, 언리얼엔진은 생성자를 여러번 호출한다는 사실이 어렴풋이 생각났다.

그래서 대충 아래와 같은 추리를 했다.

 

1. nullptr체크 안하면 에디터킬 때 터지는 이유는, 에디터를 초기화하는 시점에서 생성자를 호출했을 때 World가 정의가 안되어있기 때문에 터지는 것.

 

2. nullptr체크 후 게임 시작하면 정상적으로 데이터를 불러오는 것은, 에디터를 초기화할 때는 생성자를 호출했는데 이때는 World가 정의 안되어있기 때문에 그냥 넘어가고 게임플레이를 시작했을 때 다시 생성자를 호출하는데, 이때는 World가 정의되어 있기때문에 데이터를 불러오는 것.

 

이렇게 생각하고 if문과 else에 각각 로그를 찍어서 테스트해봤는데 생각대로였다.

에디터를 딱 켰을때는 nullptr이라고 로그가 찍혀있었고 게임플레이를 눌렀을때 not nullptr이라는 로그가 찍혀있었다.

 

여기까지 해결하고, 맨 위 링크의 글을 읽으니까 이 현상에 대해서 전반적으로 이해가 됐었다.

CDO는 World가 정의되기 이전에 만들어지니, 당연히 터졌던 것이고 게임플레이를 누르면 또 생성자를 호출해주니 데이터가 정상적으로 불러와졌던 것이다.

 

그래서 GetOwner라던가, GetWorld처럼 게임플레이 이후에 정의되는 코드들에 대한 경우는 BeginPlay쪽에 작성하는게 좋을 수 있다..라고 되어있긴 하다.

근데 위의 코드처럼 내가하려는 것은, 경로를 직접 넣어서 해당 경로의 파일내용을 불러오는 것이기 때문에, BeginPlay 호출 이전에 데이터를 불러들여서 초기화시키려 했던건데...

사실 생성자에 코드를 작성해놨을 뿐, 결국 실제로는 게임이 시작되었을 때 데이터를 불러들이니 그냥 서브시스템으로 안쓰는게 나을법도 하다.

 

어느 글에서 우연히 읽은건데, 경로를 넣어서 특정파일의 데이터를 불러들이는 것 같은 경우는 게임시작시 불러들이는 것보다는 그 전에 불러들이는게 더 낫다는 글을 읽었는데 꽤나 그럴법해서 해당 규칙을 따르려고 한다.

결국 도로묵느낌이 나긴 하다만, 이런 시도덕분에 좀 더 구조를 잘 이해하게 됐으니 이득이라 생각한다.

 

 

== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==

관련되서 이득우님이 말씀하셨던 내용을 추가한다.

어떤 질문에 대한 답변내용인데, 질문내용은 '게임인스턴스 생성자가 플레이할 때마다 호출되는 이유'이다.

https://maxlevel-trace.tistory.com/manage/newpost/727?type=post&returnURL=https%3A%2F%2Fmaxlevel-trace.tistory.com%2F727

 

 

 

게임인스턴스는 전역이라기보다는 게임 프로그램에서 유일함을 보장해주는 객체 인스턴스(싱글톤)입니다.
언리얼 에디터도 언리얼 엔진으로 제작된 프로그램이기 때문에, 에디터를 초기화 시점에서 우리가 사용하는 게임 인스턴스의 생성자가 실행됩니다.
그리고나서 에디터가 만들어 낸 샌드박스 환경(PIE, Play In Editor) 내에서 게임을 실행할 때는 마찬가지로 독립된 환경처럼 동작해야 하기 때문에 게임 인스턴스가 또 실행됩니다. 그래서 여러 번 호출됩니다만, 언리얼 엔진은 이를 감안해 실제 우리가 제작한 게임 안에서는 해당 인스턴스의 유일성을 보장해줍니다.
이러한 특수한 구조로 인해 일반적인 C++ 문법의 전역 혹은 스태틱 변수를 사용하는 경우, 이들은 에디터 프로그램의 스태틱으로 관리되어 시뮬레이션을 시작할 때 초기화되지 않아 예기치 못한 문제를 발생시킬 수 있으니 주의하시기 바랍니다.

 

그래서 만약 클래스에 int형 스태틱변수가 있고, 인스턴스를 만들때마다 ++를 해놨다고 가정했을 때, 처음 게임실행시에는 0부터 시작해서 스폰할때마다 1..2..3... 이렇게 된다.

근데 PIE를 종료 후, 다시 실행하면 0부터시작이 아니라 플레이에서 끝난 지점부터 ++을 한다.