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:
-
Version Check and Update:
- Ensure you're using a stable Unity version
- Update packages to compatible versions
- Document your Unity version for future reference
-
Quality Assurance:
- Run thorough playtesting on all target platforms
- Fix critical bugs and crashes
- Test edge cases (low memory, network interruptions, etc.)
-
Performance Optimization:
- Profile your game on target devices
- Implement quality settings for different hardware
- Remove debug code and logging
-
Asset Finalization:
- Remove unused assets and packages
- Optimize asset sizes (compress textures, audio, etc.)
- Organize project files into logical folders
-
Create Required Graphics:
- App icons in various resolutions
- Splash screens and launch images
- Store assets (screenshots, promotional art, videos)
-
Legal and Compliance:
- Verify third-party asset licenses
- Prepare privacy policy if collecting user data
- Check platform-specific requirements (ratings, etc.)
-
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:
-
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
-
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.)
-
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)
-
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:
-
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)
-
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
-
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
-
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
-
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:
-
Setup Requirements:
- Mac computer with Xcode installed
- Apple Developer Program membership ($99/year)
- Create App ID and provisioning profiles
-
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
-
Build Process:
- Build Unity project to generate Xcode project
- Open project in Xcode
- Configure signing and capabilities
- Update Info.plist if needed
-
Test Builds:
- Test on actual iOS devices
- Use TestFlight for wider testing
- Fix platform-specific issues
-
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:
-
Platform Registration:
- Apply for developer programs (PS, Xbox, Nintendo)
- Sign NDAs and platform agreements
- Purchase or rent development kits
-
Development Setup:
- Install platform-specific SDKs
- Configure Unity for console development
- Set up development hardware
-
Console-Specific Requirements:
- Implement platform-specific features (achievements, etc.)
- Follow UI/UX guidelines for each platform
- Ensure compliance with platform technical requirements
-
Certification Process:
- Submit builds to platform holder for testing
- Address feedback and compliance issues
- Resubmit until approved
-
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:
-
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)
-
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
-
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)
-
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
- Create a publishing checklist specific to your game and target platforms
- Test thoroughly on actual target devices, not just in the editor
- Automate your build process to reduce errors and save time
- Implement analytics to track user behavior and game performance
- Plan your monetization strategy before publishing
- Prepare marketing materials in advance (screenshots, videos, descriptions)
- Test the user experience from installation through gameplay
- Create a post-launch support plan for updates and bug fixes
- Document your build process for consistency in future updates
- Stay updated on platform requirements as they change frequently