Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --Constants
- local ReloadKey = Enum.KeyCode.R
- local LandDownTweenInfo = TweenInfo.new(0.1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
- local LandUpTweenInfo = TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.Out)
- --Services
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local ContentProvider = game:GetService("ContentProvider")
- local RunService = game:GetService("RunService")
- local UserInputService = game:GetService("UserInputService")
- local TweenService = game:GetService("TweenService")
- local SoundService = game:GetService("SoundService")
- local Debris = game:GetService("Debris")
- --Modules
- local Modules = ReplicatedStorage.Modules
- local LocalPlayer = require(Modules.LocalPlayer)
- --Remotes
- local Remotes = ReplicatedStorage.Remotes
- local GunRemote = Remotes.Gun
- --Other
- local Camera = game.Workspace.CurrentCamera
- local ViewModelAudio = SoundService.ViewModel
- --Functions
- local function Lerp(A, B, T)
- -- This is just a basic lerping function, not much to talk about it
- return A + (B - A) * T
- end
- local function MultiplyCFrame(CurrentCFrame, Multiplicator)
- -- Since CFrame.identity is basically CFrame.new(0, 0, 0), I lerped it to the current CFrame with the alpha being the multiplicator so it does a similar job as Vector3 * x
- return CFrame.identity:Lerp(CurrentCFrame, Multiplicator)
- end
- local function LoadAnimations(Model, Info)
- -- This is a function to load animations on a model and bind Sounds, VFX, Functions to those animations' events
- local Animations = Info.Animations and Info.Animations:GetChildren()
- if not Animations then return end
- local Animator = Model.Humanoid and Model.Humanoid.Animator
- if not Animator then return end
- local Tracks = {}
- ContentProvider:PreloadAsync(Animations)-- I preload the animations using the ContentProviderService's PreloadAsync method
- for _, Animation in Animations do
- local Track = Animator:LoadAnimation(Animation)
- if Info.Sounds then
- Track:GetMarkerReachedSignal("Sound"):Connect(function(SoundName)
- --This finds a sound named the same as the animationevent's parameter and plays it when the event is reached
- local AudioPlayer = Info.Sounds:FindFirstChild(SoundName)
- if not AudioPlayer then return end
- AudioPlayer = AudioPlayer:Clone()
- local Wire = Instance.new("Wire")-- To connect the audio to the output
- Wire.SourceInstance = AudioPlayer
- Wire.TargetInstance = ViewModelAudio
- Wire.Parent = AudioPlayer
- AudioPlayer.Parent = ViewModelAudio
- AudioPlayer:Play()
- Debris:AddItem(AudioPlayer, AudioPlayer.TimeLength + 1)--I use the AddItem method of the service "Debris" to make sure that the sound gets destroyed when it's done playing
- end)
- end
- if Info.VFX then
- Track:GetMarkerReachedSignal("VFX"):Connect(function(VFXName)
- --This finds a ParticleEmitter named the same as the animationevent's parameter and emits it when the event is reached
- local ParticleEmitter = Info.VFX:FindFirstChild(VFXName)
- if not ParticleEmitter then return end
- --This section parents the ParticleEmitter to a Part or an Attachment named the same as its "Parent" attribute
- local ParentName = ParticleEmitter:GetAttribute("Parent")
- local Parent = ParentName and Model:FindFirstChild(ParentName, true)
- if not Parent then return end
- ParticleEmitter = ParticleEmitter:Clone()
- ParticleEmitter.Parent = Parent
- ParticleEmitter:Emit(ParticleEmitter:GetAttribute("EmitCount") or 1)-- If the ParticleEmitter has an attribute called EmitCount then it emits it that many times
- Debris:AddItem(ParticleEmitter, ParticleEmitter.Lifetime.Max + 1)--I use the AddItem method of the service "Debris" to make sure that the ParticleEmitter gets destroyed when the particles disappear
- end)
- end
- if Info.FunctionFX then
- Track:GetMarkerReachedSignal("FunctionFX"):Connect(function(FunctionFXName)
- --This finds a function module named the same as the animationevent's parameter and it runs the function when the event is reached
- local FunctionModule = Info.FunctionFX:FindFirstChild(FunctionFXName)
- if FunctionModule then require(FunctionModule)(Model) end
- end)
- end
- Tracks[Animation.Name] = Track
- end
- return Tracks
- end
- local GunController = {}
- GunController.__index = GunController
- local LastCameraCFrame
- local LastRootPartCFrame
- --This is the constructor function of the class "GunController"
- function GunController.new(GunTool)
- local Info = GunRemote:InvokeServer("GetInfo", GunTool)--Requests the info of the gun from the server and doesn't run the rest of the code if the server couldn't find the information
- if not Info then return end
- local ViewModel = Info.Assets.ViewModel:Clone()
- ViewModel.Parent = ReplicatedStorage
- --This section applies the arm colors and the shirt to the ViewModel so it feels like the player is actually holding the gun
- local HumanoidDescription = Instance.new("HumanoidDescription")
- HumanoidDescription.Shirt = LocalPlayer.HumanoidDescription.Shirt
- HumanoidDescription.RightArmColor = LocalPlayer.HumanoidDescription.RightArmColor
- HumanoidDescription.LeftArmColor = LocalPlayer.HumanoidDescription.LeftArmColor
- local ViewModelHumanoid = ViewModel:FindFirstChildOfClass("Humanoid")
- ViewModelHumanoid:ApplyDescription(HumanoidDescription)
- local Animations = LoadAnimations(ViewModel, Info)--Loads the animations and gets the AnimationTracks of them using the function
- --Defines some variables and sets the objects metatable to the GunController Class
- local self = setmetatable(
- {
- Tool = GunTool,
- Info = Info,
- Animations = Animations,
- Connections = {},
- Ammo = Info.MaxAmmo,
- ViewModel = ViewModel,
- Idle = CFrame.identity,
- Sway = CFrame.identity,
- JumpOffset = CFrame.identity,
- AimOffset = CFrame.identity,
- LandingCFrame = Instance.new("CFrameValue"),
- AimDampening = 1,
- Bob = 0,
- Stride = 0,
- DefaultFOV = Camera.FieldOfView,
- FieldOfView = Camera.FieldOfView
- }, GunController)
- self:BindInputs()
- --This section makes it update the gun's CFrame constantly usind RunService, it also checks if the player is aiming (pressing down mouse button 2)
- RunService:BindToRenderStep("ViewModel", Enum.RenderPriority.Camera.Value + 1, function(DeltaTime)
- self.Aiming = self.Equipped and UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2)
- LocalPlayer.Humanoid.WalkSpeed = self.Aiming and Info.AimWalkSpeed or 16--Slows the player if aiming
- self:Update(DeltaTime)
- end)
- --This section is for making the gun "shake" when the player lands after falling
- table.insert(self.Connections, LocalPlayer.Humanoid.StateChanged:Connect(function(_, State)
- if State ~= Enum.HumanoidStateType.Landed then return end
- local Tween = TweenService:Create(self.LandingCFrame, LandDownTweenInfo, {Value = self.Info.LandingShakeOffset})
- Tween:Play()
- task.delay(Tween.TweenInfo.Time, function()
- local Tween = TweenService:Create(self.LandingCFrame, LandUpTweenInfo, {Value = CFrame.identity})
- Tween:Play()
- end)
- end))
- --This runs the Destroy method of the object if the player dies
- LocalPlayer.Humanoid.Died:Once(function()
- self:Destroy()
- end)
- LastCameraCFrame = Camera.CFrame
- LastRootPartCFrame = LocalPlayer.RootPart.CFrame
- return self
- end
- function GunController:IsFree()
- --This function is to check if the player can do an action. So for example the player cant shoot while reloading
- return not self.Equipping and not self.Shooting and not self.Reloading
- end
- function GunController:Shoot()
- if not self:IsFree() then return end
- if self.Ammo <= 0 then
- self:Reload()
- return
- end
- local Success = GunRemote:InvokeServer("Shoot", Camera.CFrame)--This is the server-sided part of the system
- if not Success then return end--Doesn't continue with the client-sided part if the server doesn't give approval
- --Plays the animation and reduces the ammo by 1
- self.Shooting = true
- self.Ammo -= 1
- self:PlayAnimation("Shoot")
- self.Shooting = nil
- end
- function GunController:Reload()
- if self.Ammo >= self.Info.MaxAmmo or not self:IsFree() then return end--Doesn't let the player reload if the ammo is already full or if the gun is currently doing an action
- self.Reloading = true
- --Plays the animation and sets the ammo to max
- self:PlayAnimation("Reload")
- self.Ammo = self.Info.MaxAmmo
- self.Reloading = nil
- end
- function GunController:Equip()
- if not self:IsFree() then
- --This makes sure that the player can't equip the gun during unequipping
- self.SkipUnequipped = true
- self.Tool.Parent = LocalPlayer.Player.Backpack
- return
- end
- UserInputService.MouseIconEnabled = false--Hides the mouse
- self.Equipping = true
- self.ViewModel.Parent = Camera
- self:PlayAnimation("Equip")
- self.Equipping = nil
- self.Equipped = true
- end
- function GunController:Unequip()
- if not self:IsFree() then
- --This makes sure that the player can't unequip the gun during an action
- self.SkipEquipped = true
- self.Tool.Parent = LocalPlayer.Character
- return
- end
- self.Equipped = nil
- self.Equipping = true
- self:PlayAnimation("Equip", nil, nil, -1)
- self.ViewModel.Parent = ReplicatedStorage
- self.Equipping = nil
- UserInputService.MouseIconEnabled = true--Makes the mouse visible again
- end
- function GunController:BindInputs()
- --This function binds the inputs to actions (not fully mobile compatible yet)
- table.insert(self.Connections, self.Tool.Equipped:Connect(function()
- if self.SkipEquipped then self.SkipEquipped = nil return end
- self:Equip()
- end))
- table.insert(self.Connections, self.Tool.Unequipped:Connect(function()
- if self.SkipUnequipped then self.SkipUnequipped = nil return end
- self:Unequip()
- end))
- table.insert(self.Connections, self.Tool.Activated:Connect(function()
- self:Shoot()
- end))
- table.insert(self.Connections, UserInputService.InputBegan:Connect(function(Input, GPE)
- if GPE then return end--This makes sure that the player doesn't accidentally reload while typing
- if Input.KeyCode == ReloadKey then self:Reload() end
- end))
- end
- function GunController:Update(DeltaTime)
- --This section calculates the movement between frames (camera and player/character)
- local CameraCFrameDelta = LastCameraCFrame:ToObjectSpace(Camera.CFrame)
- local RootPartCFrameDelta = LastRootPartCFrame:ToObjectSpace(LocalPlayer.RootPart.CFrame)
- local function GetLerpAlpha(Name)--I made this function to avoid too much repetition
- return math.min(self.Info[Name.."Transition"] * DeltaTime, 1)
- end
- local Bob = LocalPlayer.Humanoid.MoveDirection.Magnitude--This value is 1 or really close to 1 if the player is moving and 0 or really close to 0 if they aren't
- --Most of this part below is pretty much self explanatory but I will try my best to explain it further
- self.Stride = (self.Stride + DeltaTime * Bob * self.Info.BobSpeed) % (2 * math.pi)--Stride increases as the player walks and goes back to 0 when it reaches 2pi using the modulus operator(%)
- self.Bob = Lerp(self.Bob, Bob, GetLerpAlpha("Bob"))
- --This is to make the gun sway towards the direction that the camera is turning. Its more powerful if the turn is faster
- local Sway = MultiplyCFrame(CameraCFrameDelta.Rotation, self.Info.SwayIntensity)--Adjusts the intensity of the Sway depending on the custom value
- self.Sway = self.Sway:Lerp(Sway, GetLerpAlpha("Sway"))
- --This is to make the gun swing if the player is falling or jumping
- local JumpOffset = MultiplyCFrame(CFrame.new(0, RootPartCFrameDelta.Y, 0), self.Info.JumpIntensity)--Adjusts the intensity of the JumpOffset depending on the custom value
- self.JumpOffset = self.JumpOffset:Lerp(JumpOffset, GetLerpAlpha("Jump"))
- --This is to make the player sort of get to the aiming perspective if aiming
- local AimOffset = self.Aiming and self.Info.AimOffset or CFrame.identity
- self.AimOffset = self.AimOffset:Lerp(AimOffset, GetLerpAlpha("Aim"))
- --This Narrows the FOV if aiming
- local FieldOfView = self.Aiming and self.Info.AimFOV or self.DefaultFOV
- self.FieldOfView = Lerp(self.FieldOfView, FieldOfView, GetLerpAlpha("Aim"))
- --This dampens the movement if the player is aiming so it's easier to aim
- local AimDampening = self.Aiming and self.Info.AimDampening or 1
- self.AimDampening = Lerp(self.AimDampening, AimDampening, GetLerpAlpha("Aim"))
- --I lerp all these values above so the movement feels smooth and doesn't feel "jittery"
- --Calculating the bob offset (idle "animation" too)
- local BobX = math.sin(self.Stride) * self.Bob
- local BobY = math.sin(2 * (tick() + self.Stride))
- local BobOffset = Vector3.new(BobX, BobY, 0) * self.Info.BobIntensity
- local BobCFrame = CFrame.new(BobOffset)
- local TotalOffset = BobCFrame * self.Idle * self.Sway * self.JumpOffset * self.LandingCFrame.Value--Calculates the final offset
- local DampenedOffset = MultiplyCFrame(TotalOffset, self.AimDampening)--Applies the AimDampening
- self.ViewModel:PivotTo(Camera.CFrame * DampenedOffset * self.AimOffset)--Finally sets the viewmodels cframe
- Camera.FieldOfView = self.FieldOfView--Sets the FOV
- --Self explanatory I think
- LastCameraCFrame = Camera.CFrame
- LastRootPartCFrame = LocalPlayer.RootPart.CFrame
- end
- function GunController:PlayAnimation(AnimationName, ...)
- --This function looks for the animation using the name and if it finds it, plays the animation and yields until the animation is done playing
- local Animation = self.Animations[AnimationName]
- if Animation then
- Animation:Play(...)
- Animation.Stopped:Wait()
- end
- end
- function GunController:Destroy()
- --This function disconnects the connections and destroys the viewmodel
- if not self.ViewModel or not self.ViewModel.Parent then return end
- RunService:UnbindFromRenderStep("ViewModel")
- for _, Connection in self.Connections do
- if Connection.Connected then--Doesn't disconnect again if already disconnected
- Connection:Disconnect()
- end
- end
- self.ViewModel:Destroy()
- end
- return GunController
Advertisement
Add Comment
Please, Sign In to add comment