Unity와 ET 프레임워크를 활용한 실시간 멀티카드게임 프로젝트 구축

ET 프레임워크 기반의 온라인 카드게임 개발을 시작하기 앞서, 프로젝트 전체 구조와 초기 설정 방법을 체계적으로 살펴본다. 이 프레임워크는 C# 기반의 풀스택 네트워크 솔루션으로, 유니티客户端과 .NET Core 서버를 단일 코드베이스로 통합 관리할 수 있다는 점에서 개발 효율성을 크게 향상시킨다. 클라이언트와 서버가 동일한 메시지 프로토콜과 데이터 구조를 공유하므로, 전통적인 방식처럼 별도로 인터페이스 문서를 작성하고 동기화하는 수고가 줄어든다.

.NET Core 2.2 이상 버전과 유니티 2018.3 이상이 설치되어 있어야 하며, 개발 환경은 Windows 10을 기준으로 설명한다. macOS나 Linux 환경에서는 Rider나 VS Code를 활용해 유사한 개발 환경을 구축할 수 있으나, 환경 설정 과정에서 발생하는 호환성 문제는 스스로 해결해야 한다.

프로젝트 구조는 크게 클라이언트 디렉토리와 서버 디렉토리로 나뉜다. 클라이언트는 유니티 프로젝트 형태로 구성되며, 서버는 .NET Core 솔루션 형태로 구성된다. 두 부분 모두 ET.Core 공통 라이브러리를 참조하여 핵심 네트워크 로직을 공유한다.

유니티 프로젝트의 핵심 디렉토리 구성은 다음과 같다. Assets 폴더에는 실제 게임 소스와 리소스가 포함되며, 이 중에서도 Model 폴더는 게임 로직 자체를, ET.Core는 프레임워크 핵심 컴포넌트를 담고 있다. Bundles 폴더는 에셋번들 출력을 위한 디렉토리이며, Res 폴더는 설정 파일과 원시 리소스素材를 저장한다. Scenes 폴더에는 유니티 장면 파일이 위치하며, ThirdParty에는 MongoDB, LitJSON, Google.Protobuf 등 외부 라이브러리가 포함된다.

서버 프로젝트는 App, HotFix, Model 세 개의 프로젝트로 구성된다. App 프로젝트는 프로그램 진입점인 Program.cs를 포함하며, HotFix 프로젝트는 게임 서버의 핵심 로직인 Handler와 System 코드를 담는다. Model 프로젝트는 양쪽에서 공유되는 데이터 구조와 컴포넌트 정의를 포함한다. ET 프레임워크의 이러한 구조는 ECS(Entity Component System) 패턴을 따르며, 네트워크 동기화 개발에 특히 적합하다.

클라이언트 프로젝트를 생성한 후, ET 프레임워크의 참조 수집 기능을 활용하여 씬 내 오브젝트를 스크립트와 연결한다. 먼저 빈 GameObject를 생성하여 Global이라는 이름을 붙이고, 여기에 Init.cs 스크립트를 부착한다. 이 스크립트는 게임 초기화 프로세스의 시작점 역할을 수행하며, 비동기 방식으로 프레임워크 핵심 시스템을 초기화한다.

using System;
using System.Threading;
using UnityEngine;

namespace LandlordsGame
{
    public class Initializer : MonoBehaviour
    {
        private void Start()
        {
            this.InitializeAsync().Coroutine();
        }
        
        private async ETVoid InitializeAsync()
        {
            try
            {
                SynchronizationContext.SetSynchronizationContext(SingleThreadContext.Instance);

                DontDestroyOnLoad(gameObject);
                ConfigurationHelper.Setup();
                
                GameFramework.EventSystem.Register(AssemblyType.Core, typeof(Core).Assembly);
                GameFramework.EventSystem.Register(AssemblyType.Model, typeof(Initializer).Assembly);

                GameFramework.Scene.AddComponent<GlobalConfigurationComponent>();
                GameFramework.Scene.AddComponent<ResourceLoaderComponent>();

                // 설정 정보 로드 테스트
                var bundle = GameFramework.Scene.GetComponent<ResourceLoaderComponent>().LoadBundle("configuration.unity3d");
                GameFramework.Scene.AddComponent<ConfigurationComponent>();
                GameFramework.Scene.GetComponent<ResourceLoaderComponent>().UnloadBundle("configuration.unity3d");
                
                var unitData = (UnitConfiguration)GameFramework.Scene.GetComponent<ConfigurationComponent>().Get(typeof(UnitConfiguration), 1001);
                Logger.Debug($"loaded configuration: {JsonUtility.ToJson(unitData)}");
            }
            catch (Exception error)
            {
                Logger.Error(error);
            }
        }

        private void Update()
        {
            SingleThreadContext.Instance.ProcessPendingCallbacks();
            GameFramework.EventSystem.Tick();
        }

        private void LateUpdate()
        {
            GameFramework.EventSystem.LateTick();
        }

        private void OnApplicationExit()
        {
            GameFramework.Shutdown();
        }
    }
}

ET 프레임워크는 Map과 Hidden 레이어를 내부적으로 사용하므로, 유니티 프로젝트 설정에서 해당 레이어를 추가해야 한다. 그렇지 않으면 런타임 중 레이어 관련 오류가 발생한다. 레이어를 추가한 후 에디터에서 플레이 버튼을 누르면 출력 패널에 설정 정보가 표시되며, 이는 초기화 로직이 정상적으로 실행되었음을 확인해준다.

서버 프로젝트는 Visual Studio 솔루션 형태로 관리된다. 솔루션 파일을 열면 여러 csproj 프로젝트가 로드되며, HotFix 프로젝트의 코드를 수정할 때마다 반드시 해당 프로젝트를 재컴파일해야 한다. 서버를首次 실행하면 MongoDB 연결 정보와 네트워크 주소 설정이 포함된 구성 파일이 생성된다.

클라이언트와 서버 간 기본 통신을 테스트하기 위해, 테스트 메시지를 주고받는 기능을 구현해본다. 먼저 클라이언트에 네트워크 컴포넌트를 추가하고, 세션을 생성하여 서버에 연결한다.

// 네트워크 레이어 초기화
GameFramework.Scene.AddComponent<OpcodeTypeComponent>();
GameFramework.Scene.AddComponent<NetworkClientComponent>();

// 서버 연결 및 메시지 교환 테스트
var connection = GameFramework.Scene.GetComponent<NetworkClientComponent>().CreateSession(GlobalConfigurationComponent.Instance.NetworkSettings.Address);
var response = await connection.SendRequest<ServerTestResponse>(new ClientTestRequest { 
    Payload = "서버 연결 테스트 메시지" 
});

연결이 정상적으로 이루어졌다면 서버에서 응답을 반환하며, 이를 출력 패널에서 확인할 수 있다. 그러나 실제로 이 기능을 완전하게 구현하려면 메시지 프로토콜 정의,Opcode 등록, Handler 구현 등 추가적인 작업이 필요하다. 메시지Opcode는 클라이언트와 서버가 동일한 값을 사용해야 하며, 프로토콜 버퍼 파일을 기준으로 생성된 코드 파일을 양쪽 프로젝트에 복사해야 한다.

서버 연결에 실패할 경우, 주로 두 가지 원인을 의심해야 한다. 첫째는Opcode 불일치로, 이 경우 양쪽의 메시지 생성 파일을 동기화해야 한다. 둘째는 서버 주소 불일치로, 클라이언트의 서버 설정과 실제 서버 실행 주소가 일치하는지 확인해야 한다. 주소와 포트는 구성 파일에서 수정할 수 있으며, 기본적으로 로컬 호스트의 10002번 포트를 사용한다.

태그: Unity ET Framework C# Network Programming Multiplayer Game

9월 21일 14:58에 게시됨