3D FPS: Bullet Collision Detection

Started by
23 comments, last by suntzu2007 14 years, 5 months ago
Hi all, ( Apologies if this topic was addressed elsewhere, however my search remained fruitless. ) I'm relatively new to XNA and am working on a simple 3D FPS. The problem I am having is that my collision detection system does not seem to work for the bullets that I am firing. In other words, shooting at a target does not trigger a collision detection. For every target, I create a bounding sphere by extracting the bounding sphere of each mesh and then merging them. i.e.

public override void Draw(GameTime gameTime, Matrix projection, Matrix view)
        {
            Matrix[] transforms = new Matrix[_model.Bones.Count];

            _model.CopyAbsoluteBoneTransformsTo(transforms);
            bounds = new BoundingSphere();
            foreach (ModelMesh mesh in _model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.Projection = Projection;
                    effect.View = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
                    effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateRotationY(600.0f) * Matrix.CreateRotationX(300.0f) *
                          Matrix.CreateScale(1.9f) * Matrix.CreateTranslation(modelPosition);

                }
                bounds = BoundingSphere.CreateMerged(bounds, mesh.BoundingSphere);
                mesh.Draw();
            }
            bounds = bounds.Transform(Matrix.CreateTranslation(modelPosition));
        }

Bounding spheres are created for each bullet using the same method. Firing a bullet is achieved by creating a new bullet at the camera's position, and then propagating the bullet in a straight line along the Z axis by incrementing the Z-axis co-ordinates at each "Draw". At the beginning of "Draw", I am checking whether the bullet is colliding with an object (or whether it as too far from the game world to be drawn). If the bullet collides, points should be increased etc and the bullet is removed from the game world.

public override void Draw(GameTime gameTime, Matrix projection, Matrix view)
        {
            Matrix[] transforms = new Matrix[_model.Bones.Count];
            if (world.BulletCollision(this.GetBounds()))
            {
                redundant = true;
                return;
            }
            if (propagate)
            {
                modelPosition.Z += bulletSpeed;
            }
            if (modelPosition.Z > 1000.0f)
            {
                redundant = true;
            }
            _model.CopyAbsoluteBoneTransformsTo(transforms);
            bounds = new BoundingSphere();
            foreach (ModelMesh mesh in _model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.Projection = Projection;
                    effect.View = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
                    effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateRotationY(600.0f) * Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(modelPosition);

                }
                bounds = BoundingSphere.CreateMerged(bounds, mesh.BoundingSphere);
                mesh.Draw();
            }
            bounds = bounds.Transform(Matrix.CreateScale(1.0f) * Matrix.CreateTranslation(modelPosition));
        }

My collision detection looks is as follows:

        public bool BulletCollision(BoundingSphere bounds)
        {
            foreach (ISceneObject obj in worldObjects)
            {
                if (obj.GetBounds().Radius == -1)
                {
                    continue;
                }

                if (obj.GetType().ToString() != "shootingrange.GameWorld.Building")
                {
                    continue;
                }
                Console.WriteLine(bounds.Center);
                if (obj.GetBounds().Contains(bounds) == ContainmentType.Contains ||
                    obj.GetBounds().Intersects(bounds))
                {
                    Game.points++;
                    return true;
                }
            }
            return false;
        }

Any help would be greatly appreciated :-)
Advertisement
Hi!

I would say your problem is the bullet is moving too fast for this simple collision detection to work. The bullet is in front of a character in one frame, and already behind it in the next, so the collision never registers.

A simple way of fixing this is in addition to the bounding sphere test, doing a raycast forward from the bullet to check for objects the bullet is about to hit. If they are closer than the distance the bullet moves in one frame, a hit should occur.
Hi Morrandir,

Thanks for your reply. What do you mean by "raycast"? I tried moving the bullet at a slower speed and it didn't work either (maybe the bullet was still to fast though).

Also, would you recommend another technique instead?

Thanks,
I had to deal with this exact same situation (well actually it was a banana cannon and monkeys but I digress). We solved it based on this article Simple Intersection Tests For Games.

You can find a bit more information just by doing a search for "swept collision tests" in Google.

Andy

"Ars longa, vita brevis, occasio praeceps, experimentum periculosum, iudicium difficile"

"Life is short, [the] craft long, opportunity fleeting, experiment treacherous, judgement difficult."

Also by "raycast" he means calculating a line from point A, which is the position in the first frame, to point B, which is the position in the second frame.

Andy

"Ars longa, vita brevis, occasio praeceps, experimentum periculosum, iudicium difficile"

"Life is short, [the] craft long, opportunity fleeting, experiment treacherous, judgement difficult."

Just a stab in the dark;

                if (obj.GetType().ToString() != "shootingrange.GameWorld.Building")                {                    continue;                }


Sure that comparison should be !=, and not ==? ('Building' didn't sound like the name of what your target objects would be to me)
Ray casting means testing if a ray intersects an object. In this case you create a ray starting form the bullet position, in the direction it is traveling in, and test for collision with all geometry in the scene (respectively with their bounding boxes).

I'm not too well versed in XNA, but it seems you can use BoundingSphere.Intersects(Ray ray) for the testing.

Also, for testing if this really is the problem, try to decrease the bullet speed dramatically, so that there is no chance for a bullet to skip an enemy, than shoot it at a stationary enemy and see if the collision registers. Ideally you should also draw the bullet and enemy bounding spheres so you can inspect them visually.
Hi all,

Thanks very much for your help.

@NineYearCycle and Morrandir,
Trying out your tips now :-) Thanks!

@ mattd
Ah yes, I had used a building as a target for testing purposes (larger than the NPC in case it had to do with a misplaced bounding sphere). Thanks!
Ok, I finally managed to implement the Ray casting. However it's not quite working out. The only times that I seem to be able to "hit" and object is when I move backwards while shooting. I know it has "something" to do with the camera, however I can't quite figure out what it is. Also, once moving backwards when shooting, I hit the target even if pointing into opposite directions.

What I did was draw a ray at every "Draw" of the bullet (Please see the code below).

I've had problems drawing the ray, so, I just print the co-ordinates for every "hit". Here an example:

shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.075377 Z:-93.97278} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.075377 Z:-93.97278} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.075377 Z:-93.97278} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.075377 Z:-98.97278} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.075377 Z:-93.97278} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:3.94132 Z:-93.91934} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.425062 Y:3.94132 Z:-93.95586} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.22722 Y:3.941321 Z:-93.95522} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.029201 Y:4.008436 Z:-93.98106} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:111.8863 Y:4.20871 Z:-244.3544} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:111.8863 Y:4.20871 Z:-244.3544} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:111.8863 Y:4.20871 Z:-244.3544} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:111.8863 Y:4.20871 Z:-244.3544} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:133.2445 Y:4.275099 Z:-289.479} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:138.7093 Y:4.341291 Z:-300.7091} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:119.8614 Y:4.341294 Z:-266.0376} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:181.6297 Y:4.341298 Z:-201.135} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:172.7936 Y:4.3413 Z:-190.0566} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:168.4389 Y:4.341295 Z:-184.4362} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:150.1676 Y:4.275097 Z:-167.2586} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:124.8685 Y:4.275102 Z:-152.5962} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:97.7438 Y:4.208708 Z:-140.2434} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:69.82182 Y:4.142137 Z:-124.0002} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:40.14538 Y:4.142137 Z:-114.0002} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:10.46892 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]
shootingrange.GameWorld.Target Pos: {X:10 Y:-50 Z:-50}, Ray: [ {X:5.522848 Y:4.142136 Z:-94.00018} ]



Code:

Bullet.cs
 public override void Draw(GameTime gameTime, Matrix projection, Matrix view)        {            Matrix[] transforms = new Matrix[_model.Bones.Count];            Vector3 nearSource = new Vector3(cameraPosition.X, cameraPosition.Y, 0.0f);            Vector3 farSource = new Vector3(cameraPosition.X, cameraPosition.Y, 1.0f);            Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, projection, Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up), Matrix.Identity);            Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, projection, Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up), Matrix.Identity);            Vector3 direction = farPoint - nearPoint;            direction.Normalize();            // and then create a new ray using nearPoint as the source.             Ray ray = new Ray(nearPoint, farPoint);           // if (ray.Intersects(this.GetBounds()).HasValue == true)           // {           // }            if (world.BulletCollision(ray))            {                redundant = true;                return;            }            if (propagate)            {                modelPosition.Z += bulletSpeed;            }            if (modelPosition.Z > 1000.0f)            {                redundant = true;            }            _model.CopyAbsoluteBoneTransformsTo(transforms);            bounds = new BoundingSphere();            foreach (ModelMesh mesh in _model.Meshes)            {                foreach (BasicEffect effect in mesh.Effects)                {                    effect.Projection = Projection;                    effect.View = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);                    effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateRotationY(600.0f) * Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(modelPosition);                }                bounds = BoundingSphere.CreateMerged(bounds, mesh.BoundingSphere);                mesh.Draw();            }            bounds = bounds.Transform(Matrix.CreateScale(1.0f) * Matrix.CreateTranslation(modelPosition));        }



 public bool BulletCollision(Ray ray)        {            foreach (ISceneObject obj in worldObjects)            {                if (obj.GetBounds().Radius == -1)                {                    continue;                }                if (obj.GetType().ToString() != "shootingrange.GameWorld.Target")                {                    continue;                }                // Use raycasting to determine whether the bullet hits its target                if (ray.Intersects(obj.GetBounds()).HasValue == true)                 {                    Game.points++;                    Target t = (Target)obj;                    Console.WriteLine(obj.GetType() + " Pos: "  + t.GetPosition() + ", Ray: [ " + ray.Position + " ]");                    return true;                }            }            return false;        }


Any more tips? :D Thanks :-)

*bump Anybody? I really can't see where I'm going wrong...

This topic is closed to new replies.

Advertisement