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)
- Download the SimplyStream plugin from the marketplace or GitHub
- Extract to your project's
Pluginsfolder - Enable the plugin in your project settings
- 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
- Package your project in Shipping configuration
- Test the packaged build locally
- Verify all features work correctly
- 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 FPSstat unit- Show frame timesstat memory- Show memory usagestat 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
- Explore Unity Builds for Unity projects
- Learn about WebGL Applications deployment
- Check Production Deployment Checklist
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:
- Link your GitHub account to your Epic Games account under Epic → Account → Connections and accept the Unreal Engine EULA.
- That membership grants access to
SimplyStreamSDK/UnrealEnginetoo — no SimplyStream account or token is needed for the engine source. If you can opengithub.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:
- Open Package Manager (Window → Package Manager)
- Click "+" → "Add package from git URL"
- Enter:
https://github.com/simplystream/unity-sdk.git
Or manually:
- Download the SimplyStream Unity package
- 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
- Learn about WebGL Applications for browser-based streaming
- Explore Unreal Engine Builds for Unreal projects
- Review Production Deployment Checklist
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
- Explore Unity Builds for native deployments
- Learn about Custom Builds for other engines
- Review Production Deployment Checklist
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
- Learn about Unreal Engine Builds
- Explore Unity Builds for Unity-specific optimizations
- Review Production Deployment Checklist
- Set up CI/CD Integration for automated builds