Cheondi
개발 · 웹

Unity 하나인데 WebGL과 Android 빌드는 왜 이렇게 다를까

같은 Unity 프로젝트를 WebGL과 Android로 빌드하며 실행 환경의 경계를 처음 제대로 본 기록입니다.

  • #unity
  • #webgl
  • #android

Unity 에디터에서 기능이 잘 돌아가면 빌드도 비슷하게 동작할 거라고 생각했다. 같은 C# 코드와 같은 화면을 쓰는데 크게 다를 이유가 없다고 봤다. WebGL과 Android 빌드를 직접 만지기 전까지는 그랬다. 한쪽에서는 저장한 값이 새로고침 뒤 사라졌고, 다른 쪽에서는 네이티브 기능을 호출하는 순간 오류가 났다.

처음에는 플랫폼별 예외가 끝없이 생기는 느낌이었다. 문제를 실행 환경별 경계로 나누자 어디부터 확인해야 하는지 조금씩 보였다.

WebGL에는 브라우저가 하나 더 있었다

WebGL 빌드는 Unity가 브라우저 안에서 실행됐다. 파일 저장, 네트워크 요청, 클립보드처럼 에디터에서 바로 쓰던 기능도 브라우저 정책과 JavaScript 연결을 거쳤다. C#에서 JavaScript를 부르는 함수는 이름과 인자 형식이 양쪽에서 맞아야 했다.

mergeInto(LibraryManager.library, {
  SaveText: function (keyPtr, valuePtr) {
    const key = UTF8ToString(keyPtr);
    const value = UTF8ToString(valuePtr);
    localStorage.setItem(key, value);
  }
});

페이지가 아직 Unity 인스턴스를 준비하지 못했는데 메시지를 보내면 호출이 사라지기도 했다. 로딩 완료 시점을 브라우저와 Unity가 서로 알려주는 과정이 필요했다.

Android는 빌드 도구와 권한이 끼어들었다

Android에서는 Gradle, SDK 버전, AAR 의존성, 매니페스트 권한이 결과에 영향을 줬다. C# 컴파일이 성공해도 패키징 단계에서 라이브러리 버전이 충돌했고, 설치 뒤에는 네트워크 정책이나 저장소 권한 때문에 기능이 달라졌다.

Unity 코드 확인
 -> Android 프로젝트 생성
 -> Gradle 의존성 해석
 -> APK/AAB 패키징
 -> 기기 설치와 런타임 권한 확인

빌드 실패와 실행 실패를 같은 문제로 보지 않고 어느 단계까지 성공했는지 나눠 기록했다.

공통 로직과 플랫폼 연결을 나눴다

조건부 컴파일을 화면 코드 여기저기에 넣으면 읽기가 어려워졌다. 저장하거나 외부 기능을 호출하는 작은 인터페이스를 두고 플랫폼별 구현을 연결했다.

public interface ILocalStore
{
    void Save(string key, string value);
    string Load(string key);
}

화면과 데이터 로직은 ILocalStore만 알고, WebGL 구현은 브라우저 저장소를, Android 구현은 앱 저장 영역을 사용했다. 플랫폼 차이가 사라지진 않았지만 모이는 장소가 생겼다.

목표 플랫폼에서 확인해야 완료였다

에디터 성공은 공통 로직을 확인한 증거였다. WebGL에서는 브라우저 새로고침과 저장 복원, JavaScript 연결 시점을 확인했고 Android에서는 실제 빌드와 설치, 앱 재실행을 시험했다.

같은 Unity 프로젝트라도 실행 장소가 달라지면 사용할 수 있는 API와 생명주기, 저장 규칙이 달랐다. 이때부터 플랫폼 대응을 마지막 빌드 단계로 미루지 않고 기능을 설계할 때부터 어느 경계를 통과하는지 생각하게 됐다.