Preface
Alright so the developer hub and old wiki had this, although a few months ago it was removed from the former because it was just so terribly outdated it was a joke.
So I thought I should revive that old tutorial.
A lot has happened since that tutorial, like new raycasting api being added and filtering enabled being enforced and that tutorial was just never updated.
So I am making this with that all in mind.
Let’s begin!
Alright so first you will want your weapon.
I grabbed a Colt M1911A1 from the toolbox. I have welded each part to the Handle and the welds are in a Folder. The rest of the parts except the Handle and Shoot part are in another Folder called Parts
.
The Shoot part will be where the bullets are shot from.
I then added a Script and LocalScript, the former named Backend
and latter named Frontend
. I then added a RemoteEvent called OnShoot
which will be fired when the tool is activated.
Then I put the Tool in StarterPack just so the tool is instantly in your backpack for testing.
Coding the frontend
So I will start with the LocalScript.
First I will declare some variables.
local tool = script.Parent
local remote = tool:WaitForChild("OnShoot")
local Players = game:GetService("Players")
local client = Players.LocalPlayer
local cursor = client:GetMouse()
Small note
While I don’t like advertising my own stuff I have made a custom mouse module. The vanilla mouse is legacy, and is likely to be deprecated in the future. So I have made this (which exposes PlayerMouse2.Position
) as a modern alternative. It isn’t a full replacement though unfortunately due to some restrictions. If you would like to learn how to get the hit position, have a look at this reply of mine, position is either result and result.Position or origin + direction
.
But for simplicity’s sake I will just use the legacy mouse.
Pretty straightforward. Now we will want to listen for the tool’s Activated
event and just fire the remote with the mouse’s position:
local tool = script.Parent
local remote = tool:WaitForChild("OnShoot")
local Players = game:GetService("Players")
local client = Players.LocalPlayer
local cursor = client:GetMouse()
tool.Activated:Connect(function()
remote:FireServer(cursor.Hit.Position)
end)
Then in the backend script you will want to listen for the OnServerEvent
event of the OnShoot
remote.
Coding the backend
So I am going to steal some variables from the frontend script and connect to OnServerEvent
. I will also declare a variable for Workspace, in addition to one for the Shoot
part from the tool. no im not using the built-in one but you can if you want
local tool = script.Parent
local shoot_part = tool:WaitForChild("Shoot")
local remote = tool:WaitForChild("OnShoot")
local Workspace = game:GetService("Workspace")
remote.OnServerEvent:Connect(function(player, position)
-- Nothing
end)
Now to cast a ray.
local tool = script.Parent
local shoot_part = tool:WaitForChild("Shoot")
local remote = tool:WaitForChild("OnShoot")
local Workspace = game:GetService("Workspace")
local ServerStorage = game:GetService("ServerStorage")
remote.OnServerEvent:Connect(function(player, position)
local origin = shoot_part.Position
local direction = (position - origin).Unit
local result = Workspace:Raycast(shoot_part.Position, direction*300)
end)
So here we are just getting the direction of the shoot part position and the mouse position, and extending it for 300 studs since that is what the old tutorial did, although of course you can extend it further or even decrease it.
WorldRoot:Raycast
returns nil if the ray cast didn’t hit anything.
local tool = script.Parent
local shoot_part = tool:WaitForChild("Shoot")
local remote = tool:WaitForChild("OnShoot")
local Workspace = game:GetService("Workspace")
remote.OnServerEvent:Connect(function(player, position)
local origin = shoot_part.Position
local direction = (position - origin).Unit
local result = Workspace:Raycast(shoot_part.Position, direction*300)
local intersection = result and result.Position or origin + direction*300
end)
So that is what the result and result.Position or origin + direction*300
is for, since I am pretty sure even if the player doesn’t click on anything you would want a bullet to be shot nonetheless. Logically, if the ray doesn’t hit anything, the “hit position” is origin + direction
.
Now to create the bullet. You can pre-make yours, which I already have.
It is just a neon part whose color is New Yeller, sized 0.1, 0.1, 1
, it is anchored and has collisions disabled. Although in code the part will be resized. I then put it in ServerStorage.
Continuing.
local tool = script.Parent
local shoot_part = tool:WaitForChild("Shoot")
local remote = tool:WaitForChild("OnShoot")
local Workspace = game:GetService("Workspace")
local ServerStorage = game:GetService("ServerStorage")
remote.OnServerEvent:Connect(function(player, position)
local origin = shoot_part.Position
local direction = (position - origin).Unit*300
local result = Workspace:Raycast(origin, direction)
local intersection = result and result.Position or origin + direction
local distance = (origin - intersection).Magnitude
local bullet_clone = ServerStorage.Bullet:Clone()
bullet_clone.Size = Vector3.new(0.1, 0.1, distance)
bullet_clone.CFrame = CFrame.new(origin, intersection)*CFrame.new(0, 0, -distance/2)
bullet_clone.Parent = Workspace
end)
So I am doing quite a bit.
First we are getting the distance in studs between the origin and the intersection. Then cloning the bullet and resizing it on the Z axis. Then I am setting its CFrame
to the origin of the part, looking towards the intersection
. Then parenting to workspace.
Now I want to quickly explain that offset of -distance/2
studs. Without if, the center of the part would be at the origin.
So we want to get one “half” of the part and move it forward.
Now to make the gun deal damage.
local tool = script.Parent
local shoot_part = tool:WaitForChild("Shoot")
local remote = tool:WaitForChild("OnShoot")
local Workspace = game:GetService("Workspace")
local ServerStorage = game:GetService("ServerStorage")
remote.OnServerEvent:Connect(function(player, position)
local origin = shoot_part.Position
local direction = (position - origin).Unit*300
local result = Workspace:Raycast(origin, direction)
local intersection = result and result.Position or origin + direction
local distance = (origin - intersection).Magnitude
local bullet_clone = ServerStorage.Bullet:Clone()
bullet_clone.Size = Vector3.new(0.1, 0.1, distance)
bullet_clone.CFrame = CFrame.new(origin, intersection)*CFrame.new(0, 0, -distance/2)
bullet_clone.Parent = Workspace
if result then
-- Nothing
end
end)
Like I mentioned before, WorldRoot:Raycast
returns nil if the ray casted never hit anything. So we want to confirm that it did and from there check if there is a humanoid to damage. Then wait 1/4th of a second (or however long you want really) to delete the bullet!
local tool = script.Parent
local shoot_part = tool:WaitForChild("Shoot")
local remote = tool:WaitForChild("OnShoot")
local Workspace = game:GetService("Workspace")
local ServerStorage = game:GetService("ServerStorage")
remote.OnServerEvent:Connect(function(player, position)
local origin = shoot_part.Position
local direction = (position - origin).Unit*300
local result = Workspace:Raycast(origin, direction)
local intersection = result and result.Position or origin + direction
local distance = (origin - intersection).Magnitude
local bullet_clone = ServerStorage.Bullet:Clone()
bullet_clone.Size = Vector3.new(0.1, 0.1, distance)
bullet_clone.CFrame = CFrame.new(origin, intersection)*CFrame.new(0, 0, -distance/2)
bullet_clone.Parent = Workspace
if result then
local part = result.Instance
local humanoid = part.Parent:FindFirstChild("Humanoid") or part.Parent.Parent:FindFirstChild("Humanoid")
if humanoid then
humanoid:TakeDamage(10)
end
end
wait(0.25)
bullet_clone:Destroy()
end)