SDK Documentation

Complete guide to the Unity SDK.

M8 CMS SDK Documentation

Table of Contents


Overview

The M8 CMS SDK is a Unity package that provides seamless integration with the GameFlow CMS backend. It enables your Unity game to fetch game content (levels, collections, media) from Firebase in real-time, with intelligent caching and automatic content management.

Key Features

  • Automatic Content Management: Download and cache content automatically based on player progress
  • Firebase Integration: Built-in Firebase Firestore and Storage support
  • Version Management: Support for multiple content versions (Live, Latest, Specific)
  • Intelligent Caching: In-memory and file system caching with automatic cleanup
  • Built-in Content Support: Optional built-in content for offline play
  • Event-Driven Architecture: Subscribe to events for content updates
  • Cross-Platform: Supports Android, iOS, and Web platforms

Installation

Prerequisites

  • Unity 2021.3 or higher
  • Firebase Unity SDK (already included in the package)

Setup Steps

  1. Import the SDK Package

    • Import the M8 CMS SDK into your Unity project
    • The SDK includes all necessary Firebase dependencies
  2. Add to Scene

    • Drag the M8CMS_SDK_Prefab from Assets/M8CMS_SDK_Prefab.prefab into your scene
    • Or manually add the M8CMSManager component to any GameObject
  3. Configure App Key

    • Select the M8CMSManager GameObject
    • Set your appKey in the Inspector (this is your unique game identifier)
  4. Configure Firebase (Optional)

    • The SDK comes with default Firebase configuration
    • To use custom Firebase project, modify the configuration in the Inspector

Quick Start

Basic Setup

  1. Access the Manager
C#
using M8CMS;

public class MyGameManager : MonoBehaviour
{
    private M8CMSManager cmsManager;
    
    void Start()
    {
        // Get the singleton instance
        cmsManager = M8CMSManager.Instance;
        
        // Subscribe to events
        M8CMSManager.OnInitializationComplete += OnInitializationComplete;
        M8CMSManager.OnGameDataLoaded += OnGameDataLoaded;
    }
    
    void OnDestroy()
    {
        // Unsubscribe from events
        M8CMSManager.OnInitializationComplete -= OnInitializationComplete;
        M8CMSManager.OnGameDataLoaded -= OnGameDataLoaded;
    }
    
    void OnInitializationComplete(bool success)
    {
        if (success)
        {
            Debug.Log("SDK initialized successfully!");
        }
    }
    
    void OnGameDataLoaded(GameData gameData)
    {
        Debug.Log($"Game data loaded! Levels: {gameData.levels.Count}");
        Debug.Log($"Collections: {gameData.collections.Count}");
        
        // Now you can access your game content
        ProcessGameData(gameData);
    }
    
    void ProcessGameData(GameData data)
    {
        foreach (var level in data.levels)
        {
            Debug.Log($"Level {level.index}: {level.levelType} - {level.difficulty}");
        }
    }
}
  1. Load Level Content
C#
// Load level image
async void LoadLevelImage(int levelIndex)
{
    var sprite = await cmsManager.GetLevelSpriteAsync(levelIndex);
    if (sprite != null)
    {
        // Use the sprite
        myImageUI.sprite = sprite;
    }
}

// Load level video
async void LoadLevelVideo(int levelIndex)
{
    var videoData = await cmsManager.GetLevelVideoAsync(levelIndex);
    if (videoData != null)
    {
        // Use the video data (Unity VideoPlayer compatible)
        myVideoPlayer.url = Application.persistentDataPath + "/temp_video.mp4";
        File.WriteAllBytes(myVideoPlayer.url, videoData);
    }
}

Configuration

M8CMSManager Inspector Settings

Basic Configuration

  • App Key: Your unique game identifier (required)
  • Auto Initialize: Automatically initialize SDK on Start
  • Enable Debug Logs: Enable detailed logging

Version Management

  • Version Selection Mode:
    • Live: Use the live version (default)
    • Specific: Use a specific version by name
  • Refresh Version List on Start: Load available versions on startup

Download Options

  • Download Images/Videos on Start: Number of items to download immediately (0 = disabled)
  • Download New Levels Before X Level: Number of levels to pre-download ahead of player progress
  • Download X New Levels Bundle: Batch size for downloads
  • Enable Auto Download: Automatically download content based on player progress
  • Max Concurrent Downloads: Limit simultaneous downloads (1-5 recommended)
  • Max Cache Size: Maximum cache size in MB (default: 40MB)

Built-in Content (Optional)

  • Enable Built-in Content: Use offline fallback content
  • Built-in Level Count: Number of built-in levels
  • Built-in Collection Count: Number of built-in collections
  • Built-in Content Path: Resource path for offline content

Core Features

1. Automatic Content Management

The SDK automatically manages content downloads based on player progress:

C#
// Consume content when player reaches a level
cmsManager.ConsumeLevelContent(levelIndex);

// The SDK will automatically download:
// - Current level content
// - N levels ahead (configured in settings)

2. Version Management

Switch between different content versions:

C#
// Get all available versions
var versions = await cmsManager.GetAllVersionsAsync();

// Switch to a specific version
cmsManager.SetVersionSelectionMode(VersionSelectionMode.Specific);
cmsManager.SetSelectedVersionName("v1.2.0");

// Or use the latest version
var (versionId, version) = await cmsManager.GetLatestVersionAsync();

3. Caching System

The SDK includes three-level caching:

  1. In-Memory Cache: Fastest, for active content
  2. File System Cache: Persistent storage
  3. Preload Cache: For predictive downloads
C#
// Check if content is cached
bool isReady = cmsManager.IsLevelContentReady(levelIndex);

// Get cache statistics
var imageStats = cmsManager.GetImageCacheStats();
var videoStats = cmsManager.GetVideoCacheStats();
var preloadStats = cmsManager.GetPreloadCacheStats();

// Clear caches
cmsManager.ClearImageCache();
cmsManager.ClearVideoCache();
cmsManager.ClearPreloadCache();

4. Event System

Subscribe to SDK events for reactive updates:

C#
// Global events
M8CMSManager.OnInitializationComplete += HandleInit;
M8CMSManager.OnGameDataLoaded += HandleDataLoaded;
M8CMSManager.OnError += HandleError;

// Content ready events
cmsManager.OnLevelContentReady += OnLevelContentReady;
cmsManager.OnCollectionContentReady += OnCollectionContentReady;

API Reference

Initialization

M8CMSManager Instance

C#
// Singleton instance
M8CMSManager cmsManager = M8CMSManager.Instance;

bool IsInitialized()

C#
// Check if SDK is initialized
if (cmsManager.IsInitialized())
{
    // Ready to use
}

Getting Data

GameData GetCurrentGameData()

C#
// Get complete game data
GameData data = cmsManager.GetCurrentGameData();

Level GetLevelByIndex(int index)

C#
// Get specific level
Level level = cmsManager.GetLevelByIndex(5);

CollectionItem GetCollectionByIndex(int index)

C#
// Get specific collection
CollectionItem collection = cmsManager.GetCollectionByIndex(3);

List<Level> GetAllLevels()

C#
// Get all levels
List<Level> allLevels = cmsManager.GetAllLevels();

List<CollectionItem> GetAllCollections()

C#
// Get all collections
List<CollectionItem> allCollections = cmsManager.GetAllCollections();

Loading Content (Async)

Task<Texture2D> GetLevelImageAsync(int levelIndex)

C#
var texture = await cmsManager.GetLevelImageAsync(levelIndex);

Task<Sprite> GetLevelSpriteAsync(int levelIndex)

C#
var sprite = await cmsManager.GetLevelSpriteAsync(levelIndex);

Task<byte[]> GetLevelVideoAsync(int levelIndex)

C#
var videoBytes = await cmsManager.GetLevelVideoAsync(levelIndex);

Task<Texture2D> GetCollectionImageAsync(int collectionIndex)

C#
var texture = await cmsManager.GetCollectionImageAsync(collectionIndex);

Task<Sprite> GetCollectionSpriteAsync(int collectionIndex)

C#
var sprite = await cmsManager.GetCollectionSpriteAsync(collectionIndex);

Task<byte[]> GetCollectionVideoAsync(int collectionIndex)

C#
var videoBytes = await cmsManager.GetCollectionVideoAsync(collectionIndex);

Content Status

bool IsLevelContentReady(int levelIndex)

C#
// Check if level content is cached and ready
bool ready = cmsManager.IsLevelContentReady(levelIndex);

bool IsCollectionContentReady(int collectionIndex)

C#
// Check if collection content is cached and ready
bool ready = cmsManager.IsCollectionContentReady(collectionIndex);

Content Consumption

void ConsumeLevelContent(int levelIndex)

C#
// Notify SDK that player reached this level
cmsManager.ConsumeLevelContent(levelIndex);

void ConsumeCollectionContent(int collectionIndex)

C#
// Notify SDK that player reached this collection
cmsManager.ConsumeCollectionContent(collectionIndex);

Cache Management

void ClearImageCache()

C#
// Clear all cached images
cmsManager.ClearImageCache();

void ClearVideoCache()

C#
// Clear all cached videos
cmsManager.ClearVideoCache();

void ClearPreloadCache()

C#
// Clear preload cache
cmsManager.ClearPreloadCache();

(int fileCount, long totalSizeMB) GetImageCacheStats()

C#
var stats = cmsManager.GetImageCacheStats();
Debug.Log($"Cached images: {stats.fileCount}, Size: {stats.totalSizeMB}MB");

(int fileCount, long totalSizeMB) GetVideoCacheStats()

C#
var stats = cmsManager.GetVideoCacheStats();
Debug.Log($"Cached videos: {stats.fileCount}, Size: {stats.totalSizeMB}MB");

PreloadCacheStats GetPreloadCacheStats()

C#
var stats = cmsManager.GetPreloadCacheStats();
Debug.Log($"Preloaded levels: {stats.CachedLevelCount}");

Version Management

Task<List<(string versionId, Version version)>> GetAllVersionsAsync()

C#
var versions = await cmsManager.GetAllVersionsAsync();

Task<(string versionId, Version version)> GetLatestVersionAsync()

C#
var (versionId, version) = await cmsManager.GetLatestVersionAsync();

Task<Version> GetLiveVersionAsync()

C#
var liveVersion = await cmsManager.GetLiveVersionAsync();

void SetVersionSelectionMode(VersionSelectionMode mode)

C#
// Set to Live or Specific version
cmsManager.SetVersionSelectionMode(VersionSelectionMode.Live);

void SetSelectedVersionName(string versionName)

C#
// Set specific version by name
cmsManager.SetSelectedVersionName("v1.2.0");

VersionSelectionMode GetVersionSelectionMode()

C#
var mode = cmsManager.GetVersionSelectionMode();

string GetSelectedVersionName()

C#
var versionName = cmsManager.GetSelectedVersionName();

string GetCurrentVersionId()

C#
var versionId = cmsManager.GetCurrentVersionId();

List<(string versionId, Version version)> GetAvailableVersions()

C#
// Get cached versions
var versions = cmsManager.GetAvailableVersions();

Task<bool> LoadGameDataForVersionAsync(string versionId)

C#
// Load specific version
bool success = await cmsManager.LoadGameDataForVersionAsync(versionId);

Helper Methods

M8CMSContentHelper.IsContentAvailableForLevel(int levelIndex, M8CMSManager cmsManager)

C#
using M8CMS.Helpers;

// Check if content is available WITHOUT triggering downloads
bool available = M8CMSContentHelper.IsContentAvailableForLevel(levelIndex, cmsManager);

M8CMSContentHelper.IsContentAvailableForCollection(int collectionIndex, M8CMSManager cmsManager)

C#
// Check if collection content is available
bool available = M8CMSContentHelper.IsContentAvailableForCollection(collectionIndex, cmsManager);

Usage Examples

Example 1: Loading a Level UI

C#
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using M8CMS;

public class LevelButton : MonoBehaviour
{
    [SerializeField] private Image levelImage;
    [SerializeField] private Text levelText;
    [SerializeField] private Button button;
    
    private M8CMSManager cmsManager;
    private Level levelData;
    private int levelIndex;
    
    public void Setup(Level level, int index)
    {
        cmsManager = M8CMSManager.Instance;
        levelData = level;
        levelIndex = index;
        
        // Update UI text
        levelText.text = $"Level {index + 1}";
        
        // Load image asynchronously
        StartCoroutine(LoadLevelImage());
        
        // Setup button
        button.onClick.AddListener(OnButtonClicked);
    }
    
    IEnumerator LoadLevelImage()
    {
        var task = cmsManager.GetLevelSpriteAsync(levelIndex);
        yield return new WaitUntil(() => task.IsCompleted);
        
        if (task.Result != null)
        {
            levelImage.sprite = task.Result;
        }
    }
    
    void OnButtonClicked()
    {
        // Notify SDK that player is consuming this level
        cmsManager.ConsumeLevelContent(levelIndex);
        
        // Load the level scene or content
        SceneManager.LoadScene("GameLevel");
    }
}

Example 2: Gallery with Collections

C#
using System.Collections.Generic;
using M8CMS;
using M8CMS.Models;

public class CollectionGallery : MonoBehaviour
{
    [SerializeField] private Transform container;
    [SerializeField] private GameObject itemPrefab;
    
    private M8CMSManager cmsManager;
    private List<CollectionItem> collections;
    
    void Start()
    {
        cmsManager = M8CMSManager.Instance;
        
        // Wait for initialization
        M8CMSManager.OnGameDataLoaded += SetupGallery;
        
        if (cmsManager.IsGameDataLoaded())
        {
            SetupGallery(cmsManager.GetCurrentGameData());
        }
    }
    
    void SetupGallery(GameData gameData)
    {
        collections = gameData.collections;
        
        foreach (var collection in collections)
        {
            CreateCollectionItem(collection);
        }
    }
    
    void CreateCollectionItem(CollectionItem collection)
    {
        var item = Instantiate(itemPrefab, container);
        var button = item.GetComponent<Button>();
        
        // Load collection preview
        StartCoroutine(LoadCollectionPreview(item, collection));
        
        button.onClick.AddListener(() => OnCollectionClicked(collection.index));
    }
    
    IEnumerator LoadCollectionPreview(GameObject item, CollectionItem collection)
    {
        var image = item.GetComponent<Image>();
        
        if (collection.index < builtInCollectionCount)
        {
            // Use built-in content
            var sprite = cmsManager.GetCollectionImage(collection.index);
            image.sprite = sprite;
        }
        else
        {
            // Download from Firebase
            var task = cmsManager.GetCollectionSpriteAsync(collection.index);
            yield return new WaitUntil(() => task.IsCompleted);
            
            if (task.Result != null)
            {
                image.sprite = task.Result;
            }
        }
    }
    
    async void OnCollectionClicked(int collectionIndex)
    {
        // Consume collection content
        cmsManager.ConsumeCollectionContent(collectionIndex);
        
        // Load collection preview
        var videoData = await cmsManager.GetCollectionVideoAsync(collectionIndex);
        if (videoData != null)
        {
            ShowCollectionVideo(videoData);
        }
    }
}

Example 3: Progress Tracking

C#
using M8CMS;

public class ProgressTracker : MonoBehaviour
{
    private M8CMSManager cmsManager;
    
    void Start()
    {
        cmsManager = M8CMSManager.Instance;
        
        // Subscribe to content ready events
        cmsManager.OnLevelContentReady += OnLevelContentReady;
        cmsManager.OnCollectionContentReady += OnCollectionContentReady;
    }
    
    void OnLevelContentReady(int levelIndex, bool isReady)
    {
        if (isReady)
        {
            Debug.Log($"Level {levelIndex} content is ready!");
            UpdateLevelUI(levelIndex, true);
        }
        else
        {
            Debug.Log($"Level {levelIndex} is still downloading...");
        }
    }
    
    void OnCollectionContentReady(int collectionIndex, bool isReady)
    {
        if (isReady)
        {
            Debug.Log($"Collection {collectionIndex} content is ready!");
            UpdateCollectionUI(collectionIndex, true);
        }
    }
    
    void UpdateLevelUI(int levelIndex, bool isReady)
    {
        // Update UI indicators
        var indicator = GetLevelIndicator(levelIndex);
        indicator.color = isReady ? Color.green : Color.yellow;
    }
    
    void UpdateCollectionUI(int collectionIndex, bool isReady)
    {
        // Update UI indicators
        var indicator = GetCollectionIndicator(collectionIndex);
        indicator.color = isReady ? Color.green : Color.yellow;
    }
}

Example 4: Version Switching

C#
using M8CMS;
using UnityEngine.UI;

public class VersionSelector : MonoBehaviour
{
    [SerializeField] private Dropdown versionDropdown;
    
    private M8CMSManager cmsManager;
    private List<(string versionId, Version version)> versions;
    
    async void Start()
    {
        cmsManager = M8CMSManager.Instance;
        
        // Load available versions
        versions = await cmsManager.GetAllVersionsAsync();
        
        // Populate dropdown
        versionDropdown.ClearOptions();
        foreach (var (versionId, version) in versions)
        {
            versionDropdown.options.Add(new Dropdown.OptionData(version.versionName));
        }
        
        versionDropdown.onValueChanged.AddListener(OnVersionChanged);
    }
    
    void OnVersionChanged(int index)
    {
        if (index >= 0 && index < versions.Count)
        {
            var (versionId, version) = versions[index];
            LoadVersion(versionId);
        }
    }
    
    async void LoadVersion(string versionId)
    {
        Debug.Log($"Loading version: {versionId}");
        
        bool success = await cmsManager.LoadGameDataForVersionAsync(versionId);
        
        if (success)
        {
            Debug.Log("Version loaded successfully!");
            ReloadContent();
        }
        else
        {
            Debug.LogError("Failed to load version");
        }
    }
}

Best Practices

1. Always Subscribe to Events

C#
void OnEnable()
{
    M8CMSManager.OnInitializationComplete += OnInit;
    M8CMSManager.OnGameDataLoaded += OnDataLoaded;
}

void OnDisable()
{
    M8CMSManager.OnInitializationComplete -= OnInit;
    M8CMSManager.OnGameDataLoaded -= OnDataLoaded;
}

2. Use Coroutines for Async Operations

C#
public void LoadLevelAsync(int levelIndex)
{
    StartCoroutine(LoadLevelImageCoroutine(levelIndex));
}

IEnumerator LoadLevelImageCoroutine(int levelIndex)
{
    var task = cmsManager.GetLevelSpriteAsync(levelIndex);
    yield return new WaitUntil(() => task.IsCompleted);
    
    if (task.Result != null)
    {
        levelImage.sprite = task.Result;
    }
}

3. Consume Content Appropriately

C#
// Notify SDK when player reaches a level
public void StartLevel(int levelIndex)
{
    cmsManager.ConsumeLevelContent(levelIndex);
    LoadLevel(levelIndex);
}

4. Check Content Availability

C#
using M8CMS.Helpers;

void UpdateLevelButtons()
{
    foreach (var level in levels)
    {
        bool isAvailable = M8CMSContentHelper.IsContentAvailableForLevel(level.index, cmsManager);
        
        if (isAvailable)
        {
            levelButton.interactable = true;
        }
        else
        {
            levelButton.interactable = false;
            levelButton.image.color = Color.gray;
        }
    }
}

5. Optimize Cache Management

C#
void OnApplicationPause(bool pause)
{
    if (pause)
    {
        // Optionally clear in-memory cache to save RAM
        // File system cache remains intact
    }
}

6. Handle Network Errors

C#
void Start()
{
    cmsManager.OnContentLoadError += OnError;
}

void OnError(int itemIndex, string errorMessage)
{
    Debug.LogError($"Failed to load content for item {itemIndex}: {errorMessage}");
    ShowErrorPopup(errorMessage);
}

Troubleshooting

Issue: SDK Not Initializing

Solution:

  1. Check internet connection
  2. Verify appKey is correct
  3. Enable debug logs to see initialization process
  4. Check Firebase configuration

Issue: Content Not Loading

Solution:

  1. Check if content exists in Firebase
  2. Verify image/video URLs are valid
  3. Check cache size limits
  4. Ensure proper permissions in Firebase

Issue: Cache Growing Too Large

Solution:

  1. Adjust Max Cache Size setting
  2. Implement periodic cache cleanup
  3. Use ClearImageCache() and ClearVideoCache() strategically

Issue: Downloads Not Starting

Solution:

  1. Enable auto-download in settings
  2. Check Download New Levels Before X Level setting
  3. Ensure content is consumed via ConsumeLevelContent()

Issue: Built-in Content Not Working

Solution:

  1. Verify built-in content files exist in Resources/BuiltInContent
  2. Check file naming matches level/collection indices
  3. Enable built-in content in settings

Data Models

Level

C#
public class Level
{
    public int index;                    // Unique level index
    public string levelType;             // "normal", "boss", or "tutorial"
    public string difficulty;            // "normal" or "hard"
    public string gridSize;               // Grid dimensions
    public int rewardCoin;                // Coin reward
    public string imageUrl;               // Image URL from Firebase
    public string thumbnailUrl;           // Thumbnail URL
    public string videoUrl;               // Video URL from Firebase
    public bool isCustomLevel;            // Whether level is custom
    public string extra;                  // Additional data
}

CollectionItem

C#
public class CollectionItem
{
    public int index;                     // Unique collection index
    public string name;                   // Collection name
    public string grid;                   // Grid data
    public string imageUrl;               // Image URL from Firebase
    public string thumbnailUrl;            // Thumbnail URL
    public string videoUrl;                // Video URL from Firebase
    public int startLevel;                // First level where item appears
    public int endLevel;                   // Last level where item appears
}

Version

C#
public class Version
{
    public string versionName;            // Version identifier
    public bool isLive;                   // Whether version is live
    public DateTime createdAt;            // Creation timestamp
}

GameData

C#
public class GameData
{
    public Game game;                     // Game info
    public Version version;               // Current version
    public List<Level> levels;           // All levels
    public List<CollectionItem> collections; // All collections
}

Events Reference

Static Events (M8CMSManager)

  • OnInitializationComplete(bool success) - SDK initialization complete
  • OnGameDataLoaded(GameData gameData) - Game data loaded from Firebase
  • OnError(string errorMessage) - Error occurred

Instance Events (cmsManager)

  • OnLevelContentReady(int levelIndex, bool isReady) - Level content ready
  • OnCollectionContentReady(int collectionIndex, bool isReady) - Collection content ready
  • OnContentLoadError(int itemIndex, string errorMessage) - Content load error
  • OnNetworkStateChanged(bool isAvailable) - Network availability changed

Support

For additional help:

  • Check the example scenes in Assets/Scripts/M8CMS/Examples/
  • Review the inline code documentation
  • Enable debug logs for detailed information
  • Contact the M8 CMS support team

Version: 1.0
Last Updated: December 2024