The Rejourney Unity SDK is now in open beta. The initial release is 0.1.0, packaged for the Unity Package Manager (UPM). It targets Unity 6 LTS (including 6.3 and 6.6), iOS 15.1+, and Android API 24+ running IL2CPP and Mono scripting backends.
// Packages/manifest.json
"co.rejourney.unity": "https://github.com/rejourneyco/rejourney.git?path=/packages/unity#unity-v0.1.0"
// App bootstrap (e.g. in your initial scene controller)
using RejourneySDK;
Rejourney.Init("pk_live_your_public_key");
var result = await Rejourney.StartAsync();Most mobile session replay tools treat games like web applications: they either record nothing because a game renders into a single native surface, or they drop frame rates into single digits while generating thousands of false-positive “rage clicks” when a player attacks or navigates a fast inventory screen.
Building session replay and observability for games required rethinking visual capture, eliminating garbage collection on the Unity main thread, weaving HTTP calls at compilation time, and separating genuine UI frustration from active gameplay. Here is how the recorder works under the hood.
Zero-Allocation Main Thread Architecture
Mobile games live or die by frame stability. In garbage-collected runtimes like Mono and IL2CPP, allocating temporary objects during the game loop triggers stop-the-world GC sweeps that manifest as micro-stutters and dropped frames. An observability SDK cannot be the reason a game stutters.
The Rejourney Unity SDK divides responsibility strictly between managed C# and native platform controllers (Swift on iOS, Kotlin on Android):
- Managed C# (
RejourneySDK.Rejourney&RejourneyBehaviour): Owns viewport composition, redaction geometry, touch and input observation, scene transitions, managed error logging, bytecode instrumentation, and game-loop heartbeats. - Native Controllers (iOS & Android): Own session state machines, cryptographic visitor keys, remote settings fetch, durable 32MB disk spooling, network retries, and async-signal-safe crash recovery.
To guarantee zero GC overhead on the engine thread:
// Uses ThreadStatic writers to avoid allocation per event dispatch
[ThreadStatic] static JsonTextWriter sharedWriter;
[ThreadStatic] static StringBuilder sharedBuffer;
internal void CustomEvent(string name, IDictionary<string, object> properties)
{
if (!Admit(null)) return;
try {
string payload = properties == null ? "null" : SdkJson.Serialize(properties);
var writer = BeginEvent();
writer.WritePropertyName("name"); writer.WriteValue(name);
writer.WritePropertyName("payload"); writer.WriteValue(payload);
Commit(EndEvent(writer, "custom", Timestamp, SessionId));
} catch { Reject(); }
}All telemetry events serialize using thread-static StringBuilder buffers and push into lock-free ConcurrentQueue<string> queues. Native P/Invoke dispatches batch raw JSON strings directly into native memory, never holding locks that could stall the engine update loop.
The SDK even tracks its own CPU footprint: Rejourney.Health.MainThreadMilliseconds continually measures cumulative SDK execution time on the Unity main thread so developers can verify performance impact with hard numbers.
AsyncGPUReadback & GPU Privacy Shaders
Unlike standard mobile apps whose interfaces consist of view trees (UIKit or Android Views), Unity renders the entire game into a single framebuffer surface. Traditional view-tree capture sees nothing except a black box.
Rejourney uses a dual-stage GPU pipeline:
// 1. Capture into GPU RenderTexture ScreenCapture.CaptureScreenshotIntoRenderTexture(renderTexture); // 2. Apply privacy redaction shader on GPU before pixels leave VRAM Graphics.Blit(renderTexture, redactedTexture, redactionMaterial); // 3. Asynchronous readback from VRAM to system memory AsyncGPUReadback.Request(redactedTexture, 0, TextureFormat.RGB24, OnReadbackComplete);
Asynchronous Readback: Reading pixels synchronously from the GPU with Texture2D.ReadPixels() forces a CPU pipeline stall waiting for the GPU to complete all rendering commands. Rejourney uses Unity’s AsyncGPUReadback, which copies the rendered frame into system memory asynchronously across multiple frames without interrupting draw call throughput. If asynchronous readback is unavailable on an older GPU, a synchronous fallback scales down to a maximum 480px dimension to prevent frame drops.
GPU Redaction Shaders: Privacy cannot wait until frames reach disk or cloud servers. Sensitive UI elements (such as uGUI InputField, TextMeshPro TMP_InputField, or UI Toolkit TextField) have their screen-space bounding boxes calculated before readback. A custom replacement shader (RejourneyRedact.shader) burns solid privacy placeholders over those exact coordinates directly in GPU texture memory. Text or credentials never exist in pixel data outside the GPU.
For in-world objects or custom UI elements, developers simply attach the RejourneyMask component or call Rejourney.Mask(gameObject).
Quieting the Rage Tap Problem
In SaaS web applications, repeated rapid tapping on a button indicates frustration: a broken checkout form, a dead link, or an unresponsive server. Product analytics tools rely on rage tap detectors to surface broken experiences.
In gaming, rapid tapping is normal behavior. A player rapidly mashing the attack button during a boss fight or tapping rhythmically in a platformer would generate hundreds of false rage tap alerts, skewing frustration metrics and making issue feeds useless.
Rejourney introduces first-class Gameplay Markers:
// When a round, level, match, or battle begins:
Rejourney.StartGameplay("arena_boss_3", new Dictionary<string, object> {
["difficulty"] = "nightmare",
["player_gear_score"] = 450
});
// When play concludes:
Rejourney.EndGameplay(GameplayOutcome.Completed, new Dictionary<string, object> {
["score"] = 14200,
["revives_used"] = 1
});
// Or using an automatic scope:
using (Rejourney.Gameplay("bonus_stage")) {
// Play loop...
}The Atomic Segment Engine: Gameplay state lives in GameplayTracker. State is swapped atomically so that every touch and interaction recorded by the SDK checks active gameplay status with zero locks and zero allocations.
When gameplay is active:
- Touches are tagged with the active
gameplayIdand excluded from rage tap, dead tap, and UI friction calculations. - Heatmaps separate gameplay taps from menu navigation, ensuring combat action does not pollute UI heatmaps.
- The session replay timeline displays a distinct amber gameplay lane, allowing QA and designers to jump directly between menu screens and combat intervals.
- Gameplay segments outlive session boundaries: if a game background rollover occurs after 60 seconds of inactivity, the active segment seamlessly resumes in the new session.
Frame Pacing & Game-Loop Hang Detection
Games require real device performance monitoring far beyond basic crash reporting. The Unity SDK automatically collects engine health metrics without requiring custom developer code:
- Frame Pacing: Samples frame rates every 5 seconds, calculating p50, p95, and maximum frame times, alongside counting “long frames” (frames taking 50ms or longer, corresponding to dips below 20 FPS).
- Memory & Garbage Collection: Tracks managed heap size, Unity native allocated memory, and the count of GC collections occurring between samples, lining up memory spikes directly with replay footage.
- Scene Load Times: Wrapping scene transitions with
using (Rejourney.BeginSceneLoad("Dungeon"))records the exact monotonic duration of the load screen until completion. - Engine Context: Every session logs Unity engine version, scripting backend (IL2CPP vs Mono), graphics API (Metal, Vulkan, OpenGLES), render pipeline (URP, HDRP, Built-in), and the build GUID.
Detecting Game-Loop Hangs: Traditional Android ANR detectors only track the Android UI main thread. If Unity’s internal rendering loop freezes on a heavy asset load, shader compilation, or infinite script loop, the OS main thread may remain responsive while the game viewport is completely frozen.
Rejourney includes a dedicated game-loop watchdog. If the Unity frame update cycle stalls for longer than HangThresholdSeconds (default 5s, configurable down to 2s), the SDK records an engine hang incident the moment the loop recovers, capturing the stall duration, the screen it froze on, and the preceding frame times.
Mono.Cecil Bytecode Rewriting & Linker Rules
Capturing network requests in Unity is notoriously tricky. Developers make HTTP calls using UnityWebRequest, .NET HttpClient, coroutines, or modern async/await tasks. Wrapping network calls manually with SDK methods is tedious and prone to omission.
Rejourney uses Mono.Cecil in an Editor post-processor (NetworkPostProcessor.cs) to rewrite IL bytecode during player compilation:
// Rewrites calls to UnityWebRequest.SendWebRequest() into: RejourneySDK.NetworkCapture.WrapSendWebRequest(request) // Rewrites HttpClient constructors into: RejourneySDK.NetworkCapture.CreateInstrumentedHttpClient()
Because weaving happens on the compiled managed assemblies:
- No source code changes are required in gameplay scripts.
- Async state machines, lambda closures, and coroutines are woven cleanly.
- The original handlers, TLS configurations, and custom certificates are fully preserved.
- Request bodies and credentials are automatically omitted; URL query values and secrets are stripped before network timing records leave the player.
IL2CPP Linker Stripping: Unity’s IL2CPP linker strips unreferenced code aggressively in release builds. Because Unity packages cannot register standalone link.xml files directly, RejourneyLinkerProcessor hooks into UnityLinker to ensure internal serialization adapters and optional uGUI/Input System bindings remain linked at every stripping level.
Try the Unity SDK Beta
The open beta is available now. To install the package in your Unity project, add the Git URL to your Packages/manifest.json:
"co.rejourney.unity": "https://github.com/rejourneyco/rejourney.git?path=/packages/unity#unity-v0.1.0"
Or download the standalone co.rejourney.unity-0.1.0.tgz tarball from the GitHub Release and install it via **Window → Package Manager → + → Install package from tarball**.
Explore the full documentation in our Unity SDK Documentation, review the runnable sample project in examples/unity, or copy the AI integration prompt directly into Cursor or Claude to instrument your game in minutes.