RaycastCommand/SpherecastCommand inside a IJobForEach?

I’m trying to move my character controller into ECS and everything is setup correctly, but I cannot figure out how to move the RayCast logic into ECS. Here’s my current approach, but it throws an error since I cannot call Schedule from inside a job.

[BurstCompile]
public struct MovementJob : IJobForEach<PhysicsMass, PhysicsVelocity, LocalToWorld, Translation, Movement, CharacterData>
{
   [BurstCompile]
   public void Execute(ref PhysicsMass mass, ref PhysicsVelocity vel, ref LocalToWorld localToWorld, ref Translation translation, [ReadOnly] ref Movement movement, [ReadOnly] ref CharacterData characterData)
   {
       ...
       using (NativeArray<RaycastHit> results = new NativeArray<RaycastHit>(1, Allocator.Temp))
       {
           NativeArray<SpherecastCommand> commands = new NativeArray<SpherecastCommand>(1, Allocator.Temp)
           {
               [0] = new SpherecastCommand(translation.Value, characterData.groundingRadius, -localToWorld.Up)
           };

           // Schedule the batch of sphere casts
           JobHandle handle = SpherecastCommand.ScheduleBatch(commands, results, 1, default(JobHandle));

           // Wait for the batch processing job to complete
           handle.Complete();

           // Copy the result. If batchedHit.collider is null, there was no hit
           RaycastHit batchedHit = results[0];

           if (batchedHit.collider != null)
           {
               //Logic goes here
           }

           // Dispose the buffers
           commands.Dispose();
       }
       ...
   }
}

The only examples I’ve seen for RaycastCommand is using it’s own job and from the System passing all the positions and data it requires. Is there any way to use this with a IJobForEach?

I think you are looking for this documentation https://docs.unity3d.com/Packages/com.unity.physics@0.2/manual/collision_queries.html.

More specificaly for your case :

public unsafe Entity SphereCast(float3 RayFrom, float3 RayTo, float radius)
    {
        var physicsWorldSystem = World.Active.GetExistingSystem<Unity.Physics.Systems.BuildPhysicsWorld>();
        var collisionWorld = physicsWorldSystem.PhysicsWorld.CollisionWorld;

        var filter = new CollisionFilter()
        {
            BelongsTo = ~0u,
            CollidesWith = ~0u, // all 1s, so all layers, collide with everything
            GroupIndex = 0
        };

        SphereGeometry sphereGeometry = new SphereGeometry() { Center = float3.zero, Radius = radius };
        BlobAssetReference<Collider> sphereCollider = SphereCollider.Create(sphereGeometry, filter);

        ColliderCastInput input = new ColliderCastInput()
        {
            Collider = (Collider*)sphereCollider.GetUnsafePtr(),
            Orientation = quaternion.identity,
            Start = RayFrom,
            End = RayTo
        };

        ColliderCastHit hit = new ColliderCastHit();
        bool haveHit = collisionWorld.CastCollider(input, out hit);
        if (haveHit)
        {
            // see hit.Position
            // see hit.SurfaceNormal
            Entity e = physicsWorldSystem.PhysicsWorld.Bodies[hit.RigidBodyIndex].Entity;
            return e;
        }
        return Entity.Null;
    }
2 Likes

Calling the collisionWorld.CastCollider throws an error:

ArgumentException: Slice may not be used on a restricted range array
Parameter name: array
Unity.Collections.NativeSlice`1[T]..ctor (Unity.Collections.NativeArray`1[T] array, System.Int32 start, System.Int32 length) (at <0d01204b437e47c1a78b600b597a89f4>:0)
Unity.Collections.NativeSlice`1[T]..ctor (Unity.Collections.NativeArray`1[T] array) (at <0d01204b437e47c1a78b600b597a89f4>:0)
Unity.Collections.NativeSlice`1[T].op_Implicit (Unity.Collections.NativeArray`1[T] array) (at <0d01204b437e47c1a78b600b597a89f4>:0)
Unity.Physics.CollisionWorld.CastCollider[T] (Unity.Physics.ColliderCastInput input, T& collector) (at Library/PackageCache/com.unity.physics@0.2.5-preview.1/Unity.Physics/Collision/World/CollisionWorld.cs:149)
Unity.Physics.QueryWrappers.ColliderCast[T] (T& target, Unity.Physics.ColliderCastInput input, Unity.Physics.ColliderCastHit& result) (at Library/PackageCache/com.unity.physics@0.2.5-preview.1/Unity.Physics/Collision/Queries/Collidable.cs:134)
Unity.Physics.CollisionWorld.CastCollider (Unity.Physics.ColliderCastInput input, Unity.Physics.ColliderCastHit& closestHit) (at Library/PackageCache/com.unity.physics@0.2.5-preview.1/Unity.Physics/Collision/World/CollisionWorld.cs:145)

I didn’t change anything, I just pasted your code to see if it works.

I fixed this by adding a [ReadOnly] attribute to the CollisionWorld and passing the value from the system.

Glad it worked out. Yes this documentation sample is not jobified.

Just a side note, RaycastCommand and SpherecastCommand are refer to Raycast and Spherecast within PhysX, not Unity.Physics.

Well I think that I found a bug or I’m misunderstanding something. I can get SphereCast to work perfectly, but RayCast from the link https://docs.unity3d.com/Packages/com.unity.physics@0.2/manual/collision_queries.html is not triggering collision when using a different mask for CollidesWith. Here’s my code:

[BurstCompile]
unsafe bool SphereCast(float3 from, float3 to, float radius, out ColliderCastHit hit)
{
   CollisionFilter collisionFilter = new CollisionFilter()
   {
       BelongsTo = (1<<11),                //Character
       CollidesWith = ~(uint)(1<<11),      //All but character
       GroupIndex = 0
   }

   SphereGeometry sphereGeometry = new SphereGeometry() { Center = float3.zero, Radius = radius };
   BlobAssetReference<Unity.Physics.Collider> sphereCollider = Unity.Physics.SphereCollider.Create(sphereGeometry, collisionFilter);

   ColliderCastInput input = new ColliderCastInput()
   {
       Collider = (Unity.Physics.Collider*)sphereCollider.GetUnsafePtr(),
       Orientation = quaternion.identity,
       Start = from,
       End = to
   };

   return collisionWorld.CastCollider(input, out hit);
}

[BurstCompile]
bool RayCast(float3 from, float3 to, out Unity.Physics.RaycastHit hit)
{
   CollisionFilter collisionFilter = new CollisionFilter()
   {
       BelongsTo = (1<<11),                //Character
       CollidesWith = ~(uint)(1<<11),      //All but character
       GroupIndex = 0
   }
   
   RaycastInput input = new RaycastInput()
   {
       Start = from,
       End = to,
       Filter = collisionFilter
   };

   return collisionWorld.CastRay(input, out hit);
}

This when checking if grounded wouldn’t work with ray cast, but it works with sphere cast. Using the exact same from and to values. If I change the mask to use CollidesWith = ~0u, like on the link, then it detects collision with the character. Am I missing something?

Hello, You use the same from and to values but in one case, you use a ray, in the other you used a sphere. I think you have to take into consideration the sphere radius. your sphere may be hitting something because of it’s radius (schematicly, the center of the sphere did not hit anything but it’s surface did).

Even thought that would make sense, I’ve tested it with a massive distance and still returns nothing. I just gave up on the idea of using the RayCast since I never found any way to make it work.

It would be better to have a chain of jobs. After a job, pass the handle to the raycastCommand job, then back to another job.