I’ve done something similar. Here’s the code if it helps. Explanation is below the code.
public class AllyProximitySystem : JobComponentSystem
{
private EntityQuery dalmatianProximityUsers;
private EntityQuery epiroteProximityUsers;
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var awaredAlliesFromEntity =
GetBufferFromEntity<AllyWithinAwarenessRangeElement>(false);
var speakingAlliesFromEntity =
GetBufferFromEntity<AllyWithinSpeakingRangeElement>(false);
var bodyHandles = new NativeArray<JobHandle>(4, Allocator.Temp);
var dalmatianAlliesTransforms =
dalmatianProximityUsers.ToComponentDataArray<LocalToWorld>(
Allocator.TempJob,
out var dalmatianAlliesTransformsJobHandle
);
var dalmatianAlliesEntities =
dalmatianProximityUsers.ToEntityArray(
Allocator.TempJob,
out var dalmatianAlliesEntitiesJobHandle
);
var epiroteAlliesTransforms =
epiroteProximityUsers.ToComponentDataArray<LocalToWorld>(
Allocator.TempJob,
out var epiroteAlliesTransformsJobHandle
);
var epiroteAlliesEntities =
dalmatianProximityUsers.ToEntityArray(
Allocator.TempJob,
out var epiroteAlliesEntitiesJobHandle
);
bodyHandles[0] = dalmatianAlliesTransformsJobHandle;
bodyHandles[1] = dalmatianAlliesEntitiesJobHandle;
bodyHandles[2] = epiroteAlliesEntitiesJobHandle;
bodyHandles[3] = epiroteAlliesTransformsJobHandle;
inputDeps = JobHandle.CombineDependencies(
JobHandle.CombineDependencies(bodyHandles), inputDeps
);
bodyHandles.Dispose();
inputDeps = new WithinRangeJob
{
alliesTransforms = dalmatianAlliesTransforms,
alliesEntities = dalmatianAlliesEntities,
awaredAlliesFromEntity = awaredAlliesFromEntity,
speakingAlliesFromEntity = speakingAlliesFromEntity
}.ScheduleSingle(dalmatianProximityUsers, inputDeps);
inputDeps = new WithinRangeJob
{
alliesTransforms = epiroteAlliesTransforms,
alliesEntities = epiroteAlliesEntities,
awaredAlliesFromEntity = awaredAlliesFromEntity,
speakingAlliesFromEntity = speakingAlliesFromEntity
}.ScheduleSingle(epiroteProximityUsers, inputDeps);
return inputDeps;
}
[BurstCompile]
private struct WithinRangeJob : IJobForEachWithEntity<LocalToWorld, AllyProximity>
{
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<LocalToWorld> alliesTransforms;
[ReadOnly]
[DeallocateOnJobCompletion]
public NativeArray<Entity> alliesEntities;
[NativeDisableParallelForRestriction]
public BufferFromEntity<AllyWithinAwarenessRangeElement> awaredAlliesFromEntity;
[NativeDisableParallelForRestriction]
public BufferFromEntity<AllyWithinSpeakingRangeElement> speakingAlliesFromEntity;
public void Execute(
Entity entity,
int jobIndex,
ref LocalToWorld transform,
ref AllyProximity proximityStats
)
{
var awaredAllies = awaredAlliesFromEntity[entity];
var awaredAlliesLength = awaredAllies.Length;
var awaredAlliesEntities =
new NativeArray<Entity>(awaredAlliesLength,Allocator.Temp);
for (var i = 0; i < awaredAlliesLength; i++)
{
awaredAlliesEntities[i] = awaredAllies[i].ally;
}
var speakingAllies = speakingAlliesFromEntity[entity];
var speakingAlliesLength = speakingAllies.Length;
var speakingAlliesEntities =
new NativeArray<Entity>(speakingAlliesLength, Allocator.Temp);
for (var i = 0; i < speakingAlliesLength; i++)
{
speakingAlliesEntities[i] = speakingAllies[i].entity;
}
var alliesTransformsLength = alliesTransforms.Length;
for(var i = 0; i < alliesTransformsLength; i++)
{
var allyEntity = alliesEntities[i];
var allyAwareness = awaredAlliesFromEntity[allyEntity];
var allySpeaking = speakingAlliesFromEntity[allyEntity];
if (entity.Equals(allyEntity))
{
continue;
}
var allyTransform = alliesTransforms[i];
var withinAwarenessRadius =
allyTransform.Position.DistanceFrom(transform.Position)
< proximityStats.awarenessRadius;
var awarenessContainsThis =
awaredAlliesEntities.Contains(allyEntity);
if (withinAwarenessRadius
&& !awarenessContainsThis
)
{
awaredAllies.Add(new AllyWithinAwarenessRangeElement
{
ally = allyEntity
});
allyAwareness.Add(new AllyWithinAwarenessRangeElement
{
ally = entity
});
}
else if (!withinAwarenessRadius
&& awarenessContainsThis
)
{
awaredAllies.Remove(new AllyWithinAwarenessRangeElement
{
ally = allyEntity
});
allyAwareness.Remove(new AllyWithinAwarenessRangeElement
{
ally = entity
});
}
var withinSpeakingRadius =
allyTransform.Position.DistanceFrom(transform.Position)
< proximityStats.speakingRadius;
var speakingContainsThis =
speakingAlliesEntities.Contains(allyEntity);
if (withinSpeakingRadius
&& !speakingContainsThis
)
{
speakingAllies.Add(new AllyWithinSpeakingRangeElement
{
entity = allyEntity
});
allySpeaking.Add(new AllyWithinSpeakingRangeElement
{
entity = entity
});
}
else if (!withinSpeakingRadius
&& speakingContainsThis
)
{
speakingAllies.Remove(new AllyWithinSpeakingRangeElement
{
entity = allyEntity
});
allySpeaking.Remove(new AllyWithinSpeakingRangeElement
{
entity = entity
});
}
}
}
}
protected override void OnCreate()
{
dalmatianProximityUsers = GetEntityQuery(new EntityQueryDesc
{
All = new ComponentType[]
{
ComponentType.ReadOnly<DalmatianTag>(),
ComponentType.ReadOnly<AllyProximity>(),
ComponentType.ReadOnly<LocalToWorld>(),
ComponentType.ReadOnly<Translation>()
},
});
dalmatianProximityUsers.SetFilterChanged(typeof(Translation));
epiroteProximityUsers = GetEntityQuery(new EntityQueryDesc
{
All = new ComponentType[]
{
ComponentType.ReadOnly<EpiroteTag>(),
ComponentType.ReadOnly<AllyProximity>(),
ComponentType.ReadOnly<LocalToWorld>(),
ComponentType.ReadOnly<Translation>()
},
});
epiroteProximityUsers.SetFilterChanged(typeof(Translation));
}
}
You set the change filter on Translation, because Unity’s TRSToLocalToWorldSystem constantly writes Translation to LocalToWorld, but Translation is written to only if one of your systems do. This way the system only activates when an entity moves. Once it does it checks all other “allied” entities to see if they’re still within range, and update the buffer of allied entities accordingly. Your use case is different since you only want “core entities” to detect within range entities, so modify the system above to have two entity queries - one for core entities, and one for within range entities. The job would then cycle through core entities and alliesTransforms and alliesEntities would be those of your non-core entities.
I understand that keeping track of entities within range of another entity is still extra work, but it reduces code overhead and decouples systems that rely on entities within range from systems that maintain entities within range. You’d only have to have a BufferFromEntity in the depending system instead of calculating all allies within range every frame.