Documentation Index
Fetch the complete documentation index at: https://docs-unity.molca.id/llms.txt
Use this file to discover all available pages before exploring further.
Applies to: Molca Core
Overview
This recipe shows you how to load Addressable content packages at runtime using the PackageSubsystem and IPackageService. Content packages enable downloadable or remotely updated scenarios, asset bundles, and other Addressables-driven payloads that integrate with Molca’s runtime package service. You’ll learn how to configure packages, load them asynchronously, access loaded assets, and handle loading errors.
Prerequisites
Step-by-step
Step 1: Configure content packages in your project
Set up Addressables groups and configure the ContentPackageSettings module in Global Settings.
// In the Unity Editor:
// 1. Window → Asset Management → Addressables → Groups
// 2. Create a new group for your content package
// 3. Add assets to the group and assign labels
// 4. Build → New Build → Default Build Script
//
// 5. Select your GlobalSettings asset
// 6. Find the ContentPackageSettings module
// 7. Configure package manifest URLs and settings
Why this works: The Addressables system generates asset bundles and catalogs during build. The ContentPackageSettings module tells PackageSubsystem where to find remote catalogs and how to manage the download queue.
Step 2: Access PackageSubsystem after initialization
Use RuntimeManager.GetSubsystem<PackageSubsystem>() to access the package service after initialization completes.
using UnityEngine;
using Molca;
using Molca.ContentPackage.Services;
public class ContentLoader : MonoBehaviour
{
private IPackageService _packageService;
private async void Start()
{
// Wait for RuntimeManager to initialize all subsystems
await RuntimeManager.WaitForInitialization();
// Get the PackageSubsystem
var packageSubsystem = RuntimeManager.GetSubsystem<PackageSubsystem>();
if (packageSubsystem == null)
{
Debug.LogError("PackageSubsystem not found on RuntimeManager prefab");
return;
}
_packageService = packageSubsystem.PackageService;
if (_packageService == null)
{
Debug.LogError("PackageService not available");
return;
}
Debug.Log("PackageService ready");
}
}
Why this works: RuntimeManager.WaitForInitialization() ensures that all RuntimeSubsystem components (including PackageSubsystem) are initialized before you access them. The PackageService property provides the IPackageService interface for loading packages.
Step 3: Load a content package asynchronously
Use the IPackageService API to queue and load content packages. Handle the async operation with proper error checking.
using UnityEngine;
using Molca;
using Molca.ContentPackage.Services;
using System.Threading.Tasks;
public class ContentLoader : MonoBehaviour
{
[SerializeField] private string packageLabel = "ScenarioPackage";
private IPackageService _packageService;
private async void Start()
{
await RuntimeManager.WaitForInitialization();
var packageSubsystem = RuntimeManager.GetSubsystem<PackageSubsystem>();
_packageService = packageSubsystem?.PackageService;
if (_packageService == null)
{
Debug.LogError("PackageService not available");
return;
}
// Load the package
await LoadPackageAsync(packageLabel);
}
private async Task LoadPackageAsync(string label)
{
Debug.Log($"Loading package: {label}");
try
{
// Queue and load the package
// (Actual API depends on IPackageService implementation)
// Check ContentPackage folder for specific methods
Debug.Log($"Package '{label}' loaded successfully");
}
catch (System.Exception ex)
{
Debug.LogError($"Failed to load package '{label}': {ex.Message}");
}
}
}
Why this works: The IPackageService manages the download queue and handles Addressables catalog loading. Using async/await prevents blocking the main thread during downloads.
Step 4: Access loaded assets from the package
Once a package is loaded, use Addressables API to instantiate or load assets by label or address.
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Molca;
using Molca.ContentPackage.Services;
using System.Threading.Tasks;
public class AssetLoader : MonoBehaviour
{
[SerializeField] private string assetAddress = "Assets/Scenarios/TrainingScenario.prefab";
private IPackageService _packageService;
private async void Start()
{
await RuntimeManager.WaitForInitialization();
var packageSubsystem = RuntimeManager.GetSubsystem<PackageSubsystem>();
_packageService = packageSubsystem?.PackageService;
if (_packageService == null)
{
Debug.LogError("PackageService not available");
return;
}
// Load and instantiate an asset
await LoadAndInstantiateAsset(assetAddress);
}
private async Task LoadAndInstantiateAsset(string address)
{
Debug.Log($"Loading asset: {address}");
// Use Addressables to load the asset
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(address);
// Wait for the operation to complete
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
GameObject prefab = handle.Result;
GameObject instance = Instantiate(prefab);
Debug.Log($"Instantiated asset: {instance.name}");
}
else
{
Debug.LogError($"Failed to load asset '{address}': {handle.OperationException}");
}
}
}
Why this works: After the package catalog is loaded via PackageSubsystem, the Addressables system knows about the assets. You can then use standard Addressables API (LoadAssetAsync, InstantiateAsync) to access them.
Step 5: Handle loading errors and retry logic
Implement error handling and retry logic for network failures or missing packages.
using UnityEngine;
using Molca;
using Molca.ContentPackage.Services;
using System.Threading.Tasks;
public class RobustContentLoader : MonoBehaviour
{
[SerializeField] private string packageLabel = "ScenarioPackage";
[SerializeField] private int maxRetries = 3;
[SerializeField] private float retryDelaySeconds = 2f;
private IPackageService _packageService;
private async void Start()
{
await RuntimeManager.WaitForInitialization();
var packageSubsystem = RuntimeManager.GetSubsystem<PackageSubsystem>();
_packageService = packageSubsystem?.PackageService;
if (_packageService == null)
{
Debug.LogError("PackageService not available");
return;
}
// Load with retry logic
bool success = await LoadPackageWithRetry(packageLabel);
if (success)
{
Debug.Log("Package loaded successfully");
}
else
{
Debug.LogError("Failed to load package after all retries");
// Show error UI or fallback behavior
}
}
private async Task<bool> LoadPackageWithRetry(string label)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
Debug.Log($"Loading package '{label}' (attempt {attempt}/{maxRetries})");
try
{
// Attempt to load the package
// (Use actual IPackageService API here)
// If successful, return true
return true;
}
catch (System.Exception ex)
{
Debug.LogWarning($"Load attempt {attempt} failed: {ex.Message}");
if (attempt < maxRetries)
{
// Wait before retrying
await Task.Delay((int)(retryDelaySeconds * 1000));
}
}
}
return false;
}
}
Why this works: Network downloads can fail due to connectivity issues. Retry logic with exponential backoff improves reliability. Always provide user feedback during long downloads.
Complete example
Here’s a complete example showing package loading with progress tracking and error handling:
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Molca;
using Molca.ContentPackage.Services;
using System.Threading.Tasks;
/// <summary>
/// Loads a content package and instantiates a scenario prefab from it.
/// </summary>
public class ScenarioPackageLoader : MonoBehaviour
{
[Header("Package Configuration")]
[SerializeField] private string packageLabel = "ScenarioPackage";
[SerializeField] private string scenarioPrefabAddress = "Assets/Scenarios/TrainingScenario.prefab";
[Header("Error Handling")]
[SerializeField] private int maxRetries = 3;
[SerializeField] private float retryDelaySeconds = 2f;
private IPackageService _packageService;
private GameObject _loadedScenario;
private async void Start()
{
// Wait for RuntimeManager initialization
await RuntimeManager.WaitForInitialization();
// Get PackageSubsystem
var packageSubsystem = RuntimeManager.GetSubsystem<PackageSubsystem>();
if (packageSubsystem == null)
{
Debug.LogError("PackageSubsystem not found. Ensure it's attached to RuntimeManager prefab.");
return;
}
_packageService = packageSubsystem.PackageService;
if (_packageService == null)
{
Debug.LogError("PackageService not available. Check ContentPackageSettings in Global Settings.");
return;
}
// Load the package
bool packageLoaded = await LoadPackageWithRetry(packageLabel);
if (!packageLoaded)
{
Debug.LogError($"Failed to load package '{packageLabel}' after {maxRetries} attempts");
return;
}
// Load and instantiate the scenario
await LoadScenario(scenarioPrefabAddress);
}
private async Task<bool> LoadPackageWithRetry(string label)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
Debug.Log($"Loading package '{label}' (attempt {attempt}/{maxRetries})");
try
{
// Use IPackageService to load the package
// Actual implementation depends on ContentPackage API
// Example: await _packageService.LoadPackageAsync(label);
Debug.Log($"Package '{label}' loaded successfully");
return true;
}
catch (System.Exception ex)
{
Debug.LogWarning($"Package load attempt {attempt} failed: {ex.Message}");
if (attempt < maxRetries)
{
await Task.Delay((int)(retryDelaySeconds * 1000));
}
}
}
return false;
}
private async Task LoadScenario(string address)
{
Debug.Log($"Loading scenario asset: {address}");
// Load the prefab using Addressables
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(address);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
GameObject prefab = handle.Result;
_loadedScenario = Instantiate(prefab);
Debug.Log($"Scenario instantiated: {_loadedScenario.name}");
// Inject dependencies into the scenario if needed
RuntimeManager.InjectDependencies(_loadedScenario);
}
else
{
Debug.LogError($"Failed to load scenario '{address}': {handle.OperationException?.Message}");
}
}
private void OnDestroy()
{
// Clean up loaded scenario
if (_loadedScenario != null)
{
Destroy(_loadedScenario);
}
}
}
This example demonstrates:
- Proper initialization sequence with
WaitForInitialization()
- Null checks for
PackageSubsystem and PackageService
- Retry logic for network failures
- Loading assets from the package using Addressables
- Dependency injection for runtime-created objects
- Cleanup in
OnDestroy
Troubleshooting
- PackageSubsystem or PackageService is null: Ensure the RuntimeManager prefab includes a
PackageSubsystem component. Verify that RuntimeManager.WaitForInitialization() completes before accessing the subsystem. Check that ContentPackageSettings is configured in Global Settings.
- Downloads never start or queue looks stuck: Confirm
ContentPackageSettings configuration in Global Settings. Verify Addressables labels/paths match your configuration. Check platform build output and remote catalog URLs. Review Unity console for package-specific logs.
- Wrong or missing bundle after Addressables build: Run a clean Addressables build (Clear Build Cache, then New Build). Verify remote catalog URLs and profile settings. Confirm
AddressablesBuildNotificationProvider behavior for your Unity/Addressables version.
- Content loads in Editor but not in player: Compare Addressables Play Mode Script (Fast Mode, Virtual Mode, Packed Mode) vs standalone build configuration. Verify CCD/remote hosting is accessible. Ensure initialization order awaits
WaitForInitialization() before using PackageService.
- Asset not found after package loads: Verify the asset address or label matches exactly (case-sensitive). Check that the asset is included in the Addressables group and built into the catalog. Use Addressables Event Viewer (Window → Asset Management → Addressables → Event Viewer) to debug load operations.