So I’m currently learning how to code weapons, and I’ve been told the best resource for this was to look at free models. I’m a bit skeptical of the trustworthiness of free models, though, so I thought the next best thing would be to look at official Roblox gear. I decided to take a look at the Super-G LS33 Disruptor, which was one of my favorite gears growing up, using InsertService and I was able to figure out the vast majority of the code, but have been unable to figure out a few parts of it. I also have a few questions about the necessity and use for some of the code that I was less sure of.
Below is the code with my comments. There are four scripts in total; the first two are server scripts and the last two are local scripts.
RayGun:
--Rescripted by Luckymaxer
Tool = script.Parent -- assigning the gun to the variable "Tool"
Handle = Tool:WaitForChild("Handle") -- assigning the gun's brick to the variable "Handle"
Players = game:GetService("Players") -- fetching Players service
Debris = game:GetService("Debris") -- fetching Debris service
ProjectileScript = script:WaitForChild("ProjectileScript") -- fetching the disabled script childed to this script
BasePart = Instance.new("Part") -- creating a new part
BasePart.Shape = Enum.PartType.Block -- changing the shape to be cubic
BasePart.Material = Enum.Material.Plastic -- changing the material to plastic
BasePart.TopSurface = Enum.SurfaceType.Smooth -- changing the surfaces to smooth
BasePart.BottomSurface = Enum.SurfaceType.Smooth -- changing the surfaces to smooth
BasePart.FormFactor = Enum.FormFactor.Custom -- changing the resizing system to be custom
BasePart.Size = Vector3.new(0.2, 0.2, 0.2) -- changing the size to a small cube
BasePart.CanCollide = true -- setting cancollide true
BasePart.Locked = true -- making unable to be selected
BasePart.Anchored = false -- making it unanchored
BaseProjectile = BasePart:Clone() -- making the projectiles clones of the previously defined part
BaseProjectile.Name = "Projectile" -- naming them as projectiles
BaseProjectile.Shape = Enum.PartType.Ball -- making them spheres
BaseProjectile.Size = Vector3.new(1, 1, 1) -- changing their size to be a bit bigger
BaseProjectile.BrickColor = BrickColor.new("Bright yellow") -- making them yellow
BaseProjectile.Reflectance = 0.5 -- making them shiny
BaseSpark = Instance.new("Sparkles") -- creating a sparkle effect
BaseSpark.Name = "Spark" -- naming the sparkle effect
BaseSpark.SparkleColor = Color3.new(1, 1, 0) -- changing the color of the sparkles
BaseSpark.Enabled = true -- turning them on
BaseSpark.Parent = BaseProjectile -- childing them to the projectiles
Grips = {
Up = CFrame.new(0, -0.600000024, -0.550000012, 1, 0, -0, -0, 0.19592391, -0.980619073, 0, 0.980619192, 0.19592388),
Out = CFrame.new(0, -0.600000024, -0.550000012, 1, 0, -0, 0, 0, -1, 0, 1, -0),
} -- cframes of the animated change in grips (???)
Sounds = {
Equip = Handle:WaitForChild("Equip"),
PewPew = Handle:WaitForChild("PewPew"),
Hit = Handle:WaitForChild("Hit"),
} -- assigning all the sounds to variables inside a sound table
ReloadTime = 0.2 -- creating a constant for the reload time of 0.2 seconds
ServerControl = (Tool:FindFirstChild("ServerControl") or Instance.new("RemoteFunction")) -- creating a remote function referred to as "ServerControl" or assigning a pre-existing remote event of the same name to a variable
ServerControl.Name = "ServerControl" -- naming the remote function
ServerControl.Parent = Tool -- childing it to the gun
ClientControl = (Tool:FindFirstChild("ClientControl") or Instance.new("RemoteFunction")) -- see above except with "ClientControl"
ClientControl.Name = "ClientControl" -- naming the remote function
ClientControl.Parent = Tool -- childing it to the gun
ToolEquipped = false -- creating a boolean that will tell us whether the gun is equipped
Tool.Grip = Grips.Out -- sets the grip position to the default one
Tool.Enabled = true -- makes the tool able to be used
function Fire(Direction) -- function that runs when the gun fires, takes a variable "direction"
local SpawnPos = (Handle.Position + (Direction * 10)) -- identifies where the projectile is going to spawn in physical space by taking the gun's position and then adding some space by taking into account the direction
local Gravity = 196.20 -- defines the force of gravity acting on the projectile
local Force = 180 -- defines another force acting on the projectile
local Mass = (BaseProjectile:GetMass() * Gravity) -- calculates the "weight" of the projectile
local Projectile = BaseProjectile:Clone() -- clones a projectile from the previously defined projectile
Projectile.CFrame = CFrame.new(SpawnPos) -- sets the projectile's CFrame to its spawn position
Projectile.Velocity = (Direction * Force) -- sets the projectile's velocity equal to the direction it's being fired and the previously defined force
local HitSound = Sounds.Hit:Clone() -- clones the hit sound
HitSound.Parent = Projectile -- childs the sound to the projectile
local BodyVelocity = Instance.new("BodyVelocity") -- creating a bodyvelocity
BodyVelocity.maxForce = Vector3.new(Mass, Mass, Mass) -- sets the maximum force that can be exerted on the projectile in any direction to its "weight"
BodyVelocity.velocity = (Direction * Force) -- sets the velocity of the bodyvelocity to the same thing as the projectile's velocity
BodyVelocity.Parent = Projectile -- childs the bodyvelocity to the projectile
local CreatorTag = Instance.new("ObjectValue") -- creating a new objectvalue
CreatorTag.Value = Player -- assigning its value to the player firing the gun???
CreatorTag.Name = "Creator" -- naming it creator
CreatorTag.Parent = Projectile -- childing it to the projectile
local ProjectileScriptClone = ProjectileScript:Clone() -- copying the disabled script childed to this script
ProjectileScriptClone.Disabled = false -- enabling it
ProjectileScriptClone.Parent = Projectile -- childing it to the projectile
Debris:AddItem(Projectile, 5) -- destroying the projectile after five seconds in open flight
Projectile.Parent = game:GetService("Workspace") -- finally adding the projectile to physical space
Sounds.PewPew:Play() -- playing the firing sound
end
function Activated() -- function that runs when the tool is activated (clicked)
if not Tool.Enabled or not ToolEquipped or not CheckIfAlive() then -- if the gun is not able to be used, equipped or the player is dead, then...
return -- do nothing, end the function
end
local TargetPos = InvokeClient("MousePosition") -- assigns the position of the player's mouse on the screen to a variable
if not TargetPos then -- if there is no position to assign, then...
return -- do nothing, end the function
end
TargetPos = TargetPos.Position -- sets the previously defined variable to a position in 3d space???
local Direction = (TargetPos - Handle.Position).unit -- sets the direction by creating a 3d unit vector that points from the gun's brick to the target position
Tool.Enabled = false -- makes the gun unable to be used for the duration of the shot
Tool.Grip = Grips.Up -- transfers the position of the gun to its "fired" angle
Fire(Direction) -- runs the firing function with the given direction
wait(ReloadTime) -- waits for the defined period of time
Tool.Grip = Grips.Out -- transfers back to the default angle
wait(ReloadTime) -- waits again for the defined period of time
Tool.Enabled = true -- allows the gun to be used again now that the shot has been fired
end
function CheckIfAlive() -- function that checks if the player is alive, used in other functions to prevent use after death
return (((Player and Player.Parent and Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0) and true) or false) -- ends the function by providing whether or not the player, character and humanoid exist, are childed to something in the game, and the humanoid is not out of health
end
function Equipped() -- function that runs when the tool is equipped
Character = Tool.Parent -- the character is defined as the tool's parent
Player = Players:GetPlayerFromCharacter(Character) -- the player is defined as the player of the character who the tool is childed to
Humanoid = Character:FindFirstChild("Humanoid") -- the humanoid is defined as that character's humanoid
if not CheckIfAlive() then -- if the check is run and the player is dead, then...
return -- do nothing, end the function
end
ToolEquipped = true -- sets the previously defined boolean to true
Sounds.Equip:Play() -- plays the equipping sound
end
function Unequipped() -- function that runs when the tool is unequipped
Tool.Grip = Grips.Out -- sets the gun's position to the default position regardless of what's happening
for i, v in pairs(Sounds) do -- take every sound in the sound table and...
v:Stop() -- stop it playing
end
ToolEquipped = false -- sets the previously defined boolean to false
end
function InvokeClient(Mode, Value) -- ???
local ClientReturn = nil
pcall(function()
ClientReturn = ClientControl:InvokeClient(Player, Mode, Value)
end)
return ClientReturn
end
ServerControl.OnServerInvoke = (function(player, Mode, Value) -- ???
if player ~= Player or not ToolEquipped or not CheckIfAlive() or not Mode or not Value then -- if the player from the server invoke is not the same as the gun's owner, the tool is not equipped, the gun's owner is dead or "Mode" and "Value" don't exist, then... (???)
return -- do nothing, end the function
end
end)
Tool.Activated:connect(Activated) -- if the tool is activated, run the activated function
Tool.Equipped:connect(Equipped) -- if the tool is equipped, run the equipped function
Tool.Unequipped:connect(Unequipped) -- if the tool is unequipped, run the unequipped function
[Disabled] ProjectileScript:
--Rescripted by Luckymaxer
Part = script.Parent -- assigning the projectile to a variable
Players = game:GetService("Players") -- fetching Players service
Debris = game:GetService("Debris") -- fetching Debris service
Creator = Part:WaitForChild("Creator") -- assigning the objectvalue that describes the owner of the gun the projectile was fired from
Sounds = {
Hit = Part:WaitForChild("Hit")
} -- assigning all the applicable sounds to a table
Damage = 30 -- assigning the damage the projectile will do to a constant
function IsTeamMate(Player1, Player2) -- function that is used to prevent friendly fire
return (Player1 and Player2 and not Player1.Neutral and not Player2.Neutral and Player1.TeamColor == Player2.TeamColor) -- ends the function by providing whether or not both players exist, are not neutral, and have the same color team
end
function TagHumanoid(humanoid, player) -- function to add an objectvalue to a player's humanoid
local Creator_Tag = Instance.new("ObjectValue") -- creates a new objectvalue
Creator_Tag.Name = "creator" -- names it "creator"
Creator_Tag.Value = player -- sets it to the player
Debris:AddItem(Creator_Tag, 2) -- calls Debris service to set it to destroy itself within 2 seconds
Creator_Tag.Parent = humanoid -- childs it to the humanoid
end
function UntagHumanoid(humanoid) -- function to remove all tags from a humanoid
for i, v in pairs(humanoid:GetChildren()) do -- take all the children of the humanoid and...
if v:IsA("ObjectValue") and v.Name == "creator" then -- if the child is an objectvalue and it's named "creator", then...
v:Destroy() -- destroy it
end
end
end
function Touched(Hit) -- function that runs when the projectile is touched
if not Hit or not Hit.Parent then -- if "Hit" does not exist or it is not childed to something, then...
return -- do nothing, end the function
end
local character = Hit.Parent -- assigns the parent of the thing that was hit to a variable, intended to be the player's character
if character:IsA("Hat") then -- if the parent of the thing is a hat or accessory, then...
character = character.Parent -- the variable is now the previous parent's parent, aka the player's character
end
local HasForceField = false -- creating a boolean to determine whether the player has a forcefield or not
for i, v in pairs(character:GetChildren()) do -- take all the children of the character and...
if v:IsA("ForceField") then -- if one of them is a forcefield, then...
HasForceField = true -- change the boolean to reflect that they have a forcefield
end
end
local humanoid = character:FindFirstChild("Humanoid") -- assigning the character's humanoid to a variable
if not HasForceField and humanoid and humanoid.Health > 0 then -- if the player doesn't have a forcefield, the humanoid exists and its not dead, then...
local CreatorPlayer = (((Creator and Creator.Value and Creator.Value:IsA("Player")) and Creator.Value) or nil) -- ???
local player = Players:GetPlayerFromCharacter(character) -- assigns the player of the character being shot to a variable
if CreatorPlayer and player and (CreatorPlayer == player or IsTeamMate(CreatorPlayer, player)) then -- ???
return -- do nothing, end the function
end
UntagHumanoid(humanoid) -- run the untagging function to clear all previous tags
TagHumanoid(humanoid, CreatorPlayer) -- run the tagging function to add the tag to "CreatorPlayer" (???)
humanoid:TakeDamage(Damage) -- delivers the damage to the humanoid
end
Sounds.Hit:Play() -- plays the "Hit" sound
Part:Destroy() -- clears the projectile
end
Part.Touched:connect(Touched) -- when the projectile is touched, run the "Touched" function
Debris:AddItem(Part, 5) -- tells debris service to delete the projectile after five seconds
LocalScript:
--Made by Luckymaxer
Tool = script.Parent -- assigning the gun to a variable
Handle = Tool:WaitForChild("Handle") -- assigning the gun's brick to a variable
Players = game:GetService("Players") -- fetching the "Players" service
RunService = game:GetService("RunService") -- fetching the "Run" service
UserInputService = game:GetService("UserInputService") -- fetching the "User Input" service
Animations = {} -- creating a table of animations (???)
LocalObjects = {} -- creating a table of "local objects" (???)
ServerControl = Tool:WaitForChild("ServerControl") -- assigning the server control remote function to a variable
ClientControl = Tool:WaitForChild("ClientControl") -- assigning the client control remote function to a variable
InputCheck = Instance.new("ScreenGui") -- creates a new screen gui
InputCheck.Name = "InputCheck" -- names it "InputCheck"
InputButton = Instance.new("ImageButton") -- creates an image button
InputButton.Name = "InputButton" -- names it "InputButton"
InputButton.Image = "" -- changes the image to nothing
InputButton.BackgroundTransparency = 1 -- makes the background of the button transparent
InputButton.ImageTransparency = 1 -- makes the image transparent
InputButton.Size = UDim2.new(1, 0, 1, 0) -- sets the button to cover the entirety of its parent
InputButton.Parent = InputCheck -- childs the button to the previous gui
Rate = (1 / 60) -- creates a constant that describes a rate (???)
ToolEquipped = false -- creates a boolean that describes whether or not the gun is equipped
function SetAnimation(mode, value) -- ???
if mode == "PlayAnimation" and value and ToolEquipped and Humanoid then
for i, v in pairs(Animations) do
if v.Animation == value.Animation then
v.AnimationTrack:Stop()
table.remove(Animations, i)
end
end
local AnimationTrack = Humanoid:LoadAnimation(value.Animation)
table.insert(Animations, {Animation = value.Animation, AnimationTrack = AnimationTrack})
AnimationTrack:Play(value.FadeTime, value.Weight, value.Speed)
elseif mode == "StopAnimation" and value then
for i, v in pairs(Animations) do
if v.Animation == value.Animation then
v.AnimationTrack:Stop(value.FadeTime)
table.remove(Animations, i)
end
end
end
end
function CheckIfAlive() -- function that checks if the player is alive, used in other functions to prevent use after death
return (((Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0 and Player and Player.Parent) and true) or false) -- ends the function by providing whether or not the player, character and humanoid exist, are childed to something in the game, and the humanoid is not out of health
end
function Equipped(Mouse) -- function that plays when the tool is equipped
Character = Tool.Parent -- assigns the parent of the gun to a variable
Player = Players:GetPlayerFromCharacter(Character) -- assigns the player of the character to a variable
Humanoid = Character:FindFirstChild("Humanoid") -- assigns the character's humanoid to the variable
ToolEquipped = true -- set the previously defined boolean to true
if not CheckIfAlive() then -- if the player is dead, then...
return -- do nothing, end the function
end
Spawn(function() -- ???
PlayerMouse = Player:GetMouse() -- assigns a variable to the player's mouse
Mouse.Button1Down:connect(function() -- when the player presses down the left mouse button, run this function...
InvokeServer("Button1Click", {Down = true}) -- ???
end)
Mouse.Button1Up:connect(function() -- when the player releases the left mouse button, run this function...
InvokeServer("Button1Click", {Down = false}) -- ???
end)
local PlayerGui = Player:FindFirstChild("PlayerGui") -- assigns a variable to a gui inside the player called "PlayerGui" (???)
if PlayerGui then -- if this gui exists, then...
if UserInputService.TouchEnabled then -- ???
InputCheckClone = InputCheck:Clone() -- creates a clone of the gui created earlier in the script
InputCheckClone.InputButton.InputBegan:connect(function() -- when the button in the gui created earlier is pressed down, run this function...
InvokeServer("Button1Click", {Down = true}) -- does the same thing as when the left mouse button is pressed down
end)
InputCheckClone.InputButton.InputEnded:connect(function() -- when the button in the gui created arleri is released, run this function...
InvokeServer("Button1Click", {Down = false}) -- does the same thing as when the left mouse button is released
end)
InputCheckClone.Parent = PlayerGui -- sets the parent of the gui clone to the gui being assigned earlier
end
end
end)
end
function Unequipped() -- runs when the gun is unequipped
LocalObjects = {} -- ???
if InputCheckClone and InputCheckClone.Parent then -- if the clone of the gui exist and has a parent, then...
InputCheckClone:Destroy() -- destroy it
end
for i, v in pairs(Animations) do -- take every animation and...
if v and v.AnimationTrack then -- if the animation and the animation track exist, then... (???)
v.AnimationTrack:Stop() -- stop the animation from playing
end
end
for i, v in pairs({ObjectLocalTransparencyModifier}) do -- ???
if v then
v:disconnect()
end
end
Animations = {} -- ???
ToolEquipped = false -- set the boolean to false
end
function InvokeServer(mode, value) -- ???
pcall(function()
local ServerReturn = ServerControl:InvokeServer(mode, value)
return ServerReturn
end)
end
function OnClientInvoke(mode, value) -- ???
if mode == "PlayAnimation" and value and ToolEquipped and Humanoid then
SetAnimation("PlayAnimation", value)
elseif mode == "StopAnimation" and value then
SetAnimation("StopAnimation", value)
elseif mode == "PlaySound" and value then
value:Play()
elseif mode == "StopSound" and value then
value:Stop()
elseif mode == "MousePosition" then
if not PlayerMouse and CheckIfAlive() then
PlayerMouse = Player:GetMouse()
end
return {Position = PlayerMouse.Hit.p, Target = PlayerMouse.Target}
elseif mode == "SetLocalTransparencyModifier" and value and ToolEquipped then
pcall(function()
local ObjectFound = false
for i, v in pairs(LocalObjects) do
if v == value then
ObjectFound = true
end
end
if not ObjectFound then
table.insert(LocalObjects, value)
if ObjectLocalTransparencyModifier then
ObjectLocalTransparencyModifier:disconnect()
end
ObjectLocalTransparencyModifier = RunService.RenderStepped:connect(function()
for i, v in pairs(LocalObjects) do
if v.Object and v.Object.Parent then
local CurrentTransparency = v.Object.LocalTransparencyModifier
if ((not v.AutoUpdate and (CurrentTransparency == 1 or CurrentTransparency == 0)) or v.AutoUpdate) then
v.Object.LocalTransparencyModifier = v.Transparency
end
else
table.remove(LocalObjects, i)
end
end
end)
end
end)
end
end
ClientControl.OnClientInvoke = OnClientInvoke -- ???
Tool.Equipped:connect(Equipped) -- when the gun is equipped, run the "Equipped" function
Tool.Unequipped:connect(Unequipped) -- when the gun is unequipped, run the "Unequipped" function
MouseIcon:
--Made by Luckymaxer
Mouse_Icon = "rbxasset://textures/GunCursor.png" -- assigning a variable to the link for the gun cursor
Reloading_Icon = "rbxasset://textures/GunWaitCursor.png" -- assigning a variable to the link for the gun reload cursor
Tool = script.Parent -- assigning a variable to the gun
Mouse = nil -- creating a nil variable for later assignment
function UpdateIcon() -- function that updates the mouse icon when run
if Mouse then -- if "mouse" exists, then...
Mouse.Icon = Tool.Enabled and Mouse_Icon or Reloading_Icon -- ???
end
end
function OnEquipped(ToolMouse) -- function that runs when the gun is equipped
Mouse = ToolMouse -- sets the nil variable equal to the "ToolMouse" (???)
UpdateIcon() -- runs the "UpdateIcon" function
end
function OnChanged(Property) -- ???
if Property == "Enabled" then
UpdateIcon()
end
end
Tool.Equipped:connect(OnEquipped) -- when the gun is equipped, run the "Equipped" function
Tool.Changed:connect(OnChanged) -- when the gun is "changed", run the "Changed" function (???)
Here are a list of questions I was hoping could be answered:
- Anything that has triple question marks next to it I don’t understand at all and need help with
- What is the purpose of the two local scripts aside from some adding some polishing when the two server scripts seem to cover the entire functioning of the gun?
- Going off the previous question, am I correct in my assumption that LocalScript has to do with configuring the gun for mobile players?
- What are the “Animations” and “LocalObjects” in LocalScript in reference to? The only real animation I know about is the shifting of the gun’s grip and that’s done in the server scripts.
- In RayGun, how am I supposed to read the 12-argument CFrames? I’ve looked on the developer wiki but I don’t understand how to interpret the “rotation matrix” the article refers to.
- What is the utility in RayGun of creating the projectile, then cloning it and changing the clone to what the actual projectile looks like, then, in the firing function, cloning that projectile again and making the clone of the clone what’s ultimately fired? Wouldn’t it be easier to just create the projectile within the firing function?
- I need another reminder as to what differentiates remote functions and remote events, and why the former is being used here instead of the latter.
- Is any of this a good way of going about things? What are the possible negatives you see in this?
Please let me know if you need help understanding my comments for whatever reason.