Upgrading from Entities 0.17 to Entities 0.50
To upgrade to Entities 0.50 you'll need to do the following:
Entities
- Rename FixedList128 to FixedList128Bytes
- Add partial keyword to all SystemBase, ISystem, and ISystemBase types
- Upgrade JobComponentSystem to SystemBase
- Rename ISystemBase to ISystem
- Replace IJobForEach with IJobEntity
- Remove DOTS Editor package
- Remove Audio package
- Remove Animations package
- Use string literals with Entities.WithName
- Remove nested Entities.ForEach
- Add a reference to the Jobs package for Unity.Entities.RegisterGenericJobTypeAttribute
- Ensure Entities.WithReadOnly has valid parameters
- Remove platform-specific Platforms packages and add com.unity.platforms
Netcode for Entities
- Remove GhostCollectionAuthoringComponent
- Move UpdateInWorld.TargetWorld to a top-level enum
- Source-generators replace code-generation
Hybrid Renderer
Physics
Entities
Rename FixedList128 to FixedList128Bytes
FixedList128
has been renamed to FixedList128Bytes
. All other sizes and FixedStrings
have been similarly renamed. This change more accurately reflects that the struct takes up 128 bytes, rather than having 128 elements.
When the Script Updater runs, it automatically renames any usage of renamed APIs for you. If you've hard-coded any of these names in your code, you'll need to update them manually.
Add partial keyword to all SystemBase, ISystem, and ISystemBase types
Entities' internal code generation mechanism has changed from IL post-processing to Source Generators. This means you can see the underlying code that the ECS framework generates, and debug it accordingly. You can see this code in your project's /Temp/GeneratedCode
folder. Because of this, the generated code must be valid C# and the partial
keyword is needed to augment system types.
Unity generates an error for all types of this kind that don't have the appropriate keyword. You can manually add the keyword, or you can use the Editor feature below to automatically add them to all your system types (make sure to save your work before enabling this).
To auto update your project’s systems with this keyword:
- Save your project.
- Go to DOTS > DOTS Compiler > Add missing partial keyword to systems
- Let Unity recompile source files.
- Disable the Add missing partial keyword to systems setting
Important
Remember to disable this setting from the menu, or else Unity continues to overwrite your files with added partial keywords on compilation .
Upgrade JobComponentSystem to SystemBase
The JobComponentSystem
type has been fully removed in 0.50 and is replaced by SystemBase
. SystemBase
is tested internally with both unit and performance tests, gives more options for scheduling, and does implicit job ordering by default. More information can be found below and in the SystemBase section of the manual. SystemBase has been previously released and doesn't have new semantics.
Implicit job scheduling: convert JobComponentSystem types to use the Dependency field
You don't have to explicitly use dependencies when scheduling jobs inside of a system. ECS holds the current dependency that the system tracks in a Dependency field and uses it implicitly when scheduling Entities.ForEach
or Job.WithCode
. This means that OnUpdate
now neither takes a JobHandle
nor returns a JobHandle
. Instead, ECS sets the Dependency to the input dependency before OnUpdate
begins and uses it for the next System when the OnUpdate
ends.
All Entities.ForEach
and Job.WithCode
scheduling methods have versions that both take and return, or don't take or return a JobHandle
. This means you must convert existing JobComponentSystem
types to use the Dependency field, rather than using the input dependency parameter and returning one.
Sequential and parallel scheduling: Schedule Entities.ForEach as intended
Entities.ForEach
now has both a Schedule
and ScheduleParallel
method that differentiates between sequential and parallel scheduling. All new job types also follow this pattern. This means you must make sure that you've scheduled all Entities.ForEach
jobs with the intended method.
The previous JobComponentSystem
type was unable to schedule work to happen sequentially and not in parallel. It can be useful to be able to schedule work to happen off the main thread, but still in sequential order. This makes it easier to understand how the work happens, and gives the safety system more freedom when scheduling.
To upgrade to SystemBase
from JobComponentSystem
:
- Change the inherited type from
JobComponentSystem
toSystemBase
. Make sure you've marked the type with thepartial
keyword. For more information on how to do this, see the upgrade entry about adding partial keywords to SystemBase types. - Change the method signature of
OnUpdate
to no longer take or return aJobHandle
. It should now use thevoid
return type and no longer return aJobHandle
. - Change any place that the input dependency was used or returned, to use the Dependency field on
SystemBase
. This might mean you need to change some logic of your system’sOnUpdate
method. - Change any uses of
Entities.ForEach(...).Schedule
toEntities.ForEach(...).ScheduleParallel
if you would like to keep parallel execution.
Before:
public partial class RotationSpeedSystem_ForEach : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
return Entities
.ForEach((ref Rotation rotation, in RotationSpeed rotationSpeed) =>
{
})
.Schedule(inputDeps);
}
}
After:
public partial class RotationSpeedSystem_ForEach : SystemBase
{
protected override void OnUpdate()
{
Entities
.ForEach((ref Rotation rotation, in RotationSpeed rotationSpeed) =>
{
})
.ScheduleParallel();
}
}
Rename ISystemBase to ISystem
ISystemBase
is obsolete, and replaced by the ISystem
interface. You should change all usages of ISystemBase
to ISystem
. You can optionally use the script updater to change the name for you once you reload Unity, and accept the prompt that appears stating for you to backup.
You can continue to use ISystemBase in old code, but it will be removed and unsupported in a future version of Entities.
To upgrade ISystemBase to ISystem either:
- Refresh Unity, and let the script updater run, which updates the naming for you. A back-up prompt appears beforehand, so you can decide whether you want to do it manually.
- Or, change the code from:
c# partial struct ExampleSystem : ISystemBase { public void OnCreate(ref SystemState state){} public void OnDestroy(ref SystemState state){} public void OnUpdate(ref SystemState state){} }
to:c# partial struct ExampleSystem : ISystem { public void OnCreate(ref SystemState state){} public void OnDestroy(ref SystemState state){} public void OnUpdate(ref SystemState state){} }
Replace IJobForEach with IJobEntity
IJobForEach
has been removed in 0.50. You should use IJobEntity
instead, which works more like a job, and more like Entities.ForEach
. It's easier to support because it generates the underlying IJobEntityBatch
. To summarize the changes:
- You can no longer use
IJobForEach
. - Use
IJobEntity
, with job attributes, implicit scheduling, and query generation instead. - If you’re still using
ComponentSystem
, the removal ofIJobForEach
means you have to useIJobEntityBatch
manually.
IJobEntity
has code reusability: for example, if you want to copy an array of Entity positions, you can make a function to copy and schedule it with different NativeArray
structs.
IJobEntity
has an Execute
function, similar to a job. Also, each parameter describes a Component, similar to Entities.ForEach
, and you can use the in
and ref
keywords to describe its access pattern. This means that you can use the in
keyword to directly get a read-only reference, rather than writing [ReadOnly] ref
.
You can also use all job attributes. Like Entities.ForEach
in SystemBase
, this feature does implicit job scheduling. The limit is that IJobEntity
currently only works in SystemBase
. It has an overload for both queries, and a job handle. In cases where you don’t specify a query, it makes one implicitly that matches the signature of the execute function.
To upgrade your IJobForEach implementation do the following:
- Remove
IJobForEach
and replace it withIJobEntity
. - Add the
partial
keyword to the struct. - Change
[ReadOnly] ref
toin
. - Remove any scheduling code in systems.
- Make sure that any scheduling happens from a variable and not from the instantiation directly. For example, don't do this:
c# new EntityRotationJob { dt = Time.deltaTime }.Schedule();
Do this instead:c# var job = new EntityRotationJob { dt = Time.deltaTime }; job.Schedule();
To upgrade an
IJobForEachWithEntity
, use a parameter of typeEntity
, similar to what you would do withEntities.ForEach
. If you also want the Index, use the[EntityInQueryIndex]
attribute on anint
parameter. For example, you would change the following code:struct CopyPositionsJob : IJobForEachWithEntity<Translation> { public NativeArray<float3> Positions; public void Execute( Entity entity, int index, [ReadOnly] ref LocalToWorld localToWorld) { Positions[index] = localToWorld.Position; } }
To this:
partial struct CopyPositionsJob : IJobEntity { public NativeArray<float3> Positions; public void Execute( [EntityInQueryIndex] int index, in LocalToWorld localToWorld) { Positions[index] = localToWorld.Position; } }
A full example of the
IJobForEach
conversion is below:
Old code:
[BurstCompile]
struct EntityRotationJob :
IJobForEach<Rotation, RotationSpeed>
{
public float dt;
public void Execute(
ref Rotation rotation,
[ReadOnly] ref RotationSpeed speed)
{
rotation.value = math.mul(
math.normalize(rotation.value),
quaternion.axisAngle(
math.up(),
speed.speed * dt));
}
}
struct RotationSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(
JobHandle inputDeps)
{
var job = new EntityRotationJob
{
dt = Time.deltaTime
};
return job.Schedule(this, inputDeps);
}
}
New code:
[BurstCompile]
partial struct EntityRotationJob :
IJobEntity
{
public float dt;
public void Execute(
ref Rotation rotation,
in RotationSpeed speed)
{
rotation.value = math.mul(
math.normalize(rotation.value),
quaternion.axisAngle(
math.up(),
speed.speed * dt));
}
}
struct RotationSystem : SystemBase
{
protected override void OnUpdate()
{
var job = new EntityRotationJob
{
dt = Time.deltaTime
};
job.Schedule();
// Note: Schedule has to be called
// from variable for sourcegen to work
}
}
Remove DOTS Editor package
Remove any references to the DOTS Editor package (com.unity.dots.editor). The package has been merged into the Entities 0.5 package, and any previous version will conflict and cause compilation errors.
Remove Audio package
The Audio package (com.unity.audio) isn't supported in 0.50. You should remove the package and any related scripts from your project.
Remove Animations package
The Animations package (com.unity.animations) isn't supported in 0.50. You should remove the package and any related scripts from your project.
Use string literals with Entities.WithName
Before 0.50 you could use string constants with Entities.WithName
, but the requirement is now more strict and only string literals will work.
What used to work before 0.50:
const string name = "SomeName";
Entities
.WithName(name)
.ForEach((ref LocalToWorld ltw) =>
{
// ...
}).Schedule();
The above code now triggers the following error:
error DC0008: The argument to WithName needs to be a literal value.
To upgrade, do the following instead:
Entities
.WithName("SomeName")
.ForEach((ref LocalToWorld ltw) =>
{
// ...
}).Schedule();
Remove nested Entities.ForEach
Having an Entities.ForEach
within another Entities.ForEach
was never intended nor supported. In previous versions, the following syntax would compile and eventually work:
Entities.WithoutBurst().ForEach((MyComponent a) =>
{
Entities.ForEach((MyComponent b) =>
{
}).Run();
}).Run();
This is now properly rejected by the compiler with the following error:
error DC0029: Entities.ForEach Lambda expression has a nested Entities.ForEach Lambda expression. Only a single Entities.ForEach Lambda expression is currently supported.
Add a reference to the Jobs package if using Unity.Entities.RegisterGenericJobTypeAttribute
Assemblies that require Unity.Entities.RegisterGenericJobTypeAttribute
need a reference to the Unity.Jobs assembly.
Ensure Entities.WithReadOnly has valid parameters
In previous versions of the Entities package, some invalid uses of Entities.WithReadOnly
would not cause an error.
NativeArray<float> someArray = new NativeArray<float>(123, Allocator.Temp);
float someValue = 1;
Entities
.WithReadOnly(someArray) // error : someArray is not used in the lambda
.WithReadOnly(someValue) // error : someValue is not a native container
.ForEach((MyComponent a) =>
{
}).Run();
In 0.50, the example above doesn't compile and throws the following error instead:
error DC0012: Entities.WithReadOnly is called with an invalid argument XXX. You can only use Entities.WithReadOnly on local variables that are captured inside the lambda. Please assign the field to a local variable and use that instead.
Remove platform-specific Platforms packages and add com.unity.platforms
The platform-specific com.unity.platforms packages have been removed. You should remove the following platform-specific packages from your project:
- com.unity.platforms.android
- com.unity.platforms.desktop
- com.unity.platforms.ios
- com.unity.platforms.linux
- com.unity.platforms.macos
- com.unity.platforms.web
- com.unity.platforms.windows
To build your project, you should use the com.unity.platforms package, which uses Build Configuration assets to build your project. For further information, see the documentation on Building an Entities project.
Netcode for Entities
Remove GhostCollectionAuthoringComponent
Ghost collections no longer require any special setup. They only require that the Entity Prefab is loaded on both the client and server.
If you currently have a GhostCollectionAuthoringComponent
with two Prefabs, a player and an item the custom component for that would look like this:
[GenerateAuthoringComponent]
public struct CustomSpawner : IComponentData
{
public Entity Player;
public Entity Item;
}
To upgrade this code, replace the GhostCollectionAuthoringComponent
with CustomSpawner
in the scene data and make sure the player and item references point to the correct Prefabs.
The spawner component doesn't need to reference pre-spawned ghosts which are never instantiated at runtime.
The code to spawn a ghost changes to:
EntityManager.Instantiate(GetSingleton<CustomSpawner>().Player);
Move UpdateInWorld.TargetWorld to a top-level enum
The target world enum for the UpdateInWorld
attribute has been moved to a top-level enum. Replace all references to UpdateInWorld.TargetWorld
to TargetWorld
. The enum value stays the same, so for example you would replace UpdateInWorld.TargetWorld.Client
with TargetWorld.Client
.
Change LagCompensation from opt-in to opt-out
The component DisableLagCompensation
no longer exists: if you were previously using it you can remove it.
If you are using lag compensation you must add a new singleton entity with a LagCompensationConfig
component to opt-in to lag compensation through PhysicsWorldHistory
.
Source-generators replace code-generation
The code generation for Netcode for Entities has been rewritten to use roslyn based source generators. You must delete the Assets/NetCodeGenerated
folder when upgrading to this version. If you don't remove it, you might encounter compilation errors.
If your project uses Modifiers or templates to customize the code-generation you must take some extra steps:
Custom templates
Create a new folder inside your project and add an assembly reference to NetCode. For example:
+ CodeGenCustomization/
+ NetCodeRef/
NetCode.asmref
+ Templates/
Templates.asmdef (has NETCODE_CODEGEN_TEMPLATES define constraints)
These folders contain your project's templates and subtypes definition. The steps are outlined below, and you can find further information in the Netcode documentation.
Re-implementing template registration
Create a new file and add a partial class for the UserDefinedTemplates
inside the folder with the netcode.asmref
(NetCodeRef
in the example).
Then, implement the static partial void RegisterTemplates(...)
method. You will register your templates in this method.
using System.Collections.Generic;
namespace Unity.NetCode.Generators
{
public static partial class UserDefinedTemplates
{
static partial void RegisterTemplates(List<TypeRegistryEntry> templates, string defaultRootPath)
{
templates.AddRange(new[]{
new TypeRegistryEntry
{
Type = "Unity.Mathematics.float3",
SubType = Unity.NetCode.GhostFieldSubType.Translation2d,
Quantized = true,
Smoothing = SmoothingAction.InterpolateAndExtrapolate,
SupportCommand = false,
Composite = false,
Template = "Assets/Samples/NetCodeGen/Templates/Translation2d.cs",
TemplateOverride = "",
},
}
}
}
}
Subtype definition
If your template uses sub-types (as in the example above), you need to add a partial class for Unity.NetCode.GhostFieldSubType
inside the Netcode assembly reference folder. For example:
namespace Unity.NetCode
{
static public partial class GhostFieldSubType
{
public const int MySubType = 1;
}
}
The new subtypes will be available in your project everywhere you reference the Unity.NetCode
assembly.
GhostComponentModifiers
ComponentModifiers
have been removed. You should use the GhostComponentVariation
attribute to create a ghost component variant instead.
To upgrade, create a new file that contains your variants in an assembly that has visibility or access to the types you want to add a variation for. Then, for each modifier, create its respective variant implementation as in the following example:
// Old modifier
new GhostComponentModifier
{
typeFullName = "Unity.Transforms.Translation",
attribute = new GhostComponentAttribute{PrefabType = GhostPrefabType.All, OwnerPredictedSendType = GhostSendType.All, SendDataForChildEntity = false},
fields = new[]
{
new GhostFieldModifier
{
name = "Value",
attribute = new GhostFieldAttribute{Quantization = 100, Smoothing=SmoothingAction.InterpolateAndExtrapolate}
}
},
entityIndex = 0
};
// The new variant
[GhostComponentVariation(typeof(Translation))]
[GhostComponent(SendDataForChildEntity = false)]
public struct MyTranslationVariant
{
[GhostField(Quantization=100, Smoothing=SmoothingAction.InterpolateAndExtrapolate)] public float3 Value;
}
You must also declare these variants as the default to use for that Component. You must implement the RegisterDefaultVariants
method to create a concrete system implementation for the DefaultVariantSystemBase
. There are no particular restrictions on where to put this System.
class MyDefaultVariantSystem : DefaultVariantSystemBase
{
protected override void RegisterDefaultVariants(Dictionary<ComponentType, System.Type> defaultVariants)
{
defaultVariants.Add(new ComponentType(typeof(Translation)), typeof(MyTranslationVariant));
...
}
}
Hybrid Renderer
Remove Hybrid Renderer V1
Hybrid Renderer V1 has been removed from the code base. Hybrid Renderer V2 is enabled by default, so you no longer need the ENABLE_HYBRID_RENDERER_V2
scripting define.
If your project used the Hybrid Renderer V1, you need to convert it to use the Universal Render Pipeline, or the High Definition Render Pipeline. This is because the built in render pipeline isn't supported by Hybrid Renderer V2.
Physics
Update TriggerEventSystem from JobComponentSystem to SystemBase
When declaring an Entity inside ITriggerEventsJob > Execute
, use:
Entity entityA = triggerEvent.EntityA;
Instead of:
Entity entityA = triggerEvent.Entities.EntityA;
You also no longer need to use BuildPhysicsWorld
. Previously, BuildPhysicsWorld
was used like this:
protected override void OnCreate()
{
base.OnCreate();
buildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
To upgrade it, use the following code:
protected override void OnCreate()
{
stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
The following code shows the difference between using ITriggerEventJob
with JobComponentSystem
or SystemBase
. The SystemBase
example is useful for working with the 0.5 Entities package.
JobComponentSystem example:
public class PickupOnTriggerSystem : JobComponentSystem
{
private BuildPhysicsWorld buildPhysicsWorld;
private StepPhysicsWorld stepPhysicsWorld;
private EndSimulationEntityCommandBufferSystem commandBufferSystem;
protected override void OnCreate()
{
base.OnCreate();
buildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
var job = new PickupOnTriggerSystemJob();
job.allPickups = GetComponentDataFromEntity<PickupTag>(true);
job.allPlayers = GetComponentDataFromEntity<PlayerTag>(true);
job.entityCommandBuffer = commandBufferSystem.CreateCommandBuffer();
JobHandle jobHandle = job.Schedule(stepPhysicsWorld.Simulation,
inputDependencies);
commandBufferSystem.AddJobHandleForProducer(jobHandle);
return jobHandle;
}
[BurstCompile]
struct PickupOnTriggerSystemJob : ITriggerEventsJob
{
[ReadOnly] public ComponentDataFromEntity<PickupTag> allPickups;
[ReadOnly] public ComponentDataFromEntity<PlayerTag> allPlayers;
public EntityCommandBuffer entityCommandBuffer;
public void Execute(TriggerEvent triggerEvent)
{
Entity entityA = triggerEvent.EntityA;
Entity entityB = triggerEvent.EntityB;
if (allPickups.HasComponent(entityA) && allPickups.HasComponent(entityB))
return;
if (allPickups.HasComponent(entityA) && allPlayers.HasComponent(entityB))
entityCommandBuffer.DestroyEntity(entityA);
else if (allPlayers.HasComponent(entityA) && allPickups.HasComponent(entityB))
entityCommandBuffer.DestroyEntity(entityB);
}
}
}
SystemBase example:
public partial class PickupOnTriggerSystem : SystemBase
{
private StepPhysicsWorld stepPhysicsWorld;
private EndSimulationEntityCommandBufferSystem commandBufferSystem;
protected override void OnCreate()
{
stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
var job = new PickupOnTriggerSystemJob
{
allPickups = GetComponentDataFromEntity<PickupTag>(true),
allPlayers = GetComponentDataFromEntity<PlayerTag>(true),
entityCommandBuffer = commandBufferSystem.CreateCommandBuffer()
};
Dependency = job.Schedule(stepPhysicsWorld.Simulation, Dependency);
commandBufferSystem.AddJobHandleForProducer(Dependency);
}
[BurstCompile]
struct PickupOnTriggerSystemJob : ITriggerEventsJob
{
[ReadOnly] public ComponentDataFromEntity<PickupTag> allPickups;
[ReadOnly] public ComponentDataFromEntity<PlayerTag> allPlayers;
public EntityCommandBuffer entityCommandBuffer;
public void Execute(TriggerEvent triggerEvent)
{
Entity entityA = triggerEvent.EntityA;
Entity entityB = triggerEvent.EntityB;
if (allPickups.HasComponent(entityA) && allPickups.HasComponent(entityB))
return;
if (allPickups.HasComponent(entityA) && allPlayers.HasComponent(entityB))
entityCommandBuffer.DestroyEntity(entityA);
else if (allPlayers.HasComponent(entityA) && allPickups.HasComponent(entityB))
entityCommandBuffer.DestroyEntity(entityB);
}
}
}