TECHARTNOMAD | MAZELINE.TECH

UNITY3D

Unity 프로젝트 XSolution Python Wrapper 활용 사례

jplee 2026. 5. 5. 00:34

Unity 게임 클라이언트 내에서 Python 3.7 VM을 임베딩하여 게임 로직, UI 프레임워크, 데이터 파이프라인까지 Python으로 작성한 사례를 공유합니다.


개요

Unity 기반 게임 클라이언트에서 CPython 3.7 런타임을 임베딩하고, C#과 Python 간의 양방향 바인딩을 통해 게임 로직 대부분을 Python으로 작성한 사례입니다. 이 글에서는 XSolution이라는 Python Wrapper 솔루션의 기술적 아키텍처프로젝트 내 실제 활용 사례를 다룬다.

바이트댄스(ByteDance)에 인수합병된 "C4Games" 가 개발한 XSolution Python Wrapper는 Unity 클라이언트용 CPython 임베딩 솔루션으로서 필자가 2021년 부터 2023년 까지 중국 바이트댄스(ByteDance) 게임사업부 "조석광년(Nuverse)"에서 근무 할때 2개의 프로젝트에 사용했던 솔루션이다.

멀티플래폼 프로젝트인 Dragon:Heir Silent-God에서 활용한 사례를 언급하며 콘텐트 프로그래밍을 파이선 스크립트로 사용한 레이어에 대해서 간단히 소개해 본다.

참고:
Dragon:Heir Silent-God는 SGRastudio가 개발하고 Nuverse가 퍼블리싱한 판타지 오픈월드 RPG 게임입니다.
이 게임은 다양한 영웅 캐릭터와 전략적 팀 전투, 방대한 던전 탐험, 그리고 고품질 3D 그래픽으로 전 세계 유저들에게 높은 평가를 받고 있습니다.
게임 내에서는 자유도 높은 파티 구성과 스킬 조합, 몬스터와의 실시간 전투 등 깊이 있는 전략 요소를 즐길 수 있으며,
모바일과 PC 등 다양한 플랫폼을 지원합니다.


1. 기술적 측면

1.1 아키텍처 개요

XSolution은 CPython VM을 Unity의 Mono/IL2CPP 런타임 위에서 동작하도록 한 임베딩 솔루션입니다. 핵심 구성 요소는 다음과 같다:

XSolution 아키텍처 개요: Unity Game Logic, XPython Runtime Layer, Native Module Binding Layer, CPython 3.7

1.2 멀티플랫폼 네이티브 플러그인

Python VM과 확장 모듈을 플랫폼별 네이티브 라이브러리로 제공하며, Unity의 PluginImporter를 통해 자동 할당된다:

플랫폼아키텍처라이브러리 형식

Windows AMD64 .dll
macOS x86_64 .dylib
iOS aarch64 .a (Static, __Internal)
Android aarch64 / armv7-a .so

플러그인 자동 임포트 시스템 (ImportPlugins.cs):

// 경로에서 OS/CPU를 파싱하여 플랫폼 호환성 자동 설정
// 예: "XPlugins/Windows-AMD64/python37.dll" → StandaloneWindows64
private static readonly Dictionary<string, BuildTarget> kPlatformConfig = ...

1.3 Python VM 라이프사이클 관리

PythonEnvManager가 초기화부터 종료까지 전체 라이프사이클을 관리한다:

// 초기화 흐름
Init()
  → OnStartInit()    // PyImport_AppendInittab으로 네이티브 모듈 등록
  → XPythonEnv.Init(path)  // python37.zip 경로 설정 후 VM 초기화
  → OnFinishInit()   // VM 초기화 후 훅 실행

// 매 프레임 갱신 (GC 처리)
Update()
  → CollectUpdate()  // 스레드 세이프 큐에서 Py_DECREF 처리

// 종료
Destroy()
  → OnStartDestroy()
  → XPythonEnv.Final()  // VM 파이널라이즈
  → OnFinishDestroy()

PyImport_AppendInittab 패턴: 각 네이티브 모듈이 PyInit_<module_name>() 함수를 익스포트하고, 이를 Python VM 초기화 이전에 inittab에 등록하여 sys.path 검색 없이 모듈을 로드한다. 이는 임베딩 환경에서 경로 의존성을 제거하는 핵심 기법이다.

// 예: SgrNavMesh 모듈 등록
[DllImport(SgrNavMeshDllName)]
public static extern IntPtr PyInit_sgr_nav_mesh();

public static void OnStartInit()
{
    CPython.PyImport_AppendInittab2("sgr_nav_mesh",
        Marshal.GetFunctionPointerForDelegate<InitFuncT>(PyInit_sgr_nav_mesh));
}

1.4 C# → Python 타입 자동 익스포트 시스템

Unity의 C# 타입을 Python에서 사용 가능하도록 바인딩하는 코드 생성 시스템을 갖추고 있다:

T4 템플릿 기반 코드 생성:

  • XPythonClassTemplate.tt → 클래스 바인더 생성
  • XPythonBinderTemplate.tt → 모듈 바인더 생성
  • XPythonDelegateWrapTemplate.tt → 델리게이트/이벤트 랩퍼 생성

설정 파일 (XPythonExportConfig.cs):

// 익스포트할 C# 타입 정의 (170+ UnityEngine 타입)
public static HashSet<Type> ExportClassSet = new HashSet<Type>
{
    typeof(GameObject), typeof(Transform), typeof(Camera),
    typeof(Button), typeof(Image), typeof(Canvas),
    typeof(Animator), typeof(ParticleSystem), ...
};

// 정적 클래스
public static HashSet<Type> ExportStaticClassSet = new HashSet<Type>
{
    typeof(Application), typeof(Time), typeof(Input),
    typeof(Resources), typeof(Physics), ...
};

// 멤버 블랙리스트, 매크로 조건부 익스포트 등 지원

생성된 바인딩 코드 예시 (Py_UnityEngine_ShaderVariantCollection.cs):

// 타입 등록
XPythonEnv.RegTypeBegin("ShaderVariantCollection", typeof(...), typeId,
    ShaderVariantCollectionConstructor, ShaderVariantCollectionDestructor);
XPythonEnv.RegTypeFunction(typeId, "Add", Add, XPythonEnv.METH_VARARGS, "");
XPythonEnv.RegTypeMember(typeId, "shaderCount", getter_shaderCount, null, "");
XPythonEnv.RegTypeEnd(typeId);

// 생성자 래핑
[AOT.MonoPInvokeCallbackAttribute(typeof(XPythonEnv.PyCFunction_Constructor))]
static IntPtr ShaderVariantCollectionConstructor(IntPtr self, IntPtr args)
{
    var obj = new UnityEngine.ShaderVariantCollection();
    return XPythonEnv.AddObject(obj, self);  // C# 객체 ID 매핑
}

1.5 PyObject 래퍼와 참조 카운팅

Python의 GIL과 참조 카운팅을 C# 측에서 안전하게 관리한다:

public class PyObject : IDisposable
{
    private IntPtr obj;

    public PyObject(IntPtr pyObj)
    {
        obj = pyObj;
        XPythonDll.XPython_PyIncRef(obj);  // 참조 증가
    }

    public void Dispose()
    {
        if (obj != IntPtr.Zero)
            XPythonEnv.CollectPyObject(obj);  // GC 큐에 등록 (즉시 해제 아님)
    }
}

// 스레드 세이프 GC 큐
static Queue gcQueue = new Queue();
public static void CollectUpdate()
{
    lock (gcQueue.SyncRoot)
    {
        while (gcQueue.Count > 0)
        {
            IntPtr pyObj = (IntPtr)gcQueue.Dequeue();
            XPythonDll.XPython_PyDecRef(pyObj);  // 실제 Py_DECREF
        }
    }
}

1.6 임베딩된 네이티브 모듈 목록

XSolution과 함께 번들된 C++ 기반 Python 확장 모듈:

모듈용도

xlog spdlog 기반 비동기 로깅 (Trace~Critical, 파일/콘솔)
xnet 네트워크 통신 모듈
sgr_math 게임 수학 라이브러리 (벡터, 행렬 등)
sgr_nav_mesh Recast Navigation 기반 네비게이션 메쉬
sgr_aoi Area of Interest 공간 분할
sgr_tile_mesh 타일 메쉬 처리
sgr_pixel_mesh 픽셀 메쉬 처리
sgr_stark_mesh_client 메시 클라이언트
chrono date-tz 기반 시간/타임존 라이브러리
xtimer 타이머 시스템
xfield ECS 필드 시스템 (FixedDict, SyncMode 등)
bson BSON 직렬화/역직렬화
msgpack MessagePack 직렬화/역직렬화
ujson UltraJSON 고속 JSON 파서
hiredis Redis 클라이언트

1.7 PyInjectionStation - C# 핫스왑 지원

Python에서 C# 메서드를 동적으로 재정의할 수 있는 인젝션 시스템:

// PyInjectionStation: 인덱스 → PyObject 매핑 캐시
static PyObject[] injectFunctionCache;

public static PyObject GetInjectionFunction(int index)
{
    return injectFunctionCache[index];
}

public static void CacheInjectFunction(int index, PyObject func)
{
    injectFunctionCache[index] = func;
}

이 구조를 통해 Python 스크립트에서 C# 메서드를 오버라이드하는 핫 업데이트가 가능하다.


2. Dragon 프로젝트 활용 심층 분석

Dragon:Heir Silent-God 프로젝트에서는 "XSolution Python Wrapper"를 단순한 API 바인딩 수준을 넘어, 전체 게임 파이프라인과 핵심 개발·운영 프로세스에 적극적으로 도입했습니다. 다음은 실제 사용 사례와 심화된 활용 구조에 대한 상세 분석이다.

2.1 Python 기반 UI 프레임워크(xgui) – 전체 UI를 Python으로

Dragon 프로젝트는 레거시 Unity UI/UGUI 시스템 대신, UI 전 영역을 Python 스크립트(xgui 프레임워크)로 재구성했습니다. 이 시스템은 대규모 라이브 게임 특성을 고려하여, 매우 유연하고 핫스왑 가능한 구조로 설계되어 있다.

주요 구조 및 실제 사용 패턴

  • 폴더/모듈 아키텍처 (리포지토리 기준)
  • game/xsolution/xgui/ ├── ui_manager.py # UI 매니저: 레이어·뷰 생성·동적 import ├── ui_enum.py # 레이어·UI 열거형 ├── framework/ │ ├── view.py # 뷰 라이프사이클 │ ├── subview.py # 서브뷰·복합 UI │ ├── rtt/ # View 간 요청-응답(RT) │ ├── tools/ │ │ └── view_coexist.py # 뷰 공존·전경/배경 제어 │ └── objects/ # 버튼·리스트 등 위젯 래퍼 game/client/ui/ # 화면별 ui.<패키지>.<모듈> 규칙의 실제 뷰·컨트롤
  • 핫 업데이트 및 실시간 배포
    UI 관련 거의 모든 로직은 Python으로 작성되어 런타임에 로드/교체가 가능합니다. 라이브 서비스 중 클라이언트 재배포 없이 스크립트 갱신으로 대응할 수 있는 구조다.
  • UIManager 주요 책임
    • 9개 UI 레이어 관리 (REVERSO, NOTIFY, LOADING, TOUCH, TIPS, ACTIVITY, MAIN, FILTER, SCENE): Canvas·정렬 순서·표시/숨김을 레이어별로 분리. 예) LOADING 등 특정 레이어 해제 시 다른 레이어와의 공존 규칙은 ViewConexist로 처리
    • View와 Controller 완전 분리: CreateView와 CreateViewControl로 ui.<이름>.main_view / ui.<이름>.main_control 모듈을 각각 로드
    • Widget/SubView 시스템: 복합 UI의 재사용과 중첩(SubView) 지원
    • 동적 모듈 로딩: ImportCls가 sys.modules 캐시와 __import__로 모듈을 로드
    • # 요지: game/xsolution/xgui/ui_manager.py def CreateView(self, name, view_name=None, guid=None, model_guid=0, callback=None, *args, **kwargs): view_name = view_name if view_name else "main_view" module_name = "ui.{}.{}".format(name, view_name) view_module = self.ImportCls(module_name) view = view_module.View(name_with_guid, name, guid) view.Awake(*args, **kwargs) view.__CommonLoading__(template, functools.partial(self.CreatePrefabOver, view, model_guid, callback))
    • CoexistGroup/Floating 뷰 관리: HUD, 알림 등 전경/배경 전환 시 다른 뷰 격납·복귀

프로젝트 내 근거 (경로·역할) — 2.1

경로역할

game/client/game_manager.py GameClientManager.Start()에서 xgui.ui_manager.UIManager() 생성, 입력·씬·스토리지 등과 함께 클라이언트 부트스트랩
game/xsolution/xgui/ui_manager.py 레이어 GameObject·Canvas 생성, CreateView / ImportCls, ViewConexist
game/xsolution/xgui/framework/view.py, subview.py 뷰·서브뷰 베이스
game/xsolution/xgui/framework/tools/view_coexist.py 뷰 공존·오버드로우 완화용 표시 제어
game/xsolution/xgui/framework/rtt/ 화면 간 RT 요청-응답
game/client/ui/ 실제 화면별 뷰·컨트롤 패키지

실제 효과 및 운영 사례

  • 다수의 UI 뷰: 이벤트·프로모션 등으로 뷰 모듈이 지속적으로 추가·갱신되는 라이브 구조에 맞춤
  • 런타임 수정: 특정 지역·채널 요구에 맞춰 텍스트·배치·동작을 스크립트 단에서 빠르게 조정
  • 출시 지역별 분기: 지역별 UI 기능 개폐와 스크립트 분기를 서버·Python 레이어에서 운영

2.2 ECS-스타일 엔티티 시스템 (sgr_basic)

  • xfield 기반의 커스텀 엔티티 시스템으로 게임 내 등장하는 영웅, 스킬, NPC, 도메인 오브젝트 등을 관리합니다.
  • 코드 패턴 (실제 구현은 game/xsolution/sgr_basic/objects/entity.py):
  • 장점: 스키마화된 필드 정의, 서버·클라이언트 도구와의 연동, 직렬화·동기화에 유리

프로젝트 내 근거 (경로·역할) — 2.2

경로역할

game/xsolution/sgr_basic/objects/entity.py Entity / EntityMeta, FixedDict + Assembler
game/xsolution/sgr_server/objects/components/entity_field_comp.py 엔티티 필드·FixedDict 직렬화 등 서버 컴포넌트 측 처리
game/server/svr_common/objects/ 하위 다수 모듈 FixedDict를 상속한 히어로·아이템·매치 등 도메인 객체 정의

2.3 대용량 데이터 관리(xstorage)

  • 스토리지 계층 분리 구조 (패키지: game/xsolution/xstorage/)
    • cli_storage.py: 클라이언트 로컬 캐시·기기별 설정
    • svr_storage.py: 서버와 동기화되는 상태 층
    • storage_data_item.py: 저장·동기화 대상 필드 정의
    • storage_util.py: 공통 유틸
  • 활용: 대규모 테이블·로컬/원격 설정·리플레이·개인화 옵션 등을 Python storage 레이어에서 일관되게 다룸

프로젝트 내 근거 (경로·역할) — 2.3

경로역할

game/xsolution/xstorage/cli_storage.py 클라이언트 로컬 스토리지
game/xsolution/xstorage/svr_storage.py 서버 연동 스토리지
game/xsolution/xstorage/storage_data_item.py 스토리지 항목·필드 정의
game/xsolution/xstorage/storage_util.py 공통 헬퍼
game/client/game_manager.py CliStorage() 등 클라이언트 기동 시 스토리지 연결

2.4 데이터 변환 및 파이프라인 자동화

Dragon 프로젝트에서는 컨텐츠·밸런스·로컬라이제이션 관련 작업에 Python 변환 스크립트를 광범위하게 사용했다.

  • 주요 구조 (data/convert_scripts/)
  • data/convert_scripts/ ├── main_convert.py # 파이프라인 진입점 ├── excel_util.py # Excel 수식·병합·검증 ├── convert_multilanguage.py # 다국어 변환·리소스 배분 ├── export_anim_info.py # 애니메이션 정보 추출 ├── branch_translate_tool.py # 브랜치·번역 검수 파이프라인 ├── post_process.py # 후처리 ├── design_data.py # 디자인 데이터 처리 └── libs/openpyxl/ # 리포지토리 번들 openpyxl
  • 특이점: CI·자동화 환경과 로컬 PC에서 동일한 스크립트로 실행 가능, 변환 단계에서 오류를 조기에 검출

프로젝트 내 근거 (경로·역할) — 2.4

경로역할

data/convert_scripts/main_convert.py 전체 변환 파이프라인 오케스트레이션
data/convert_scripts/excel_util.py Excel 입력 검증·유틸
data/convert_scripts/convert_multilanguage.py 다국어 파이프라인
data/convert_scripts/branch_translate_tool.py 브랜치·번역 보조
data/convert_scripts/export_anim_info.py 애니메이션 메타 추출
data/convert_scripts/convert_util.py, file_util.py 공통 변환·파일 처리
data/convert_scripts/libs/openpyxl/ Excel 처리 라이브러리 번들

2.5 활용 통계 및 정량적 효과

카테고리지표 및 Dragon내 실제 활용

익스포트된 C# 타입 170+ (UnityEngine, Cinemachine, Timeline 등 직접 래핑, 애니메이션/이펙트/사운드 즉시 제어)
자동 생성 바인딩 파일 150+ (UI, 엔티티, 커스텀 데이터구조 – 빌드시 T4 기반 자동 생성)
게임 로직 Python 파일 500+ (모든 주요 시스템 – 퀘스트/스킬/배틀/이벤트 등)
지원 플랫폼 Windows, macOS, iOS, Android (배포 자동화 및 플랫폼별 코드 분기 포함)
네이티브 확장 모듈 14+ (xlog, sgr_nav_mesh, sgr_aoi 등 고성능 시스템을 Native C++로 직접 엮어 Python Layer서 호출)
데이터 변환 스크립트 40+ (컨텐츠팀에서 직접 개발/운용, 텍스트/이미지/엑셀/번역 등 자동 집계 및 변환)

요약:

Dragon 프로젝트에서 XSolution Python Wrapper는 단순한 언어바인딩을 넘어, 게임 운영·서비스·툴자동화 전체에 Python을 퍼스트 클래스 언어로 채택한 예시이며, 이는 라이브게임에서 빠른 대응력, 개발 생산성, 실시간 운영 효율성 모두를 크게 높이는 핵심 인프라가 되었다.

3. 포스트모템

장점

  1. 개발 속도: Python의 동적 타입과 REPL 특성으로 게임 로직 반복 작업이 빠름
  2. 핫 업데이트: PyInjectionStation을 통한 런타임 코드 교체 가능
  3. 도구 생태계: openpyxl, JSON, BSON 등 Python의 풍부한 라이브러리를 게임에서 활용
  4. 타입 안전성: T4 템플릿 기반 자동 코드 생성으로 수작업 바인딩 오류 최소화
  5. 멀티플랫폼: 단일 Python 코드로 4개 플랫폼 지원

고려사항

  1. IL2CPP AOT 제약: AOT.MonoPInvokeCallback 속성이 필수, 제네릭 메서드 바인딩 제한
  2. 메모리 관리: PyObject의 참조 카운팅과 C# GC 간의 동기화 필요 (CollectUpdate 매 프레임 호출)
  3. Python 표준 라이브러리: python37.zip을 스트리밍 에셋에 번들, Android에서는 persistentDataPath로 추출
  4. 스레드 안전: Python VM 호출은 메인 스레드에서만 실행 필요

4. 결론

XSolution Python Wrapper는 Unity 게임 개발에서 "C#은 엔진 바인딩, Python은 게임 로직"이라는 명확한 역할 분리를 가능하게 합니다. 170개 이상의 UnityEngine 타입을 Python에서 직접 호출할 수 있으며, UI 프레임워크에서 데이터 파이프라인까지 광범위하게 활용되고 있습니다.

이 아키텍처는 특히 다음과 같은 프로젝트에 적합합니다: - 반복적인 게임 로직 변경과 핫 업데이트가 필요한 라이브 서비스 게임

  • UI가 복잡하고 자주 변경되는 프로젝트
  • Python 기반 도구 체인을 게임 런타임과 통합하고 싶은 경우
  • 네비게이션 메쉬, AOI 등 C++로 구현된 고성능 모듈을 Python에서 호출해야 하는 경우