Skip to main content

Driving Scenario Data Collection

Driving scenarios use continuous data collection: the system samples all tracked actors at a fixed time interval throughout the entire session, producing a time-series record of everything that happened. You configure the sampling interval and output destination in Data Collection Settings.

How It Works​

The driving scenario data collection pipeline has three layers:

Log Analysis is the coordinator. Add one LogAnalysis component to the scene, and it automatically triggers a collection pass every N seconds (default 0.1 s). It holds data in memory as the scenario runs, spills to a temporary disk cache if that memory fills up, and sends everything to the export destination when the scene ends.

Data Collectors represent individual actors. Place a DataCollector component at the root of each actor GameObject (vehicle, pedestrian, AI agent, and so on) that you want to track. When Log Analysis runs its collection pass, it asks each Data Collector for a snapshot, which the collector assembles from its own hierarchy.

Data Loggers define what is recorded per actor. Add one or more data logger components to an actor's hierarchy. The Data Collector discovers them automatically on startup, so no manual wiring is needed.

Setup​

1
Add Log Analysis to the scene

Image of Log Analysis in a scene hierarchy

Add a LogAnalysis component to a persistent GameObject in your driving scenario scene. There should be exactly one LogAnalysis per scene. Scenes created from the Mountain View Scene Template already contain one on the Analysis GameObject.

note

LogAnalysis depends on the save and data-send services provided by the DataManager prefab, which is loaded only when the simulator starts from the Initialization (bootloader) scene. If those services are missing, or if data collection is disabled for the current build target in Data Collection Settings, LogAnalysis disables itself and nothing is recorded. The LogAnalysis Inspector shows the current collection status.

Optional — Tag Filters: If you only want to record specific actor types (for example, only the player vehicle and not AI cars), enable Tag Filters on the LogAnalysis component and assign the relevant Scriptable Tags. An actor passes the filter when an ActorIdentifier component on the same GameObject as its DataCollector has at least one matching tag; actors without an ActorIdentifier are skipped. When Tag Filters are disabled, every actor with a DataCollector in the scene is recorded.

2
Add a Data Collector to each actor

For each actor you want to track, add a DataCollector component to the root GameObject of that actor's hierarchy. At startup, the collector automatically walks its own subtree and claims every IDataLoggable component it finds, so no manual wiring is needed.

note

Nested collectors: If a child object inside an actor has its own DataCollector, that child is treated as a separate, independent actor. Its IDataLoggable components belong to it, not to the parent's collector. This prevents duplicate entries when an actor contains a sub-actor with its own tracking needs.

tip

Disabling a DataCollector component stops data collection for that actor. Destroying the actor mid-session automatically unregisters its collector so no errors occur.

3
Add data logger components to each actor

A data logger is any component that implements the IDataLoggable interface. The DataCollector discovers them automatically: it scans its own hierarchy at startup and polls every IDataLoggable it owns on each collection pass.

The project ships the following built-in data loggers:

Component
What it records
TransformDataLoggable
World-space position (x, y, z) and rotation (Euler angles).
ActorsSpeedTracker
Forward speed in m/s, MPH, and KPH.
ActorIdentifier
The actor's runtime GUID, name, and assigned Scriptable Tags.
PlayerInputDataLoggable
All inputs of the enabled action maps on the actor's PlayerInput (for example, Move.x and Move.y).
CollisionDataLoggable
Timestamped collision events: the other object's name and GUID, the relative velocity, and the first contact point.
BoundBoxDataLoggable
The actor's bounding box (local center, world center, and extents).

Add whichever of these you need to an actor's hierarchy (they do not have to be on the root GameObject). Most prefabs in the project already have a few loggers attached (for example, ActorsSpeedTracker and ActorIdentifier on the vehicle base prefabs).

Several other systems also implement IDataLoggable and record their own events when they are inside a DataCollector's hierarchy: TrafficSignalController (signal and flashing changes), BatterySystem (battery level and remaining life), BaseEventTrigger (event trigger activations), InteractionModel (interaction system events), TypewriterSequencer (dialogue lines), ProsocialMetricCalculator, and RemoteControl (not recorded on WebGL).

In addition, two values are recorded automatically in every snapshot, without any component:

Field
Description
fps
Frames per second at the moment of collection, computed from the last frame's duration.
game_play_time
Time elapsed since the scene started. Time during which the game is paused (for example, while an Overlay Event survey is shown) is excluded.

Event-based data (interaction events, dialogue lines, event trigger activations) is recorded by attaching a DataCollector to the relevant event trigger or interaction object. See Event Trigger for details.

Creating a custom data logger

This section requires C# programming knowledge.

To record data not covered by the built-in loggers, create two classes:

  1. A DataLog subclass — a plain serializable data container holding the fields you want to capture. Mark it [Serializable] so Unity's JSON serializer can write its fields to the output file.
  2. An IDataLoggable MonoBehaviour — a component that reads the actor's state each collection pass and returns a populated instance of your DataLog subclass. Return null when the component is disabled or has nothing to contribute; the collector silently skips it.

Place both files anywhere in your project. Then add your IDataLoggable component to the actor's hierarchy. The DataCollector discovers it automatically on startup, alongside the built-in loggers.

using System;
using UnityEngine;
using mobisim.Analysis.DataCollections;

// 1. Define the data container.
// Fields on this class become keys in the "data" array of the JSON output.
[Serializable]
public class HealthDataLog : DataLog
{
public float currentHealth;
public float maxHealth;
}

// 2. Implement IDataLoggable on a MonoBehaviour.
// The DataCollector in the actor's hierarchy calls Collect() automatically.
public class HealthDataLoggable : MonoBehaviour, IDataLoggable
{
[SerializeField] private HealthComponent _health;

public DataLog Collect()
{
if (!enabled || _health == null) return null;

return new HealthDataLog
{
currentHealth = _health.Current,
maxHealth = _health.Max
};
}
}

On each collection pass, this produces an entry in the actor's data array:

{
"currentHealth": 85.0,
"maxHealth": 100.0
}

Data Collection​

Participant data is saved as a JSON file at the end of each scenario session. The file contains a time-series record of all tracked actors sampled throughout the run.

Property
Description
sessionInfo
Session metadata.
└SessionData
Participant ID, subject ID, and other session metadata.
list
Array of timed collection snapshots, ordered chronologically.
└unix_timestamp
Wall-clock time at the moment of collection (Unix time, milliseconds).
└game_play_time
In-game time elapsed since the scene started, in seconds. Time-paused periods (for example, Overlay Event surveys) are excluded.
└fps
Frames per second at the moment of collection, computed from the duration of the last frame.
└logs
One entry per tracked actor in the scene.
└name
The name of the actor's root GameObject.
└unixTimestamp
Wall-clock time for this actor snapshot (Unix time, milliseconds).
└data
Array of readings from all data logger components attached to this actor.

Here is an example snippet of the saved data:

Sample Output
{
"sessionInfo": {
"SessionData": { "ParticipantID": "W1234", "subjectID": 3 }
},
"list": [
{
"unix_timestamp": 1748000005000,
"game_play_time": 5.0,
"fps": 60,
"logs": [
{
"name": "PlayerScooter",
"unixTimestamp": 1748000005000,
"data": [
{
"position": { "x": 12.45, "y": 0.0, "z": -8.32 },
"rotation": { "x": 0.0, "y": 87.5, "z": 0.0 }
},
{
"rawSpeed": 5.5,
"SpeedMPH": 12.3,
"SpeedKPH": 19.8
},
{
"ActionMaps": [
{
"ActionMap_name": "Vehicle",
"ActionMap_inputs": [
{ "Input_name": "Move.x", "Input_value": 0.35 },
{ "Input_name": "Move.y", "Input_value": 1.0 }
]
}
]
}
]
},
{
"name": "AICar_01",
"unixTimestamp": 1748000005001,
"data": [
{
"position": { "x": 30.12, "y": 0.0, "z": -8.50 },
"rotation": { "x": 0.0, "y": 90.0, "z": 0.0 }
},
{
"rawSpeed": 8.3,
"SpeedMPH": 18.6,
"SpeedKPH": 29.9
}
]
}
]
}
]
}

The JSON file uses the following naming convention:

[SceneName]_Analysis_[yyyyMMddHHmmss]

On WebGL builds (online studies), the participant and subject IDs are prepended automatically:

mTkID[WorkerID]_subID[SubjectID]_[SceneName]_Analysis_[yyyyMMddHHmmss]

For example, if the WorkerID is A1B2C3D4E5F6G7H8I9J0, the SubjectID is 101, the scene is IntersectionScenario, and the session ends at 3:30:45 PM on August 22, 2025:

mTkIDA1B2C3D4E5F6G7H8I9J0_subID101_IntersectionScenario_Analysis_20250822153045