WWW wrapper using generics and other C# goodness

Hey Guys,

While working on my first AssetStore submission (not that it’s in any way relevant to this post) I had a constant urge to download stuff stored locally and sometimes remotely.

Since I’m lazy and have recently developed an unhealthy attachment to Extension and Anonymous methods I wrote a wrapper to help me deal with my downloading needs. To complicate things I threw in a little bit of generics for good measure.

Anywho. First, add a fresh .cs file to your solution and drop this in :

#region >>>> Usings

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#endregion

/// <summary>
/// Some useful extension methods for all kinds of stuff.
/// </summary>
public static partial class UnityExtensions
{

    #region >>>> WWW
    
    /// <summary>
    /// Use this extension method to download assets from a given URL.
    /// It will attach a temporary wwwDowloader component to the target game object that will be destroyed once the download is complete.    
    /// </summary>
    /// <typeparam name="T">
    /// Supported Types :
    /// 
    /// AssetBundle - to download WWW.assetBundle
    /// AudioClip - to download WWW.audioClip
    /// Object - to download WWW.bytes (once the download is complete cast returned value to a byte array)
    /// Texture - to download WWW.texture
    /// MovieTexture - to download WWW.movie
    /// String - to download WWW.text
    /// 
    /// </typeparam>
    /// <param name="gameObject">Parent GameObject used to host a temporary instance of the wwwDownloader class.</param>
    /// <param name="url">URL of the asset to be downloaded.</param>
    /// <param name="callback">Method to be called once the asset has finished downloading.</param>    
    public static void Download<T>(this GameObject gameObject, string url, Action<T> callback)  
    {        
        var downloader = (wwwDownloader)gameObject.AddComponent<wwwDownloader>();

        downloader.Download<T>(url, 
            (param) => 
            { 
                // Destroy the downloader component
                GameObject.Destroy(downloader);               
 
                // Return downloaded data to the caller
                callback(param);
            });       
    }
    
    #endregion

}

#region >>>> Additional Utility Types

    /// <summary>
    /// Utility class used by the Dowload<T> extension method to download assets.
    /// </summary>
    public class wwwDownloader : MonoBehaviour
    {
            
        /// <summary>
        /// Call this method to start downloading.
        /// </summary>        
        public void Download<T>(string url, Action<T> callback)
        {
            if (string.IsNullOrEmpty(url))
            {
                Debug.LogWarning(@"Download : url is null or empty.");

                // Return a default value of whichever type was supplied to this method
                callback(default(T));
            }
            
            // Start the downloader
            StartCoroutine(DownloadAsset<T>(url, callback));
        }

        /// <summary>
        /// Download worker.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        private IEnumerator DownloadAsset<T>(string url, Action<T> callback)
        {          
            object downloadedAsset;

            // Get name of the asset type we are going to download
            var assetType = typeof(T).ToString();

            // Download the asset
            var www = new WWW(url);

            // Wait for download to complete            
            yield return www;
            
            // Retrieve correct asset type from the downloader based on the type's name
            switch (assetType)
            {
                case "UnityEngine.AssetBundle":
                    downloadedAsset = www.assetBundle;
                    break;

                case "UnityEngine.AudioClip":
                    downloadedAsset = www.audioClip;
                    break;

                case "System.Object":
                    downloadedAsset = www.bytes;
                    break;

                case "UnityEngine.MovieTexture":
                    downloadedAsset = www.movie;
                    break;

                case "UnityEngine.Texture":                     
                    downloadedAsset = www.texture;
                    break;

                case "System.String":                   
                    downloadedAsset = www.text;
                    break;           
     
                default :
                    downloadedAsset = default(T);
                    break;
            }

            // Return downloaded asset
            callback((T)downloadedAsset);
        }

    }

#endregion

So, now we have ourselves a nifty extension method to deal with downloads.

“Big deal. Guy wrote a wrapper class. I need to lie down!” some of you might say. I completely agree. Big deal it is not.

But look how shiny!

#region >>>> Usings

using UnityEngine;

#endregion

public class FooClassOfHugeSignificance  : MonoBehaviour
{

    private Texture _someTexture;

    void Start()
    {

        // We can load textures (or anything else for that matter) with that nifty extension method that wraps up all the WWW nonsense.

        // Step 1 : First we define our URL. It can be anything even local paths work, just change the first parameter to "file://". See Unity documentation on WWW class for a list of supported protocols.
        var url = string.Format("{0}{1}", "http://", "images.earthcam.com/ec_metros/ourcams/fridays.jpg");
        
        // Step 2 : Use the game object to host our downloader.
        // You can actually pass whichever supported type you want instead of Texture. Currently Unity's WWW class supports downloading AssetBundle, AudioClip, String, Texture, MovieTexture and 
        // Byte[] (use Object instead then cast to byte[])
        gameObject.Download<Texture>(url, (downloadedTexture) =>
        {
              // Step 3 : This method will get called once the download is complete. 
              // Assign downloaded texture to a local variable that we can then use in our OnGui() method.
              _someTexture = downloadedTexture;            
             
              // Note : You can actually use your own callbacks here. Personally I prefer it this way, seems cleaner.
        });
        
    }

    void OnGUI()
    {

        // Step 4 : Draw our texture as soon as its done downloading.
        if (Event.current.type == EventType.Repaint)
        {
            if (_someTexture != null)
            {
                // Draw texture
                GUI.DrawTexture(new Rect(400, 400, 200, 200), _someTexture);
            }
        }
    }

}

Cool! Right?

Ok…‘Cool!’ might have been a bit of a stretch, but I still think it’s pretty nice considering you never ever ever have to deal with all the plumbing code for downloads ever again.

To me the biggest advantage of using something like this is the simplicity of end user code. Basically we can get an asset bundle in a single line of code.

gameObject.Download<AssetBundle>(someURL, (downloadedAsset) => { _localAssetVariable = downloadedAsset; });

I think I went a little overboard with self praise here and there. Honestly, didn’t mean to :slight_smile:

Anyway I would love to hear what you guys and gals think. Criticism, constructive or otherwise, is always welcome.
Perhaps someone more knowledgeable could point out some pitfalls of this approach or whatnot.

Looking forward to your feedback.

Regards,
Alex

I see nobody has responded to your post yet. I tried what you did. A little simpler than my workflow; however, we both know that most people will never download just one remote asset. Here is a video of how I work with Asset bundles. I just feed the web address of the remote asset in UniDDatabase 3.0, run, and done. However, your code does have something that peaked my interest. When I’m working with C# and I do a coroutine yield. It went do the update before everything finished. So for the AssetBundle tutorial I switched over to Js and the yield function worked fine. However, I still have to do in a touch with code if I only want a subset of my remote Assets. Your vision opens up the theoretical possibility of being able to get a subset of assets at runtime.
I hope that made sense. If I can get it to work. Do I have your permission to use the modified code in my project. You will be given full credit for the use of your code.

https://vimeo.com/29558330

Hey namoricoo,

Feel free to do whatever you please with this.
I constantly cannibalize other peoples samples and code from these forums. Finally found something useful to contribute :slight_smile:

Pretty cool vid by the way.

Happy coding,
Alex

It’s part of my upcoming update to UniDDatabase 3.0. At the moment I’m at 2.5. In 3.0 people will be able to import their existing files using either Comma Separated Values or Sql. I got the request for Asset Bundles so I wanted to give the people what they want. You contribution came just in time. I hate releasing an incomplete product. Thanks again and have a nice day.

Nice one.

But with WWW I had a couple of problems. I don’t know if it frees memory or not. Looks like it doesn’t o.O For example I load a PNG, it converts data to texture but this unpacked PNG data seems to be still there. And the second problem is that sometimes when I try load lots of textures (even one by one waiting for the previous one) I get “Too many threads” error and the game won’t start.

That’s uber annoying.

from my understanding of the WWW class. You don’t have to have all of your assets build into the game in order to get going. You can have remote Assets. So in a sense it reduces the size of the game. On the minus side. If the user does not have internet access. They won’t get those assets at the time they are playing the game.

"this unpacked PNG data seems to be still there. "

In my video. You can see they data only stays there only I hit stop. Next time I run it will re-download. So If yours is doing something exotic. You might want to fill out a Bug report.

@ Moderators : If it’s not too much trouble please move this post to the Scripting section.
Not quite sure why I thought this is the right place for something like this in the first place :slight_smile:

@ Everyone : I’ll make some more changes to this today / tomorrow to make the whole thing a little more flexible. Add download priority etc. Oh and there does seem to be some stuff in Unity docs on the whole texture issue. I’ll try to see what I can do with it over the weekend.

@alexfeature : Nice :slight_smile: Does this work in webplayer? Would be exactly what I need!

EDIT:
Tested it and works great! Thanks!