#Capsule Cast from Job?

1 messages · Page 1 of 1 (latest)

high dragon
#

How would I go about Capsule Casting every Bullet in my game to figure out if they are inside something or about to be? They move by transforming the LocalTransform every tick so they don't use Physics to move.

The code examples I could find were kind of overwhelming and using unsafe. I was thinking I could do an IJobEntity or something to find every Bullet in my game, do a collision check, then if they are within range do something with the Entity it hit or just continue on if nothing was hit.

soft mulch
high dragon
#

I'm thinking 100 raycasts might be better than checking 100 bullets distance to 100 enemies each?

#

And I was hoping there was a safe way to do it rather than unsafe

#

But it seems that's not possible

soft mulch
high dragon
#

One of those solutions that work...but likely ain't performant at scale.

[BurstCompile]
public partial struct BulletCollisionJob : IJobEntity
{
    [ReadOnly] public NativeArray<LocalTransform> MonsterTransforms;
    [ReadOnly] public NativeArray<Entity> MonsterEntities;
    public EntityCommandBuffer ECB;

    public void Execute(Entity bulletEntity, in LocalTransform bulletTransform, in Bullet bullet)
    {
        for (int index = 0; index < MonsterEntities.Length; index++)
        {
            if (MonsterEntities[index] == Entity.Null) continue;
            if (math.distancesq(bulletTransform.Position, MonsterTransforms[index].Position) <= 0.5f)
            {
                ECB.AddComponent<Destroy>(bulletEntity);
                ECB.AddComponent(MonsterEntities[index], new Damage
                {
                    Value = bullet.Damage
                });
                break;
            }
        }
    }
}
high dragon
#

Does this look right to you @soft mulch ?

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
    var physicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld;
    var bulletQuery = SystemAPI.QueryBuilder()
        .WithAll<LocalTransform, Bullet>()
        .Build();

    var bulletTransforms = bulletQuery.ToComponentDataArray<LocalTransform>(Allocator.TempJob);
    var bullets = bulletQuery.ToComponentDataArray<Bullet>(Allocator.Temp);
    var bulletEntities = bulletQuery.ToEntityArray(Allocator.Temp);

    NativeArray<RaycastInput> raycastInputs = new(bulletTransforms.Length, Allocator.TempJob);
    NativeArray<RaycastHit> raycastResults = new(bulletTransforms.Length, Allocator.TempJob);

    for (int index = 0; index < bulletTransforms.Length; index++)
    {
        float3 start = bulletTransforms[index].Position;
        float3 end = start * 1.25f;
        raycastInputs[index] = new RaycastInput()
        {
            Start = start,
            End = end,
            Filter = new CollisionFilter()
            {
                BelongsTo = ~0u,
                CollidesWith = ~0u, // all 1s, so all layers, collide with everything
                GroupIndex = 0
            }
        };
    }

    var batchRaycastResult = ScheduleBatchRayCast(physicsWorld, raycastInputs, raycastResults);
    batchRaycastResult.Complete();

    for (int index = 0; index < raycastResults.Length; index++)
    {
        if (raycastResults[index].Entity == Entity.Null)
        {
            state.EntityManager.AddComponent<Damage>(raycastResults[index].Entity);
            state.EntityManager.SetComponentData<Damage>(raycastResults[index].Entity, new()
            {
                Value = bullets[index].Damage
            });
            state.EntityManager.AddComponent<Destroy>(bulletEntities[index]);
        }
    }
}
#
private JobHandle ScheduleBatchRayCast(CollisionWorld world,
NativeArray<RaycastInput> inputs, NativeArray<RaycastHit> results)
{
    JobHandle bulletPhysicsCollisionJob = new BulletPhysicsCollisionJob
    {
        inputs = inputs,
        results = results,
        world = world

    }.Schedule(inputs.Length, 4);
    return bulletPhysicsCollisionJob;
}
#
[BurstCompile]
public partial struct BulletPhysicsCollisionJob : IJobParallelFor
{
    [ReadOnly] public CollisionWorld world;
    [ReadOnly] public NativeArray<RaycastInput> inputs;
    public NativeArray<RaycastHit> results;

    public unsafe void Execute(int index)
    {
        world.CastRay(inputs[index], out RaycastHit hit);
        results[index] = hit;
    }
}
soft mulch
#
    float3 start = bulletTransforms[index].Position;
    float3 end = start * 1.25f;

That seems weird to me, why are you doing the position times 1.25?

high dragon
#

Well, I need to fix that. But I need to go forward a bit from the position of the bullet.

soft mulch
#

What is normal, is to either predict the next frame, or recalculate what happened last frame.

high dragon
#

Otherwise I can't tell if I hit a monster

soft mulch
#

What I saw another guy do, is have the Bullet have the oldPosition, and just draw a ray from current position to old position

high dragon
#

So..backwards?

soft mulch
#

Yeah, that's what he did

#

But you can also add a forward vector depending on how you actually move the bullet

#

The issue with that is if you have low FPS

#

You either need to predict the vector very far away, or risk the bullet going through objects

#

With the backwards calculation you don't have any issue with FPS

high dragon
#

I don't need to calculate something far ahead of me do I?
I just need to look a little into the future.
You are right that this is not continous so if the FPS drops then the bullet could pass through something between two frames.

#

But won't I then just have the same issue but in reverse?

soft mulch
#

No, in reverse if you have 1 FPS, you know the last position of 1 second ago still

#

So its all 1 continuous line that way

high dragon
#

So how would I store the position of every bullet from a frame ago?
Or would I compute it?

soft mulch
#

Yeah that's how he did it, I just copied the style in a test I made a few years ago.

#

Just have the Bullet component have a LastPosition, and always set that every frame

#

Anyhow, I don't know if this is the best way. That's just how he and I did it.

high dragon
#

Hm. I have a movement component which I can attach to anything, including the bullet. Think it might be a good idea to store it there instead?
Or perhaps a separate component.

#
public struct MovementDirection : IComponentData
{
    public float3 Direction;
    public float Speed;
}
soft mulch
#

Up to you. You could use that for VFX too, so that might be a better idea yeah.

high dragon
#

Which of the two did you agree to? The former or latter?

soft mulch
#

The seperate component

high dragon
#

Ah

#

So a component that always notes down the position of the last frame.

soft mulch
#

Yeah

high dragon
#

Alright

#

I'll try that

#

Something like this probably

[UpdateBefore(typeof(BulletMovementSystem))]
public partial struct LastFrameSystem : ISystem
{
    [BurstCompile]
    public readonly void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<LastFramePosition>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var lastFrameJob = new LastFrameJob();
        lastFrameJob.Schedule();
    }
}

[WithAbsent(typeof(Destroy))]
[BurstCompile]
public partial struct LastFrameJob : IJobEntity
{
    public void Execute(in LocalTransform transform, ref LastFramePosition lastFramePos)
    {
        lastFramePos.Position = transform.Position;
    }
}
high dragon
#

Yeah I think I got something that works.

#

Went from 195-210 FPS to 235-240 like before I added the bullet collision system.

#

So basically no loss of performance at all

#

Thanks for your help @soft mulch

soft mulch
#

No problem mate, good luck with your game