Easy Learn C#

Unity Publishing

Introduction to Unity Publishing

Publishing your Unity game involves preparing your project for distribution, building it for target platforms, and distributing it through various channels. This process requires careful planning and consideration of platform-specific requirements.

In this guide, you'll learn:

  • Pre-publishing preparations and best practices
  • Building for PC, Mac, and Linux
  • Mobile publishing for iOS and Android
  • Console publishing considerations
  • Web publishing with WebGL
  • Distribution platforms and stores
  • Post-launch support and updates

Pre-Publishing Preparations

Before building your game for any platform, you should complete several important preparation steps to ensure a smooth publishing process.

Pre-Publishing Checklist:

  1. Version Check and Update:
    • Ensure you're using a stable Unity version
    • Update packages to compatible versions
    • Document your Unity version for future reference
  2. Quality Assurance:
    • Run thorough playtesting on all target platforms
    • Fix critical bugs and crashes
    • Test edge cases (low memory, network interruptions, etc.)
  3. Performance Optimization:
    • Profile your game on target devices
    • Implement quality settings for different hardware
    • Remove debug code and logging
  4. Asset Finalization:
    • Remove unused assets and packages
    • Optimize asset sizes (compress textures, audio, etc.)
    • Organize project files into logical folders
  5. Create Required Graphics:
    • App icons in various resolutions
    • Splash screens and launch images
    • Store assets (screenshots, promotional art, videos)
  6. Legal and Compliance:
    • Verify third-party asset licenses
    • Prepare privacy policy if collecting user data
    • Check platform-specific requirements (ratings, etc.)
  7. Build Configuration:
    • Set up correct player settings for each platform
    • Configure appropriate quality settings
    • Set version numbers and bundle identifiers

Version Management Script:

using UnityEngine;

public class VersionManager : MonoBehaviour
{
    // Version information (set these in the Inspector)
    [Header("Version Settings")]
    public int majorVersion = 1;
    public int minorVersion = 0;
    public int patchVersion = 0;
    
    [Tooltip("Optional build identifier (e.g., 'alpha', 'beta', 'rc1')")]
    public string buildIdentifier = "";
    
    [Tooltip("Build number, incremented automatically")]
    public int buildNumber = 0;
    
    // Version display text
    [Header("Display")]
    public TMPro.TextMeshProUGUI versionText;
    
    private void Awake()
    {
        // Set Application version at runtime
        string version = FormatVersion();
        Application.version = version;
        
        // Update UI display if assigned
        if (versionText != null)
        {
            versionText.text = "v" + version;
        }
        
        Debug.Log("Application Version: " + version);
    }
    
    // Format the version string
    public string FormatVersion()
    {
        string version = $"{majorVersion}.{minorVersion}.{patchVersion}";
        
        // Add build identifier if present
        if (!string.IsNullOrEmpty(buildIdentifier))
        {
            version += "-" + buildIdentifier;
        }
        
        // Add build number
        version += "+" + buildNumber.ToString("D3");
        
        return version;
    }
    
    // Call this method before building
    public void IncrementBuildNumber()
    {
        buildNumber++;
        
        // You would save this value to PlayerPrefs or a ScriptableObject
        // so it persists between editor sessions
    }
    
#if UNITY_EDITOR
    // Editor-only method to increment the build number
    [UnityEditor.MenuItem("Build/Increment Build Number")]
    static void IncrementBuild()
    {
        // Find the VersionManager in the scene
        VersionManager vm = GameObject.FindObjectOfType();
        if (vm != null)
        {
            vm.IncrementBuildNumber();
            UnityEditor.EditorUtility.SetDirty(vm);
            UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
            Debug.Log("Build number incremented to: " + vm.buildNumber);
        }
        else
        {
            Debug.LogError("VersionManager not found in scene!");
        }
    }
#endif
}

Benefits of proper version management:

  • Helps track and identify builds during testing
  • Simplifies update management on distribution platforms
  • Makes user support easier when troubleshooting version-specific issues
  • Follows semantic versioning standards for clear version progression

Building for PC, Mac, and Linux

Desktop platforms remain popular targets for game distribution, offering substantial hardware capabilities and multiple distribution channels.

Desktop Build Process:

  1. Configure Player Settings (Edit → Project Settings → Player):
    • Set company name, product name, and version
    • Configure default screen size and resolution options
    • Set icons for each platform
    • Choose appropriate graphics APIs
  2. Set Up Build Settings (File → Build Settings):
    • Add all scenes to the build
    • Arrange scenes in correct order
    • Select target platform (Windows, macOS, or Linux)
    • Choose architecture (x86, x64, Universal, etc.)
  3. Configure Platform-Specific Options:
    • Windows: Choose between Standalone or Windows Store
    • macOS: Set bundle identifier (com.company.game)
    • Linux: Select file format (.x86, .x86_64)
  4. Build the Game:
    • Click "Build" or "Build And Run"
    • Choose output folder
    • Wait for the build process to complete

Automating Builds with Script:

using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
using System;
using System.IO;

public class BuildScript
{
    // Menu item to trigger the build process
    [MenuItem("Build/Build All Platforms")]
    public static void BuildAllPlatforms()
    {
        // Get version from some source (PlayerPrefs, ScriptableObject, etc.)
        string version = PlayerPrefs.GetString("GameVersion", "1.0.0");
        
        // Set up common build settings
        string[] scenes = GetEnabledScenes();
        
        // Build for each platform
        BuildWindows(scenes, version);
        BuildMac(scenes, version);
        BuildLinux(scenes, version);
        
        Debug.Log("All builds completed!");
    }
    
    private static string[] GetEnabledScenes()
    {
        // Get all scenes set in the build settings
        EditorBuildSettingsScene[] editorScenes = EditorBuildSettings.scenes;
        string[] scenePaths = new string[editorScenes.Length];
        
        for (int i = 0; i < editorScenes.Length; i++)
        {
            if (editorScenes[i].enabled)
            {
                scenePaths[i] = editorScenes[i].path;
            }
        }
        
        return scenePaths;
    }
    
    private static void BuildWindows(string[] scenes, string version)
    {
        // Create directory if it doesn't exist
        string buildPath = Path.Combine("Builds", "Windows", version);
        Directory.CreateDirectory(buildPath);
        
        // Configure player settings for Windows
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.Mono2x);
        
        // Build options
        BuildPlayerOptions buildOptions = new BuildPlayerOptions
        {
            scenes = scenes,
            locationPathName = Path.Combine(buildPath, "MyGame.exe"),
            target = BuildTarget.StandaloneWindows64,
            options = BuildOptions.None
        };
        
        // Start the build
        BuildReport report = BuildPipeline.BuildPlayer(buildOptions);
        BuildSummary summary = report.summary;
        
        // Check the result
        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log("Windows build succeeded: " + summary.totalSize + " bytes");
        }
        else
        {
            Debug.LogError("Windows build failed: " + summary.result);
        }
    }
    
    private static void BuildMac(string[] scenes, string version)
    {
        // Create directory if it doesn't exist
        string buildPath = Path.Combine("Builds", "Mac", version);
        Directory.CreateDirectory(buildPath);
        
        // Configure player settings for Mac
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.Mono2x);
        
        // Build options
        BuildPlayerOptions buildOptions = new BuildPlayerOptions
        {
            scenes = scenes,
            locationPathName = Path.Combine(buildPath, "MyGame.app"),
            target = BuildTarget.StandaloneOSX,
            options = BuildOptions.None
        };
        
        // Start the build
        BuildReport report = BuildPipeline.BuildPlayer(buildOptions);
        BuildSummary summary = report.summary;
        
        // Check the result
        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log("Mac build succeeded: " + summary.totalSize + " bytes");
        }
        else
        {
            Debug.LogError("Mac build failed: " + summary.result);
        }
    }
    
    private static void BuildLinux(string[] scenes, string version)
    {
        // Create directory if it doesn't exist
        string buildPath = Path.Combine("Builds", "Linux", version);
        Directory.CreateDirectory(buildPath);
        
        // Configure player settings for Linux
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.Mono2x);
        
        // Build options
        BuildPlayerOptions buildOptions = new BuildPlayerOptions
        {
            scenes = scenes,
            locationPathName = Path.Combine(buildPath, "MyGame.x86_64"),
            target = BuildTarget.StandaloneLinux64,
            options = BuildOptions.None
        };
        
        // Start the build
        BuildReport report = BuildPipeline.BuildPlayer(buildOptions);
        BuildSummary summary = report.summary;
        
        // Check the result
        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log("Linux build succeeded: " + summary.totalSize + " bytes");
        }
        else
        {
            Debug.LogError("Linux build failed: " + summary.result);
        }
    }
}

Note: This script must be placed in an Editor folder within your project to work. To run automated builds from command line:

Unity.exe -batchmode -nographics -quit -executeMethod BuildScript.BuildAllPlatforms -logFile build_log.txt

Desktop Platform Considerations

  • Windows: Most games include both 32-bit and 64-bit executables for maximum compatibility
  • macOS: Requires code signing and notarization for distribution outside the App Store
  • Linux: Target the most common distributions (Ubuntu-based) for best compatibility
  • File Size: Consider splitting large games into base install and downloadable content
  • DRM: Implement appropriate copy protection if needed (Steam DRM, etc.)
  • Installers: Consider using installer creators (InnoSetup, InstallShield) for professional presentation

Mobile Publishing (iOS and Android)

Mobile platforms represent the largest gaming market by user count, but have specific requirements and limitations.

Android Publishing Process:

  1. Setup Requirements:
    • Install Android SDK and NDK (through Unity Hub)
    • Create a keystore for signing your app
    • Set up a Google Play Developer account ($25 one-time fee)
  2. Configure Player Settings:
    • Set unique Package Name (com.yourcompany.yourgame)
    • Configure minimum API level (21+ recommended)
    • Set icons and splash screens
    • Configure required device features and permissions
  3. Publishing Settings:
    • Select keystore and create/enter key alias
    • Choose between APK and Android App Bundle (AAB) format
    • Set up split APKs for different device configurations if needed
  4. Build and Test:
    • Make test builds for different device types
    • Test on actual devices before submission
    • Use Unity's Internal or Firebase Test Lab for broader testing
  5. Google Play Submission:
    • Create store listing with screenshots, descriptions, etc.
    • Upload APK/AAB file
    • Set pricing and countries
    • Complete content rating questionnaire
    • Submit for review

iOS Publishing Process:

  1. Setup Requirements:
    • Mac computer with Xcode installed
    • Apple Developer Program membership ($99/year)
    • Create App ID and provisioning profiles
  2. Configure Player Settings:
    • Set Bundle Identifier (com.yourcompany.yourgame)
    • Configure minimum iOS version (13.0+ recommended)
    • Set appropriate camera, microphone, location usage descriptions
    • Configure icons and splash screens
  3. Build Process:
    • Build Unity project to generate Xcode project
    • Open project in Xcode
    • Configure signing and capabilities
    • Update Info.plist if needed
  4. Test Builds:
    • Test on actual iOS devices
    • Use TestFlight for wider testing
    • Fix platform-specific issues
  5. App Store Submission:
    • Create app record in App Store Connect
    • Fill in metadata, screenshots, keywords
    • Upload build through Xcode or Application Loader
    • Submit for review (can take 1-3 days typically)

Mobile Platform Detection Script:

using UnityEngine;

public class PlatformManager : MonoBehaviour
{
    // Settings for different platforms
    [System.Serializable]
    public class PlatformSettings
    {
        public bool enablePostProcessing = true;
        public int targetFrameRate = 60;
        public int qualityLevel = 3;
        public bool enableVibration = true;
        public bool enableNotifications = true;
    }
    
    // Platform-specific settings
    public PlatformSettings iOSSettings;
    public PlatformSettings androidSettings;
    public PlatformSettings highEndSettings;
    public PlatformSettings lowEndSettings;
    
    // Device tiers detection thresholds
    public int systemMemoryThreshold = 3000; // MB
    public int processorCountThreshold = 4;
    
    private void Awake()
    {
        // Apply settings based on platform and device capabilities
        ApplyOptimalSettings();
    }
    
    void ApplyOptimalSettings()
    {
        PlatformSettings settings = null;
        
        // First check device type
        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            settings = iOSSettings;
            Debug.Log("Applying iOS settings");
        }
        else if (Application.platform == RuntimePlatform.Android)
        {
            settings = androidSettings;
            Debug.Log("Applying Android settings");
        }
        else
        {
            // For desktop platforms, use high-end settings
            settings = highEndSettings;
            Debug.Log("Applying high-end settings for desktop platform");
            
            // Apply settings and return early - no need for further device tier checks
            ApplySettings(settings);
            return;
        }
        
        // Then check device tier (for mobile)
        bool isLowEndDevice = IsLowEndDevice();
        
        if (isLowEndDevice)
        {
            Debug.Log("Low-end device detected, overriding with low-end settings");
            settings = lowEndSettings;
        }
        
        // Apply the selected settings
        ApplySettings(settings);
    }
    
    bool IsLowEndDevice()
    {
        // Get system memory size (MB)
        int systemMemory = SystemInfo.systemMemorySize;
        
        // Get processor count
        int processorCount = SystemInfo.processorCount;
        
        // Check if device is below thresholds
        bool lowMemory = systemMemory < systemMemoryThreshold;
        bool lowCPU = processorCount < processorCountThreshold;
        
        // Log the detected specs
        Debug.Log($"Device specs - Memory: {systemMemory}MB, CPU cores: {processorCount}");
        
        // Consider it low-end if either memory or CPU is below threshold
        return lowMemory || lowCPU;
    }
    
    void ApplySettings(PlatformSettings settings)
    {
        if (settings == null) return;
        
        // Apply frame rate cap
        Application.targetFrameRate = settings.targetFrameRate;
        
        // Apply quality settings
        QualitySettings.SetQualityLevel(settings.qualityLevel);
        
        // Apply post-processing
        TogglePostProcessing(settings.enablePostProcessing);
        
        // Apply other platform-specific settings
        if (Application.platform == RuntimePlatform.Android)
        {
            // Android-specific optimizations
            if (!settings.enableVibration)
            {
                // Disable vibration
                // Handheld.Vibrate() calls would be skipped
            }
        }
        
        // Log applied settings
        Debug.Log($"Applied settings - FPS: {settings.targetFrameRate}, " +
                 $"Quality: {settings.qualityLevel}, " +
                 $"Post-processing: {settings.enablePostProcessing}");
    }
    
    void TogglePostProcessing(bool enabled)
    {
        // Find post-processing volume or component
        // This depends on which post-processing system you're using (URP, HDRP, PPv2, etc.)
        
        // Example for Post Processing V2:
        var postProcessing = FindObjectOfType();
        
        if (postProcessing != null)
        {
            postProcessing.enabled = enabled;
        }
        else
        {
            Debug.LogWarning("Post-processing volume not found");
        }
    }
}

Mobile optimization considerations:

  • Lower frame rate targets can greatly extend battery life
  • Detect device capabilities at runtime for optimal settings
  • Disable expensive visual effects on low-end devices
  • Consider different control schemes for different screen sizes

Console Publishing

Publishing to consoles requires developer approval, special SDKs, and adherence to platform-specific requirements.

Console Publishing Overview:

  1. Platform Registration:
    • Apply for developer programs (PS, Xbox, Nintendo)
    • Sign NDAs and platform agreements
    • Purchase or rent development kits
  2. Development Setup:
    • Install platform-specific SDKs
    • Configure Unity for console development
    • Set up development hardware
  3. Console-Specific Requirements:
    • Implement platform-specific features (achievements, etc.)
    • Follow UI/UX guidelines for each platform
    • Ensure compliance with platform technical requirements
  4. Certification Process:
    • Submit builds to platform holder for testing
    • Address feedback and compliance issues
    • Resubmit until approved
  5. Release and Updates:
    • Coordinate release date with platform
    • Create store assets and marketing materials
    • Plan post-launch support and update schedule

Console Publishing Notes

  • Console certification processes are typically more rigorous than PC or mobile
  • Technical requirements focus on stability, adherence to platform norms, and user experience
  • Plan extra time for platform approval, testing, and certification
  • Updates go through similar certification processes, though often expedited
  • Many console platforms have specific testing scenarios that must pass (suspend/resume, etc.)

WebGL Publishing

WebGL allows your game to run directly in web browsers, offering wide accessibility without requiring installation.

WebGL Build Process:

  1. Configure WebGL Player Settings:
    • Set appropriate template (Default, Minimal, etc.)
    • Configure memory allocation (higher for complex games)
    • Set compression method (Brotli, Gzip, or Disabled)
    • Configure publishing settings (code optimization level)
  2. Build Considerations:
    • Reduce texture sizes and asset quality for faster loading
    • Avoid dependencies on native plugins
    • Optimize memory usage to prevent crashes
    • Implement asynchronous loading for large assets
  3. Build the Game:
    • Switch platform to WebGL in Build Settings
    • Build the project to a directory
    • Test locally using a web server (not by opening HTML directly)
  4. Hosting and Distribution:
    • Upload build files to a web server
    • Configure proper MIME types on your server
    • Consider using game portals (itch.io, Kongregate, etc.)

WebGL Loading Progress Script:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;

public class WebGLLoader : MonoBehaviour
{
    // UI elements for showing loading progress
    public Slider progressBar;
    public Text progressText;
    public Text loadingInfoText;
    public GameObject loadingScreen;
    public GameObject startButton;
    
    // Loading configuration
    public string mainSceneName = "MainMenu";
    public float minDisplayTime = 3f; // Minimum time to show loading screen
    
    // Audio to play when loading completes
    public AudioSource loadingCompleteSound;
    
    // State tracking
    private bool isLoading = false;
    private float loadStartTime;
    
    void Start()
    {
        // Initialize UI
        if (progressBar != null) progressBar.value = 0;
        if (progressText != null) progressText.text = "0%";
        if (loadingInfoText != null) loadingInfoText.text = "Preparing...";
        if (startButton != null) startButton.SetActive(false);
        
        // Start loading the main scene in the background
        StartCoroutine(LoadMainScene());
    }
    
    IEnumerator LoadMainScene()
    {
        isLoading = true;
        loadStartTime = Time.time;
        
        // Start async loading operation
        AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(mainSceneName);
        
        // Don't let the scene activate yet
        asyncOperation.allowSceneActivation = false;
        
        #if UNITY_WEBGL && !UNITY_EDITOR
            // WebGL specific loading messages
            yield return StartCoroutine(UpdateLoadingInfo());
        #endif
        
        // Monitor the loading progress
        while (!asyncOperation.isDone)
        {
            // Progress goes from 0 to 0.9 (90%) when scene is ready to activate
            float progress = Mathf.Clamp01(asyncOperation.progress / 0.9f);
            
            // Update the progress bar
            if (progressBar != null) progressBar.value = progress;
            if (progressText != null) progressText.text = (progress * 100).ToString("F0") + "%";
            
            // If loading is almost done
            if (asyncOperation.progress >= 0.9f)
            {
                // Check if we've displayed the loading screen for the minimum time
                float elapsedTime = Time.time - loadStartTime;
                if (elapsedTime >= minDisplayTime)
                {
                    // Show the start button
                    if (startButton != null)
                    {
                        startButton.SetActive(true);
                        
                        // Play loading complete sound if available
                        if (loadingCompleteSound != null)
                        {
                            loadingCompleteSound.Play();
                        }
                        
                        if (loadingInfoText != null)
                        {
                            loadingInfoText.text = "Loading complete! Click to start.";
                        }
                    }
                    else
                    {
                        // No start button, so activate right away
                        asyncOperation.allowSceneActivation = true;
                    }
                }
            }
            
            yield return null;
        }
        
        isLoading = false;
    }
    
    // WebGL-specific loading messages
    IEnumerator UpdateLoadingInfo()
    {
        string[] loadingMessages = new string[]
        {
            "Downloading game data...",
            "Preparing graphics...",
            "Optimizing for your browser...",
            "Almost there..."
        };
        
        int messageIndex = 0;
        float nextMessageTime = Time.time + 2.5f;
        
        while (isLoading)
        {
            if (Time.time > nextMessageTime)
            {
                messageIndex = (messageIndex + 1) % loadingMessages.Length;
                if (loadingInfoText != null)
                {
                    loadingInfoText.text = loadingMessages[messageIndex];
                }
                nextMessageTime = Time.time + 2.5f;
            }
            
            yield return null;
        }
    }
    
    // Called by the start button
    public void OnStartButtonClick()
    {
        // Hide the loading screen
        if (loadingScreen != null)
        {
            loadingScreen.SetActive(false);
        }
        
        // Activate the main scene
        SceneManager.LoadSceneAsync(mainSceneName).allowSceneActivation = true;
    }
}

WebGL platform considerations:

  • Browser security restrictions limit certain features (file system access, etc.)
  • WebGL builds are usually larger than native builds
  • Performance is generally 50-70% of native builds
  • Loading feedback is crucial as WebGL builds can take time to initialize

Best Practices for Unity Publishing

  1. Create a publishing checklist specific to your game and target platforms
  2. Test thoroughly on actual target devices, not just in the editor
  3. Automate your build process to reduce errors and save time
  4. Implement analytics to track user behavior and game performance
  5. Plan your monetization strategy before publishing
  6. Prepare marketing materials in advance (screenshots, videos, descriptions)
  7. Test the user experience from installation through gameplay
  8. Create a post-launch support plan for updates and bug fixes
  9. Document your build process for consistency in future updates
  10. Stay updated on platform requirements as they change frequently