I’ve been working with ECS for a couple of years now and I’ve run into several use cases where I want to change what an entity can collide with, either temporarily or permanently. It seems like something that should be relatively simple to do, but is surprisingly difficult.
For context, here is what change collision filter system might look like:
public partial struct ChangeCollisionFilterSystem : ISystem
{
private NativeList<PhysicsCollider> _createdColliders;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<MyEntity>();
_createdColliders = new NativeList<PhysicsCollider>(Allocator.Persistent);
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
foreach (PhysicsCollider collider in _createdColliders)
{
collider.Value.Dispose();
}
_createdColliders.Dispose();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
Entity entity = SystemAPI.GetSingletonEntity<MyEntity>();
var collider = SystemAPI.GetComponent<PhysicsCollider>(entity);
BlobAssetReference<Collider> colliderCopy = collider.Value.Value.Clone();
CollisionFilter filter = colliderCopy.Value.GetCollisionFilter();
filter.BelongsTo = 16;
colliderCopy.Value.SetCollisionFilter(filter);
PhysicsCollider newCollider = colliderCopy.AsComponent();
_createdColliders.Add(newCollider);
SystemAPI.SetComponent(entity, newCollider);
}
}
This code isn’t super complex, but it highlights a couple of problem areas that new ECS users can easily run into.
- It is not obvious that SetCollisionFilter() is going to modify ALL entities using that particular collider. This makes sense because the baking systems use a blob asset store to deduplicate colliders, but this detail is hidden from the user. If you only want to modify one collider, you have to call Clone first.
- Calling Clone creates a new blob that must be disposed of when no longer needed. The comments for this function do call this out, but it is annoying to deal with. This problem is even more complicated when considering saving and loading the game and restoring the correct colliders.
I understand the rationale for storing colliders as blobs. Colliders are sometimes primitive shapes but can also be large meshes. Storing this data outside of a component makes a lot of sense to save on memory. It isn’t super common (at least for me) to have to change a collider shape and often there are better alternatives like swapping an entire prefab.
However, the CollisionFilter is something I’m wanting to change a lot. For example, if I want the player to pick up an object, I may want to temporarily disable collision on the object, but still be collidable with cursor raycasts. It feels like I should be able to just modify an integer on a component somewhere. But instead I have to clone the entire collider and make sure I correctly handle the lifetime of it.
Internally, a CollisionFilter only contains three integers. Even if it would increase the size of entities slightly, I feel like moving CollisionFilter to its own IComponentData (not in a blob) would reduce a lot of the complexity here. Abstractly, this also makes more sense to me. When I think of a “collider” I think of the physical shape and bounds of the object. A “collision filter” describes how that collider interacts with the world which is a separate concept from the collider itself.
If it is too late to change something like this, an alternative suggestion would be to add a PhysicsCollisionFilterOverride component added that functions similarly to the existing PhysicsMassOverride, but for override collision filters instead of changing kinematic/dynamic. This PhysicsCollisionFilterOverride would simply contain a CollisionFilter inside of it, no blob required. This component would be optional, but if present the physics system would use the override collision filter for all physics calculations.
I realize that there might be some internal complexity with both suggestions (especially considering stuff like compound colliders), but I hope that something like this is considered.
Cheers!