모바일 대용량 게임 다운로더

Android와 iOS 같은 플랫폼은 앱 스토어에서 다운로드할 수 있는 앱 크기를 제한합니다. 더 큰 게임을 지원하기 위해 Ren'Py는 본 게임을 다운로드하는 작은 게임인 다운로더를 지원합니다.

요구 사항

이 기능을 사용하려면 충분한 성능을 갖춘 웹 호스팅 서비스가 필요합니다. mega 같은 파일 업로드 서비스나 itch.io 같은 게임 호스팅 사이트는 사용할 수 없습니다. 다운로더는 Nginx를 실행하는 Digital Ocean 드롭릿과, 10GB를 무료로 호스팅할 수 있는 Cloudflare R2에서 테스트되었습니다.

구체적으로 웹 서버는 파일 다운로드를 지원해야 하며, 여러 HTTP Range 요청을 동시에 지원하면 더 원활하게 작동합니다.

작동 방식

다운로더를 사용하려면 게임을 두 개 빌드해야 합니다. 첫 번째는 Ren'Py 엔진과 다운로더가 들어 있는 다운로더 게임으로, 사용자 휴대전화에 처음 전달되는 앱입니다.

두 번째는 사용자가 실제로 플레이할 비주얼 노벨인 본 게임입니다. 다운로더 게임이 이를 다운로드하며, 데이터가 준비되면 기기에 설치합니다.

두 게임은 같은 Ren'Py 버전으로 빌드해야 합니다. 버전이 다르면 다운로더가 본 게임을 업데이트합니다. 그 밖의 업데이트는 본 게임 자체에서 처리해야 합니다. 이제 Ren'Py 업데이터가 모바일 플랫폼에서도 작동하므로 이 방식이 가능합니다.

다운로더 게임은 시작할 때 본 게임의 설치 여부를 확인합니다. 본 게임이 설치되어 있고 버전이 맞으면 본 게임을 시작합니다. 그렇지 않으면 다운로더를 실행하여 플레이어에게 본 게임 다운로드를 안내합니다.

본 게임 빌드와 업로드

본 게임에서 해야 할 핵심 변경은 다음 줄을 추가하는 것입니다.

define build.game_only_update = True

options.rpy 파일의 맨 위에 추가하세요. 그러면 런처의 배포판 빌드(Build Distributions) 화면에 “모바일용 게임 데이터만 업데이트(Game-Only Update for Mobile)” 옵션이 나타납니다.

“배포판 빌드(Build Distributions)”에서 “모바일용 게임 데이터만 업데이트(Game-Only Update for Mobile)”를 선택하고 다른 빌드 패키지는 모두 선택 해제하세요. “옵션(Options)”에서 “업데이트 빌드(Build Updates)”가 설정되어 있는지 확인하고 나머지 옵션은 원하는 대로 설정하세요. 기본값을 사용해도 됩니다.

그런 다음 빌드를 실행합니다.

게임 빌드가 끝나면 필요한 파일이 모두 준비됩니다. updates.json, updates.ecdsa, rpu 디렉터리의 모든 파일을 웹 서버에 업로드하세요. (rpu 디렉터리는 두 업데이트 파일과 같은 위치에 두고, .rpu 파일은 rpu 디렉터리 안에 두어야 합니다.)

다운로더 게임 만들기

다운로더 게임도 Ren'Py 게임이지만 크기를 100MB보다 훨씬 작게 유지해야 합니다. 오프닝 장면이나 음악 재생 등 작은 게임에서 가능한 기능을 사용할 수 있습니다. 다운로더 게임은 별도로 제작하고 일반적인 방식으로 Android 또는 iOS용으로 빌드해야 합니다.

또한 플레이어에게 다운로드 여부를 묻고, WiFi에 연결되어 있지 않으면 요금이 발생할 수 있음을 알린 뒤 게임을 다운로드해야 합니다.

보안을 최대한 확보하려면 본 게임을 빌드할 때 생성된 public_key.pem 파일을 다운로더 게임의 기본 디렉터리로 복사하세요. 이 공개 키로 다운로드를 검증합니다.

다음은 튜토리얼 게임의 에셋을 사용한 다운로더 게임의 스크립트 예제입니다.

# The url to updates.json, on your web server.
define URL = "https://www.domain.com/game-updates/updates.json"

# Disable saving in the downloader game.
define config.save = False

define e = Character("에일린", image="eileen")

label splashscreen:

    scene bg washington
    show eileen happy at left

    $ downloader = updater.start_game_download(URL)

    e "다운로더 게임에 오신 것을 환영해요."

    e "게임을 플레이할 수 있도록 휴대폰에 본편을 다운로드할 거예요."

    if downloader.download_total:
        $ download_mb = int(round(downloader.download_total / 1024 / 1024, 0))

        e "이 게임을 플레이하려면 [download_mb]MB의 데이터를 다운로드해야 해요. WiFi에 연결되어 있지 않으면 데이터 요금이 부과될 수 있어요. 계속하려면 화면을 터치해 주세요."

    else:

        e "이 게임을 플레이하려면 데이터를 다운로드해야 해요. WiFi에 연결되어 있지 않으면 데이터 요금이 부과될 수 있어요. 계속하려면 화면을 터치해 주세요."

    $ updater.continue_game_download()

간단한 예제이지만 몇 가지 짚을 점이 있습니다. 먼저 config.save를 False로 설정합니다. 환경설정을 포함한 저장과 불러오기를 비활성화하여 다운로더의 저장 데이터가 본 게임에 영향을 주지 않도록 하는 중요한 설정입니다.

또한 모든 작업을 splashscreen에서 수행하여 즉시 시작하고 게임 메뉴에 들어가지 못하게 합니다.

게임 초반에 updater.start_game_download()를 호출하여 다운로드 정보를 수집합니다. 그다음 게임이 다운로드 크기를 확인하는 동안 Eileen의 대사를 보여 주며 잠시 시간을 보냅니다.

플레이어가 여러 번 클릭한 후에도 다운로드 크기를 알아냈을 수도, 아직 알아내지 못했을 수도 있으므로 조건문으로 확인합니다. 실제 크기를 알면 메가바이트로 변환하여 표시하고, 모르면 좀 더 일반적인 안내 문구를 표시합니다. (다운로드 크기의 예상값을 게임에 직접 넣을 수도 있습니다.)

마지막으로 updater.continue_game_download()를 호출하여 다운로드를 시작합니다. 스크린이 나타나고 대부분의 동작은 할 수 없지만 음악은 계속 재생되고 ATL도 계속 실행됩니다.

다운로드가 끝나면 본 게임이 자동으로 시작됩니다. 실패하면 다운로더가 다시 시작되어 플레이어가 재시도할 수 있습니다.

다운로더 스크린

다운로더는 다운로드 진행 상황을 표시하는 스크린입니다. 기본 스크린은 renpy/common/00updater.rpy에 있으며 게임 디렉터리에 복사본을 만들어 사용자 지정할 수 있습니다.

기본 스크린은 다음과 같습니다.

screen downloader(u):

    style_prefix "downloader"

    frame:

        has vbox

        if u.state == u.CHECKING or u.state == u.PREPARING:
            text _("게임 데이터 다운로드를 준비하고 있습니다.")
        elif u.state == u.DOWNLOADING or u.state == u.UNPACKING:
            text _("게임 데이터를 다운로드하고 있습니다.")
        elif u.state == u.FINISHING or u.state == u.DONE:
            text _("게임 데이터 다운로드가 완료되었습니다.")
        else: # An error or unknown state.
            text _("게임 데이터를 다운로드하는 중 오류가 발생했습니다:")

            if u.message is not None:
                text "[u.message!q]"

            text _("게임 데이터 다운로드가 완료되어야 게임을 실행할 수 있습니다.")

        if u.progress is not None:
            null height gui._scale(10)
            bar value (u.progress or 0.0) range 1.0

        if u.can_proceed:
            textbutton _("다시 시도") action u.proceed

style downloader_frame:
    xalign 0.5
    xsize 0.5
    xpadding gui._scale(20)

    ypos .25
    ypadding gui._scale(20)

style downloader_vbox:
    xfill True
    spacing gui._scale(10)

style downloader_text:
    xalign 0.5
    text_align 0.5
    layout "subtitle"

style downloader_label:
    xalign 0.5

style downloader_button:
    xalign 0.5

다운로더 함수

updater.continue_game_download(screen='downloader')

게임 데이터 다운로드를 계속합니다. 다운로드가 끝나거나 사용자가 게임을 종료할 때까지 반복합니다.

updater.start_game_download(url, **kwargs)

url에서 게임 데이터 다운로드를 시작합니다. 다운로드해야 할 항목을 확인하는 과정을 시작하고 Update 객체를 반환합니다.