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:-
(I know that it is not really apparent in short range but in long range it works noticeably)
The advantage of this is that:-
- 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:-
- 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)
- 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.