Unity AssetBundles
C# Architecture
LiveOps
Remote Download
Mobile Game Development
Caching

Updating Unity Mobile Games Without Store Submissions: C# Remote Download & Versioning Pipeline (Part 2)

4 min read
Eshan Naithani

Welcome to Part 2 of our 3-Part Unity LiveOps & Dynamic Asset Update Masterclass:

In this second installment, we write a production-ready C# manager (RemoteAssetManager) that compares remote bundle hashes (Hash128), streams assets over HTTPS using UnityWebRequestAssetBundle, updates loading screen progress bars, and handles network dropouts gracefully on cellular connections.


1. How Hash128 Version Caching Works

Unity's built-in Caching system maintains an on-device disk storage pool for AssetBundles.

  • When UnityWebRequestAssetBundle.GetAssetBundle(url, hash, crc) is called:
    • If the requested Hash128 is already stored on disk, Unity loads it directly from local storage with zero network traffic.
    • If the Hash128 differs from local storage, Unity streams the updated bundle, overwrites the old version, and saves the new hash.

2. Production C# Remote Asset Manager (RemoteAssetManager.cs)

CSHARP
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class RemoteAssetManager : MonoBehaviour
{
    private static RemoteAssetManager instance;
    public static RemoteAssetManager Instance => instance;

    [Header("CDN Configuration")]
    [SerializeField] private string cdnBaseUrl = "https://cdn.yourgame.com/AssetBundles/Android/";

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        instance = this;
        DontDestroyOnLoad(gameObject);
    }

    // =========================================================================
    // DOWNLOAD & CACHE ASSETBUNDLE OVER HTTPS
    // =========================================================================
    public IEnumerator DownloadOrLoadBundle(
        string bundleName, 
        string hashString, 
        uint crc, 
        Action<float> onProgress, 
        Action<AssetBundle> onSuccess, 
        Action<string> onError
    ) {
        string fullUrl = $"{cdnBaseUrl}{bundleName}";
        Hash128 bundleHash = Hash128.Parse(hashString);

        // Check if bundle version is already cached on device
        bool isCached = Caching.IsVersionCached(fullUrl, bundleHash);
        Debug.Log($"[LiveOps] Requesting Bundle '{bundleName}' | Cached: {isCached}");

        using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(fullUrl, bundleHash, crc))
        {
            // Start async request
            UnityWebRequestAsyncOperation operation = request.SendWebRequest();

            while (!operation.isDone)
            {
                // Report download progress (0.0f to 1.0f)
                onProgress?.Invoke(operation.progress);
                yield return null;
            }

            if (request.result != UnityWebRequest.Result.Success)
            {
                string errorMsg = $"[LiveOps Download Error] Failed to fetch '{bundleName}': {request.error}";
                Debug.LogError(errorMsg);
                onError?.Invoke(errorMsg);
            }
            else
            {
                onProgress?.Invoke(1.0f);
                AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
                Debug.Log($"[LiveOps Success] Successfully loaded bundle '{bundleName}'!");
                onSuccess?.Invoke(bundle);
            }
        }
    }
}

3. Instantiating Remote Assets at Runtime

Once the AssetBundle finishes downloading, extract and spawn prefabs cleanly.

CSHARP
public class LevelLoader : MonoBehaviour
{
    public void LoadRemoteLevel(string levelBundleName, string hash, uint crc)
    {
        StartCoroutine(RemoteAssetManager.Instance.DownloadOrLoadBundle(
            levelBundleName,
            hash,
            crc,
            progress => Debug.Log($"Downloading Level... {(progress * 100):F0}%"),
            bundle => {
                // Load level prefab from bundle
                GameObject levelPrefab = bundle.LoadAsset<GameObject>("Level_05_Halloween");
                Instantiate(levelPrefab);

                // Unload bundle compressed header metadata (keep loaded objects)
                bundle.Unload(false);
            },
            error => Debug.LogError(error)
        ));
    }
}

4. Part 2 Summary & Next Steps

In this second guide, we built a production C# RemoteAssetManager, utilized Hash128 caching to prevent duplicate downloads, and instantiated dynamic content at runtime.

👉 Continue to Part 3: Addressables Migration, Catalog Updates & Memory Unloading →


💡 Need a Custom LiveOps & Asset Delivery Strategy for Your Game?

Struggling with high binary sizes, long store review delays, or memory leaks during bundle loading? I work directly with game studios to architect scalable LiveOps and remote asset pipelines:

  • OTA Remote Pipelines: Setting up AWS S3, Cloudflare R2, or Unity Cloud Content Delivery.
  • Sub-150MB App Binary Setup: Deferring heavy game assets to post-install dynamic downloads.
  • Addressables Migration: Upgrading legacy AssetBundle setups to zero-leak Addressables architectures.

👉 Book a LiveOps & Remote Architecture Session or reach out directly to discuss your game's engineering roadmap.


🎮 Shipped Mobile Games & Official Store Profiles

Explore live commercial titles built with Unity and published across official app stores:


Planning to launch a mobile game or optimize your engineering budget? Check out our 2026 Mobile Game Development Cost Guide or book an engineering consultation.

Share this article

Looking to build a production-ready game?

See how I built Bird Sort Mania in 20 days using AI, or check out my full Mobile Games Portfolio to see my shipped titles on Android and iOS.

Join 5,000+ Game Developers

Get weekly insights on Unity performance optimization, AI gameplay architectures, and robust system design. No spam, just deep technical breakdowns.

Unsubscribe at any time. Your data is never shared.

Recommended Reading

More articles in Unity AssetBundles