Skip to Content
2026마인크래프트는 왜 React로 게임 UI를 만들었나 — 고성능 게임 UI 아키텍처 정리

마인크래프트는 왜 React로 게임 UI를 만들었나

React Advanced 2021에서 Mojang Studios의 Paulo Ragonha가 발표한 Using React to Build Performant Game UIs in Minecraft를 바탕으로 정리했다.

한눈에 보는 결론

이 발표의 핵심은 단순히 마인크래프트 UI를 React로 만들었다는 이야기가 아니다.

Mojang은 웹 개발의 생산성을 게임 UI에 가져오고 싶었다. 그래서 React, TypeScript, Webpack, Jest 같은 익숙한 웹 생태계를 사용했다. 하지만 게임 UI는 일반 웹앱보다 훨씬 높은 빈도로 값이 바뀌고, Xbox·PlayStation·Switch 같은 콘솔에서는 JavaScript JIT 최적화도 기대하기 어렵다.

결국 Mojang이 택한 구조는 다음과 같다.

  1. UI는 React의 선언형 컴포넌트 모델로 작성한다.
  2. 실제 게임 상태의 원본은 JavaScript가 아니라 C++ 게임 엔진에 둔다.
  3. UI가 필요한 상태 조각만 Facet이라는 observable 계열 데이터로 구독한다.
  4. 자주 바뀌는 값은 React state로 풀어내지 않는다.
  5. Facet을 이해하는 커스텀 렌더러가 값 변경을 DOM까지 직접 전달한다.
  6. 그 결과 값이 바뀔 때마다 React 컴포넌트 트리를 다시 렌더링하고 reconciliation 하는 비용을 피한다.

즉, React를 버리지 않고 React가 가장 비싼 구간을 선택적으로 우회한 설계다.


1. 왜 마인크래프트가 웹 기술을 선택했나

Minecraft Bedrock Edition은 Unity나 Unreal 기반 게임이 아니라 자체 엔진을 사용한다. 기존 UI 시스템 역시 사내 전용 기술이었다.

이 방식은 오래 운영할수록 문제가 생겼다.

  • 새 개발자가 사내 전용 UI 기술을 익히는 데 시간이 오래 걸린다.
  • 범용 도구와 라이브러리를 그대로 활용하기 어렵다.
  • UI 제작과 수정의 반복 속도가 느려진다.
  • 플랫폼이 늘어날수록 유지보수 부담이 커진다.

Mojang은 UI 기술을 공개 표준과 보편적인 개발 생태계 쪽으로 이동시키려 했다. 웹 개발자가 입사한 첫날부터 익숙한 React·TypeScript 기반 코드에 기여할 수 있는 환경이 목표였다.

당시 새 UI 시스템은 실험 수준이 아니라 실제 제품에도 들어가 있었다. 발표자는 Minecraft Bedrock Edition의 업적(Achievements) 화면이 이미 웹 표준과 React 기반으로 배포됐다고 설명한다.

여기서 어려운 점은 마인크래프트의 플랫폼 범위다.

  • Xbox
  • PlayStation
  • Nintendo Switch
  • Windows
  • Android
  • iOS
  • macOS
  • 터치
  • 게임패드
  • VR 등 다양한 입력 체계

웹처럼 특정 브라우저 몇 개만 상대하는 것이 아니라, 성능 특성이 크게 다른 기기와 입력 장치를 모두 감당해야 했다.


2. Chromium을 게임 안에 넣은 것은 아니다

웹 기술로 UI를 만든다고 해서 Minecraft 안에 Chromium을 통째로 임베드한 것은 아니다.

Mojang은 Coherent Labs의 Gameface를 사용했다. Gameface는 HTML·CSS·JavaScript 기반으로 게임 UI를 만들 수 있게 하는 전용 미들웨어다.

일반 브라우저의 모든 기능을 구현하는 대신 게임 UI에 필요한 웹 표준의 부분집합을 중심으로 구현한다. 예를 들어 Flexbox처럼 UI 배치에 중요한 기능은 지원하지만, 전통적인 브라우저와 완전히 동일한 기능 집합을 목표로 하지는 않는다.

중요한 차이는 목적이다.

브라우저는 범용 웹 페이지 실행기가 목표지만 Gameface는 실시간 게임 UI의 성능과 임베딩이 목표다.

그래서 개발 스택 자체는 웹 개발자에게 매우 친숙하다.

  • React
  • TypeScript
  • Webpack
  • Jest
  • HTML / CSS / JavaScript

게임 UI 전용 런타임 위에 웹 생태계의 생산성을 얹는 구조다.

참고: GitNation 전사본에는 Gameface 제작사가 ‘Korean Labs’처럼 잘못 인식된 부분이 있으나 실제 회사명은 Coherent Labs다.


3. 웹앱과 게임은 ‘상태가 사는 곳’이 다르다

일반적인 웹앱은 서버와 브라우저가 분리돼 있다.

서버의 데이터를 HTTP 등으로 가져온 뒤 브라우저 쪽 JavaScript 메모리에 별도의 상태를 만들고, 그 상태를 React가 렌더링한다.

게임은 다르다.

게임 엔진과 UI JavaScript가 같은 애플리케이션 안에서 동작하고, 실제 게임 세계의 진짜 상태는 C++ 엔진에 있다.

예를 들어 Minecraft HUD에는 다음과 같은 값이 존재한다.

  • 체력
  • 배고픔
  • 핫바의 아이템
  • 현재 선택한 아이템

플레이어가 공격을 받으면 C++ 게임 상태의 체력이 바뀐다. 아이템을 바꾸면 선택된 슬롯도 바뀐다. UI는 이 상태를 가능한 한 빠르게 반영해야 한다.

Mojang은 이런 게임 상태의 작은 조각을 Facet이라고 불렀다.

Facet은 개념적으로 Observable과 비슷하다.

  • 하나의 값을 들고 있는 단순 state라기보다
  • 시간에 따라 값이 계속 변하고
  • 필요한 쪽에서 구독하면
  • 이후 변경값을 계속 전달받는 데이터 스트림에 가깝다.

예를 들어 playerStats라는 큰 상태가 있다면 UI 전체가 그것을 받는 대신 체력만 선택하는 selector를 만들 수 있다.

핵심 원칙은 다음과 같다.

게임 상태의 원본은 C++에 두고, JavaScript는 필요한 부분만 구독한다.


4. Remote Facet: C++ 상태와 React 사이의 접착제

Mojang은 Recoil이나 Jotai 같은 원자형 글로벌 상태관리 라이브러리에서 아이디어를 얻었다.

다만 일반적인 웹 상태 라이브러리와 결정적인 차이가 있다.

Facet은 구독되기 전까지 굳이 값을 활성화할 필요가 없다.

동작 흐름은 대략 다음과 같다.

React 컴포넌트 ↓ 구독 Remote Facet ↓ 필요한 상태 ID 요청 JavaScript ↔ C++ 연결 계층 ↓ C++ 게임 엔진의 실제 상태

컴포넌트가 처음 구독하면 JavaScript 쪽에서 이벤트 리스너를 설치하고 C++ 쪽에 해당 데이터가 필요하다고 알린다.

컴포넌트가 사라지면 cleanup을 실행해 구독을 끊고, 게임 엔진에도 더 이상 업데이트를 보내지 않아도 된다고 알린다.

이 구조의 장점은 명확하다.

  • UI에 보이지 않는 데이터까지 계속 동기화할 필요가 없다.
  • 구독 수명주기가 컴포넌트 수명주기와 맞물린다.
  • C++ 상태를 JavaScript 쪽에 거대한 복제본으로 유지하지 않아도 된다.

5. 진짜 성능 문제: React가 느린 게 아니라 ‘매번 React를 통과하는 것’

발표자는 먼저 React 자체는 충분히 빠르다고 전제한다.

일반적인 웹 애플리케이션이라면 React의 표준 렌더링 모델로 충분하고, 불필요한 re-render를 줄이는 식의 최적화가 보통 맞다.

하지만 게임 UI는 상황이 다르다.

진행도, 체력, 게이지, 숫자, 애니메이션 연동 값처럼 매 프레임에 가까운 빈도로 바뀌는 값이 많다.

일반적인 React 흐름은 이렇다.

게임 상태 변경 → JavaScript 상태 변경 → React state 업데이트 → React render → Virtual DOM 비교 / reconciliation → 실제 DOM 변경

Mojang의 질문은 단순했다.

‘불필요한 re-render를 줄이는 것을 넘어, 이 종류의 업데이트에서는 React re-render 자체를 없앨 수 없을까?’

그래서 다음과 같은 흐름을 만들었다.

게임 상태 변경 → Facet 변경 → 필요한 변환만 수행 → 실제 DOM 속성/텍스트 변경

React reconciliation을 중간에서 생략하는 것이다.


6. Facet을 state로 풀지 않고 그대로 흘려보낸다

예를 들어 진행도 progress가 있다고 하자.

일반적인 React 코드라면 Facet에서 현재 값을 꺼내 React state나 숫자로 만든 뒤, 그 숫자를 컴포넌트의 props로 전달할 수 있다.

그런데 그렇게 하면 Facet 값이 바뀔 때마다 React state 업데이트가 발생하고 결국 reconciliation으로 돌아온다.

Mojang은 반대로 Facet 자체를 컴포넌트 트리에 전달한다.

중간 계산 역시 Facet을 유지한다.

예를 들어 숫자형 진행도 Facet을 CSS width 값으로 변환할 때 useFacetMap 같은 API를 사용해:

Facet<number> → map → Facet<string>

처럼 처리한다.

문제는 일반 React DOM이 Facet<string> 같은 사용자 정의 데이터 타입을 이해하지 못한다는 점이다.

그래서 렌더러까지 바꿨다.


7. FastDiv와 커스텀 React Renderer

Mojang은 Facet을 props로 받을 수 있는 FastDiv, FastSpan 계열 컴포넌트를 만들었다.

이 컴포넌트는 일반 문자열·숫자뿐 아니라 Facet을 직접 받는다.

예를 들어 진행도 UI의 개념적 흐름은 다음과 같다.

C++ playerStats → Remote Facet → health selector → useRemoteFacet → useFacetMap → Facet-aware DOM component → 실제 DOM node 업데이트

여기서 중요한 점은 Facet 값이 바뀌어도 React 컴포넌트 함수가 다시 실행될 필요가 없다는 것이다.

React는 초기 선언과 컴포넌트 구조를 만드는 역할을 한다. 이후 고빈도 값 업데이트는 Facet 구독과 커스텀 렌더러가 처리한다.

따라서 개발자는 여전히 React식으로 UI 구조를 작성하지만, 런타임의 핫패스에서는 Virtual DOM과 reconciliation을 건너뛸 수 있다.


8. 실제 벤치마크

발표에서는 표준 React state 기반 구현을 100%의 처리 시간으로 놓고 Facet 구현을 비교한다.

테스트Xbox OneChrome
진행도 값을 매 프레임 변경Facet 구현이 기준 시간의 약 32%Facet 쪽이 더 빠르지만 Xbox보다 격차는 작음
큰 리스트의 많은 항목을 동시에 업데이트약 12%약 30%

즉 두 번째 테스트에서 Xbox One 기준으로는 같은 작업에 걸리는 시간이 표준 React 구현의 약 12% 수준까지 내려갔다.

다만 이 수치를 **‘React Facet이 언제나 React보다 8배 빠르다’**고 일반화하면 안 된다.

이 벤치마크는 발표자가 준비한 특정 업데이트 패턴과 당시의 런타임·하드웨어 조건에서 나온 결과다.

오히려 더 중요한 관찰은 플랫폼 차이다.

콘솔에서는 JIT를 기대할 수 없다

Chrome의 V8은 JIT(Just-In-Time) 컴파일을 적극적으로 사용한다. 반면 발표 당시 Xbox·PlayStation·Switch 같은 콘솔 환경에서는 JavaScript JIT를 사용할 수 없는 조건이 있었다.

웹에서 당연하게 누리던 JavaScript 런타임 최적화가 사라지면:

  • React 컴포넌트 트리 순회
  • 함수 호출
  • reconciliation
  • 각종 추상화 계층

의 비용이 상대적으로 더 크게 보인다.

또 발표자는 React 트리가 복잡해질수록 Facet 방식과 표준 React 방식의 차이가 더 커지는 경향을 언급한다.


9. 이 구조를 한 문장으로 표현하면

‘React는 UI 구조를 선언하는 언어처럼 쓰고, 실시간 값 전달은 Observable 기반 별도 파이프라인으로 처리한다.’

React를 제거한 것이 아니다.

반대로 React가 잘하는 부분은 최대한 유지했다.

  • 컴포넌트 모델
  • 선언형 UI
  • Hooks와 유사한 API
  • TypeScript 생태계
  • 테스트와 빌드 도구
  • 웹 개발자의 낮은 진입 장벽

그리고 게임에서 문제가 되는 부분만 대체했다.

  • 고빈도 state 업데이트
  • 반복적인 React render
  • Virtual DOM reconciliation

이 경계 설정이 이 발표에서 가장 중요한 설계 포인트다.


10. React Facet과 Ore UI

이 기술은 이후 Mojang의 오픈소스 프로젝트 Ore UI 형태로 공개됐다.

현재 공개 저장소에서는 다음과 같이 설명한다.

  • Minecraft Bedrock Edition과 Minecraft Legends 등에서 사용하는 웹 기반 게임 UI 구축 기술
  • React + TypeScript 기반
  • @react-facet: Observable 기반의 고성능 React 게임 UI 상태관리

발표에서 소개된 구성은 대략 다음 역할로 나뉜다.

react-facet-core

Facet이라는 경량 observable 데이터 타입과 React 친화적인 Hooks를 제공한다.

useFacetState, useFacetCallback처럼 기존 React Hook과 비슷한 사고방식을 유지하면서 Facet을 입력·출력으로 사용할 수 있게 한다.

Remote 계층

C++ 게임 엔진에 존재하는 실제 상태를 JavaScript UI에서 구독하는 메커니즘이다.

DOM / custom renderer 계층

Facet 값을 React state로 풀어내지 않고 DOM 업데이트까지 전달한다.

DOM components

커스텀 host renderer 전체를 도입하지 않아도 일반 React DOM 환경에서 Facet 방식의 ‘fast component’를 사용해볼 수 있게 만든 계층이다.

공개 저장소: https://github.com/Mojang/ore-ui 


11. 게임 UI를 만드는 개발자가 가져갈 수 있는 포인트

1) React를 쓸지 말지가 아니라 ‘어디까지 React가 책임질지’를 정해야 한다

React 기반 게임 UI에서 성능 문제가 생기면 곧바로 ‘React가 게임에 부적합하다’는 결론을 내릴 필요는 없다.

UI 구조·컴포넌트 재사용·툴링은 React에 맡기고, 매 프레임에 가까운 데이터 업데이트만 별도 경로로 분리할 수 있다.

2) 게임 상태를 UI 프레임워크의 state와 동일시하지 않는다

체력 82라는 값의 진짜 주인은 React가 아니라 게임 엔진이다.

UI 쪽 store에 모든 게임 상태를 복제하면 두 개의 진실(source of truth)이 생기기 쉽다.

Mojang은 C++ 엔진을 원본 상태 저장소로 보고 UI는 필요한 값만 구독하도록 했다.

3) 고빈도 값과 저빈도 상태를 구분해야 한다

모든 상태를 같은 방식으로 처리할 이유가 없다.

  • 메뉴 열림/닫힘
  • 설정 변경
  • 화면 전환

같은 이벤트는 일반적인 React state로 충분하다.

반면:

  • 게이지
  • 실시간 카운터
  • 매 프레임 변하는 수치
  • 대량 리스트의 동시 업데이트

같은 데이터는 별도 fast path의 효과가 커질 수 있다.

4) PC 브라우저에서 빠르다고 콘솔에서도 빠른 것은 아니다

개발 PC의 Chrome 벤치마크만으로 게임 UI 성능을 판단하면 위험하다.

JIT 가능 여부, CPU 성능, 메모리 제약, 렌더링 엔진 특성이 다르기 때문이다.

실제 타깃 하드웨어에서 측정해야 한다.

5) 범용 표준 + 목적 특화 런타임 조합은 강력하다

Mojang의 선택은 ‘웹 브라우저를 게임에 넣기’가 아니라:

웹의 개발 경험 + 게임 전용 런타임

의 조합이었다.

이 방식은 생산성과 실행 성능을 서로 완전히 포기하지 않고 절충할 수 있는 좋은 사례다.


정리

마인크래프트의 React UI 아키텍처에서 가장 인상적인 부분은 특정 라이브러리가 아니다.

개발자가 작성하는 모델과 실제 실행 모델을 분리한 것이다.

개발자는 React로 선언형 UI를 만든다.

하지만 실시간 게임 데이터가 갱신될 때마다 반드시 React 전체 메커니즘을 통과할 필요는 없다.

Mojang은 Facet이라는 observable형 데이터 모델, C++ 원본 상태 구독, Facet-aware 컴포넌트와 커스텀 renderer를 조합해 이 문제를 해결했다.

결국 이 발표의 메시지는 다음처럼 압축할 수 있다.

생산성을 위해 React를 선택하되, 성능이 중요한 경로에서는 React의 기본 업데이트 모델에 종속되지 않아도 된다.

게임 UI에서 웹 기술을 쓰고 싶다면 ‘React냐 네이티브냐’의 이분법보다, 어떤 상태가 얼마나 자주 바뀌며 그 업데이트가 어떤 경로를 지나야 하는가를 먼저 설계하는 편이 훨씬 중요하다.

참고 자료

Last updated on