본문 바로가기

게임 인스턴스 활용 데이터 공유

레벨 전환에도 유지되는 GameInstance를 설정하고 여러 레벨과 블루프린트가 공유할 데이터를 관리합니다.

첫 레벨에서 모은 점수를 플레이어 캐릭터의 변수에 저장해 두었는데, 다음 레벨로 넘어가자 점수가 0으로 돌아갔습니다. 레벨을 바꾸면 그 레벨에 있던 캐릭터는 사라지고 새 레벨에서 새 캐릭터가 만들어지기 때문입니다. 지금까지 본 직접 참조, 이벤트 디스패처, 인터페이스는 모두 같은 월드 안의 액터끼리 주고받는 방법이라서, 레벨을 넘어 살아남아야 하는 값에는 맞지 않습니다.

플레이어 점수, 잠금 해제한 아이템, 게임 설정처럼 레벨이 바뀌어도 유지되어야 하는 데이터를 위해 언리얼 엔진은 게임 인스턴스(Game Instance)를 제공합니다. 이 글에서는 게임 인스턴스가 얼마나 오래 유지되는지 살펴보고, 직접 만든 게임 인스턴스를 프로젝트에 지정한 뒤, 점수를 두 레벨 사이에서 공유하는 예제를 따라갑니다.


게임이 실행되는 동안 유지되는 오브젝트

게임 인스턴스는 게임 하나가 실행되는 동안 처음부터 끝까지 유지되는 오브젝트입니다. 공식 문서의 게임플레이 프레임워크 설명에 따르면 게임 인스턴스는 엔진이 시작될 때 만들어져 엔진이 종료될 때까지 살아 있으며, 레벨을 불러오는 사이에도 유지되어야 하는 것은 여기에 두라고 안내합니다. 일반적인 레벨 전환에서도 같은 게임 인스턴스를 계속 씁니다.

반면 게임 모드(Game Mode), 플레이어 컨트롤러(Player Controller), HUD 같은 다른 주요 클래스는 레벨이 바뀔 때 새로 만들어지거나 사라질 수 있습니다. 공식 문서도 게임 모드는 레벨을 불러올 때 만들어지므로 레벨 사이에 유지되지 않는다고 설명합니다. 그래서 게임 인스턴스는 다음과 같은 용도에 알맞습니다.

  • 게임 세션 전체에 걸쳐 유지되어야 하는 데이터: 플레이어 점수, 인벤토리, 잠금 해제 상태, 게임 설정 등.
  • 레벨 사이의 데이터 전달: 한 레벨에서 얻은 정보를 다음 레벨로 넘겨야 할 때.
  • 게임 전체에 대한 알림: 게임 전체에 영향을 주는 사건을 이벤트 디스패처로 알릴 때.

주의할 점도 있습니다. 게임 인스턴스는 실행 하나마다 따로 있습니다. 에디터에서 PIE(Play In Editor) 창을 여러 개 띄우면 창마다 게임 인스턴스가 따로 만들어지고, 멀티플레이어에서도 서버와 각 클라이언트가 각자의 게임 인스턴스를 가집니다. 공식 문서도 게임 인스턴스는 복제되지 않는다고 밝히므로, 여기에 넣은 변수가 서버와 클라이언트 사이에 저절로 공유되지는 않습니다.


직접 만든 게임 인스턴스를 프로젝트에 지정하기

언리얼 엔진은 기본으로 UGameInstance 클래스를 씁니다. 여기에 변수와 로직을 더하려면 이 클래스를 부모로 하는 게임 인스턴스 블루프린트를 만들고, 프로젝트가 그 블루프린트를 쓰도록 지정해야 합니다.

콘텐츠 브라우저에서 마우스 오른쪽 버튼을 누르고 Blueprint Class를 고릅니다. 부모 클래스 선택 창에서 All Classes를 펼쳐 GameInstance를 검색해 고르고, 이름을 BP_MyGameInstance로 정합니다. 그다음 메뉴의 Edit에서 Project Settings를 열고, 왼쪽 목록의 Maps & Modes로 이동해 Game Instance Class에서 BP_MyGameInstance를 고릅니다. 이제 게임이 시작될 때마다 BP_MyGameInstance의 인스턴스가 만들어져 쓰입니다.

이 지정을 빠뜨리는 것이 가장 흔한 실수입니다. 블루프린트만 만들고 프로젝트 설정을 바꾸지 않으면 게임은 여전히 기본 GameInstance를 쓰므로, 뒤에서 볼 캐스트가 실패합니다.


점수를 게임 인스턴스에 저장하기

이제 BP_MyGameInstance에 점수를 두고 다른 블루프린트에서 읽고 바꿔 보겠습니다. BP_MyGameInstance를 열고 Integer 타입의 변수 PlayerScore를 추가한 뒤 컴파일하고 저장합니다.

다음으로 BP_PlayerCharacter를 엽니다. Get Game Instance 노드는 값만 내주는 노드이므로, Event BeginPlay의 실행 선은 그 뒤에 둘 캐스트 노드에 연결합니다. Get Game Instance의 출력 핀에서 드래그해 Cast To BP_MyGameInstance 노드를 연결하고, 캐스트가 성공하는 실행 핀에서만 다음 저장을 실행합니다. Cast Failed로 흐른다면 앞에서 본 프로젝트 설정의 Game Instance Class부터 확인합니다. 참고로 Is Valid는 참조가 비어 있는지만 확인할 뿐 캐스트가 성공할지는 알려 주지 않습니다. 캐스트의 As BP My Game Instance 출력 핀은 MyGameInstanceRef 같은 변수로 승격해 저장해 두면 편리합니다.

이제 점수를 올리는 로직을 만듭니다. 실제 게임이라면 적을 처치하거나 아이템을 얻을 때 점수를 올리겠지만, 여기서는 F 키를 누를 때마다 10점씩 올립니다. 캐릭터가 입력을 받도록 설정되어 있다고 가정하며, 향상된 입력(Enhanced Input)을 쓰는 프로젝트라면 이 키에 대응하는 Input Action의 이벤트를 씁니다. 입력 이벤트의 실행 핀을 실행 분기가 있는 Is Valid에 연결해 MyGameInstanceRef를 확인하고, 유효한 쪽에서 Set PlayerScore를 거쳐 Print String으로 잇습니다. Set PlayerScore에 넣을 값은 MyGameInstanceRef의 Get PlayerScore에 10을 더한 결과이고, Print String에는 갱신한 PlayerScore를 다시 읽어 연결합니다.


다른 레벨에서 점수 읽기

새 레벨 Level_2를 만들고 저장합니다. Level_2에서도 입력을 받는 BP_PlayerCharacter를 써야 합니다. 게임 모드의 기본 폰(Default Pawn)으로 스폰되게 하거나, 레벨에 직접 배치했다면 빙의(Possess)나 자동 빙의(Auto Possess Player) 설정을 맞춥니다. 두 방식을 겹치면 쓸데없는 캐릭터가 하나 더 생기므로 하나만 씁니다.

같은 BP_PlayerCharacter 클래스에는 앞에서 만든 초기화 그래프가 이미 있으므로, Level_2의 새 캐릭터도 자신의 BeginPlay에서 그 그래프를 다시 실행해 MyGameInstanceRef를 얻습니다. 이어서 MyGameInstanceRef의 Get PlayerScore를 Print String으로 출력하게 합니다. 마지막으로 첫 레벨에서 다음 레벨로 넘어가는 로직을 Open Level (by Name) 노드로 만듭니다.

플레이해서 첫 레벨에서 F 키로 점수를 올린 뒤 레벨을 바꾸면, Level_2에서도 이전 점수가 그대로 출력됩니다. 레벨이 바뀌면서 첫 레벨의 캐릭터는 사라졌지만, 점수는 그 캐릭터가 아니라 계속 살아 있는 게임 인스턴스에 있었기 때문입니다.

레벨 전환 중 점수 보관

Level1에서 같은 GameInstance 점수를 갱신하고 레벨 전환 후 새 캐릭터가 30을 읽는 실제 세션 상태이다. 새 Play는 별도 인스턴스의 초깃값 0에서 시작하며 저장 파일의 영속성으로 확대하지 않는다.

ONE RUN · TWO LEVELS · SHARED VALUE

같은 실행의 GameInstance에서 다음 레벨이 값을 읽습니다

Level_1의 캐릭터는 GameInstance 메모리의 PlayerScore를 갱신합니다. 일반적인 레벨 전환 후 Level_2의 새 캐릭터는 같은 GameInstance 참조를 다시 얻어 값을 읽습니다.

같은 게임 실행 안에서 레벨 사이에 점수 전달Level_1 캐릭터가 GameInstance의 PlayerScore를 갱신하고, Level_2의 새 캐릭터가 같은 GameInstance에서 값을 읽습니다.Level_1 캐릭터점수 변경GameInstancePlayerScoreLevel_2 캐릭터점수 읽기메모리 갱신조회값
같은 게임 실행 안에서 레벨 사이에 점수 전달Level_1 캐릭터가 GameInstance의 PlayerScore를 갱신하고, Level_2의 새 캐릭터가 같은 GameInstance에서 값을 읽습니다. 같은 실행의 공유값 흐름이며 캐릭터 참조를 다음 레벨에 넘기는 관계가 아닙니다.
Level_1 캐릭터점수 변경
메모리 갱신메모리 갱신Level_1 캐릭터 · 점수 변경 → GameInstance · PlayerScore
GameInstancePlayerScore
조회값조회값GameInstance · PlayerScore → Level_2 캐릭터 · 점수 읽기
Level_2 캐릭터점수 읽기

일반적인 레벨 전환 예시입니다. 캐릭터 참조를 넘기는 것이 아니라 같은 GameInstance의 값을 새 캐릭터가 읽습니다.

메모리 보관과 디스크 저장의 경계

실행 종료 후에도 값을 남기려면 SaveGame 등으로 명시적으로 저장하고 불러옵니다. 각 PIE 게임 인스턴스는 별도의 GameInstance를 사용하며 서버·클라이언트 간 자동 공유도 아닙니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
Level1 기록

같은 GameInstance에 점수를 저장한다.

  • 점수: 30
  • 원천: 현재 세션 GameInstance
  • 레벨 로컬 캐릭터 값과 세션 소유 값을 구분한다.
레벨 전환

Level1 캐릭터가 끝나고 새 캐릭터가 준비된다.

  • GameInstance: 같은 인스턴스 유지
  • 캐릭터: 교체
  • 레벨 전환은 새 Play 세션 생성과 다르다.
새 캐릭터 읽기

새 레벨 캐릭터가 30을 읽는다.

  • 조회: 30
  • 새 Play의 별도 값: 0
  • 이 유지 기능을 디스크 저장 파일 영속성으로 확대하지 않는다.

레벨 전환 중 점수 보관

Level1에서 같은 GameInstance 점수를 갱신하고 레벨 전환 후 새 캐릭터가 30을 읽는 실제 세션 상태이다. 새 Play는 별도 인스턴스의 초깃값 0에서 시작하며 저장 파일의 영속성으로 확대하지 않는다.

ONE RUN · TWO LEVELS · SHARED VALUE

같은 실행의 GameInstance에서 다음 레벨이 값을 읽습니다

Level_1의 캐릭터는 GameInstance 메모리의 PlayerScore를 갱신합니다. 일반적인 레벨 전환 후 Level_2의 새 캐릭터는 같은 GameInstance 참조를 다시 얻어 값을 읽습니다.

같은 게임 실행 안에서 레벨 사이에 점수 전달Level_1 캐릭터가 GameInstance의 PlayerScore를 갱신하고, Level_2의 새 캐릭터가 같은 GameInstance에서 값을 읽습니다.Level_1 캐릭터점수 변경GameInstancePlayerScoreLevel_2 캐릭터점수 읽기메모리 갱신조회값
같은 게임 실행 안에서 레벨 사이에 점수 전달Level_1 캐릭터가 GameInstance의 PlayerScore를 갱신하고, Level_2의 새 캐릭터가 같은 GameInstance에서 값을 읽습니다. 같은 실행의 공유값 흐름이며 캐릭터 참조를 다음 레벨에 넘기는 관계가 아닙니다.
Level_1 캐릭터점수 변경
메모리 갱신메모리 갱신Level_1 캐릭터 · 점수 변경 → GameInstance · PlayerScore
GameInstancePlayerScore
조회값조회값GameInstance · PlayerScore → Level_2 캐릭터 · 점수 읽기
Level_2 캐릭터점수 읽기

일반적인 레벨 전환 예시입니다. 캐릭터 참조를 넘기는 것이 아니라 같은 GameInstance의 값을 새 캐릭터가 읽습니다.

메모리 보관과 디스크 저장의 경계

실행 종료 후에도 값을 남기려면 SaveGame 등으로 명시적으로 저장하고 불러옵니다. 각 PIE 게임 인스턴스는 별도의 GameInstance를 사용하며 서버·클라이언트 간 자동 공유도 아닙니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
Level1 기록

같은 GameInstance에 점수를 저장한다.

  • 점수: 30
  • 원천: 현재 세션 GameInstance
  • 레벨 로컬 캐릭터 값과 세션 소유 값을 구분한다.
레벨 전환

Level1 캐릭터가 끝나고 새 캐릭터가 준비된다.

  • GameInstance: 같은 인스턴스 유지
  • 캐릭터: 교체
  • 레벨 전환은 새 Play 세션 생성과 다르다.
새 캐릭터 읽기

새 레벨 캐릭터가 30을 읽는다.

  • 조회: 30
  • 새 Play의 별도 값: 0
  • 이 유지 기능을 디스크 저장 파일 영속성으로 확대하지 않는다.

위 그림은 같은 게임 실행 안에서 두 레벨 사이에 점수가 전달되는 흐름입니다. Level_1 캐릭터가 게임 인스턴스 메모리의 PlayerScore를 갱신하고, 일반적인 레벨 전환 뒤 Level_2의 새 캐릭터가 같은 게임 인스턴스의 참조를 다시 얻어 그 값을 읽습니다. 화살표는 공유된 값이 흐르는 방향이며, 첫 레벨의 캐릭터 참조를 다음 레벨로 넘기는 관계가 아닙니다. 그림 아래의 설명처럼 실행이 끝난 뒤에도 값을 남기려면 SaveGame 등으로 명시적으로 저장하고 불러와야 하며, PIE 게임 인스턴스는 각각 별도이고 서버와 클라이언트 사이에 자동으로 공유되지도 않습니다.

단계 그림은 이 흐름을 세 단계로 나눕니다. 먼저 Level_1에서 캐릭터가 같은 게임 인스턴스에 점수를 저장합니다. 다음으로 레벨이 전환되면서 Level_1의 캐릭터는 끝나고 새 레벨의 캐릭터가 준비됩니다. 마지막으로 새 레벨의 캐릭터가 게임 인스턴스에서 점수 30을 읽습니다. 30은 아래 연습 문제처럼 기본값 0에서 10을 세 번 더한 경우의 값입니다.


게임 인스턴스를 쓸 때 주의할 점

게임 인스턴스의 변수는 메모리에만 있으므로, 게임을 끄고 다시 실행해도 값이 필요하다면 SaveGame 등으로 디스크에 직접 저장하고 불러와야 합니다. 저장과 불러오기는 저장 및 로드 기본 구현에서 다룹니다.

게임 인스턴스에는 Get Game Instance 노드로 접근하는데, 이 노드는 유효한 월드 문맥(World Context)을 가진 액터 같은 블루프린트에서 쓸 수 있습니다. 모든 UObject가 같은 접근 문맥을 가지는 것은 아니므로, 월드와 연결되지 않은 오브젝트에서는 바로 쓰지 못할 수 있습니다. 이렇게 어디서든 접근할 수 있는 한곳이 생기는 만큼, 모든 데이터를 게임 인스턴스에 몰아넣고 싶어지기 쉽습니다. 하지만 특정 레벨에만 필요한 데이터나 특정 액터에만 해당하는 데이터는 그 레벨이나 액터 안에서 관리하는 편이 효율적이고 읽기도 쉽습니다. 게임 인스턴스에는 정말로 게임 세션 전체에 걸쳐 유지되어야 하는 데이터만 둡니다.

게임 인스턴스 자체의 초기화와 정리는 블루프린트의 Event Init과 Event Shutdown에서 합니다. 다만 월드의 액터가 준비되어 있어야 하는 로직은 Event Init에 두지 말고, 그 월드에서 플레이가 시작되는 시점에 액터의 BeginPlay 같은 곳에서 실행합니다. 게임 인스턴스는 월드보다 먼저 만들어지므로, Event Init 시점에는 아직 레벨의 액터가 없을 수 있기 때문입니다.

또 게임 인스턴스에 액터의 참조를 보관해도, 그 액터가 Destroy되거나 레벨이 언로드되는 것을 막지는 못합니다. 레벨이 바뀌면 이전 레벨의 액터는 사라지므로, 다음 레벨에 넘길 것은 액터 참조가 아니라 값이나 식별자로 보관하고, 새 월드에서 필요한 액터를 다시 찾습니다.

더 깊이: 게임 인스턴스가 커질 때 쓰는 서브시스템

여러 기능의 데이터와 함수를 게임 인스턴스 하나에 계속 더하면 이 클래스가 금방 비대해집니다. C++를 함께 쓰는 프로젝트라면 서브시스템(subsystem)이 대안이 됩니다. 서브시스템 문서에 따르면 서브시스템은 엔진이 수명을 관리하며 자동으로 만드는 클래스입니다. UGameInstanceSubsystem을 상속한 클래스는 게임 인스턴스가 만들어진 뒤 함께 만들어지고, 게임 인스턴스가 초기화될 때 Initialize가, 종료될 때 Deinitialize가 호출됩니다. 그래서 게임 인스턴스와 같은 수명을 가지면서도 기능마다 별도의 클래스로 나눌 수 있습니다.

서브시스템 클래스 자체는 C++로 만들어야 하지만, 블루프린트에는 캐스트 없이 쓸 수 있는 전용 노드로 자동 노출됩니다. 그래프의 노드 검색에서 subsystems를 찾으면 종류별 분류 아래에 각 서브시스템이 나타납니다. 공식 문서도 앞으로 계속 커질 통계 기능을 게임 인스턴스에 함수로 더하는 대신 서브시스템으로 떼어 내는 예를 듭니다. 문서가 드는 수명 범위는 엔진, 에디터, 게임 인스턴스, 로컬 플레이어이므로, 레벨을 넘어 유지할 데이터라면 게임 인스턴스 서브시스템을 고릅니다.


정리

  • 게임 인스턴스는 엔진이 시작될 때 만들어져 종료될 때까지 유지되므로, 레벨이 바뀌어도 남아야 하는 데이터를 두는 곳입니다.
  • 직접 만든 게임 인스턴스는 Project Settings의 Maps & Modes에서 Game Instance Class로 지정해야 쓰이며, 지정하지 않으면 캐스트가 실패합니다.
  • 다른 블루프린트에서는 Get Game Instance를 Cast To로 바꿔 참조를 얻고, 레벨이 바뀐 뒤의 새 액터도 같은 방식으로 참조를 다시 얻습니다.
  • 게임 인스턴스의 값은 메모리에만 있어 실행이 끝나면 사라지며, PIE 창마다, 서버와 클라이언트마다 따로 있고 복제되지 않습니다.
  • 레벨 사이에는 액터 참조가 아니라 값이나 식별자를 보관하고, 레벨이나 액터에만 필요한 데이터는 그쪽에서 관리합니다.

연습 문제

1. 레벨 이동과 새 실행

게임 인스턴스의 PlayerScore 기본값은 0입니다. Level_1에서 F 입력을 세 번 처리해 매번 10을 더한 뒤, 같은 실행에서 Open Level로 Level_2를 엽니다. Level_2의 BeginPlay는 참조를 얻어 점수를 읽기만 하고 초기화하지 않습니다. 첫 출력은 얼마일까요? SaveGame 처리가 전혀 없는 상태에서 플레이를 멈추고 새로 플레이하면 그 값이 남아 있을까요?

해설 보기

같은 실행의 Level_2에서는 0 + 10 + 10 + 10 = 30을 읽습니다. 레벨이 바뀌어도 게임 인스턴스는 그대로이기 때문입니다.

플레이를 멈추고 새로 시작하면 새 게임 인스턴스가 만들어지므로, 이 조건에서는 기본값 0부터 다시 시작합니다. 실행이 끝난 뒤에도 값을 남기려면 별도의 저장과 불러오기 기능이 필요합니다.

2. 레벨마다 0이 되는 점수

1번과 같은 구성인데, 누군가 BP_PlayerCharacter의 BeginPlay에 "점수를 0으로 Set"하는 노드를 추가했습니다. Level_2의 첫 출력은 얼마가 될까요?

해설 보기

0이 출력됩니다. Level_2의 새 캐릭터도 같은 클래스의 BeginPlay를 실행하므로, 같은 게임 인스턴스의 값을 직접 0으로 덮어씁니다. 게임 인스턴스가 값을 보존해도, 그 값을 덮어쓰는 로직까지 막아 주지는 않습니다.

점수를 처음 한 번만 초기화하려면 게임 인스턴스의 Event Init에서 하거나, 새 게임을 시작하는 경로에서만 초기화하도록 나눕니다. 레벨 이동에서 값이 사라진다면 보존 기능과 초기화 로직을 함께 점검해야 합니다.

3. 게임 인스턴스에 맡긴 상자 참조

Level_1의 보물 상자 액터 참조를 게임 인스턴스의 변수에 넣어 두고, Level_2에서 그 참조로 상자가 열렸는지 확인하려고 합니다. 이 설계는 의도대로 동작할까요?

해설 보기

의도대로 동작하지 않습니다. 레벨이 바뀌면 Level_1의 상자 액터는 사라지며, 게임 인스턴스가 참조를 들고 있어도 그 파괴를 막지 못합니다. Level_2에서 그 참조는 더 이상 유효한 상자를 가리키지 않습니다.

대신 "상자가 열렸다"는 불리언 값이나 상자를 구별하는 식별자를 게임 인스턴스에 저장합니다. 그리고 다시 Level_1로 돌아왔을 때 새로 만들어진 상자가 그 값을 읽어 자신의 상태를 맞추게 합니다.

게임 인스턴스와 게임 모드 같은 프레임워크 클래스의 수명은 언리얼 엔진 공식 문서의 게임플레이 프레임워크 설명에서 확인할 수 있습니다.

다음 글 레벨 블루프린트와의 상호작용에서는 레벨 하나에만 속한 레벨 블루프린트가 레벨의 액터들과 어떻게 정보를 주고받는지 살펴봅니다.