ChronoStream
ChronoStreamĀ lets you record and replay your game's state, enabling you to implement robust replay functionality for highlights, analysis, debugging, and more. It works by recording the state of your game at regular intervals and saving it to a file. The recorded data can then be played back at any time, allowing you to rewind and replay the game state from any point.
ChronoStream simplifies this process by providing easy-to-use components that attach to your GameObjects and handle state recording automatically.
Dependencies
Newtonsoft.Json (JSON.NET)
ChronoStream requiresĀ Newtonsoft.JsonĀ for serialization and deserialization of replay data.
š Installation (via Unity Package Manager)
-
OpenĀ Packages/manifest.json
-
Add the following entry to theĀ dependenciesĀ section:
"com.unity.nuget.newtonsoft-json": "3.2.1"
ā¹ļø Or install via Unity Package Manager GUI:Go toĀ Window > Package Manager > + > Add package by name...Enter:Ā com.unity.nuget.newtonsoft-jsonĀ and clickĀ Add
Features
š Transform Recording
-
Records GameObject transforms (position, rotation, scale)
-
Configurable recording frequency withĀ skipTransformFrames
-
Smooth interpolation between keyframes during playback
-
Optional hierarchical recording usingĀ recordChildrenĀ andĀ recordChildren_deep
š Animator State Recording
-
Records and replays Animator parameters (bool,Ā float,Ā int)
-
Automatic change detection to minimize storage usage
-
Synchronized playback with transform data
š§ Dynamic Object Management
-
Supports recording of object creation and destruction viaĀ RecordableCreator
-
Includes automatic object pooling for memory efficiency
-
Preserves complete object state during recreation
-
Supports custom spawn data types
ā” Event System
-
Frame-accurate event recording and playback
-
Supports custom events usingĀ ChronoEventEmitter
-
Simple attribute-based event registration viaĀ [ChronoEvent]
Quick Start
1. Setup Recording
-
Add theĀ ChronoStreamManagerĀ component to the root GameObject that is the parent of all GameObjects to be recorded.
-
Attach theĀ ChronoRecordedĀ component to each GameObject you want to record.
-
In theĀ Recorded PropertiesĀ field, add the components you want to capture (currently supportsĀ TransformĀ andĀ Animator).
Note:By default, GameObject active state is always recorded.
2. Record Gameplay
// Start recording
chronoStreamManager.StartRecording();
// Stop and save recording
chronoStreamManager.SaveRecording();
3. Playback Control
// Start replay asynchronously (deserialization can take time)
await chronoStreamManager.StartReplayAsync();
// Control playback
chronoStreamManager.PlaybackSpeed(2.0f); // Play at 2x speed
chronoStreamManager.PauseReplay();
chronoStreamManager.UnPauseReplay();
šÆ Custom Event Recording
MonoBehaviours on GameObjects with aĀ ChronoRecorderĀ component can record and replay custom events like so:
public class PlayerController : MonoBehaviour
{
ChronoRecorder recorder;
void Start()
{
recorder = GetComponent<ChronoRecorder>();
}
// This method is called during replay when the "playerJump" event occurs
[ChronoEvent("playerJump")]
private void OnJump()
{
recorder.EmitEvent("playerJump"); // Records the event
}
}
Use this system for one-off events such as:
-
Particle emissions
-
Material changes
-
Audio triggers
-
Any non-transform/animator state change
ā Ā Warning:Avoid recording overly frequent events, as this may lead to bloated replay files.
š§± Object Spawning System
To properly record GameObject instantiation, use aĀ RecordableCreator. This class keeps track of created objects and their initialization data, enabling accurate re-creation during replay.
Example:
public class EnemySpawner : RecordableCreator<EnemySpawnData>
{
protected override GameObject CreateObject(EnemySpawnData data)
{
var enemyObj = Instantiate(enemyPrefab);
var enemy = enemyObj.GetComponent<Enemy>();
enemy.type = data.type;
enemy.hp = data.hp;
enemy.damage = data.damage;
return enemyObj;
}
protected override void OnDestroy(GameObject obj)
{
Destroy(obj);
}
[Serializable]
public class EnemySpawnData
{
public EnemyType type;
public float hp;
public float damage;
}
}
public class GameManager : MonoBehaviour
{
public EnemySpawner _enemySpawner;
public void SpawnEnemyA()
{
var enemy = _enemySpawner.CreateRecordable(new EnemySpawnData
{
type = EnemyType.EnemyA,
hp = 100f,
damage = 2f
}).GetComponent<Enemy>();
enemy.onDie += () => Destroy(enemy.gameObject);
}
void DestroyEnemy(Enemy enemy)
{
_enemySpawner.DestroyRecordable(enemy.gameObject);
}
}
š”Ā Note:Unity types likeĀ Vector3,Ā Quaternion,Ā ColorĀ are not directly supported by Newtonsoft.Json. Create serializable wrapper types or useĀ custom JSON converters.
āļø Performance Settings
// In ChronoStreamManager Inspector
[SerializeField] int initialFramePoolSize = 10000; // Size of initial recording buffer
[SerializeField] int preloadBatchSize = 1000; // Number of frames to preload
[SerializeField] int maxCacheLimit = 1000; // Maximum number of cached frames
Best Practices
ā Recording
-
Record only the necessary components
-
UseĀ skipTransformFramesĀ for static or slow-moving objects
-
Set frame pool size based on the expected recording duration
-
Use events judiciously and group state changes if possible
ā Playback
-
Tune preload settings for smoother playback
-
Monitor interpolation performance in complex scenes
-
Pool frequently instantiated objects
-
Double-check event timing and order during replays
Troubleshooting
ā Common Issues
-
Jerky Playback
-
Check transform recording frequency
-
Verify interpolation settings
-
Ensure preload batching is sufficient
-
-
High Memory Usage
-
Reduce frame pool size
-
Optimize cache size
-
Use object pooling effectively
-
-
Events Not Triggering
-
Ensure correctĀ [ChronoEvent]Ā annotations
-
Confirm synchronization with recorded frames
-
Check for missingĀ ChronoRecorderĀ components
-
Contributing
-
Follow existing code conventions
-
Add unit tests for new features
-
Document any performance-impacting changes
-
Update the README when introducing major features