post

Hoy en dia hay muchos videojuegos que utilizan recursos almacenados en lugares remotos fuera del archivo o paquete instalable. Desde hacer una APK para Android más ligera hasta poder tener cierto control sobre el acceso a elementos en un juego de manera más o menos legal, pasando simplemente por no incluir en un juego contenido que un jugador no va a acceder si no descubre cierta zona o desbloquea cierto nivel.

Este tutorial habla de cómo acceder a recursos (texturas, sonidos, incluso escenas) almacenados en un servidor web y descargarlos en “runtime” para poder utilizarlos en adelante. Usando Unity es posible hacer esto de dos formas; una rápida y más sencilla en la que es necesario tener la licencia Pro, y otra que a priori puede ser un poco más costosa y que tiene algunas limitaciones, pero que también puede resultarnos útil. Hoy se verá como poder hacerlo utilizando la versión Pro de unity.

Si se dispone de una licencia Pro de Unity lo mejor y más sencillo es utilizar los AssetBundles. Estos, son paquetes de información comprimida que sólo Unity (o un juego creado en Unity) puede leer. La estrategia a seguir con los asset bundles es la siguiente:

guia

Una vez descrita en líneas generales la forma de trabajo, se describe paso a paso el proceso a seguir y los scripts necesarios para poder conseguir lo que queremos.

Lo primero que se necesita es un script para poder seleccionar los archivos y poder comprimirlos en un paquete AssetBundle. Aquí hay que tener en cuenta la plataforma objetivo de desarrollo. Es importante remarcar que los paquete generados por ejemplo para ser utilizados en un dispositivo Android NO van a ser compatibles en un dispositivo IOS y viceversa.

using UnityEngine;
using UnityEditor;
 
public class ExportAssetBundles
{
        [MenuItem("Assets/Buid_AssetBundle_for_Android")]
 
        static void ExportResourceAndroid ()
        {
                string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "android.unity3d");
                if (path.Length != 0) 
                {
                        Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
                        BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies, BuildTarget.Android);
                        Selection.objects = selection;
                }
 
        }
 
        [MenuItem("Assets/Buid_AssetBundle_for_IOS")]
       
        static void ExportResourceIOS ()
        { 
                string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "iphone.unity3d");
                if (path.Length != 0) 
                {
                        Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
                        BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies, BuildTarget.iPhone);
                        Selection.objects = selection;
                } 
        }
}

Una vez ya está el script redactado y guardado en la carpeta correspondiente, se seleccionan los archivos que queramos comprimir en el AssetBundle y en la barra superior de herramientas, en Assets veremos lo siguiente:

Articulo1

Con esto quedará creado el archivo que se almacenará en la nube, de forma que el juego accederá a él cuando así se requiera.

Ahora, una vez alojado el paquete en el servidor, se toma nota de la url a la que se debe llamar para conseguir ese recurso. Con esta información y utilizando la clase WWW de Unity se puede acceder a todos los assets que hay en el paquete. Antes de eso es recomendable hacer que el usuario descargue el paquete en su dispositivo. De esta forma, el usuario no tendrá que acceder a la web cada vez que juegue. Con este objetivo escribimos un script que se ejecutará justo antes de que se vayan a necesitar los recursos. Póngase como ejemplo una situación hipotética en la que se va a cargar una escena, pero que los audios que se van a reproducir en esa escena no estaban alojados en el juego de forma inicial. Lo que se consigue con el siguiente script es la comprobación de que el usuario dispone del paquete con los audios pertenecientes a la escena a cargar. De no ser así, procederá a la descarga del paquete y lo almacenará en el dispositivo.

using UnityEngine;
using System.Collections;
using System.IO;
 
public class AssetBundleDownloader : MonoBehaviour {
       
        private string pathToDownload;
        private string fileName;
        private bool alreadyDownloaded;
 
        // Use this for initialization
        void Start () {
       
        }
       
        // Update is called once per frame
        void Update () {
       
        }
 
        IEnumerator AssetDownloader (string urlResource)
        {
                /*
                 *Lo primero es hacer que el tratamiento de las rutas sea distinto según las plataformas a utilizar.
                 *Importante advertir la diferencia que hay entre utilizar el streamingAssetsPath y el persistentDataPath
                 *Esto se debe al tratamiento que le da unity al ejecutar los juegos en las aplicaciones.
                 *Si utilizamos el streamingAssetsPath, hemos de tener una carpeta en el proyecto que se llama
                 *"streamingAssetsPath" donde se guardarán estos paquetes descargados.
                 */
 
#if UNITY_ANDROID && !UNITY_EDITOR
                pathToDownload = Application.persistentDataPath + "/MyFolder";
                fileName = pathToDownload + "/resource.android.unity3d";
#endif
 
#if UNITY_IOS && !UNITY_EDITOR
                pathToDownload = Application.persistentDataPath + "/MyFolder";
                fileName = pathToDownload + "/resource.iphone.unity3d";
#endif
 
#if UNITY_EDITOR && UNITY_ANDROID
                pathToDownload = Application.streamingAssetsPath + "/MyFolder";
                fileName = pathToDownload + "/resource.android.unity3d";
#endif
 
#if UNITY_EDITOR && UNITY_IOS
                pathToDownload = Application.streamingAssetsPath + "/MyFolder";
                fileName = pathToDownload + "/resource.iphone.unity3d";
#endif
                /*
                 * Ahora detectamos si ya tenemos ese archivo en nuestro dispositivo para no volver a descargarlo
                 * Si no existe creamos ese directorio.
                */
 
                if(!File.Exists(fileName))
                {
                        Directory.CreateDirectory(pathToDownload);
                }
 
                
                //Utilizamos la clase WWW para descargar la información del paquete en el objeto creado.
                WWW wwwBundle = new WWW(urlResource);
                yield return wwwBundle;
 
                
                //Ahora procedemos a guardar el archivo descargado.
                File.WriteAllBytes(pathToDownload + fileName, wwwBundle.bytes);
        }
}

Una vez se ha comprobado que se dispone del paquete necesario para utilizar los audios, es posible ya se puede cargar la escena que los utilizará.

Ahora, cargar los audios es posible hacerlo de varias formas. Se puede generar un array con los elementos que habían en el AssetBundle, o también se puede cargar audio a audio, desde el AssetBundle, según se vaya necesitando. Esto se hace utilizando la la función AssetBundle.loadAsset(string name). Es posible hacer la carga del asset indicando el tipo de objeto que se está cargando.

using UnityEngine;
using System.Collections;
 
 
public class AssetBundleLoader : MonoBehaviour {
 
        private AssetBundle bundleResource;
        private SortedList sounds;
        private AudioClip sound;
 
        /*
        *Ahora que ya tenemos el paquete creado y sabemos que está en el dispositivo, debemmos de cargar el asset o los assets que tenemos dentro del paquete.
        *Para este ejemplo imaginemos que los assets que hemos comprimido son audios.
        *Podemos, o bien cargar todos los audios al inicio de la escena en un array de audios para poder acceder a ellos directamente, o
        * bien acceder cada vez que queramos al assetBundle y sacar el asset que necesitamos en ese momento.
        */
 
        IEnumerator LoadInArrayFromAssetBundle(string assetBundlePath) //el parametro assetBundlePath es la localización del paquete asstBundle (recordad el distinto tratamiento seún el dispositivo)
        {
                //Cargamos los audios en un array ******************
 
                if(bundleResource != null)
                {
                        bundleResource.Unload(false);
                }
                WWW bundleArray = new WWW (assetBundlePath);
                yield return bundleArray;
 
                bundleResource = bundleArray.assetBundle;
 
                sounds.Clear ();
 
                foreach(AudioClip myClip in bundleResource.LoadAll(typeof(AudioClip)))
                {
                        sounds.Add(myClip.name, myClip);
                }
 
                /*
                 *Ahora ya tenemos todos los audios que estaban en el paquete assetBundle cargados en un array.
                 * Para acceder a ellos solo tendremos que cargar los audios desde "sounds"
                 */
        }
 
        IEnumerator LoadDirectlyFromAssetBundle(string assetBundlePath, string assetName) //el parametro assetBundlePath es la localización del paquete asstBundle (recordad el distinto tratamiento según el dispositivo)
        {
                //Cargamos el audio directamente desde el assetBudnle ******************
               
                if (bundleResource != null)
                {
                        bundleResource.Unload (false);
                }
                
                WWW bundle = new WWW (assetBundlePath);
                yield return bundle;
 
                bundleResource = bundle.assetBundle;
                if(bundleResource.Contains(assetName))
                {
                        sound = bundleResource.Load (assetName) as AudioClip;
                }
                else
                {
                        Debug.Log("Asset '" + assetName + "' not found");
                }
        }
 
        /*Ahora para poder acceder a ese audio o a ese array de audio es utilizamos las tipicas funciones "get"
         */
 
        public AudioClip GetSound()
        {
                return sound;
        }
 
        public SortedList GetSounds()
        {
                return sounds;
        }
}

Cuando ya no se vayan a necesitar los assets que están contenidos en el AssetBundle, es muy recomendable utilizar la función AssetBunlde.Unload() para poder liberar memoria.

Con esta metodología lo que se ha conseguido es que se pueda crear un juego de forma más ligera. Se ha creado un paquete instalable en el que no estaban los audios, y más adelante, durante la ejecución del juego se ha comprobado que esos audios no estaban, se han descargado y guardado en el dispositivo, y se ha podido acceder a esos assets para utilizarlos en la reproducción del juego. La próxima vez que se ejecute el juego, se detectará que el paquete AssetBundle ya está descargado y por lo tanto en lugar de acceder a la web, accederá al recurso directamente desde el propio dispositivo.

En las siguientes semanas se explicará cómo poder hacer algo parecido sin disponer de los assetBundles que solo son accesibles si se utiliza la versión Pro de Unity. Hacer instalables mas ligeros o acceder a recursos externos al juego es posible con la versión gratuita de Unity, y se explicará como hacerlo en un siguiente tutorial.

Acerca de Raul Diaz

Siempre me ha gustado el tema de los videojuegos y su desarrollo pos lo que desde bien joven empecé a hacer mis 'pinitos' con herramientas como rpg maker o MUGEN. Con el tiempo, pasé a hacer alguna cosilla con el Source engine. El salto a Unity fue lo siguiente, y con ello pase a dedicarme profesionalmente a la programación de juegos y aplicaciones. Actualmente me encuentro enfrascado en Vaccine War; un juego desarrollado por GamesForTutti donde a parte de programas, meto mano en el diseño de juego y en el pixelArt.

6.692 respuestas sobre “Unity: Utilizando recursos alojados fuera del proyecto en Android y iOS

  1. Hola, que tal:

    Muy buen aporte y gracias por compartir. Sabes que cambios se tendrían que realzar si se quiere hace para un ejecutable de windows?
    Gracias y saludos

  2. Muy bueno el tutorial, gracias, para la nueva version de unity hay pequeños cambios , pero nada que no se pueda resolver(cambiar bundleResource.Load por bundleResource.LoadAsset y bundleResource.LoadAll por bundleResource.LoadAllAssets). y eL tutorial sigue? en que link?
    mi consulta mas importante, veo que aqui trabajas con archivos tipos AudioClip, si son objetos 3d en fbx? que tipo son, me quedo la duda

    • Mientras trabajes con AssetBundles puedes guardar todos los tipos de archivos que Unity pueda importar. (Como siempre es mejor trabajar con prefabs, te ahorrarás dolores de cabeza).

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *