Build-Specific Guides

Optimize your builds for different engines and platforms

Unreal Engine Builds

Package and optimize Unreal Engine projects

Unreal Engine Builds

Package and optimize Unreal Engine projects for streaming with SimplyStream.

Prerequisites

  • Unreal Engine 4.27+ or Unreal Engine 5.x
  • Windows, macOS, or Linux development environment
  • SimplyStream Unreal Engine Plugin (optional)
  • Sufficient disk space for packaging (20GB+ recommended)

Project Setup

Install SimplyStream Plugin (Optional)

  1. Download the SimplyStream plugin from the marketplace or GitHub
  2. Extract to your project's Plugins folder
  3. Enable the plugin in your project settings
  4. Restart Unreal Editor

Configure Project Settings

Edit → Project Settings → Platforms → Windows:

Target Platform: Windows
Build Configuration: Shipping
Architecture: x64

Project Settings → Project → Packaging:

Build Configuration: Shipping
Staging Directory: [YourProject]/Build
Full Rebuild: ✓
Use Pak File: ✓
Generate Chunks: ✗ (unless needed)
Compress Content: ✓

Packaging for Streaming

Windows Build

# Using Unreal Automation Tool (UAT)
"C:\Program Files\Epic Games\UE_5.1\Engine\Build\BatchFiles\RunUAT.bat" BuildCookRun ^
  -project="D:\MyProject\MyProject.uproject" ^
  -noP4 ^
  -platform=Win64 ^
  -clientconfig=Shipping ^
  -serverconfig=Shipping ^
  -cook ^
  -allmaps ^
  -build ^
  -stage ^
  -pak ^
  -archive ^
  -archivedirectory="D:\MyProject\Build"

Linux Build

# Using UAT on Linux
/path/to/UnrealEngine/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
  -project="/path/to/MyProject.uproject" \
  -noP4 \
  -platform=Linux \
  -clientconfig=Shipping \
  -cook \
  -allmaps \
  -build \
  -stage \
  -pak \
  -archive \
  -archivedirectory="/path/to/Build"

Optimization

Graphics Settings

Engine.ini or DefaultEngine.ini:

[/Script/Engine.RendererSettings]
r.Streaming.PoolSize=2000
r.Streaming.MaxEffectiveScreenSize=0
r.Shadow.MaxResolution=2048
r.Shadow.MaxCSMResolution=2048

[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsInGame=2097152
gc.MaxObjectsInEditor=12001024

[SystemSettings]
r.DefaultFeature.AutoExposure=False
r.DefaultFeature.MotionBlur=False

Network Optimization

DefaultEngine.ini:

[/Script/OnlineSubsystemUtils.IpNetDriver]
MaxClientRate=25000
MaxInternetClientRate=25000
NetServerMaxTickRate=60
LanServerMaxTickRate=60

[/Script/Engine.Player]
ConfiguredInternetSpeed=25000
ConfiguredLanSpeed=25000

Compression Settings

Project Settings → Packaging:

  • Compress Content: Enabled
  • Compression Method: Oodle
  • Compression Level: Normal (balance size/speed)

Exclude Development Content

Add to DefaultGame.ini:

[/Script/UnrealEd.ProjectPackagingSettings]
+DirectoriesToNeverCook=(Path="/Game/Developers")
+DirectoriesToNeverCook=(Path="/Game/Test")
+DirectoriesToNeverCook=(Path="/Game/Unused")

SimplyStream Integration

Using the Plugin

// MyGameInstance.h
#include "SimplyStreamSubsystem.h"

UCLASS()
class UMyGameInstance : public UGameInstance
{
    GENERATED_BODY()

public:
    virtual void Init() override;

private:
    USimplyStreamSubsystem* StreamSubsystem;
};
// MyGameInstance.cpp
void UMyGameInstance::Init()
{
    Super::Init();

    StreamSubsystem = GetSubsystem<USimplyStreamSubsystem>();
    if (StreamSubsystem)
    {
        StreamSubsystem->OnSessionStarted.AddDynamic(this, &UMyGameInstance::HandleSessionStarted);
        StreamSubsystem->OnSessionEnded.AddDynamic(this, &UMyGameInstance::HandleSessionEnded);

        // Initialize streaming
        StreamSubsystem->Initialize();
    }
}

Blueprint Integration

Create Blueprint nodes for streaming:

// SimplyStreamBlueprintLibrary.h
UCLASS()
class USimplyStreamBlueprintLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, Category = "SimplyStream")
    static FString GetSessionId();

    UFUNCTION(BlueprintCallable, Category = "SimplyStream")
    static void SendAnalyticsEvent(const FString& EventName, const TMap<FString, FString>& Properties);
};

Deployment

Package Structure

MyProject/
├── MyProject.exe
├── MyProject/
│   ├── Binaries/
│   ├── Content/
│   └── Config/
└── Engine/
    ├── Binaries/
    └── Content/

Upload to SimplyStream

# Using SimplyStream CLI
simplystream upload \
  --build-path "./Build/WindowsNoEditor" \
  --platform windows \
  --engine unreal \
  --engine-version 5.1 \
  --name "MyProject v1.0.0"

Via API

import { SimplyStreamClient } from '@simplystream/sdk';
import fs from 'fs';

const client = new SimplyStreamClient({ apiKey: process.env.API_KEY });

// Create build
const build = await client.builds.create({
	name: 'MyProject v1.0.0',
	platform: 'windows',
	engine: 'unreal',
	engineVersion: '5.1'
});

// Upload build files
const stream = fs.createReadStream('./Build/MyProject.zip');
await client.builds.uploadFiles(build.id, stream);

// Mark as ready
await client.builds.markReady(build.id);

Performance Optimization

Level Streaming

// Implement level streaming for large worlds
void AMyGameMode::BeginPlay()
{
    Super::BeginPlay();

    // Load initial levels
    FLatentActionInfo LatentInfo;
    LatentInfo.CallbackTarget = this;
    UGameplayStatics::LoadStreamLevel(this, FName("Level_Main"), true, true, LatentInfo);
}

Texture Streaming

DefaultEngine.ini:

[/Script/Engine.RendererSettings]
r.Streaming.PoolSize=3000
r.Streaming.MaxTempMemoryAllowed=100
r.Streaming.BoostPlayerTextures=3.0

[TextureStreaming]
PoolSize=2000
MaxEffectiveScreenSize=0

Asset Management

// Use Asset Manager for efficient loading
UAssetManager& AssetManager = UAssetManager::Get();

// Asynchronous asset loading
FStreamableManager& Streamable = AssetManager.GetStreamableManager();
FSoftObjectPath AssetPath(TEXT("/Game/Assets/MyAsset"));

TSharedPtr<FStreamableHandle> Handle = Streamable.RequestAsyncLoad(
    AssetPath,
    FStreamableDelegate::CreateUObject(this, &AMyActor::OnAssetLoaded)
);

Common Issues

Large Build Size

Solutions:

  • Enable content compression
  • Remove unused assets
  • Use texture LOD bias
  • Exclude development content
[/Script/UnrealEd.ProjectPackagingSettings]
+MapsToCook=(FilePath="/Game/Maps/MainMenu")
+MapsToCook=(FilePath="/Game/Maps/Level1")
# Only cook maps you need

Slow Startup Time

Solutions:

  • Reduce initial load content
  • Use async loading
  • Implement splash screen
  • Optimize startup scripts
// Async initialization
void AMyGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
    Super::InitGame(MapName, Options, ErrorMessage);

    // Start async tasks
    Async(EAsyncExecution::ThreadPool, [this]()
    {
        // Load data asynchronously
        LoadGameData();
    });
}

Memory Issues

Solutions:

  • Reduce texture pool size
  • Enable texture streaming
  • Use object pooling
  • Implement garbage collection calls
// Force garbage collection during loading screens
void UMyLoadingScreen::BeginLoad()
{
    GEngine->ForceGarbageCollection(true);
}

Testing

Local Testing

  1. Package your project in Shipping configuration
  2. Test the packaged build locally
  3. Verify all features work correctly
  4. Check performance metrics

Performance Profiling

// Add performance markers
SCOPE_CYCLE_COUNTER(STAT_MyFunction);

void MyFunction()
{
    // Your code here
}

Use Unreal's built-in profiler:

  • stat fps - Show FPS
  • stat unit - Show frame times
  • stat memory - Show memory usage
  • stat streaming - Show streaming stats

Best Practices

1. Use Dedicated Servers

For multiplayer, separate client and server builds:

# Server build
RunUAT.bat BuildCookRun \
  -project="MyProject.uproject" \
  -platform=Win64 \
  -server \
  -serverconfig=Shipping \
  -noclient \
  -cook \
  -build \
  -stage \
  -pak

2. Implement Proper Logging

// MyProject.h
DECLARE_LOG_CATEGORY_EXTERN(LogMyProject, Log, All);

// MyProject.cpp
DEFINE_LOG_CATEGORY(LogMyProject);

// Usage
UE_LOG(LogMyProject, Warning, TEXT("Something happened: %s"), *Message);

3. Handle Crashes Gracefully

// Implement crash reporter
void UMyGameInstance::Init()
{
    Super::Init();

    FCoreDelegates::OnHandleSystemError.AddUObject(this, &UMyGameInstance::HandleCrash);
}

void UMyGameInstance::HandleCrash()
{
    // Send crash report to analytics
    // Save player progress
    // Display friendly error message
}

4. Version Your Builds

[/Script/EngineSettings.GeneralProjectSettings]
ProjectVersion=1.0.0
CompanyName=YourCompany
ProjectName=YourProject

Example: Complete Build Script

#!/bin/bash

# Configuration
PROJECT_NAME="MyProject"
PROJECT_PATH="/path/to/${PROJECT_NAME}.uproject"
UE_PATH="/path/to/UnrealEngine"
BUILD_PATH="/path/to/Builds"
VERSION="1.0.0"

# Package the game
"${UE_PATH}/Engine/Build/BatchFiles/RunUAT.sh" BuildCookRun \
  -project="${PROJECT_PATH}" \
  -noP4 \
  -platform=Win64 \
  -clientconfig=Shipping \
  -cook \
  -allmaps \
  -build \
  -stage \
  -pak \
  -archive \
  -archivedirectory="${BUILD_PATH}/${VERSION}"

# Compress build
cd "${BUILD_PATH}"
zip -r "${PROJECT_NAME}-${VERSION}.zip" "${VERSION}"

# Upload to SimplyStream
simplystream upload \
  --build-path "${BUILD_PATH}/${VERSION}" \
  --name "${PROJECT_NAME} v${VERSION}" \
  --platform windows \
  --engine unreal

echo "Build complete and uploaded!"

Next Steps

SimplyStream Engine SDK

Clone the UE 5.8 source fork and build your project for the SimplyStream platform

SimplyStream Engine SDK

The SimplyStream WebGPU/wasm engine is distributed as a source fork of Unreal Engine 5.8: stock UE 5.8 plus the SimplyStream platform, with the proprietary WebGPU modules shipped as precompiled objects. Clone it, build it like any UE source engine, and the WebGPU/wasm platform is already in the tree.

Access

The fork is SimplyStreamSDK/UnrealEngine, gated by Epic's own GitHub access — the same link you already use to clone EpicGames/UnrealEngine:

  1. Link your GitHub account to your Epic Games account under Epic → Account → Connections and accept the Unreal Engine EULA.
  2. That membership grants access to SimplyStreamSDK/UnrealEngine too — no SimplyStream account or token is needed for the engine source. If you can open github.com/EpicGames/UnrealEngine, you already have access.

Clone

git clone -b 5.8 https://github.com/SimplyStreamSDK/UnrealEngine.git

The 5.8 branch tracks the latest blessed engine: official UE 5.8 + the SimplyStream platform.

Build

Set it up like any UE source build:

cd UnrealEngine
./Setup.sh                 # Setup.bat on Windows — fetch binary dependencies
./GenerateProjectFiles.sh  # GenerateProjectFiles.bat on Windows

Then build the editor as you would any UE source engine. The proprietary SimplyStream WebGPU modules are already compiled (shipped as objects), so they link instead of recompiling; everything else builds from source, exactly like stock UE.

Build & cook your project

Point your project at this engine, then build it for the SimplyStream platform:

Engine/Build/BatchFiles/RunUBT.sh <YourGame> SimplyStream Development

Cook and deploy as usual — see Unreal Engine Builds.

Updates

git pull on 5.8 to take the latest engine. Each update is a clean snapshot of the official UE 5.8 tip plus the platform; rebuild your project against it. Because the WebGPU modules are precompiled per engine revision, build your project against the same clone you pulled — don't mix a project built on one snapshot with a different engine tree.

Unity Builds

Build and configure Unity applications

Unity Builds

Build and configure Unity applications for streaming with SimplyStream.

Prerequisites

  • Unity 2020.3 LTS or newer
  • Platform-specific build support installed
  • SimplyStream Unity SDK (optional)
  • Build target platform configured

Project Setup

Install SimplyStream SDK

Via Unity Package Manager:

  1. Open Package Manager (Window → Package Manager)
  2. Click "+" → "Add package from git URL"
  3. Enter: https://github.com/simplystream/unity-sdk.git

Or manually:

  1. Download the SimplyStream Unity package
  2. Import into your project (Assets → Import Package)

Configure Build Settings

File → Build Settings:

Platform: Windows/Linux/macOS
Architecture: x86_64
Development Build: ✗ (unchecked for production)
Compression Method: LZ4 or LZ4HC

Player Settings:

Company Name: Your Company
Product Name: Your Product
Version: 1.0.0

Building for Different Platforms

Windows Build

// Editor/BuildScript.cs
using UnityEditor;
using UnityEditor.Build.Reporting;

public class BuildScript
{
    [MenuItem("Build/Build Windows")]
    public static void BuildWindows()
    {
        BuildPlayerOptions buildOptions = new BuildPlayerOptions
        {
            scenes = GetScenePaths(),
            locationPathName = "Builds/Windows/MyGame.exe",
            target = BuildTarget.StandaloneWindows64,
            options = BuildOptions.None
        };

        BuildReport report = BuildPipeline.BuildPlayer(buildOptions);
        BuildSummary summary = report.summary;

        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log($"Build succeeded: {summary.totalSize} bytes");
        }
        else
        {
            Debug.LogError($"Build failed");
        }
    }

    static string[] GetScenePaths()
    {
        return new[]
        {
            "Assets/Scenes/MainMenu.unity",
            "Assets/Scenes/Level1.unity",
            "Assets/Scenes/Level2.unity"
        };
    }
}

Linux Build

[MenuItem("Build/Build Linux")]
public static void BuildLinux()
{
    BuildPlayerOptions buildOptions = new BuildPlayerOptions
    {
        scenes = GetScenePaths(),
        locationPathName = "Builds/Linux/MyGame.x86_64",
        target = BuildTarget.StandaloneLinux64,
        options = BuildOptions.None
    };

    BuildPipeline.BuildPlayer(buildOptions);
}

macOS Build

[MenuItem("Build/Build macOS")]
public static void BuildMacOS()
{
    BuildPlayerOptions buildOptions = new BuildPlayerOptions
    {
        scenes = GetScenePaths(),
        locationPathName = "Builds/macOS/MyGame.app",
        target = BuildTarget.StandaloneOSX,
        options = BuildOptions.None
    };

    BuildPipeline.BuildPlayer(buildOptions);
}

SimplyStream Integration

Initialize SDK

// SimplyStreamManager.cs
using SimplyStream;
using UnityEngine;

public class SimplyStreamManager : MonoBehaviour
{
    private SimplyStreamClient client;

    void Start()
    {
        // Initialize SimplyStream
        client = new SimplyStreamClient(new SimplyStreamConfig
        {
            ApiKey = GetApiKey(),
            ProjectId = GetProjectId(),
            Environment = "production"
        });

        // Register event handlers
        client.OnSessionStarted += HandleSessionStarted;
        client.OnSessionEnded += HandleSessionEnded;

        // Start session
        StartSession();
    }

    async void StartSession()
    {
        try
        {
            var session = await client.StartSession(new SessionOptions
            {
                BuildId = GetBuildId(),
                Region = "us-west-2"
            });

            Debug.Log($"Session started: {session.Id}");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"Failed to start session: {e.Message}");
        }
    }

    void HandleSessionStarted(Session session)
    {
        Debug.Log($"Session {session.Id} started");
    }

    void HandleSessionEnded(Session session)
    {
        Debug.Log($"Session {session.Id} ended after {session.Duration}s");
    }

    string GetApiKey()
    {
        // In production, load from secure storage
        return Environment.GetEnvironmentVariable("SIMPLYSTREAM_API_KEY");
    }

    string GetProjectId()
    {
        return Environment.GetEnvironmentVariable("SIMPLYSTREAM_PROJECT_ID");
    }

    string GetBuildId()
    {
        return PlayerPrefs.GetString("BuildId", "");
    }
}

Analytics Integration

// AnalyticsManager.cs
using SimplyStream.Analytics;
using UnityEngine;

public class AnalyticsManager : MonoBehaviour
{
    private SimplyStreamAnalytics analytics;

    void Start()
    {
        analytics = SimplyStreamAnalytics.Instance;

        // Track events
        analytics.TrackEvent("GameStarted", new Dictionary<string, object>
        {
            { "platform", Application.platform.ToString() },
            { "version", Application.version },
            { "timestamp", System.DateTime.Now }
        });
    }

    public void TrackLevelCompleted(string levelName, float time)
    {
        analytics.TrackEvent("LevelCompleted", new Dictionary<string, object>
        {
            { "level", levelName },
            { "completionTime", time },
            { "score", GetCurrentScore() }
        });
    }

    int GetCurrentScore()
    {
        return PlayerPrefs.GetInt("CurrentScore", 0);
    }
}

Optimization

Build Size Optimization

Project Settings → Player → Other Settings:

Scripting Backend: IL2CPP
API Compatibility Level: .NET Standard 2.1
Managed Stripping Level: High
Enable Code Optimization: ✓

Asset Optimization

// Editor/AssetOptimizer.cs
using UnityEditor;
using UnityEngine;

public class AssetOptimizer
{
    [MenuItem("Tools/Optimize Assets")]
    public static void OptimizeAssets()
    {
        // Compress textures
        CompressTextures();

        // Optimize audio files
        OptimizeAudio();

        // Remove unused assets
        RemoveUnusedAssets();
    }

    static void CompressTextures()
    {
        string[] texturePaths = AssetDatabase.FindAssets("t:Texture2D");

        foreach (string guid in texturePaths)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;

            if (importer != null)
            {
                importer.textureCompression = TextureImporterCompression.Compressed;
                importer.SaveAndReimport();
            }
        }
    }

    static void OptimizeAudio()
    {
        string[] audioPaths = AssetDatabase.FindAssets("t:AudioClip");

        foreach (string guid in audioPaths)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            AudioImporter importer = AssetImporter.GetAtPath(path) as AudioImporter;

            if (importer != null)
            {
                AudioImporterSampleSettings settings = importer.defaultSampleSettings;
                settings.compressionFormat = AudioCompressionFormat.Vorbis;
                settings.quality = 0.7f;
                importer.defaultSampleSettings = settings;
                importer.SaveAndReimport();
            }
        }
    }

    static void RemoveUnusedAssets()
    {
        AssetDatabase.Refresh();
        EditorUtility.UnloadUnusedAssetsImmediate();
    }
}

Graphics Optimization

// GraphicsManager.cs
using UnityEngine;
using UnityEngine.Rendering;

public class GraphicsManager : MonoBehaviour
{
    void Start()
    {
        // Set quality based on platform
        if (SystemInfo.graphicsMemorySize < 2000)
        {
            QualitySettings.SetQualityLevel(0); // Low
        }
        else if (SystemInfo.graphicsMemorySize < 4000)
        {
            QualitySettings.SetQualityLevel(1); // Medium
        }
        else
        {
            QualitySettings.SetQualityLevel(2); // High
        }

        // Optimize rendering
        QualitySettings.vSyncCount = 0;
        Application.targetFrameRate = 60;

        // Configure shadows
        QualitySettings.shadows = ShadowQuality.All;
        QualitySettings.shadowDistance = 50f;
    }
}

Deployment

Automated Build Pipeline

// Editor/BuildPipeline.cs
using UnityEditor;
using UnityEditor.Build.Reporting;
using System.IO;

public class AutomatedBuild
{
    [MenuItem("Build/Build All Platforms")]
    public static void BuildAllPlatforms()
    {
        string version = PlayerSettings.bundleVersion;

        BuildWindows(version);
        BuildLinux(version);
        BuildMacOS(version);

        UploadToSimplyStream(version);
    }

    static void BuildWindows(string version)
    {
        string path = $"Builds/{version}/Windows/MyGame.exe";
        Build(path, BuildTarget.StandaloneWindows64);
    }

    static void BuildLinux(string version)
    {
        string path = $"Builds/{version}/Linux/MyGame.x86_64";
        Build(path, BuildTarget.StandaloneLinux64);
    }

    static void BuildMacOS(string version)
    {
        string path = $"Builds/{version}/macOS/MyGame.app";
        Build(path, BuildTarget.StandaloneOSX);
    }

    static void Build(string path, BuildTarget target)
    {
        Directory.CreateDirectory(Path.GetDirectoryName(path));

        BuildPlayerOptions options = new BuildPlayerOptions
        {
            scenes = GetScenePaths(),
            locationPathName = path,
            target = target,
            options = BuildOptions.None
        };

        BuildReport report = BuildPipeline.BuildPlayer(options);

        if (report.summary.result != BuildResult.Succeeded)
        {
            throw new System.Exception($"Build failed for {target}");
        }

        Debug.Log($"Build succeeded: {target} ({report.summary.totalSize} bytes)");
    }

    static void UploadToSimplyStream(string version)
    {
        // Upload via CLI or API
        string command = $"simplystream upload --build-path Builds/{version} --version {version}";
        System.Diagnostics.Process.Start("cmd.exe", $"/c {command}");
    }

    static string[] GetScenePaths()
    {
        var scenes = new System.Collections.Generic.List<string>();

        foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
        {
            if (scene.enabled)
            {
                scenes.Add(scene.path);
            }
        }

        return scenes.ToArray();
    }
}

Command Line Build

#!/bin/bash

# Configuration
UNITY_PATH="/Applications/Unity/Hub/Editor/2021.3.1f1/Unity.app/Contents/MacOS/Unity"
PROJECT_PATH="/path/to/project"
BUILD_PATH="/path/to/builds"
VERSION="1.0.0"

# Build Windows
"${UNITY_PATH}" \
  -quit \
  -batchmode \
  -projectPath "${PROJECT_PATH}" \
  -executeMethod BuildScript.BuildWindows \
  -logFile "${BUILD_PATH}/build.log"

# Build Linux
"${UNITY_PATH}" \
  -quit \
  -batchmode \
  -projectPath "${PROJECT_PATH}" \
  -executeMethod BuildScript.BuildLinux \
  -logFile "${BUILD_PATH}/build.log"

# Upload to SimplyStream
simplystream upload \
  --build-path "${BUILD_PATH}/${VERSION}" \
  --name "MyGame v${VERSION}" \
  --platform windows \
  --engine unity

echo "Build complete!"

Common Issues

Missing Dependencies

Solution: Include all required DLLs

// Editor/PostBuildProcessor.cs
using UnityEditor;
using UnityEditor.Callbacks;
using System.IO;

public class PostBuildProcessor
{
    [PostProcessBuild(1)]
    public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
    {
        string dataPath = Path.GetDirectoryName(pathToBuiltProject);

        // Copy additional DLLs
        CopyDLL(dataPath, "MyDependency.dll");
        CopyDLL(dataPath, "AnotherDependency.dll");
    }

    static void CopyDLL(string targetPath, string dllName)
    {
        string sourcePath = Path.Combine(Application.dataPath, "Plugins", dllName);
        string destPath = Path.Combine(targetPath, dllName);

        if (File.Exists(sourcePath))
        {
            File.Copy(sourcePath, destPath, true);
        }
    }
}

Large Build Size

Solutions:

  • Enable code stripping
  • Use asset bundles
  • Compress textures
  • Remove unused packages
// Packages/manifest.json - Remove unused packages
{
	"dependencies": {
		"com.unity.collab-proxy": "1.15.12"
		// Comment out or remove unused packages
		// "com.unity.ide.rider": "3.0.7",
		// "com.unity.ide.visualstudio": "2.0.14"
	}
}

Best Practices

1. Use Addressables

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class AssetLoader : MonoBehaviour
{
    async void LoadAsset()
    {
        AsyncOperationHandle<GameObject> handle =
            Addressables.LoadAssetAsync<GameObject>("MyAsset");

        await handle.Task;

        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            Instantiate(handle.Result);
        }
    }
}

2. Implement Proper Error Handling

public class ErrorHandler : MonoBehaviour
{
    void OnEnable()
    {
        Application.logMessageReceived += HandleLog;
    }

    void OnDisable()
    {
        Application.logMessageReceived -= HandleLog;
    }

    void HandleLog(string logString, string stackTrace, LogType type)
    {
        if (type == LogType.Exception || type == LogType.Error)
        {
            // Send to analytics/error tracking
            SendErrorReport(logString, stackTrace);
        }
    }

    void SendErrorReport(string error, string stackTrace)
    {
        // Send to your error tracking service
    }
}

3. Version Your Builds

// BuildVersion.cs
[CreateAssetMenu(fileName = "BuildVersion", menuName = "SimplyStream/Build Version")]
public class BuildVersion : ScriptableObject
{
    public string version = "1.0.0";
    public string buildNumber;
    public System.DateTime buildDate;
}

Next Steps

WebGL Applications

Deploy WebGL builds effectively

WebGL Applications

Deploy WebGL builds effectively with SimplyStream for browser-based streaming.

Overview

WebGL applications run directly in the browser without plugins, making them ideal for:

  • Instant access experiences
  • Cross-platform compatibility
  • No download required
  • Easy sharing and embedding

Prerequisites

  • Modern web browser (Chrome, Firefox, Safari, Edge)
  • Web server for hosting
  • Understanding of JavaScript/WebAssembly basics
  • HTTPS certificate (required for many features)

Unity WebGL

Build Settings

File → Build Settings → WebGL:

Compression Format: Brotli (best compression)
Exception Support: Explicitly Thrown Exceptions Only
Enable Exceptions: Full Without Stacktrace
Memory Size: Auto

Player Settings → WebGL:

WebGL Template: SimplyStream (custom) or Default
Run in Background: ✓

Custom WebGL Template

Create a custom template in Assets/WebGLTemplates/SimplyStream/:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
		<title>%UNITY_WEB_NAME%</title>
		<style>
			body {
				margin: 0;
				padding: 0;
				overflow: hidden;
			}
			#unity-container {
				width: 100vw;
				height: 100vh;
			}
			#unity-canvas {
				width: 100%;
				height: 100%;
				background: #000;
			}
			#loading-overlay {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				background: #000;
				display: flex;
				align-items: center;
				justify-content: center;
			}
			.progress-bar {
				width: 50%;
				height: 20px;
				background: #333;
				border-radius: 10px;
				overflow: hidden;
			}
			.progress-fill {
				height: 100%;
				background: #4caf50;
				transition: width 0.3s;
			}
		</style>
	</head>
	<body>
		<div id="unity-container">
			<canvas id="unity-canvas"></canvas>
			<div id="loading-overlay">
				<div class="progress-bar">
					<div class="progress-fill" id="progress"></div>
				</div>
			</div>
		</div>

		<script src="%UNITY_WEBGL_LOADER_URL%"></script>
		<script>
			const canvas = document.querySelector('#unity-canvas');
			const loadingOverlay = document.querySelector('#loading-overlay');
			const progressFill = document.querySelector('#progress-fill');

			const buildUrl = 'Build';
			const config = {
				dataUrl: buildUrl + '/%UNITY_WEBGL_BUILD_URL%.data.br',
				frameworkUrl: buildUrl + '/%UNITY_WEBGL_BUILD_URL%.framework.js.br',
				codeUrl: buildUrl + '/%UNITY_WEBGL_BUILD_URL%.wasm.br',
				streamingAssetsUrl: 'StreamingAssets',
				companyName: '%UNITY_COMPANY_NAME%',
				productName: '%UNITY_PRODUCT_NAME%',
				productVersion: '%UNITY_VERSION%'
			};

			createUnityInstance(canvas, config, (progress) => {
				progressFill.style.width = 100 * progress + '%';
			})
				.then((unityInstance) => {
					loadingOverlay.style.display = 'none';
					window.unityInstance = unityInstance;

					// Initialize SimplyStream
					if (window.SimplyStream) {
						window.SimplyStream.init({
							apiKey: '%SIMPLYSTREAM_API_KEY%',
							projectId: '%SIMPLYSTREAM_PROJECT_ID%'
						});
					}
				})
				.catch((message) => {
					alert(message);
				});
		</script>
	</body>
</html>

Build Script

// Editor/WebGLBuilder.cs
using UnityEditor;

public class WebGLBuilder
{
    [MenuItem("Build/Build WebGL")]
    public static void Build()
    {
        BuildPlayerOptions options = new BuildPlayerOptions
        {
            scenes = GetScenes(),
            locationPathName = "Builds/WebGL",
            target = BuildTarget.WebGL,
            options = BuildOptions.None
        };

        BuildPipeline.BuildPlayer(options);
    }

    static string[] GetScenes()
    {
        var scenes = new List<string>();
        foreach (var scene in EditorBuildSettings.scenes)
        {
            if (scene.enabled) scenes.Add(scene.path);
        }
        return scenes.ToArray();
    }
}

Unreal Engine WebGL

Using Emscripten

# Build with HTML5 platform
cd /path/to/UnrealEngine

# Configure for HTML5
./GenerateProjectFiles.sh

# Build
make UnrealPakHTML5Shipping

Project Settings

Project Settings → HTML5:

Target Platform: HTML5
Enable Multithreading: ✓
Total Memory: 1024MB
Fixed Resolution: ✗ (responsive)

Optimization

Compression

Enable Brotli compression:

// server.js (Node.js example)
const express = require('express');
const compression = require('compression');
const app = express();

// Enable Brotli compression
app.use(
	compression({
		brotli: {
			enabled: true,
			zlib: {
				level: 11
			}
		}
	})
);

// Serve with correct headers
app.use(
	'/Build',
	express.static('Build', {
		setHeaders: (res, path) => {
			if (path.endsWith('.br')) {
				res.set('Content-Encoding', 'br');
			}
			if (path.endsWith('.wasm')) {
				res.set('Content-Type', 'application/wasm');
			}
		}
	})
);

app.listen(3000);

Code Splitting

// Lazy load non-critical features
async function loadFeature(featureName) {
	const module = await import(`./features/${featureName}.js`);
	return module.default;
}

// Load when needed
document.getElementById('feature-button').addEventListener('click', async () => {
	const feature = await loadFeature('advancedGraphics');
	feature.initialize();
});

Asset Optimization

// Progressive loading
class AssetLoader {
	constructor() {
		this.loaded = new Set();
		this.loading = new Map();
	}

	async loadAsset(url) {
		if (this.loaded.has(url)) {
			return this.loaded.get(url);
		}

		if (this.loading.has(url)) {
			return this.loading.get(url);
		}

		const promise = fetch(url)
			.then((response) => response.blob())
			.then((blob) => {
				this.loaded.add(url);
				return blob;
			});

		this.loading.set(url, promise);
		return promise;
	}
}

Deployment

Static Hosting

nginx.conf:

server {
    listen 443 ssl http2;
    server_name yourapp.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    root /var/www/webgl;

    # Enable compression
    gzip on;
    gzip_types application/javascript application/wasm text/css;
    gzip_min_length 1000;

    # Brotli compression (if module available)
    brotli on;
    brotli_types application/javascript application/wasm text/css;

    # CORS headers for WebGL
    add_header Cross-Origin-Opener-Policy same-origin;
    add_header Cross-Origin-Embedder-Policy require-corp;

    # Cache settings
    location /Build/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location ~* \.(wasm|data)$ {
        add_header Content-Type application/wasm;
        expires 1y;
    }

    # Fallback to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }
}

CDN Deployment

// Configure CDN URLs
const config = {
	dataUrl: 'https://cdn.yourapp.com/builds/v1.0.0/game.data.br',
	frameworkUrl: 'https://cdn.yourapp.com/builds/v1.0.0/game.framework.js.br',
	codeUrl: 'https://cdn.yourapp.com/builds/v1.0.0/game.wasm.br',
	streamingAssetsUrl: 'https://cdn.yourapp.com/assets'
};

SimplyStream Integration

JavaScript SDK

<script src="https://cdn.simplystream.io/sdk/v1/simplystream.js"></script>
<script>
	const simplystream = new SimplyStream({
		apiKey: 'your_api_key',
		projectId: 'your_project_id'
	});

	// Track session start
	simplystream.sessions
		.create({
			buildId: 'build_123',
			metadata: {
				browser: navigator.userAgent,
				screenSize: `${window.innerWidth}x${window.innerHeight}`
			}
		})
		.then((session) => {
			console.log('Session started:', session.id);

			// Track events
			simplystream.analytics.track('GameStarted', {
				sessionId: session.id,
				timestamp: new Date()
			});
		});

	// Communicate with Unity
	window.addEventListener('message', (event) => {
		if (event.data.type === 'UnityEvent') {
			simplystream.analytics.track(event.data.eventName, event.data.data);
		}
	});
</script>

Unity to JavaScript Communication

// WebGLBridge.cs
using UnityEngine;
using System.Runtime.InteropServices;

public class WebGLBridge : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void SendToJavaScript(string eventName, string data);

    public static void TrackEvent(string eventName, string data)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        SendToJavaScript(eventName, data);
        #endif
    }
}
// In your HTML template
mergeInto(LibraryManager.library, {
	SendToJavaScript: function (eventNamePtr, dataPtr) {
		const eventName = UTF8ToString(eventNamePtr);
		const data = UTF8ToString(dataPtr);

		// Send to SimplyStream
		window.simplystream.analytics.track(eventName, JSON.parse(data));
	}
});

Performance Optimization

Memory Management

// MemoryManager.cs
using UnityEngine;

public class MemoryManager : MonoBehaviour
{
    void Start()
    {
        // Optimize for WebGL
        #if UNITY_WEBGL
        QualitySettings.SetQualityLevel(1); // Medium quality
        Application.targetFrameRate = 60;

        // Reduce memory footprint
        Resources.UnloadUnusedAssets();
        System.GC.Collect();
        #endif
    }

    void OnLevelWasLoaded(int level)
    {
        // Clean up between levels
        Resources.UnloadUnusedAssets();
        System.GC.Collect();
    }
}

Loading Strategy

// Progressive loading with priorities
class ProgressiveLoader {
	constructor() {
		this.queue = {
			critical: [],
			high: [],
			normal: [],
			low: []
		};
	}

	addToQueue(asset, priority = 'normal') {
		this.queue[priority].push(asset);
	}

	async loadAll() {
		// Load critical assets first
		await this.loadPriority('critical');
		await this.loadPriority('high');

		// Load others in background
		this.loadPriority('normal');
		this.loadPriority('low');
	}

	async loadPriority(priority) {
		const assets = this.queue[priority];
		await Promise.all(assets.map((asset) => this.loadAsset(asset)));
	}

	async loadAsset(url) {
		const response = await fetch(url);
		return response.blob();
	}
}

Common Issues

Large Initial Download

Solutions:

  • Enable Brotli compression
  • Use code splitting
  • Implement progressive loading
  • Reduce texture quality for WebGL

Memory Limitations

Solutions:

  • Reduce total memory in build settings
  • Implement aggressive garbage collection
  • Use object pooling
  • Unload unused assets frequently
public class MemoryOptimizer : MonoBehaviour
{
    void Update()
    {
        if (GetAvailableMemory() < 50 * 1024 * 1024) // 50MB
        {
            Resources.UnloadUnusedAssets();
            System.GC.Collect();
        }
    }

    long GetAvailableMemory()
    {
        #if UNITY_WEBGL
        return (long)SystemInfo.systemMemorySize * 1024 * 1024;
        #else
        return 0;
        #endif
    }
}

CORS Issues

Solution: Configure proper CORS headers

// Express.js middleware
app.use((req, res, next) => {
	res.header('Access-Control-Allow-Origin', '*');
	res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
	res.header('Access-Control-Allow-Headers', 'Content-Type');
	next();
});

Best Practices

1. Use Web Workers

// worker.js
self.addEventListener('message', (e) => {
	const result = expensiveCalculation(e.data);
	self.postMessage(result);
});

// main.js
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.addEventListener('message', (e) => {
	console.log('Result:', e.data);
});

2. Implement Service Worker

// sw.js
const CACHE_NAME = 'simplystream-v1';
const urlsToCache = [
	'/',
	'/Build/game.data.br',
	'/Build/game.framework.js.br',
	'/Build/game.wasm.br'
];

self.addEventListener('install', (event) => {
	event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(urlsToCache)));
});

self.addEventListener('fetch', (event) => {
	event.respondWith(
		caches.match(event.request).then((response) => response || fetch(event.request))
	);
});

3. Monitor Performance

// Performance monitoring
const observer = new PerformanceObserver((list) => {
	for (const entry of list.getEntries()) {
		console.log(entry);
		// Send to analytics
		simplystream.analytics.track('PerformanceMetric', {
			name: entry.name,
			duration: entry.duration,
			type: entry.entryType
		});
	}
});

observer.observe({ entryTypes: ['measure', 'navigation'] });

Next Steps

Custom Builds

Working with custom engines and frameworks

Custom Builds

Working with custom engines, frameworks, and build systems on SimplyStream.

Overview

SimplyStream supports custom builds from various engines and frameworks beyond Unity and Unreal, including:

  • Godot Engine
  • Custom C++/C# applications
  • Electron applications
  • Native desktop applications
  • Custom game engines

General Requirements

Build Structure

SimplyStream expects the following structure:

MyApplication/
├── executable (MyApp.exe, MyApp, MyApp.app)
├── data/ (game data, assets)
├── lib/ or libs/ (shared libraries)
└── config/ (configuration files)

Manifest File

Create a simplystream.json in your build root:

{
	"name": "MyApplication",
	"version": "1.0.0",
	"engine": "custom",
	"engineVersion": "1.0",
	"platform": "windows",
	"executable": "MyApp.exe",
	"workingDirectory": "./",
	"arguments": [],
	"environment": {
		"DISPLAY_MODE": "fullscreen",
		"RENDERER": "vulkan"
	},
	"resources": {
		"minMemory": "2GB",
		"recommendedMemory": "4GB",
		"minCPU": "2",
		"recommendedCPU": "4"
	},
	"features": {
		"multiplayer": true,
		"cloudSave": true,
		"voiceChat": false
	}
}

Godot Engine

Export Settings

Project → Export:

Platform: Windows Desktop / Linux/X11
Runnable: ✓
Export With Debug: ✗ (for production)

Export Script

# export.gd
extends SceneTree

func _init():
    var export_path = "builds/windows/MyGame.exe"
    var preset = "Windows Desktop"

    var err = EditorExportPlatform.export_project(
        preset,
        export_path,
        EditorExportPlatform.DEBUG_FLAG_DUMB_CLIENT
    )

    if err == OK:
        print("Export successful")
    else:
        print("Export failed: ", err)

    quit()

Integration

# SimplyStreamSDK.gd
extends Node

signal session_started(session_id)
signal session_ended(session_id)

var api_key: String
var project_id: String
var session_id: String

func _ready():
    api_key = OS.get_environment("SIMPLYSTREAM_API_KEY")
    project_id = OS.get_environment("SIMPLYSTREAM_PROJECT_ID")

    start_session()

func start_session():
    var http = HTTPRequest.new()
    add_child(http)

    http.request_completed.connect(_on_session_created)

    var headers = ["Content-Type: application/json", "Authorization: Bearer " + api_key]
    var body = JSON.stringify({
        "projectId": project_id,
        "buildId": OS.get_environment("BUILD_ID")
    })

    http.request("https://api.simplystream.io/sessions", headers, HTTPClient.METHOD_POST, body)

func _on_session_created(result, response_code, headers, body):
    if response_code == 200:
        var json = JSON.parse_string(body.get_string_from_utf8())
        session_id = json.id
        emit_signal("session_started", session_id)

Electron Applications

Build Configuration

// forge.config.js
module.exports = {
	packagerConfig: {
		name: 'MyApp',
		executableName: 'myapp',
		icon: './assets/icon',
		asar: true
	},
	makers: [
		{
			name: '@electron-forge/maker-squirrel',
			config: {
				name: 'MyApp'
			}
		},
		{
			name: '@electron-forge/maker-zip',
			platforms: ['darwin']
		},
		{
			name: '@electron-forge/maker-deb',
			config: {}
		}
	]
};

SimplyStream Integration

// main.js
const { app, BrowserWindow } = require('electron');
const { SimplyStreamClient } = require('@simplystream/sdk');

let mainWindow;
let simplystream;

app.on('ready', async () => {
	// Initialize SimplyStream
	simplystream = new SimplyStreamClient({
		apiKey: process.env.SIMPLYSTREAM_API_KEY,
		projectId: process.env.SIMPLYSTREAM_PROJECT_ID
	});

	// Create session
	const session = await simplystream.sessions.create({
		buildId: process.env.BUILD_ID,
		metadata: {
			platform: process.platform,
			arch: process.arch,
			version: app.getVersion()
		}
	});

	console.log('Session started:', session.id);

	// Create window
	mainWindow = new BrowserWindow({
		width: 1280,
		height: 720,
		webPreferences: {
			nodeIntegration: true,
			contextIsolation: false
		}
	});

	mainWindow.loadFile('index.html');

	// Track events
	mainWindow.on('ready-to-show', () => {
		simplystream.analytics.track('WindowReady', {
			sessionId: session.id
		});
	});

	mainWindow.on('closed', async () => {
		await simplystream.sessions.end(session.id);
		mainWindow = null;
	});
});

Native C++ Application

CMake Build

# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyApp)

set(CMAKE_CXX_STANDARD 17)

# SimplyStream SDK
find_package(SimplyStream REQUIRED)

# Source files
file(GLOB_RECURSE SOURCES "src/*.cpp")

# Create executable
add_executable(${PROJECT_NAME} ${SOURCES})

# Link SimplyStream
target_link_libraries(${PROJECT_NAME} SimplyStream::SDK)

# Install rules
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
install(DIRECTORY assets DESTINATION share/${PROJECT_NAME})

Integration

// main.cpp
#include <simplystream/client.h>
#include <iostream>

int main(int argc, char* argv[]) {
    // Initialize SimplyStream
    simplystream::Client client(
        std::getenv("SIMPLYSTREAM_API_KEY"),
        std::getenv("SIMPLYSTREAM_PROJECT_ID")
    );

    // Create session
    auto session = client.sessions().create({
        .buildId = std::getenv("BUILD_ID"),
        .metadata = {
            {"platform", "linux"},
            {"version", "1.0.0"}
        }
    });

    std::cout << "Session started: " << session.id << std::endl;

    // Run application
    run_application();

    // End session
    client.sessions().end(session.id);

    return 0;
}

Custom Build Pipeline

Build Script

# build.py
import os
import subprocess
import json
import requests

class CustomBuilder:
    def __init__(self, config_path='build.json'):
        with open(config_path) as f:
            self.config = json.load(f)

    def build(self):
        """Build the application"""
        print("Building application...")

        # Run custom build command
        result = subprocess.run(
            self.config['buildCommand'],
            shell=True,
            check=True
        )

        if result.returncode == 0:
            print("Build successful")
            return True
        return False

    def package(self):
        """Package the build"""
        print("Packaging build...")

        # Create simplystream.json
        manifest = {
            "name": self.config['name'],
            "version": self.config['version'],
            "engine": "custom",
            "platform": self.config['platform'],
            "executable": self.config['executable']
        }

        with open(f"{self.config['outputDir']}/simplystream.json", 'w') as f:
            json.dump(manifest, f, indent=2)

        # Create archive
        subprocess.run([
            'zip', '-r',
            f"{self.config['name']}-{self.config['version']}.zip",
            self.config['outputDir']
        ])

    def upload(self):
        """Upload to SimplyStream"""
        print("Uploading to SimplyStream...")

        api_key = os.getenv('SIMPLYSTREAM_API_KEY')
        project_id = os.getenv('SIMPLYSTREAM_PROJECT_ID')

        # Create build
        response = requests.post(
            'https://api.simplystream.io/builds',
            headers={'Authorization': f'Bearer {api_key}'},
            json={
                'projectId': project_id,
                'name': f"{self.config['name']} v{self.config['version']}",
                'platform': self.config['platform'],
                'engine': 'custom'
            }
        )

        build_id = response.json()['id']

        # Upload files
        with open(f"{self.config['name']}-{self.config['version']}.zip", 'rb') as f:
            requests.post(
                f'https://api.simplystream.io/builds/{build_id}/upload',
                headers={'Authorization': f'Bearer {api_key}'},
                files={'file': f}
            )

        print(f"Upload complete. Build ID: {build_id}")

if __name__ == '__main__':
    builder = CustomBuilder()
    if builder.build():
        builder.package()
        builder.upload()

Configuration

{
	"name": "MyCustomApp",
	"version": "1.0.0",
	"platform": "linux",
	"executable": "myapp",
	"buildCommand": "make release",
	"outputDir": "build/release"
}

Deployment

Upload via CLI

# Package your build
tar -czf myapp-1.0.0.tar.gz build/

# Upload to SimplyStream
simplystream upload \
  --build-path myapp-1.0.0.tar.gz \
  --platform linux \
  --engine custom \
  --name "MyApp v1.0.0" \
  --executable myapp

Upload via API

# Create build
BUILD_ID=$(curl -X POST https://api.simplystream.io/builds \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "'$PROJECT_ID'",
    "name": "MyApp v1.0.0",
    "platform": "linux",
    "engine": "custom"
  }' | jq -r '.id')

# Upload file
curl -X POST https://api.simplystream.io/builds/$BUILD_ID/upload \
  -H "Authorization: Bearer $API_KEY" \
  -F "[email protected]"

# Mark as ready
curl -X POST https://api.simplystream.io/builds/$BUILD_ID/ready \
  -H "Authorization: Bearer $API_KEY"

Best Practices

1. Include All Dependencies

# Linux - check dependencies
ldd myapp

# Copy all .so files to lib/
mkdir -p lib
cp /usr/lib/libfoo.so lib/
cp /usr/lib/libbar.so lib/

# Set rpath
patchelf --set-rpath '$ORIGIN/lib' myapp

2. Static Linking When Possible

# CMakeLists.txt
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")

3. Configuration Files

// config.json
{
	"graphics": {
		"resolution": "1920x1080",
		"fullscreen": true,
		"vsync": true
	},
	"audio": {
		"masterVolume": 0.8,
		"musicVolume": 0.6
	},
	"simplystream": {
		"enabled": true,
		"analytics": true
	}
}

4. Error Logging

// logger.h
class Logger {
public:
    static void log(const std::string& message) {
        std::ofstream file("app.log", std::ios::app);
        file << getCurrentTime() << " - " << message << std::endl;

        // Send to SimplyStream
        if (simplystream_enabled) {
            sendToSimplyStream(message);
        }
    }
};

Troubleshooting

Missing Dependencies

Check and bundle all required libraries:

# Linux
ldd myapp | grep "not found"

# macOS
otool -L myapp

# Windows
dumpbin /dependents myapp.exe

Platform-Specific Issues

# build_helper.py
import platform

def get_platform_libs():
    system = platform.system()

    if system == 'Windows':
        return ['user32.dll', 'kernel32.dll']
    elif system == 'Linux':
        return ['libGL.so', 'libX11.so']
    elif system == 'Darwin':
        return ['CoreFoundation.framework']

    return []

Next Steps