How to check if a bullet is shot close to a player's head?

Apologies in advance if I word this a little weird.

So, I have a typical gun system that registers hits via raycasts and I want to play a bullet whiffing sound whenever someone shoots close to a player’s head. Previously in another game (I think) I had tried making a part the distance of the raycast and checked the distance between the player’s head and the part and that was not very efficient. To visualize gunshots, I use a separate script and a remote event which the server will fire to the client to render the shot.

Would be appreciated if I could get some ideas/ways how to go about this because I’m honestly not sure what to do at this point. I checked around the forum to see if there was any topics covering this but I couldn’t find anything.

1 Like

can’t you use .Magnitude to detect the distance?

Magnitude is a vector property that describes how long a vector is. This is a ray. For that to work, a vector needs to be retrieved in some mannerism, though this has been addressed in the OP.

3 Likes

You could try casting a bunch of rays near the initial ray. If one hits a player, tell that player to play the sound.

Or you could do something similar to the part method and make a Region3 from the ray start to the ray end, only slightly bigger. If a player is in the Region, play the sound for them. I would actually try this first now that I’m thinking about it.

The reason your original method didn’t work is because you were probably comparing the center of your part’s position to the character head’s position. You would need to compare some point along your part near the head’s position with the head’s actual position to figure out if the bullet was near the head or not. I’m not mathematically inclined but I’m pretty sure that’s possible if the other two ideas I gave you fail.

2 Likes

I’ll definitely try this out later on as I can’t do it at the moment. Thanks for your input!

This is probably a super hacky solution, but I would most likely create a separate hitbox that is colinear to the ray, and shares the same origin, midpoint, and endpoint. The width & height will depend on how far away you want the shot to be heard.

For example, when a client renders a raycast bullet, they will simultaneously render an invisible hitbox that will detect if the bullet “whizzed past them” by using some type of collision detection of your choice (it can really be anything; GetTouchingParts(), rotated Region3, Touched, etc. ). In addition, you can negate the client that shot the bullet from hearing the whizz sound by using a simple if player ~= playerfromremoteevent check if you are using FireAllClients() to render your projectiles.

I’d personally use GetTouchingParts() because it detects parts that are intersecting, even if they have collisions turned off.

Hope this helped!

1 Like

You can make a part that is put in the players head that sticks out on the side to detect a bullet that touched the part but not the player.

3 Likes

This is a fun vector problem! Feel free to skip to the end if you only want the code.

Step 1: The problem

Imagine that you are the blue point and an enemy is the green point. Your gun shoots in the direction of the blue vector a, and the enemy is in the direction of the green vector b.

Step1

The shortest distance from the enemy to the bullet is c, which is perpendicular to the gunshot. Hence, we have a right triangle.
1

Step 2: The setup

Let’s call the angle between the two vectors θ.

Step2

Let a be the third side of the triangle (I know the blue vector is longer, but I’m not redoing the graphic). If you’re familiar with trigonometry, we know that sin(θ) = opposite / hypotenuse, or in this case, sin(θ) = c / b. So if we find θ and b, we can find c.

Step 3: Solve for b

This is rather simple. The length of vector b is its magnitude. We write this as |b|.

Step 4: Solve for θ

There is a special operation we can perform on two vectors called the dot product. I’ll let EgoMoose explain it here.

We can calculate the dot product of vectors a and b as follows:

a = (a1, a2)
b = (b1, b2)
a • b = a1*b1 + a2*b2

What makes the dot product super special is that it can actually be defined in two ways.

a • b = |a||b|cos(θ)

Setting them equal to eachother, we can solve for θ.

a1*b1 + a2*b2 = |a||b|cos(θ)
⇒ θ = acos((a1*b1 + a2*b2) / |a||b|)
Step 5: Putting it all together

We’ve solved for b and θ. Rearranging the sine formula, we get c = sin(θ) * b. Now we can plug in b and θ and solve.

c = sin(acos((a1*b1 + a2*b2) / |a||b|)) * |b|
Step 6: A loose end

We have one final loose end to deal with. If the gun shoots away from the enemy, we can throw out all of our math. The enemy’s distance to the bullet simply becomes their distance to the gun.

Loose%20end

We know this occurs when the angle is greater than 90 degrees. This also means the bullet doesn’t whiz by the enemy’s ear at all, so do with that what you will.

-- @param origin Vector3 The position of the gun
-- @param a 	 Vector3 The vector from the gun to the enemy
-- @param b	     Vector3 The vector of the bullet
local function DistToBullet(origin, a, b)
	local theta = math.acos(math.clamp(a:Dot(b) / (a.Magnitude * b.Magnitude), -1, 1))
	local hypotenuse = a.Magnitude

	if theta > math.pi / 2 then
		return hypotenuse
	else
		return math.sin(theta) * hypotenuse
	end
end

EDIT: My solution is trig-based, whereas sircfenner’s uses simpler vector math. I suggest you use his because it also has early exits and runs faster.

13 Likes

This is an alternative way to find the distance between a bullet’s (straight) path and some point (e.g. a player’s head). It uses some concepts from AstroCode’s answer above so you should look there for explanations and helpful diagrams.

Note that in this case the parameters are different, and bulletDirection should be a unit vector.

local eps = 0.0001

local function distToBullet(bulletOrigin, bulletDirection, enemyPosition)
	local toEnemy = enemyPosition - bulletOrigin
	local toEnemyDist = toEnemy.magnitude
	
	if toEnemyDist <= eps then
		-- bullet fired from inside/beside the enemy (edge case)
		-- return distance of 0
		return 0
	end

	local along = toEnemy:Dot(bulletDirection)
	if along - eps <= 0 then
		-- pointing away from / perpendicular to enemy
		-- return distance from bullet origin to enemy
		return toEnemyDist
	end
	
	-- pointing somewhat toward the enemy
	-- return closest distance from bullet's path to the enemy
	return math.sqrt(toEnemyDist ^ 2 - along ^ 2)
end
3 Likes

I mean, it would seem to me the easiest thing to do would be to ensure you are using Unit Rays, then use Ray:ClosestPoint to find the closest location to the player’s head, than use the magnitude between their head and that position to determine if the sound should be played.

2 Likes
local bulletUnitRay = Ray.new(bulletRay.Origin, bulletRay.UnitDirection)
local distanceToHead = bulletUnitRay:Distance(playerHead.Position)
3 Likes

I believe @TecmagDiams and @mr_smellyman have offered the most efficient solutions here.

2 Likes

Or rather than construct the Unit Ray, all Ray’s have .Unit which returns a Unit Ray from them.

local distanceToHead = bulletRay.Unit:Distance(playerHead.Position)

Neat, I didn’t know this method existed. It certainly is simpler and faster. Here’s the elapsed time of a million iterations of each:

image

1 Like

Hope I don’t get out of the topic here, but by simply putting the sound inside the bullet part and play it, it works, at least for me. Make sure to use the right SoundDistance and RollOffMode.
Here’s the sound effect that I use:
https://www.roblox.com/library/3593129248/Bullet-Ricochet

1 Like

You should really consider that rays are not the best solution for what you’re trying to do. Assuming your gun uses projectile motion and isn’t a point-click-laser gun, you could just do vector math on the true position of both objects.

If the gun is entirely based off of rays and does not involve projectile motion, then I would recommend reconsidering that path and focusing on projecitle motion when making guns.

The most mathematically correct correct way (in my opinion) of doing this is through a simple vector projection. ROBLOX already has this implemented with Ray:ClosestPoint() and Ray:Distance() which handles everything for you.

This works, thanks! Decided to go with this because it’s simple and efficient and barely complicated. Though I do appreciate the other solutions everyone else has given me. Sorry for the long wait on my reply.

2 Likes

No problem. @TecmagDiams is right though, his version is even easier. I just have some weird aversion to using Ray.Unit because it feels weird.

1 Like

Personally I think welding a part to the bullet would be the best solution, and use part.Touched.