How to make ranged weapons with bullet movement

Hello, developers! ShakesRocker here…

(Now before starting here is a DISCLAIMER! several people here in the devforum already know this method of making guns with bullet motion or perhaps a LOT of them know a much better way of making ranged weapons. The reason I am posting this is because initially when I wanted to make ranged weapons, I couldn’t figure out how to make ranged weapons efficiently and I had struggled a bit to figure it out myself. I tried to search the whole internet, including the devforum and YouTube but couldn’t find any tutorials. Still I couldn’t find any clear explanation on how to make ranged weapons anywhere so I thought this post may be useful to some…

Now I am personally a kind of beginner myself so some of the code or methods I used maybe inefficient.
The actual thing is, more than a tutorial…I posted this just to show my idea on making ranged weapons…So if anybody knows a better way of making ranged weapons, please share your ideas as well!)

I am not from an English speaking nation so I am sorry for my bad grammar

Now in this post I am assuming that you at least know how to make a simple gun using pure raycasting and you know how to send the shoot signal to the server via a remoteEvent and all of that sort of stuffs because if I go on explaining that here it’s going to be a very lengthy post, and also everybody has a different approach of how to make the gun fire and all those kind of things so I dont want to mess with that…

Also I am making an another assumption that you have at least very basic understanding of CFrames and Vectors

So lets get started..

So before getting to the actual code make sure that you have the following variables

local speed = -- whatever bullet speed --
local dropDistance = -- whatever drop distance --
local dropAngle = -- can be calculated from drop distance and workspace gravity but in this post I am not going into actual bullet drop but just the effect it has on the damage so I set its value to 0 --
local damage = -- whatever damage --

Now for creating the bullet motion I recommend to make a separate function named CreateBullet (or whatever name you wold like) that takes 2 parameter, one is the start position of the bullet and the other is the direction (a unit vector) of its motion…

Now in that function you will have to create a bullet, or clone one if you have it stored somewhere

For this post I will go with creating the bullet…

local function CreateFire(startPosition, direction)
	-- Initializing
	local ray, object, position

	-- Creating the bullet
	local bullet = Instance.new("Part", nil)
	bullet.Name = "Bullet"
	bullet.FormFactor = Enum.FormFactor.Custom
	bullet.Anchored = true
	bullet.CanCollide = false
	bullet.Massless = true
	bullet.Locked = true

	--
	bullet.Color = Color3.fromRGB(255, 150, 0)
	bullet.Material = Enum.Material.Metal
	bullet.Transparency = 0

	--
	bullet.Size = Vector3.new(0.05, 0.05, 0.7)

	-- Setting the initial position and orientation of the bullet
	bullet.CFrame = CFrame.new(startPosition, startPosition + direction) * CFrame.new(0, 0, -0.7)
end

(Do not worry about the 3 variables I defined initially, we will use them later on)

Now you have to create a coroutine inside the CreateBullet function that updates the position of the bullet every 1/30th of a second(approximately, as due to some latency it can vary…). (Correct me if I am wrong but I think the length of an empty wait() function is around 1/30th of a second)

local createBulletTrajectory = coroutine.wrap(function()
	while bullet do
		-- Updating the position
		bullet.CFrame = bullet.CFrame * CFrame.new(0, 0, -speed) * CFrame.Angles(-dropAngle, 0, 0) -- Note: Drop angle should be in radians

		wait() -- This is to update the loop every 1/30th a second.
	end
end)

Now the next thing we are going to do add a function that checks if the bullet ‘hits’ the target.

So now we are going to use the 3 variables that we defined at the start of the CreateBullet function and create a function called RayCast.

local function RayCast()
	ray = Ray.new(bullet.CFrame.p, bullet.CFrame.LookVector * speed) -- Since drop angle is 0 here LookVector = direction
	object, position = game.Workspace:FindPartOnRayWithIgnoreList(ray, {-- Your character probably --}, false, true)

	local dropDamageModifier = (position - startPosition).Magnitude/dropDistance
	local _damage = damage/dropDamageModifier

	if _damage > damage then
		_damage = damage
	end

	if _damage < 1 then
		_damage = 0
		game:GetService("Debris"):AddItem(bullet, 0.01)
		bullet = nil -- To remove the bullet object
	end

	if object and bullet then
		local humanoid = object.Parent:FindFirstChildWhichIsA("Humanoid")
		if humanoid then
			humanoid:TakeDamage(_damage)
		end
		game:GetService("Debris"):AddItem(bullet, 0.01)
		bullet = nil -- To remove the bullet object
	end
end

Now If you did not understand the code above let me explain…

Initially we are casting a ray from the bullet’s position to the direction of it’s motion(extending to a distance equal to the magnitude of the speed) of the bullet. Then we are checking if any part hits the ray

Then we define a variable named dropModifier which is equal the ratio of distance of the bullet from its start position and the distance after which the bullet drops (dropDistance).

In the next line we create a new damage variable which is the ratio of the actual damage and the dropModifier

Now as the distance increases, the dropModifier increases as well so, and as the dropModifier increases, the new damage decreases .Eventually as distance increases, the new damage decreases…

But here is another issue:- The reverse is also possible! That if the distance is less than the drop distance then the new damage would be more than the actual damage and we don’t want that! so what we do is, check if the new damage is more than that of the actual damage…and if so then set it back to the original damage.

Now when the distance becomes sufficiently large (distance >> dropDistance) then the damage drops considerably, so in that case if the damage is less than 1, then that damage is more or less useless. So we just make the damage equal to 0 and destroy the bullet make sure that you set the bullet variable’s value to nil so as to stop the loop inside the coroutine we defined earlier.

Then at last we check if the ray hits an object(and sure, if the bullet exists) and then we check if it is hitting a character. If so, then decrease the health of the character’s humanoid by :TakeDamage() function.
Also make sure that once the bullet ‘hits’ an object, destroy it.(since we don’t need the bullet after that and also it works as a good debounce mechanism)

(Now if you want to know the reason why I used ray casting for hit detection than using a Region3 or .Touched event… here is the explanation:-

Now say for instance that the speed of the bullet is 10 studs per 1/30th of a second
so it means that after 1/30th of a second, it will get shifted a 10 studs forward or in other words it ‘lands’ 10 studs ahead of it’s current position
This means that it does not go through all the positions in between.
so if an object is in between, it just passes through it leaving it unaffected which is not good.

Note:
Red: Missed points.
Blue: Initial bullet position
Orange: Final bullet position
Purple: Object
Green: Ray

So if we attempt to cast a ray that starts from the position of the bullet and is directed along the direction of the bullet’s motion and extends 10 studs forwards then even if the bullet does not go throught all the points in the line of it’s motion… the ray still ‘covers’ all those points and just by simple ray-hit detection we can detect if the bullet hits something.)

So finally you just have to put the function inside the CreateBullet function and call it after each time you set the CFrame of the bullet. (Both initially as well as inside the loop)

local function CreateFire(startPosition, direction)
	-- Initializing
	local ray, object, position

	-- Creating the bullet
	local bullet = Instance.new("Part")
	bullet.Name = "Bullet"
	bullet.FormFactor = Enum.FormFactor.Custom
	bullet.Anchored = true
	bullet.CanCollide = false
	bullet.Massless = true
	bullet.Locked = true
	bullet.Parent = game.Workspace -- Or anything you want to like in a folder in workspace

	-- Ray casting function
	local function RayCast()
		ray = Ray.new(bullet.CFrame.p, bullet.CFrame.LookVector * speed)
		object, position = game.Workspace:FindPartOnRayWithIgnoreList(ray, {-- Your character probably --}, false, true)

		local dropDamageModifier = (position - startPosition).Magnitude/dropDistance
		local _damage = damage/dropDamageModifier

		if _damage > damage then
			_damage = damage
		end

		if _damage < 1 then
			_damage = 0
			game:GetService("Debris"):AddItem(bullet, 0.01)
			bullet = nil -- To remove the bullet object
		end

		if object and bullet then
			local humanoid = object.Parent:FindFirstChildWhichIsA("Humanoid")
			if humanoid then
				humanoid:TakeDamage(_damage)
			end
			game:GetService("Debris"):AddItem(bullet, 0.01)
			bullet = nil -- To remove the bullet object
		end
	end
	--
	bullet.Color = Color3.fromRGB(255, 150, 0)
	bullet.Material = Enum.Material.Metal
	bullet.Transparency = 0

	--
	bullet.Size = Vector3.new(0.05, 0.05, 0.7)

	-- Setting the initial position and orientation of the bullet
	bullet.CFrame = CFrame.new(startPosition, startPosition + direction) * CFrame.new(0, 0, -0.7)
	RayCast() -- Initial checking

	local createBulletTrajectory = coroutine.wrap(function()
		while bullet do
			-- Updating the position
			bullet.CFrame = bullet.CFrame * CFrame.new(0, 0, -speed) * CFrame.Angles(-dropAngle, 0, 0) -- Note: Drop angle should be in radians

			RayCast() -- Hit detection

			wait()
		end
	end)
    createBulletTrajectory() -- Running the coroutine thread
end

So now the CreateBullet function is ready so you can call it whenever you fire the gun!
You can add a trail, smoke or whatever effects you like to add to you bullet…

So this is how I make range weapons.
Here is an example of a test gun I made using this method:-
result

(I know that it is not really apparent in short range but in long range it works noticeably)

The advantage of this is that:-

  1. It’s a lot faster in server than using a BodyVelocity! (I have tested it and for me it seems to work great)

Now this has certain drawbacks as well:-

  1. If the weapon is somehow destroyed during bullet motion, the bullet freezes mid air (but that is still unlikely to happen in case of weapons)
  2. This method does not hold good for sniper rifles as in case of sniper rifles, the damage should be more when the distance increases (But that’s easily changeable…)

Please feel free correct any mistakes I made and any better methods or suggestions will be highly appreciated

I hope that at least a some people find it useful…
Thanks a lot for your patience. :+1:

77 Likes

Hello, thank you for this tutorial. I am kind of new to scripting and was wondering if you have an example place that you could set up real quick. I am having some difficulties setting this up. Thanks.

Sure I am working on it…
Once I finish, I will attach the rblx place file.

Here you go:-
TestsWithWeapons.rbxl (35.6 KB)

10 Likes

Great tutorial! Really easy to understand and learn. However I do have a minor problem, I would like to add a trail to the bullet so I tried creating the trail inside the bullet using scripts, but that didn’t work out. So instead of making the trail inside the script I copied a replica bullet in replicated storage and used that instead of creating the bullet inside the script and in that replica I had the trail inside. It worked perfectly but the trail no matter how you position it would go sideways. So I’m wondering how to add a trail to the bullet? Thanks!

3 Likes