Using TransformHandle with Burst
The TransformHandle API is compatible with the Burst compiler in code that runs on the main thread.
Read time 5 minutesLast updated 12 days ago
The API is compatible with the Burst compiler in code that runs on the main thread. This lets you avoid copying data to and from native collections, which is necessary when using managed components with Burst-compiled code.
TransformHandleTransformThis page demonstrates how simplifies Burst workflows compared to the API.
TransformHandleTransformBenefits of using TransformHandle with Burst
When working with Burst-compiled code, provides the following advantages:
TransformHandle-
Direct access: Read and write transform positions, rotations, and other properties directly from Burst-compiled methods.
-
Native array compatibility: Storeinstances directly in
TransformHandleand other native collections. Avoid the overhead of copying data to and fromNativeArraybuffers, which is necessary when using theNativeArrayAPI. For examples, refer to Example implementation using TransformHandle API compared with Transform API.Transform -
Simpler code structure: Perform all operations in a single main-thread Burst-compiled pass instead of splitting logic into separate Burst-compatible and non-Burst phases.
TransformHandle and the job system
The struct is Burst-compatible, but it isn't safe to access from worker threads. Reading or writing a property, or calling one of its methods, inside a job that implements , , or throws a safety exception. The examples on this page use static methods marked with the attribute that run on the main thread.
TransformHandleTransformHandleIJobIJobParallelForIJobParallelForTransform[BurstCompile]To parallelize reads and writes of transform values across worker threads, use the struct with a job that implements the interface. You can construct a instance from a collection before you schedule the job:
TransformAccessArrayIJobParallelForTransformTransformAccessArrayNativeArray<TransformHandle>NativeArray<TransformHandle> handles = ...;TransformAccessArray accessArray = new TransformAccessArray(handles);
This pattern passes a struct as a parameter to the job's method. The struct exposes transform values only. It doesn't expose parent or child handles, and it doesn't make methods, such as , callable from a job.
TransformAccessExecuteTransformHandleSetParentExample implementation using TransformHandle API compared with Transform API
The following examples demonstrate the difference between using and API when working with Burst compilation. Both examples move 1000 game objects toward randomly selected targets each frame.
TransformHandleTransformImplementation using TransformHandle API with Burst
The API eliminates the need for intermediate buffers and separate copy phases. All position reads and writes occur directly in Burst-compiled code, resulting in simpler, more maintainable code. You can perform all necessary operations in a single Burst-compiled method.
TransformHandleusing Unity.Burst;using Unity.Collections;using Unity.Mathematics;using UnityEngine;public class TransformHandleMoveExample : MonoBehaviour{ public int SpawnCount = 1000; // TransformHandle can be stored in NativeArray and accessed from Burst-compiled code NativeArray<TransformHandle> SpawnedTransforms; public Unity.Mathematics.Random Random; void Start() { Random = Unity.Mathematics.Random.CreateFromIndex(0); // Allocate native array of handles (no separate float3 buffer needed) SpawnedTransforms = new NativeArray<TransformHandle>(SpawnCount, Allocator.Persistent); // Create transforms and assign random start positions for (int i = 0; i < SpawnedTransforms.Length; i++) { TransformHandle transformHandle = new GameObject($"Transform{i}").transformHandle; SpawnedTransforms[i] = transformHandle; transformHandle.position = Random.NextFloat3(new float3(-100f), new float3(100f)); } } void OnDestroy() { if (SpawnedTransforms.IsCreated) { SpawnedTransforms.Dispose(); } } void Update() { // Single Burst-compiled call: reads positions, computes movement, and writes new positions. No manual copying needed. TransformHandleMoveExampleUtils.ComputeAndApplyRandomMovements(ref SpawnedTransforms, ref Random, 1f * Time.deltaTime); }}[BurstCompile]public static class TransformHandleMoveExampleUtils{ [BurstCompile] public static void ComputeAndApplyRandomMovements(ref NativeArray<TransformHandle> transforms, ref Unity.Mathematics.Random random, float movementMagnitude) { for (int i = 0; i < transforms.Length; i++) { // TransformHandle positions can be read and written directly in Burst-compiled code TransformHandle transformHandle = transforms[i]; float3 movement = math.normalizesafe(transforms[random.NextInt(0, transforms.Length)] .position - transformHandle.position) * movementMagnitude; transformHandle.position = transformHandle.position + (Vector3)movement; } }}
Implementation using Transform API with Burst
With the API, you must split the operation into three phases:
Transform-
Copy transform positions fromcomponents into a
Transform(non-Burst code).NativeArray -
Perform calculations on the copied data (Burst-compiled code).
-
Copy the results back to thecomponents (non-Burst code).
Transform
This approach requires maintaining a separate buffer and performing two copy operations every frame, which adds overhead and increases code complexity.
NativeArray<float3>using Unity.Burst;using Unity.Collections;using Unity.Mathematics;using UnityEngine;public class TransformMoveExample : MonoBehaviour{ public int SpawnCount = 1000; // Transform API cannot be accessed from Burst-compiled code public Transform[] SpawnedTransforms; // Separate data buffer required to connect Transform and Burst NativeArray<float3> Positions; public Unity.Mathematics.Random Random; void Start() { Random = Unity.Mathematics.Random.CreateFromIndex(0); // Initialize managed Transform array and native float3 array // (extra overhead compared to TransformHandle) SpawnedTransforms = new Transform[SpawnCount]; Positions = new NativeArray<float3>(SpawnCount, Allocator.Persistent); // Create transforms and assign random start positions for (int i = 0; i < SpawnedTransforms.Length; i++) { SpawnedTransforms[i] = new GameObject($"Transform{i}").transform; SpawnedTransforms[i].position = Random.NextFloat3(new float3(-100f), new float3(100f)); } } void OnDestroy() { if (Positions.IsCreated) { Positions.Dispose(); } } void Update() { // 1) Copy Transform positions into NativeArray (Transform cannot be accessed in Burst). for (int i = 0; i < SpawnedTransforms.Length; i++) { Positions[i] = SpawnedTransforms[i].position; } // 2) Burst-compiled compute on the copied data. TransformMoveExampleUtils.ComputeRandomMovements(ref Positions, ref Random, 1f * Time.deltaTime); // 3) Copy results back to each Transform to update scene state. for (int i = 0; i < SpawnedTransforms.Length; i++) { SpawnedTransforms[i].position = Positions[i]; } }}[BurstCompile]public static class TransformMoveExampleUtils{ [BurstCompile] public static void ComputeRandomMovements(ref NativeArray<float3> positions, ref Unity.Mathematics.Random random, float movementMagnitude) { for (int i = 0; i < positions.Length; i++) { // Calculate movement from copied position data float3 movement = math.normalizesafe(positions[random.NextInt(0, positions.Length)] - positions[i]) * movementMagnitude; // Updated positions will be copied back to Transforms positions[i] += movement; } }}