Hello,
This gun system is primary more functional programming than object oriented programming with the exception of a few functions stored within a module. Every gun automatically inherits the functions defined in the module script, and obviously this makes the system so much easier to patch bugs and build off of. But there are still many variables and functions that are being copied for every gun which is a big no-no.
I am new to OOP and ANY advice would really help. I want to make as many of these functions and variables object oriented as possible. I know there has to be a way to build this system entirely OO since every gun shares common characteristics like equip animations, firing sounds, muzzle flash, aimpart etc. I just don’t know how to go about building it OO.
Side note: If there is any way I can improve the formatting or syntax I would appreciate any suggestions.
-- Player
local Player = game:GetService("Players").LocalPlayer;
local PlayerGui = Player:WaitForChild("PlayerGui");
local PlayerData = Player:WaitForChild("PlayerData");
local Mouse = Player:GetMouse();
local Camera = workspace.CurrentCamera;
workspace:WaitForChild(Player.Name);
-- Character
local Character = Player.Character or Player.CharacterAdded:Wait();
local Humanoid = Character:WaitForChild("Humanoid");
local Torso = Character:WaitForChild("Torso");
local RightShoulderM6D = Torso:WaitForChild("Right Shoulder");
local LeftShoulderM6D = Torso:WaitForChild("Left Shoulder");
local HumanoidAnimator = Humanoid:WaitForChild("TestAnimator");
-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage");
local RunService = game:GetService("RunService");
local UserInputService = game:GetService("UserInputService");
local PhysicsService = game:GetService("PhysicsService");
local TweenService = game:GetService("TweenService");
-- Modules
local SpringModule = require(ReplicatedStorage:WaitForChild("Spring"));
local GameSettings = require(ReplicatedStorage:WaitForChild("GameSettings"));
local GunSettings = require(ReplicatedStorage:WaitForChild("GunSettings"));
local FunctionLibrary = require(ReplicatedStorage:WaitForChild("FunctionLibrary"))
local CameraControl = require(ReplicatedStorage:WaitForChild("CameraControl"));
-- Viewmodel
local ViewModelStorage = ReplicatedStorage:WaitForChild(Player.Name.."ViewModelStorage");
local ViewModel = Camera:FindFirstChild("ViewModel")
or ViewModelStorage:WaitForChild("ViewModel");
local ViewModelToolGrip = ViewModel:WaitForChild("Torso"):WaitForChild("ToolGrip");
-- Values changed in the viewmodel system for custom effects
local Offset = Character:WaitForChild("ViewModelController"):WaitForChild("Offset");
local MouseSwayIntensity = Character:WaitForChild("ViewModelController"):WaitForChild("MouseSwayIntensity");
local MovementSwayIntensity = Character:WaitForChild("ViewModelController"):WaitForChild("MovementSwayIntensity");
-- Items in RS
local GameAssets = ReplicatedStorage:WaitForChild("GameAssets");
local RemoteEvents = GameAssets:WaitForChild("RemoteEvents");
local DamageHumanoidEvent = RemoteEvents:WaitForChild("DamageHumanoid");
local CreateBloodEvent = RemoteEvents:WaitForChild("CreateBlood");
-- Gui elements
local MainGui = PlayerGui:WaitForChild("MainGui");
local Inventory = MainGui:WaitForChild("Inventory");
local Items = Inventory:WaitForChild("Items");
local MagHUD = MainGui:WaitForChild("MagHUD");
local ModeIndicator = MagHUD:WaitForChild("ModeIndicator");
local GunCursor = MainGui:WaitForChild("GunCursor");
-- Mobile controls
local MobileControls = PlayerGui:WaitForChild("MobileControls"):WaitForChild("GunControls");
local ShootButton = MobileControls:WaitForChild("Shoot");
local ReloadButton = MobileControls:WaitForChild("Reload");
local AimButton = MobileControls:WaitForChild("Aim");
local FireModeButton = MobileControls:WaitForChild("FireMode");
local UsingMobileControls = false;
local EquipAnimation = Instance.new("Animation");
EquipAnimation.Parent = script.Parent;
EquipAnimation.AnimationId = "rbxassetid://12732854269"
EquipAnimation = HumanoidAnimator:LoadAnimation(EquipAnimation);
EquipAnimation:SetAttribute("ViewModelAnimation", true);
-- animations with attribute "ViewModelAnimation" will also be played on the viewmodel
local ReloadAnimation = Instance.new("Animation");
ReloadAnimation.Parent = script.Parent;
ReloadAnimation.AnimationId = "rbxassetid://12733162324";
ReloadAnimation = HumanoidAnimator:LoadAnimation(ReloadAnimation);
ReloadAnimation:SetAttribute("ViewModelAnimation", true);
local AimAnimation = Instance.new("Animation");
AimAnimation.Parent = script.Parent;
AimAnimation.AnimationId = "rbxassetid://12734776468";
AimAnimation = HumanoidAnimator:LoadAnimation(AimAnimation);
-- this animation does not play on viewmodel
local SprintAnimation = Instance.new("Animation");
SprintAnimation.Parent = script.Parent;
SprintAnimation.AnimationId = "rbxassetid://12745621182";
SprintAnimation = HumanoidAnimator:LoadAnimation(SprintAnimation);
SprintAnimation:SetAttribute("ViewModelAnimation", true)
SprintAnimation:SetAttribute("Sprint", true);
local SlotLink = script:WaitForChild("Data"):WaitForChild("SlotLink")
--SlotLink is an object value that stores a link to the current inventory slot where a magazine exists (if found)
-- Gun Objects
local Gun = script.Parent;
local Handle = Gun:WaitForChild("Handle");
local FireSound = Handle:WaitForChild("Fire");
local ReloadSound = Handle:WaitForChild("Reload");
local ToggleSound = Handle:WaitForChild("Toggle");
local MuzzleLight = Gun:WaitForChild("Muzzle"):WaitForChild("Light");
local MuzzleFlash = Gun:WaitForChild("Muzzle"):WaitForChild("Flash");
local CloneLight = nil;
local CloneFlash = nil;
local AimPart;
local GunClone;
-- Gun States
local GunEquipped = false
local FoundMagazine = false;
local Firing = false;
local Reloading = false;
-- Gun Settings
local AimingMouseSensitivity = UserInputService.MouseDeltaSensitivity / 2 -- reduce mouse movement sensitivity while aiming
local DefaultMouseSensitivity = UserInputService.MouseDeltaSensitivity;
local DefaultMouseIcon = UserInputService.MouseIcon;
local MouseIcon = "http://www.roblox.com/asset/?id=9947313248";
local LastFire = 0;
local ShootDelay = 1/12; -- rate of fire
local ReloadDelay = 3; -- amount of time it takes to reload
local HeadDamage = GunSettings.M16A2HeadDamage;
local BodyDamage = GunSettings.M61A2BodyDamage;
local MouseSway = 35; -- Amount of mouse sway effect the viewmodel will show. More sway makes the gun feel heavier
local MovementSway = 20; -- Amount of movement sway on viewmodel. More movement sway the heavier the gun feels
local Range = GunSettings.M16A2Range -- Range of gun
local HipfireMaxSpread = 7; -- Maximum spread for hipfire. spread = math.random(-HipFireMaxSpread, HipeFireMaxSpread)
local MaxSpreadDivisor = 5;-- Divides random spread (direction = spread/math.random(1, MaxSpreadDivisor))
local AimingMaxSpread = 0; -- Amount of inaccuracy the gun has while aiming
local AppliedBulletSpread = Vector3.new();
local FireModes = {"Semi", "Burst", "Auto"}; -- firing modes gun can use
local FireModeIndex = 1; -- default firing mode for gun
local AimZoomIn = {FieldOfView = 55}; -- Aiming FOV
local AimZoomOut = {FieldOfView = 70}; -- Default FOV
local AimTweenInfo = TweenInfo.new(.35, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut);
local AimInTween = TweenService:Create(Camera, AimTweenInfo, AimZoomIn);
local AimOutTween = TweenService:Create(Camera, AimTweenInfo, AimZoomOut);
-- Player States
local Sprinting = PlayerData:WaitForChild("Sprinting");
local IsAiming = PlayerData:WaitForChild("IsAiming");
local LockMouseCenter = PlayerData:WaitForChild("LockMouseCenter");
local LockThirdPerson = PlayerData:WaitForChild("LockThirdPerson");
local InFirstPerson;
local Aiming = false;
local AimSpring = SpringModule.new(0)
AimSpring.Damper = 1
AimSpring.Speed = 20;
-- the faster the speed the faster the gun aims down sights
local Recoil = SpringModule.new(Vector3.new())
Recoil.Speed = 100
-- the lower the recoil speed, the more the gun kicks
local CamRecoil = SpringModule.new(0)
CamRecoil.Speed = 20;
CamRecoil.Damper = 0.8
-- the lower the speed, the more camera recoil there is
-- the lower the damper, the more bounce there is
local PreviousCamTransform = CFrame.new();
local function UpdateMagHUD()
ModeIndicator.Text = FireModes[FireModeIndex];
MagHUD.Visible = true;
if SlotLink.Value ~= nil and SlotLink.Value.Data.BulletsHeld.Value > 1 then
MagHUD.Text = SlotLink.Value.Data.BulletsHeld.Value.."/"..SlotLink.Value.Data.MaxBulletsHeld.Value;
else
MagHUD.Text = "Empty";
end
end
local function EquipGun()
UpdateMagHUD();
GunCursor.Visible = true;
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter;
if not FoundMagazine then
CheckForMagazineInInventory();
end
local OnMobileDevice = FunctionLibrary.CheckIfMobileDevice();
if OnMobileDevice then
UsingMobileControls = true
MobileControls.Visible = true
end
ModeIndicator.Text = FireModes[FireModeIndex];
Character["Right Arm"]:WaitForChild("RightGrip"):Destroy();
ViewModel.Parent = Camera;
GunClone = Gun:Clone();
GunClone.Parent = ViewModel;
local GunCloneMuzzle = GunClone.Muzzle;
-- used for creating flash effects on viewmodel
CloneLight = GunCloneMuzzle.Light;
CloneFlash = GunCloneMuzzle.Flash;
ViewModelToolGrip.Part1 = GunClone.BodyAttach
AimPart = GunClone.AimPart;
local M6D = Character.Torso:WaitForChild("BodyAttachMotor");
M6D.Part1 = Gun.BodyAttach;
UserInputService.MouseIconEnabled = false;
MovementSwayIntensity.Value = MovementSway;
MouseSwayIntensity.Value = MouseSway;
EquipAnimation:Play();
EquipAnimation:GetMarkerReachedSignal("AnimationPause"):Connect(function()
EquipAnimation:AdjustSpeed(0);
print("Animation frozen.")
end)
InFirstPerson = FunctionLibrary.CheckIfInFirstPerson();
if InFirstPerson then
ModifyTransparency(ViewModel, 0)
ModifyTransparency(Gun, 1)
else
ModifyTransparency(ViewModel, 1)
ModifyTransparency(Gun, 0);
end
for _, Object in ipairs(GunClone:GetChildren()) do
if Object.ClassName =="Part"
or Object.ClassName == "MeshPart"
then
-- don't destroy the object
else
Object:Destroy();
end
end
end
Gun.Equipped:Connect(EquipGun)
local function UnequipGun()
AimOut();
MagHUD.Visible = false;
ViewModel.Parent = ViewModelStorage;
EquipAnimation:Stop();
SprintAnimation:Stop();
GunClone:Destroy();
GunCursor.Visible = false;
end
Gun.Unequipped:Connect(UnequipGun);
function CheckForMagazineInInventory()
local PlayerHasMagazine, Slot = FunctionLibrary.CheckForMagazineInInventory(SlotLink, script.Data);
if PlayerHasMagazine == true then
FoundMagazine = true
SlotLink.Value = Slot
UpdateMagHUD()
else
FoundMagazine = false
SlotLink.Value = nil;
UpdateMagHUD()
end
end
local function FireGun()
if (Gun.Parent ~= Character)
then return
else
if FoundMagazine == true
and SlotLink.Value.Data.BulletsHeld.Value > 0
and not Reloading then
-- make sure gun does not fire faster than ShootDelay
local CurrentFire = tick()
if CurrentFire - LastFire >= ShootDelay
then
-- fire gun
if InFirstPerson then
FunctionLibrary.MuzzleFlashEffect(CloneFlash, CloneLight);
else
FunctionLibrary.MuzzleFlashEffect(MuzzleFlash, MuzzleLight);
end
LastFire = CurrentFire;
Recoil.Velocity = Vector3.new(0, 0, 12)
CamRecoil.Velocity = 100
FireSound:Play();
SlotLink.Value.Data.BulletsHeld.Value -= 1;
SlotLink.Value.MagazineDisplay.Text = SlotLink.Value.Data.BulletsHeld.Value.."/"..SlotLink.Value.Data.MaxBulletsHeld.Value;
UpdateMagHUD();
if Aiming then
-- generate random bullet spread
AppliedBulletSpread = Vector3.new(math.random(-AimingMaxSpread, AimingMaxSpread), math.random(-AimingMaxSpread, AimingMaxSpread), math.random(-AimingMaxSpread, AimingMaxSpread))/math.random(MaxSpreadDivisor);
else
AppliedBulletSpread = Vector3.new(math.random(-HipfireMaxSpread, HipfireMaxSpread),math.random(-HipfireMaxSpread, HipfireMaxSpread), math.random(-HipfireMaxSpread, HipfireMaxSpread))/math.random(1, MaxSpreadDivisor);
end
local RaycastResult, SubjectHumanoid = FunctionLibrary.FireGunRaycast(Gun, SlotLink.Value, AppliedBulletSpread, Range);
if RaycastResult then
local Damage = nil;
print("Ray hit "..RaycastResult.Instance.Name)
if SubjectHumanoid then
print("Do damage");
if RaycastResult.Instance.Name == "Head" then
Damage = HeadDamage;
print("Hit head")
else
Damage = BodyDamage;
print("Hit body")
end
SubjectHumanoid:TakeDamage(Damage);
DamageHumanoidEvent:FireServer(SubjectHumanoid, Damage)
CreateBloodEvent:FireServer(RaycastResult.Instance);
else
print("Creat effect for".. RaycastResult.Instance.Name);
FunctionLibrary.CreateBulletHitEffect(RaycastResult);
end
end
end
else print("Attempting reload.."); -- if there is not a magazine, then try to reload
if not Reloading then
ReloadGun();
end
end
end
end
function ReloadGun()
CheckForMagazineInInventory();
if FoundMagazine then
Reloading = true
ReloadAnimation:Play();
ReloadSound:Play();
ReloadSound:Play();
MagHUD.Text = "Reloading..";
wait(ReloadDelay);
Reloading = false;
print("Reloaded");
UpdateMagHUD();
end
end
function AimIn()
print("Aim in")
Aiming = true;
AimAnimation:Play();
AimInTween:Play();
Humanoid.WalkSpeed = GameSettings.AimingWalkSpeed;
GunCursor.Visible = false;
AimAnimation:GetMarkerReachedSignal("AnimationPause"):Connect(function()
AimAnimation:AdjustSpeed(0);
end)
end
function AimOut()
print("Aim out")
Aiming = false; -- stop aiming gun
AimOutTween:Play();
AimAnimation:Stop();
Humanoid.WalkSpeed = GameSettings.WalkSpeed;
GunCursor.Visible = true;
UserInputService.MouseDeltaSensitivity = DefaultMouseSensitivity;
end
function ModifyTransparency(Descendants, Transparency)
for _, Part in ipairs(Descendants:GetDescendants()) do
if Part.ClassName == "Part"
or Part.ClassName == "MeshPart"
then
Part.LocalTransparencyModifier = Transparency;
end
end
end
local function UpdateSystem()
if Gun.Parent == Character
and GunClone
and ViewModel
then
GunCursor.Position = UDim2.new(0, Mouse.X, 0, Mouse.Y);
InFirstPerson = FunctionLibrary.CheckIfInFirstPerson();
if InFirstPerson == true and Character.Torso.LocalTransparencyModifier ~= 1
and Gun.Handle and Gun.Handle.LocalTransparencyModifier ~= 1
then
ModifyTransparency(ViewModel, 0);
ModifyTransparency(Gun, 1);
print("show viewmodel")
elseif InFirstPerson == false and Character.Torso.LocalTransparencyModifier ~= 0
and Gun.Handle.LocalTransparencyModifier ~= 0
then
ModifyTransparency(ViewModel, 1);
ModifyTransparency(Gun, 0)
print("hide viewmodel");
AimOut();
end
local CamTransform = CFrame.fromEulerAnglesXYZ(math.rad(CamRecoil.Position), 0, 0)
Camera.CFrame = Camera.CFrame * (CamTransform * PreviousCamTransform:Lerp(CFrame.new(), 0.02):Inverse())
PreviousCamTransform = CamTransform
local HandleTransform = ViewModel:GetPrimaryPartCFrame():ToObjectSpace(GunClone.Handle.CFrame)
local OriginalTransform = HandleTransform * CFrame.new(Recoil.Position) * HandleTransform:Inverse()
local AimTransform = AimPart.CFrame:ToObjectSpace(GunClone.Handle.CFrame) * CFrame.new(Recoil.Position) * HandleTransform:Inverse()
Offset.Value = OriginalTransform:Lerp(AimTransform, AimSpring.Position)
if Aiming then
AimSpring.Target = 1
UserInputService.MouseDeltaSensitivity = AimingMouseSensitivity;
IsAiming.Value = true;
else
AimSpring.Target = 0
UserInputService.MouseDeltaSensitivity = DefaultMouseSensitivity;
IsAiming.Value = false;
end
if Sprinting.Value == true then
if not SprintAnimation.IsPlaying then
SprintAnimation:Play();
SprintAnimation:GetMarkerReachedSignal("AnimationPause"):Connect(function()
SprintAnimation:AdjustSpeed(0);
end)
end
else
SprintAnimation:Stop();
end
end
end
RunService.RenderStepped:Connect(UpdateSystem)
function CheckFireMode()
if FireModes[FireModeIndex] == "Semi" then
FireGun()
end
if FireModes[FireModeIndex] == "Burst" then
for x = 1, 3 do
wait(ShootDelay)
FireGun()
end
end
if FireModes[FireModeIndex] == "Auto" then
Firing = true
while Firing do
wait(ShootDelay)
FireGun();
end
end
end
local function ToggleFireMode()
ToggleSound:Play();
if FireModes[FireModeIndex + 1] ~= nil then
FireModeIndex += 1
else
FireModeIndex = 1;
end
print(FireModes[FireModeIndex]);
ModeIndicator.Text = FireModes[FireModeIndex]
end
UserInputService.InputBegan:Connect(function(Input, GameProcessed)
-- detect all relevant user input and procede with corresponding actions
if Input.UserInputType == Enum.UserInputType.MouseButton1
and not GameProcessed and not Reloading
then
Firing = true;
CheckFireMode();
end
if Input.UserInputType == Enum.UserInputType.MouseButton2
and Sprinting.Value == false
and not GameProcessed
and Character.Head.LocalTransparencyModifier > 0.6 then -- aim gun
AimIn()
end
if Input.KeyCode == Enum.KeyCode.R --reload gun
and not GameProcessed and Gun.Parent == Character then
ReloadGun();
end
if Input.KeyCode == Enum.KeyCode.T --toggle fire mode
and not GameProcessed and Gun.Parent == Character then
ToggleFireMode()
end
end)
UserInputService.InputEnded:Connect(function(Input, GameProcessed)
if Input.UserInputType == Enum.UserInputType.MouseButton1 then
Firing = false; -- stop firing gun
end
if Input.UserInputType == Enum.UserInputType.MouseButton2
and Character.Head.LocalTransparencyModifier > 0.6 then
AimOut();
end
end)
ShootButton.MouseButton1Down:Connect(function()
Firing = true
CheckFireMode();
end)
ShootButton.MouseButton1Up:Connect(function()
Firing = false
end)
ReloadButton.MouseButton1Click:Connect(function()
ReloadGun();
end)
FireModeButton.MouseButton1Click:Connect(function()
ToggleFireMode();
end)
AimButton.MouseButton1Click:Connect(function()
if not Aiming
and Sprinting.Value == false
and Character.Head.LocalTransparencyModifier > 0.6 then -- aim gun
AimIn()
else
if Aiming then -- aim gun
AimOut()
end
end
end)