서브컬처 게이머

세상의 모든 아름다운 것들을 위하여


나니노벨 Resource Providers

개요

※이 글은 유니티 다이얼로그 시스템 에셋 ‘Naninovel(나니노벨)’의 한국어 번역 페이지입니다.

※모든 내용의 저작권 및 내용의 책임과 권한은 Naninovel에 있습니다.

※원문 페이지: (링크)


리소스 제공자는 Naninovel 관련 데이터를 가져오는 데 쓰입니다.

  • 시나리오 스크립트의 “.nani” 텍스트 파일
  • 캐릭터 스프라이트 텍스처
  • 음악의 오디오 클립
  • 등등

각 제공자는 특정 소스에서 데이터를 가져오는 데 특화되어 있습니다.

  • 프로젝트의 “리소스” 폴더
  • Unity Addressable asset system
  • 로컬 파일 저장소
  • Google Drive 계정
  • 등등

리소스 제공자의 구성은 Naninovel -> Configuration -> Resource Provider 메뉴에서 가능합니다.

리소스 관리(Resources Management)

리소스 정책(Resource Policy) 프로퍼티는 스크립트 실행 중에 리소스가 로드 및 언로드되는 시기를 지정합니다.

Static(정적)

  • 스크립트 실행에 필요한 모든 리소스는 재생을 시작할 때 미리 로드되고(로딩 화면으로 가려짐) 스크립트 재생이 완료된 후에만 언로드됩니다. 이 정책은 기본값이며 대부분의 경우에 권장됩니다.

Dynamic(동적)

  • 재생할 때 다음 동적 정책 단계(Dynamic Policy Steps) 명령어에 필요한 리소스만 미리 로드되고 사용되지 않은 모든 리소스는 즉시 언로드됩니다. 엄격한 메모리 제한이 있는 플랫폼이나 naninovel 스크립트를 제대로 구성하는 것이 불가능한 경우 이 모드를 사용합니다. 게임이 진행되는 동안 리소스가 백그라운드에서 로드될 때 버벅임이 발생할 수 있습니다.

메모리 관리 가이드에서 Naninovel이 리소스를 로드하고 로드하는 방법에 대해 자세히 알아보세요.

로그 리소스 로딩(Log Resources Loading) 활성화 시, 다양한 제공자 관련 로그 메시지가 기본 로딩 화면 UI에 표시됩니다.

빌드 처리(Build Processing)

빌드 처리 활성화(Enable Build Processing)는 에디터 메뉴에서 할당된 에셋을 빌드에서 사용 가능한지 확인하는 데 필요한 빌드 사전 처리 절차를 활성화합니다.

커스텀 빌드 환경을 사용하거나 자체 빌드 후크를 연결하는 경우 처리를 비활성화해야 할 수 있습니다.

해당 속성을 활성화하거나 비활성화할 때 변경 사항을 적용하려면 Unity 에디터를 다시 시작하세요.

어드레서블 시스템(addressable system)이 사용 가능한 상태에서는, Use Addressables 옵션을 활성화하면 처리 단계가 최적화되어 빌드 시간이 향상됩니다.

자동 빌드 번들(Auto build Bundles)을 동시에 활성화하면 빌드할 때 에셋 번들이 자동으로 컴파일됩니다.

configuration 메뉴의 다른 프로퍼티는 공급자별로 다르며 아래에 설명되어 있습니다.

특정 리소스 제공자의 동작은 해당 구성 메뉴에서 사용할 수 있는 로더 속성을 통해 구성됩니다.

예를 들어 오디오 리소스(BGM 및 SFX)를 검색하는 데 사용되는 기본 로더 구성은 다음과 같습니다.

Path Prefix 속성으로 특정 유형의 리소스에 대해 공급자의 루트 경로를 통해 추가 경로를 지정할 수 있습니다.

예를 들어, 프로젝트의 “Resources” 폴더에서 “Explosion” 오디오 파일을 검색하려는 경우 경로 접두사를 “Audio”로 설정하면 다음과 같은 리소스 요청이 발생합니다.

  • Resources.Load(“Audio/Explosion”)

제공자 리스트(Providers List) 옵션으로, 사용할 공급자 유형과 순서를 지정할 수 있습니다.

예를 들어, 위의 예에서 오디오 리소스를 요청할 때 먼저 “Addressable” 공급자가 사용되고, 공급자가 요청된 리소스를 찾을 수 없는 경우 “Project” 공급자가 사용됩니다.

에디터에 있는 동안에는 (로더 구성에 관계없이) 특별한 “Editor” 리소스 제공자가 항상 먼저 사용된다는 점에 유의하세요.

제공자는 Naninovel의 구성 및 리소스 관리자 메뉴(Naninovel -> Resources-> …)를 통해 할당된 모든 리소스에 액세스할 수 있습니다.

게임이 빌드되면 이러한 모든 리소스는 임시 “Resources” 폴더에 자동으로 복사되거나 (어드레서블 시스템이 설치 및 활성화된 경우) 어드레서블 구성에 등록되어 에셋 번들로 컴파일됩니다.

Unity 편집기가 아닌 빌드에서 제공자 관련 테스트를 항상 수행하는 것을 잊지 마십시오.

어드레서블(Addressable)

어드레서블 에셋 시스템은 “주소”로 에셋을 로드할 수 있는 Unity 패키지입니다. 비동기 로딩을 사용하여 종속성 컬렉션이 있는 모든 위치(로컬 스토리지, 원격 웹 호스팅 등)에서 로드를 지원합니다. 시스템 설정, 구성 및 사용 방법에 대한 Unity 설명서를 참조하세요.

Naninovel은 프로젝트에 패키지가 설치되고 리소스 제공자 구성에서 Use Addressables 옵션이 활성화되면 어드레서블 항목을 자동으로 사용되며 추가 설정이 필요하지 않습니다.

Naninovel의 구성 메뉴에 할당된 모든 에셋(예: 시나리오 스크립트, 캐릭터 스프라이트, 오디오 클립 등)은 플레이어를 구축할 때 시스템에 등록(주소 할당)됩니다.

<경고>

Unity 버그로 인해 addressable은 공통 번들에 패킹된 개별 에셋을 언로드할 수 없습니다. 일시적인 해결 방법은 번들 간에 에셋을 분배하거나 Bundle Mode의 Addressable Group 설정을 Pack Separately로 설정합니다.

Naninovel 어드레서블 에셋이 제공되는 방식을 구성하려는 경우(예: 원격 웹 호스트 지정) 메뉴를 통해 Naninovel 그룹을 편집합니다.

Window -> Asset Management -> Addressables -> Groups

그룹은 게임을 처음 빌드할 때 자동으로 생성됩니다. 누락된 경우 수동으로 만들 수 있습니다.

<메모>

Naninovel 주소 지정 가능 그룹 아래의 에셋 레코드는 각 빌드에서 자동으로 생성됩니다.

변경 사항은 빌드 시 손실되므로 레코드를 수동으로 편집하지 마세요.

기본적으로 리소스 공급자 구성의 Group By Category은 비활성화되어 모든 Naninovel 리소스가 단일 “Naninovel” 어드레서블 그룹 아래에 추가됩니다.

범주별로 리소스를 그룹화하려는 경우(예: 개별로 패킹하거나 특정 옵션 활성화 시) 속성을 활성화하고 다시 빌드하세요.

활성화되면 각 리소스 범주(예: 스크립트, 오디오, 문자 등)가 “Naninovel-Category”라는 고유한 어드레서블 그룹 아래에 추가됩니다.

여기서 Category는 리소스의 범주입니다.

에디터 메뉴를 사용하지 않고 어드레서블 에셋을 Naninovel에 노출하려면 커스텀 어드레서블 그룹을 사용하십시오.

커스텀 그룹은 선점된 이름인 “Naninovel”로 시작할 수 없다는 점을 제외하고 모든 이름을 가질 수 있습니다. 그렇지 않으면 자동 생성된 것으로 인식되어 빌드 시 지워집니다.

노출된 에셋의 주소는 “Naninovel/”로 시작해야 하며 “Naninovel” 라벨이 할당되어 있어야 합니다.

리소스 공급자 구성 메뉴의 추가 레이블 속성을 통해 Naninovel에서 사용하는 에셋을 필터링하기 위해 추가 레이블(Extra Label)을 지정할 수 있습니다.

어드레서블 제공자(Addressable provider)는 런타임 빌드에서만 사용되며 기본적으로 에디터에서는 비활성화되어 있습니다.

Naninovel의 리소스 관리자를 사용하여 할당하는 대신 어드레서블 주소를 통해 리소스를 수동으로 노출하는 경우, 리소스 제공자 구성 메뉴의 에디터에서 어드레서블 속성을 허용하기(Allow Addressable In Editor) 옵션을 사용하여 활성화할 수 있습니다.

<예시>

Naninovel 리소스를 어드레서블 공급자에 수동으로 노출하고(리소스 편집기 메뉴를 사용하지 않고) 원격 호스트에서 특정 에셋을 제공하는 방법에 대한 예제 프로젝트를 확인하세요.

어드레서블에 대한 공식 Unity 학습 자료를 참고하셔도 좋습니다.

<메모>

저희는 에셋의 원격 웹 호스팅 설정이나 기타 배포/제공 시나리오와 같이 Unity의 어드레서블 에셋 시스템 자체에 대한 튜토리얼이나 지원을 제공하지 않습니다.

자세한 내용은 지원 페이지를 참조하세요.

프로젝트

프로젝트 공급자는 Unity 프로젝트의 “Resources” 폴더에 있는 에셋을 제공합니다. 프로젝트 리소스 로딩 API에 대한 자세한 내용은 Unity 가이드를 참조하십시오.

대부분의 경우 “Resources” 폴더를 사용하지 않는 것이 좋습니다.

가능하면 Naninovel 리소스 관리자 메뉴를 통해 리소스를 할당하거나 대안으로 어드레서블 시스템을 사용하는 것이 좋습니다.

그후에 에셋을 “Resources” 폴더 밖으로 이동하는 것을 잊지 마십시오.

로컬(Local)

로컬 제공자(Local Provider)

로컬 공급자를 사용하면 로컬 파일 시스템의 임의의 위치에서 간단한(시나리오 스크립트 및 관리 텍스트, 스프라이트 캐릭터 및 배경, 오디오) 에셋을 제공할 수 있습니다.

<경고>

로컬 공급자는 파일 시스템에서 raw 파일(원본 파일)을 로드하고 런타임에 변환하므로 다른 공급자에 비해 속도가 느리고 지원되는 파일 형식이 제한됩니다.

개발 또는 특정 기능(예: 커뮤니티 모딩)에만 사용하십시오.

<지원되는 파일 형식>

  • .nani: 시나리오 스크립트
  • .png / .jpg: 이미지/텍스처
  • .wav: 오디오(PCM16 44100Hz 스테레오)

<>

지원되는 파일 형식을 더 추가할 수 있습니다. 나니노벨 엔진 서비스를 재정의하고 로컬 공급자에 대한 사용자 지정 변환기를 추가하는 방법이 있습니다.

  • IResourceProviderManager의 예시

Local Path Root 리소스 제공자 구성의 속성은 로컬 리소스가 저장된 폴더를 가리켜야 합니다.

절대 경로(예: C:\Resources) 또는 상대 경로를 사용할 수 있으며, 다음 원본 중 하나로 시작합니다.

  • %DATA% — 대상 디바이스의 게임 데이터 폴더(Application.dataPath)
  • %PDATA% — 대상 디바이스의 영구 데이터 디렉토리(Application.persistentDataPath)
  • %STREAM% — “StreamingAssets” 폴더(Application.streamingAssetsPath)
  • %SPECIAL{F}% — OS 특수 폴더로, 여기서 F는 특수 폴더 열거형 값의 이름입니다.

기본값(%DATA%/Resources)은 게임의 데이터 디렉터리 내에 있는 “Resources” 폴더를 가리킵니다. (단, 대상 플랫폼에 따라 다를 수 있습니다)

사용 예 중 하나로, 시나리오 공동 작업을 위해 공유 폴더에서 naninovel 스크립트를 로드한다고 가정해 보겠습니다.

C:/Users/Admin/Dropbox/MyGame/Scripts

루트 폴더에 대한 절대 경로(C:/Users/Admin/Dropbox/MyGame)를 지정하는 것도 가능하지만, 이를 위해서는 모든 공동 작업자가 정확히 동일한 경로(동일한 드라이브 레이블 및 사용자 아래)로 폴더를 저장해야 합니다. 이름).

대신 “UserProfile” 특수 폴더 원본에 대해 다음 상대 경로를 사용하는 것을 권장합니다.

%SPECIAL{UserProfile}%/Dropbox/MyGame

스크립트 설정에서 주어진 Path Prefix가 Scripts로 설정되고 로컬 제공자(local provider)가 리스트에 추가되었다면, 스크립트 내비게이터는(nav 콘솔 명령으로 액세스 가능한) 폴더 아래에 저장된 모든 “.nani” 텍스트 파일을 선택해야 합니다.

Google 드라이브

구글 드라이브 제공자(Google Drive Provider)

오픈소스(MIT 라이선스) 서드 파티 패키지를 통해 구현된 UnityGoogleDrive를 사용하면 Google Drive를 다음 리소스의 제공자로 사용할 수 있습니다.

  • Naninovel 스크립트 및 관리 텍스트(Google 문서를 통해)
  • 캐릭터와 배경(스프라이트 구현만 해당);
  • BGM, SFX 및 음성.

Google 드라이브 리소스 폴더를 다른 사용자와 공유하여 형상 관리 시스템이나 기타 협업을 위한 복잡한 도구를 사용할 필요 없이 공동으로 작업할 수 있습니다.

Google 드라이브를 리소스 제공자로 선택하려면 먼저 UnityGoogleDrive를 설치해야 합니다. 설치 및 설정 지침은 GitHub 프로젝트 추가 정보를 참조하세요.

<메모>

Git 리포지토리에서 패키지를 설치하기 전에 Git 클라이언트가 컴퓨터에 설치되어 있고 Git 실행 파일 경로가 PATH 시스템 환경 변수(일반적으로 설치 중에 자동으로 수행됨)로 설정되어 있는지 확인하십시오.

UnityGoogleDrive 패키지가 구성되면 Resource Provider 메뉴에 관련 속성이 표시됩니다.

Google Drive Root Path는 Google 드라이브 폴더 내의 디렉터리에 대한 상대 경로로, 리소스 루트 경로로 간주되어야 합니다.

예를 들어, 시나리오 스크립트를 MyGame/Scripts에 저장하려면 Google Drive Root Path를 MyGame로 지정합니다.

Google Drive Request Limit 속성을 사용하면 Google Drive API에 연결할 때 허용되는 최대 동시 요청을 설정할 수 있습니다. 이는 허용되는 동시 요청 수를 제한하는 개인 Google 드라이브 요금제를 사용할 때 통신 오류를 방지하기 위해 필요합니다.

Google Drive Cache Policy은 다운로드한 리소스의 캐싱 동작을 지시합니다.

  • Smart는 Changes API를 사용하여 요청된(캐시된) 리소스가 다운로드하기 전에 원격 폴더에서 변경되었는지 확인하려고 시도합니다.
  • Purge All On Init는 엔진 초기화 시 캐시를 제거하고 첫 번째 다운로드 후 항상 캐시된 버전을 사용합니다.
    • 캐시는 purge 콘솔 명령으로 언제든지 수동으로 제거할 수도 있습니다.

검색하려는 리소스에 대한 제공자 목록에 Google 드라이브를 추가하는 것을 잊지 마세요.

예를 들어 다음은 스크립트 관리자가 주소 지정 가능한 프로젝트 소스 외에도 Google 드라이브에서 스크립트를 찾도록 만듭니다.

<예시>

Google 드라이브 Provider(제공자)를 설정하고 사용하는 방법에 대한 예는 NaninovelSandbox 프로젝트를 확인하세요.

프로젝트에서 스크립트, 캐릭터, 배경 및 오디오 리소스는 인증된 사용자 드라이브에 저장된 해당 파일에서 제공됩니다.

커스텀 제공자(Custom Providers)

리소스 제공자의 커스텀 구현을 추가하고 Naninovel이 이를 내장 공급자와 함께(또는 대신) 사용하도록 하는 것이 가능합니다.

커스텀 제공자를 추가하려면 매개 변수가 없는 생성자를 사용하여 C# 클래스를 만들고 IResourceProvider 인터페이스를 구현합니다.

일단 생성되면 커스텀 제공자 유형이 빌트인 타입과 함께 모든 로더 구성 메뉴에 나타납니다.

Naninovel/Runtime/Common/ResourceProvider 패키지 디렉터리에서 내장 리소스 공급자 구현을 찾을 수 있습니다. 자신의 버전을 구현할 때 이를 참조로 자유롭게 사용하십시오.

다음은 아무 작업도 수행하지 않고 사용될 때 메시지를 기록하는 커스텀 제공자의 예입니다.

using Naninovel;
using System;
using System.Collections.Generic;

public class CustomResourceProvider : IResourceProvider
{
    public event Action<float> OnLoadProgress;
    public event Action<string> OnMessage;

    public bool IsLoading => default;
    public float LoadProgress => default;
    public IEnumerable<Resource> LoadedResources => default;

    public Resource<T> GetLoadedResourceOrNull<T> (string path)
        where T : UnityEngine.Object
    {
        OnMessage?.Invoke($"GetLoadedResourceOrNull: {path}");
        return default;
    }

    public UniTask<Resource<T>> LoadResourceAsync<T> (string path)
        where T : UnityEngine.Object
    {
        OnMessage?.Invoke($"LoadResourceAsync: {path}");
        OnLoadProgress?.Invoke(1f);
        return default;
    }

    public UniTask<IEnumerable<Resource<T>>> LoadResourcesAsync<T> (string path)
        where T : UnityEngine.Object
    {
        OnMessage?.Invoke($"LoadResourcesAsync: {path}");
        OnLoadProgress?.Invoke(1f);
        return default;
    }

    public UniTask<IEnumerable<Folder>> LocateFoldersAsync (string path)
    {
        OnMessage?.Invoke($"LocateFoldersAsync: {path}");
        return default;
    }

    public UniTask<IEnumerable<string>> LocateResourcesAsync<T> (string path)
        where T : UnityEngine.Object
    {
        OnMessage?.Invoke($"LocateResourcesAsync: {path}");
        return default;
    }

    public UniTask<bool> ResourceExistsAsync<T> (string path)
        where T : UnityEngine.Object
    {
        OnMessage?.Invoke($"ResourceExistsAsync: {path}");
        return default;
    }

    public bool ResourceLoaded (string path)
    {
        OnMessage?.Invoke($"ResourceLoaded: {path}");
        return default;
    }

    public bool ResourceLoading (string path)
    {
        OnMessage?.Invoke($"ResourceLoading: {path}");
        return default;
    }

    public bool SupportsType<T> ()
        where T : UnityEngine.Object
    {
        OnMessage?.Invoke($"SupportsType: {typeof(T).Name}");
        return default;
    }

    public void UnloadResource (string path)
    {
        OnMessage?.Invoke($"UnloadResource: {path}");
    }

    public void UnloadResources ()
    {
        OnMessage?.Invoke("UnloadResources");
    }
}

메모리 관리

Naninovel은 Unity 엔진 씬과 독립적이므로 재생된 스크립트에서 필요할 때 에셋(텍스처, 오디오, 프리팹 등)이 로드되고 자동으로 언로드됩니다.

리소스 제공자 구성 메뉴의 Resource Policy 프로퍼티로 특정 정책을 선택할 수 있습니다.

리소스 제공 관리자는 로드된 리소스에 대한 참조를 추적하고 사용자(“보유자”)가 리소스를 사용하지 않을 때(“보유”) 해당 리소스를 삭제(언로드)합니다.

이 메커니즘은 스크립트 명령에서 가장 두드러집니다.

예를 들어 사용자 정의 명령을 사용하여 배경 음악을 재생한다고 가정해 보겠습니다. 오디오 플레이어를 재생하려면 오디오 클립 에(리소스)이 필요하므로 명령을 실행하기 전에 에셋을 미리 로드하고 “보류”하고 명령을 실행한 후에 해제해야 합니다.

public class PlayMusic : Command, Command.IPreloadable
{
    public StringParameter MusicName;

    private IAudioManager audio => Engine.GetService<IAudioManager>();

    public async UniTask PreloadResourcesAsync ()
    {
        await audio.AudioLoader.LoadAndHoldAsync(MusicName, this);
    }

    public void ReleasePreloadedResources ()
    {
        audio.AudioLoader.Release(MusicName, this);
    }

    public override async UniTask ExecuteAsync (AsyncToken asyncToken = default)
    {
        await audio.PlayBgmAsync(MusicName, asyncToken: asyncToken);
    }
}

이 명령은 Command.IPreloadable 인터페이스를 구현합니다.

스크립트 플레이어는 이러한 명령을 감지하고 사전 로드 및 언로드 메서드를 호출하여 명령이 실행되기 전에 에셋이 준비되고 실행 후에 해제되는지 확인합니다.

자원 공유

Naninovel과 유니티 엔진 게임 플레이 모드 간에 리소스를 공유하고 싶을 수도 있습니다.

커스텀 게임플레이가 Naninovel과 독립적으로 구현되는 경우(사용자 정의 모드가 활성화되면 엔진이 비활성화됨) 아무런 문제가 없어야 합니다.

하지만 커스텀 모드와 나니노벨을 동시에 사용하는 경우에는 리소스 사용 방식에 주의를 기울여야 합니다.

예를 들어 일부 UI 요소의 소스로도 사용되는 모양 텍스처가 있는 Naninovel의 스프라이트 배경이 있다고 가정해 보겠습니다.

어느 시점에서 Naninovel은 텍스처 해제를 시도하고 UI 요소에서도 사라질 것입니다.

나니노벨 엔진이 해당 텍스처를 유니티 엔진에서도 사용하고 있고 언로드되어서는 안 된다는 사실을 인식하지 못해서 이런 일이 발생합니다.

해당 에셋을 사용하고 있음을 Naninovel에 알리려면 리소스 제공 서비스의 Hold 메서드를 사용하십시오.

var resourceManager = Engine.GetService<IResourceProviderManager>();
resourceManager.Hold(asset, holder);

에셋이 실행되는 동안에는 Naninovel에 의해 언로드되지 않으므로 메모리 누수를 방지하기 위해 에셋을 끄는 것은 귀하의 몫입니다.

var holdersCount = resourceManager.Release(asset, holder);

// 에셋을 보유하고 있는 케이스가 없는 경우 해당 에셋을 언로드해야 합니다.
if (holdersCount == 0) Resources.UnloadAsset(asset);

“홀더(Holder)”는 모든 개체에 대한 참조가 될 수 있습니다. 일반적으로 에셋을 사용하는 클래스와 동일합니다.

이는 홀더끼리 구별하고 동일한 제공자(Provider)가 실수로 리소스를 여러 개 보유하는 것을 방지하는 데 사용됩니다.

이하는 Naninovel이 에셋 언로드를 방지하는 Unity 구성 요소의 예입니다.

using Naninovel;
using UnityEngine;

public class HoldObject : MonoBehaviour
{
    public Object ObjectToHold;

    private async void Start()
    {
        while (!Engine.Initialized) await UniTask.DelayFrame(1);
        Engine.GetService<IResourceProviderManager>().Hold(ObjectToHold, this);
    }
}

연관글 목록

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다