Aprenda a economizar o uso da memória melhorando a maneira como você usa os AssetBundles

Se o seu aplicativo transmite ativos de uma rede de distribuição de conteúdo (CDN) ou os empacota em um grande binário, você provavelmente já ouviu falar de AssetBundles. Um AssetBundle é um arquivo que contém um ou mais ativos serializados (texturas, malhas, clipes de áudio, shaders etc.) e pode ser carregado em tempo de execução.
Os AssetBundles podem ser usados diretamente ou por meio de sistemas como o Unity Addressable Asset System (também conhecido como Addressables). O sistema Addressables é um pacote que oferece uma maneira mais acessível e suportada de gerenciar ativos em seus projetos. É uma abstração sobre os AssetBundles. Embora o Addressables minimize as interações diretas que os desenvolvedores têm com os AssetBundles, é útil entender como o uso dos AssetBundles pode afetar o uso da memória. Para obter uma visão geral do sistema Addressables, consulte esta publicação no blog e esta sessão da Unite Copenhagen 2019.
Os desenvolvedores que trabalham em novos projetos devem considerar o uso de Addressables em vez de trabalhar diretamente com AssetBundles. Se estiver trabalhando em um projeto com uma abordagem de AssetBundles já estabelecida, as informações aqui sobre como os AssetBundles afetam a memória de tempo de execução o ajudarão a obter os melhores resultados possíveis.
Quando o Unity baixa um AssetBundle LZMA usando a classe WWW (que agora está obsoleta) ou UnityWebRequestAssetBundle (UWR), o Unity otimiza a busca, a recompressão e o controle de versão dos AssetBundles usando dois caches: o cache de memória e o cache de disco.
Os AssetBundles carregados no cache de memória consomem uma grande quantidade de memória. A menos que você queira especificamente acessar com frequência e rapidez o conteúdo de um AssetBundle, o cache de memória provavelmente não vale o custo da memória. Em vez disso, use o cache de disco.
Se você fornecer um argumento de versão ou hash à API UnityWebRequestAssetBundle, o Unity armazenará os dados do AssetBundle no cache do disco. Se você não fornecer esses argumentos, o Unity usará o cache de memória. Observe que o Addressables usa o cache de disco por padrão. Esse comportamento pode ser controlado por meio do campo UseAssetBundleCache.
AssetBundle.LoadFromFile() e AssetBundle.LoadFromFileAsync() sempre usam o cache de memória para AssetBundles LZMA. Portanto, recomendamos o uso da API UnityWebRequestAssetBundle. Se não for possível usar a API UnityWebRequestAssetBundle, você poderá usar AssetBundle.RecompressAssetBundleAsync() para reescrever um AssetBundle LZMA no disco.
Testes internos mostram que há uma diferença de pelo menos uma ordem de magnitude na RAM entre o uso do cache de disco e o uso do cache de memória. Você deve ponderar o custo da memória em relação aos requisitos adicionais de espaço em disco e ao tempo de instanciação do Asset para o seu aplicativo.
Para determinar o efeito que o cache de memória do AssetBundle pode ter no uso de memória do seu aplicativo, use um profiler nativo (nossa ferramenta preferida é o Allocations Instrument do Xcode) para examinar as alocações da classe ArchiveStorageConverter. Se essa classe estiver usando mais de 10 MB de RAM, provavelmente você está usando o cache de memória.

Ao criar AssetBundles para grandes projetos, não presuma que o Unity, por padrão, minimizará a quantidade de informações duplicadas entre eles. Para identificar instâncias de dados duplicados nos AssetBundles gerados, você pode usar o prático AssetBundle Analyzer, escrito em Python por um de nossos colegas do grupo de Consultoria e Desenvolvimento. Usada por meio da linha de comando, a ferramenta extrai informações dos AssetBundles gerados, que são armazenados em um banco de dados SQLite que apresenta várias exibições úteis. Em seguida, você pode consultar o banco de dados usando ferramentas como o DB Browser for SQLite. Essa ferramenta pode ajudá-lo a encontrar e resolver quaisquer ineficiências em seu pipeline de compilação, quer você tenha criado pacotes manualmente ou por meio de Addressables.

Como alternativa, confira a ferramenta AssetBundle Browser, que pode ser baixada e integrada ao seu projeto imediatamente. Observe que essa ferramenta oferece funcionalidade semelhante à do Addressables, portanto, se você estiver usando o Addressables, essa ferramenta não será relevante.
A ferramenta AssetBundle Browser permite que você visualize e edite a configuração dos AssetBundles em um determinado projeto Unity e fornece funcionalidade de compilação. Ele também oferece alguns recursos interessantes, como informar os usuários sobre ativos duplicados que estão sendo extraídos devido a dependências, como texturas.

Ao decidir como organizar seus ativos em AssetBundles, você precisa ter cuidado com as dependências. Independentemente da topologia do seu AssetBundle, o Unity faz uma distinção entre os assets que vivem dentro do binário do aplicativo (dentro ou envolvendo uma pasta Resources) e aqueles que você precisa carregar a partir de AssetBundles. Você pode pensar nesses dois tipos de ativos como se vivessem em mundos diferentes. É impossível criar um AssetBundle que tenha uma referência rígida à instância de um Asset dentro do mundo da pasta Resources. Para fazer referência a esses ativos, o Unity faz uma cópia dos ativos que ele usa no mundo do AssetBundle.


Veja, por exemplo, o logotipo de um jogo. O logotipo pode ser exibido na interface do usuário de uma cena de carregamento enquanto o jogo é iniciado. Como essa tela de carregamento deve ser exibida antes que os ativos remotos sejam transmitidos para o disco, você pode incluir o ativo do logotipo na compilação para que ele possa ser usado imediatamente.
Esse mesmo logotipo também é usado em um painel de opções na interface do usuário, onde os usuários podem selecionar o idioma, as preferências de som e outras configurações. Se esse painel da interface do usuário for carregado de um AssetBundle, esse AssetBundle criará sua própria cópia do Asset do logotipo.
Se a tela de carregamento e o painel de opções forem carregados ao mesmo tempo, as duas cópias do logotipo Asset serão carregadas, o que é uma duplicação que custa memória.
A solução para esse problema é quebrar o hard link entre uma ou ambas as telas. Se o logotipo estiver em um AssetBundle, será necessário realizar algum tipo de streaming antes de obter uma referência ao Asset. Se o logotipo estiver no binário (dentro de uma pasta Resources, por exemplo), o painel da interface do usuário precisará ter uma referência fraca ao Asset do logotipo e ser carregado por meio de uma API como Resources.Load.

Os scripts do usuário precisarão usar a string para carregar a imagem em tempo de execução e atribuí-la ao componente adequado. Um meio-termo satisfatório pode ser incluir o AssetBundle que contém o Asset do logotipo dentro do diretório StreamingAssets do aplicativo. Você ainda carregará o Asset do AssetBundle, mas como está hospedando o pacote localmente, não pagará o custo em tempo necessário para baixar o conteúdo.
Não é intuitivo usar strings, caminhos ou GUIDs para fazer referência a ativos, mas você pode querer criar inspetores personalizados que ativem a funcionalidade padrão de referência de arrastar e soltar do Unity em seus campos com referência fraca. E não se esqueça de usar o pacote MemoryProfiler do Unity para identificar os ativos que estão duplicados na memória. Observe que o sistema Addressables tem seu próprio mecanismo de verificação de duplicatas em dependências (para obter mais informações, consulte a documentação).
Embora o sistema Addressables forneça uma abstração sobre os AssetBundles, saber como as coisas funcionam nos bastidores pode ajudá-lo a evitar problemas de desempenho dispendiosos, como os descritos neste artigo.
Se você estiver usando Addressables no momento, queremos saber sua opinião por meio desta breve pesquisa.
Estamos planejando um roteiro para futuras entradas desta série. Há alguma área em que você gostaria que nos concentrássemos? Deixe um comentário para nos informar!
