ECS Development Status for Unity 6.6

Once again, we are excited to share more on our current work on Unity 6.6 and ongoing plans for ECS in Unity. All of the ECS packages are now core packages and we have been developing them in line with Unity releases. This has allowed us to coordinate work between package and engine and move towards our goal of unifying Entities and GameObject workflows to bring the power of entities to all users and to integrate entities with new features moving forward.

EntityId Unification

A continuing focus in Unity 6.6 is the alignment of the EntityId identifier type between GameObjects and Entities. This lays the groundwork for integrating ECS into the rest of the engine and making Entities APIs available for GameObjects.

As part of this work, we’ve completed the EntityId 8-byte migration (up from 4 bytes), providing a larger address space and better alignment with modern architectures. Memory optimizations have also landed, including EntityId virtual memory support for the Editor and improved metadata paging, reducing memory overhead for EntityId storage.

Managed Component Deprecation

We are deprecating Managed Components in favor of UnityObjectRef<T>. Managed Components were a convenient, but performance-limited way to reference Unity Objects from ECS. With UnityObjectRef<T>, you get the same functionality with better performance characteristics that align with ECS principles. This work also allows us to further focus ECS components to help develop authoring of components on GameObjects in the future.

Migration example:

// Old approach (deprecated)
public class MyManagedComponent : IComponentData
{
  public GameObject prefab;
}

// New approach
public struct MyComponent : IComponentData
{
  public UnityObjectRef<GameObject> prefab;
}

The deprecation covers, in broad terms:

  • Class-based IComponentData (managed components). Convert your component to a struct IComponentData, using UnityObjectRef for any references to UnityEngine.Object instances.
  • Managed ISharedComponentData and the *Managed shared-component APIs. Make the shared component an unmanaged struct and use the shared-component APIs without the Managed suffix.
  • The managed-object access APIs EntityManager.AddComponentObject / GetComponentObject / SetComponentObject. Use an unmanaged IComponentData with the unmanaged Add/Get/SetComponentData APIs instead.
  • PostLoadCommandBuffer is also deprecated. Use the new ImportEntity field on SceneSystem.LoadParameters instead.

A new analyzer warning, EA0017, additionally flags struct shared components that still contain managed fields. Define UNITY_DISABLE_MANAGED_SHARED_COMPONENT_WARNINGS to silence it.

Existing managed-component code continues to work for now; these warnings indicate code that should be migrated before the managed paths are removed. See the Entities upgrade guide section “Convert managed components to unmanaged components” for migration guidance. For data that is neither blittable nor a UnityEngine.Object, the ScriptableObjectHost baking sample demonstrates a last-resort pattern (bake the managed value onto a host ScriptableObject and hold a UnityObjectRef to it).

Editor Tooling

New Hierarchy

The Editor tooling is being unified so that we can support Entities and GameObjects with the same set of windows in the Editor.

The new Hierarchy window is now the default for viewing entities. This unified hierarchy provides a single place to see both GameObjects and Entities together, replacing the separate Entities Hierarchy window, which is now deprecated.

With the new Hierarchy, you can:

  • Explore runtime entities across all active worlds via world nodes
  • View both authoring SubScenes and their runtime entities together
  • Filter entities by component type with autocomplete suggestions
  • Double-click or right-click SubScenes to quickly open or close them

Systems Window: Streamlined and More Informative

The Systems window has been significantly refined to help you understand system execution at a glance:

New scheduling column: To easily see system dependencies defined with the UpdateBefore/UpdateAfter attributes.

Simplified interface: We’ve removed visual clutter that wasn’t providing value:

  • The world column is gone (you select the world from the dropdown instead)
  • Unused popup windows and inspector sections have been removed
  • The Relationships and Attributes tabs have been removed
  • The Query and Relationships tabs have been merged together

Detail view toggle: A new toggle lets you show or hide the detail panel, giving you more screen space when you just need the system list.

System selection history: We added navigation buttons to the toolbar for easy navigation between selected systems.

Memory Profiler Integration

Entities allocations are now properly labeled in Unity’s Memory Profiler. When investigating memory usage, you’ll see distinct categories for:

  • Entities allocations
  • Collections allocations
  • Entities Graphics allocations

This makes it easier to identify which package is responsible for specific allocations.

SubScene Baking Determinism Validation

Non-deterministic baking can cause unnecessary rebuilds and subtle bugs. We’ve added EntitySceneImporterDeterminismChecker.Check() to help you verify that your custom bakers produce consistent results.

The API forces two fresh imports of a SubScene and compares their artifact hashes:

using Unity.Scenes.Editor;

var result = EntitySceneImporterDeterminismChecker.Check("Assets/Scenes/MySubScene.unity");

if (!result.Success)
  Debug.LogError($"Check failed: {result.Error}");
else if (result.IsDeterministic)
  Debug.Log("SubScene bakes deterministically!");
else
  Debug.LogError($"Non-deterministic baking detected:\n First hash: {result.FirstHash}\n Second hash: {result.SecondHash}");

Common causes of non-determinism in bakers include iterating over unordered collections (HashSet, Dictionary) or using EntityIds that change between sessions.

Component Lifecycle Debug Callbacks

Tracking down why a component was added or removed can be tedious. The new Component Lifecycle API lets you set breakpoints or log directly at the moment a component changes. Implement IDebugOnAdded or IDebugOnRemoved on your component and add a static callback method. When that component is added or removed, your code runs immediately, giving you a callstack to inspect.

[BurstCompile]
public struct Health : IComponentData, IDebugOnAdded, IDebugOnRemoved
{
  public float Value;
  public static void OnAdded(ref Health health)
  {
    Debug.Log($"Health added with value {health.Value}");
    // Set a breakpoint here to inspect the callstack
  }

  public static void OnRemoved(ref Health health)
  {
    Debug.Log($"Health removed, was {health.Value}");
  }
}

These callbacks only fire in debug and collection-checks builds. They’re stripped from release builds, so there’s no performance cost in production. The static callbacks can also be Burst-compiled for fast execution during development. Callbacks fire during EntityManager operations and EntityCommandBuffer playback, covering both immediate and deferred changes.

This API is particularly useful for:

  • Debugging when and where components are added or removed
  • Validating component state during development
  • Tracking structural changes without the overhead of Journaling

API Cleanup and Improvements

We’ve been cleaning up legacy code paths to improve maintainability and reduce binary size:

  • EntityCommandBuffer operations now properly return created Entities that can be used immediately.
  • EntityCommandBuffer multi-playback has been deprecated. This feature could cause bugs that could occur when an ECB was played back multiple times.
  • The legacy Entity Store V1 implementation has been removed.
  • com.unity.entities no longer depends on the unsupported com.unity.serialization package.

We’d love to hear your feedback on these changes.
Thank you for your continued support!

37 Likes

Release Notes:

Entities: Added: Introduced IOnAdded and IOnRemoved, which can be added to an IComponentData to receive callbacks when the component has been added/removed to/from an Entity.

I suppose it’s a typo and there’s only IDebug versions?

Also, will managed components remain in 6.8?

No, these are diagnostic APIs and not meant to be used for actual game production. They’re meant to be used to identify and debug problems during development.

We had quite a discussion on IAdded and IRemoved. Lots of different opinions.
The verdict is mostly, quite useless for actual debug. I still can’t wrap my head around when I’d have ever needed that. I used Journaling twice in my whole entities experience and in that cases it was a life saver. IAdded/IRemoved would not have saved me because it’s quite primitive to solve a complex problem. We can always debug breakpoint before an Add/Remove. This is not something unexpected that happens in a codebase. Any strongly typed component can be found easily.
Maybe you can give me an example because I’m sure you have a reason to add it.

Outside of debug though, I find the idea very interesting. It would make managing NativeContainers in components a lot easier. Right now we have to write a lot of lifetime cycle code around it to achieve this. Such hooks in general I find very interesting. I do have a very capable and generic lifecycle for entities but when it comes to handling components, I really lack the proper low level hooks that are only really possible with forking entities.

7 Likes

Can we quickly visualize the read/write relationships between systems and components?

For example:

  • When a component is selected:
    • Highlight every system that reads it.
    • Highlight every system that writes to it.
  • When a system is selected:
    • Highlight all systems that write to the components it reads.
    • Highlight all systems that read from the components it writes.

Mod Organizer 2 has a great UI for visualizing override relationships. I think the way it manages mod file override order is quite similar to how systems override each other’s writes to components, so it could be a useful source of inspiration.

What is the plan for making these two types the same size?

struct IntAndEntity // Size of 12 bytes
{
    public int a;
    public Entity entity;
}

struct IntWithEntityId // Size of 16 bytes
{
    public int b;
    public EntityId entityId;
}
2 Likes

Honestly, I like this approach a lot cos a part of me have not really been comfortable with using managed components in ECS especially when working with jobs and burst.

Fair enough, but it’s not meant to be a feature in and of itself, rather it’s a component to build debug scaffolding on top of (and/or used in conjunction IDE tools).

What are you doing? Per-instance container cleanup logic?

I suppose I have a basic question.

First I want to preface by saying I think these are all good changes, and they do resolve a few things I felt iffy about (such as sticking a GameObject into a component class).

However, I’ve been reluctant to get too committed to serious ECS work because it felt like the API was always changing. Today’s ECS code looks very different from even just a couple of years ago.

Do you (Unity) feel confident that ECS is nearing a state of API complete with minimal changes only? Or are more big changes expected in 6.8 which would require a significant refactor?

When I saw the release notes, I initially thought it was addressing this:

3 Likes

Yeah, exactly!
I know these kind of events from other ECS solutions exist. Seems useful in a lot of cases. You can write it of course manually but I miss the resulting elegance tbh.

Not really per-instance, for the most part that’s too fine grained and is mostly code smell but I had to write a reinterpret of a DynamicBuffer to a hashmap because of it. I’d rather use the Native or Unsafe HashMap instead. There would be no difference as, in my case, 100% of DynamicBuffers are heap allocated anyway. That particular case worked out fine though because I prefer the smaller footprint of the DB instead of the larger NativeHashMap just to fit more entities in a chunk. On the other hand, getting Count from chunk-memory directly instead of heap would be nicer. And all in all, reinterpreting the DB byte array still feels like a workaround. So you see, I/we (initially tertles idea because I fought the system and he just went, eh, let’s use a DB instead) have just settled on a generic byte buffer because life cycle is handled for us.
For just a missing constructor/deconstructor call we have to jump through quite a few hoops to get what we want.

Another example I have is handling chunk components better that way. I’d have made quite a few cool optimzations with cached chunk data but the life cycle is too hard to handle for. So many edge cases to think about in all the ways a chunk components NativeContainer could be invalidated, I just stopped doing it. Outside of writing extensions I’m not in the mood of fighting the system.

Not quite. What you can do today is to filter on component type to show all systems which has a query that includes the component. You then have to select each system to see how the system makes use of the component.

Would this help your particular use case, or could we improve on something?

Ever since the Entities package went from 0.5 to 1.0, the API surface has been fairly stable. Like any feature set in the Unity engine, the API surface will change over time to accommodate new features, performance improvements, etc.

In the Unity 6.x life cycle, we have started to highlight breaking changes in the engine through “Planned breaking changes”-posts (e.g. Planned breaking changes in Unity 6.5 [updated 2026-03-27]), and so far we’ve had good feedback from this type of communication.

I don’t think we will ever be nearing a state of API completion in any part of the Unity engine, as we need to be able to adapt the engine to support modern development, wherever that may take us in the future.

If you have concerns or feedback on how we can work with breaking changes in a more transparent way, which makes it easier for you to lean on our code base, do let us know!

3 Likes

PLEASE! When you deprecate something, make sure you have proper replacement (yes, i’m talking abount Companion Component)

2 Likes

Sorry about the inconvenience. Companion components will be accessible from an unmanaged API before the full release. The underlying mechanism is expected to remain unchanged for the moment, and the transition to the replacement should require very little work.

I have a HashSet, a list and other stuff in my ManagedComponent .. no UnityObjects at all

The same object also has a DynamicBuffer and other stuff and the ManagedComponent is just for some top Level info with a state ..

This is what it currently looks like:

	[System.Serializable]
	public class LayerSolverData : IComponentData
	{
		public HashSet<int> isDirtySet = new();
		public List<int> isDirty = new();
		public List<BuildingIsland_S> islands = new();
		public bool isSolving;
		public HashSet<int> isSolvingIndex = new();
		public int currentMaxIslandIndex;
		public float destroyForce;
		public float verticalMultiplier;
		public int solveDispatchCount;
		public int playerChangeOrderCounter;
	}

How to migrate this to the new system?

2 Likes

As been mentioned in post - store all of that in scriptable object and reference it with UnityObjectRef

2 Likes

when can we expect componion component be deprecated ? 6.8 time frame ?

Can you make each system a foldout so we can have an overview on multiple systems in question? Or any way we can compare multiple systems?

IMO, inspecting one system at a time is not really useful, especially while debugging the relationship between systems, emerged by how each system accesses a set of components.