(note: This is my first forum post so I’m sorry if I am clueless or unsure of how this works.)
-
What do you want to achieve?
I am trying to make a simple recoil system for the guns in my game with randomly generated patterns, a little bit similar to the CS:GO or Valorant recoil pattern. -
What is the issue?
The main issue (which isn’t too big, but changes the gameplay a bit) is that the recoil amount slightly changes depending on how much fps the client is getting. For example, if u have 60 fps, it works fine, but if u have 120 fps or more then the recoil decreases and is slightly less shaky, which can give a gameplay changing advantage. -
What solutions have you tried so far?
I have tried looking into many (around 4?) different forum posts with a similar issue to mine, which most of them use the same or similar module script as mine, but my issue still seems to remain unfixable. On the other forums, they don’t really have the solution to my problem I’m having with the recoil.
Suggestions or maybe even the solution to the problem would be a great help
Here is my code for the client (In Starter Player):
local rs = game:GetService("ReplicatedStorage")
local uis = game:GetService("UserInputService")
local r_s = game:GetService("RunService")
local springModule = require(rs.SpringModule)
local player = game.Players.LocalPlayer
local cam = workspace.CurrentCamera
local mouse = player:GetMouse()
local recoilSpring = springModule.new()
local mouseDown = false
local canFire = true
local framework = {
inventory = {
"COLT M4A1"; -- Slot 1 (Primary)
"H&K USP"; -- Slot 2 (Secondary)
"NONE"; -- Slot 3 (Melee)
"NONE"; -- Slot 1 (Misc)
};
viewmodel = nil;
module = nil;
}
local function addWeapon(weapon, slot)
framework.inventory[slot] = weapon
end
local function loadslot(slot)
local weapon = framework.inventory[slot]
if weapon == "NONE" then return end
mouseDown = false
framework.module = require(rs.Modules[weapon])
if framework.viewmodel then
framework.viewmodel:Destroy()
end
framework.viewmodel = rs.Viewmodels["VM_" .. weapon]:Clone()
framework.viewmodel.Parent = cam
end
local function shoot()
-- Raycast the bullet
local muzPos = framework.viewmodel.GunModel.Components.Muzzle.Position
local mouseHit = mouse.Hit.Position
rs.Shoot:FireServer(framework.module.damage, muzPos, mouseHit)
-- Recoil
local x = math.random(20, 40)
local y = math.random(-10, 10)
local z = math.random(-50, 50)
recoilSpring:shove(Vector3.new(x, y, z)) -- Up, Sideways, Shake
end
uis.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
if framework.module.mode == "Auto" then
mouseDown = true
else
shoot()
end
end
-- Inventory
if input.KeyCode == Enum.KeyCode.One then
loadslot(1)
end
if input.KeyCode == Enum.KeyCode.Two then
loadslot(2)
end
if input.KeyCode == Enum.KeyCode.Three then
loadslot(3)
end
if input.KeyCode == Enum.KeyCode.Four then
loadslot(4)
end
end)
uis.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
mouseDown = false
end
end)
local oldPos = recoilSpring.Position
r_s.RenderStepped:Connect(function(dt)
local newPos = recoilSpring:update(dt)
local diff = newPos - oldPos
oldPos = newPos
cam.CFrame *= CFrame.Angles(math.rad(diff.X), math.rad(diff.Y), math.rad(diff.Z))
if framework.viewmodel then
framework.viewmodel:PivotTo(cam.CFrame)
end
end)
r_s.Heartbeat:Connect(function(dt)
if mouseDown and canFire then
canFire = false
shoot()
task.wait(framework.module.fireRate)
canFire = true
end
end)
loadslot(1)
and the module script (In ReplicatedStorage):
-- Constants
local ITERATIONS = 8
-- Module
local SPRING = {}
-- Functions
function SPRING.new(self, mass, force, damping, speed)
local spring = {
Target = Vector3.new();
Position = Vector3.new();
Velocity = Vector3.new();
Mass = mass or 5;
Force = force or 50;
Damping = damping or 4;
Speed = speed or 4;
}
function spring.getstats(self)
return self.Mass, self.Force, self.Damping, self.Speed
end
function spring.changestats(self, mass, force, damping, speed)
self.Mass = mass or self.Mass
self.Force = force or self.Force
self.Damping = damping or self.Damping
self.Speed = speed or self.Speed
end
function spring.shove(self, force)
local x, y, z = force.X, force.Y, force.Z
if x ~= x or x == math.huge or x == -math.huge then
x = 0
end
if y ~= y or y == math.huge or y == -math.huge then
y = 0
end
if z ~= z or z == math.huge or z == -math.huge then
z = 0
end
self.Velocity = self.Velocity + Vector3.new(x, y, z)
end
function spring.update(self, dt)
local scaledDeltaTime = dt * self.Speed / ITERATIONS
for i = 1, ITERATIONS do
local iterationForce= self.Target - self.Position
local acceleration = (iterationForce * self.Force) / self.Mass
acceleration = acceleration - self.Velocity * self.Damping
self.Velocity = self.Velocity + acceleration * scaledDeltaTime
self.Position = self.Position + self.Velocity * scaledDeltaTime
end
return self.Position
end
return spring
end
-- Return
return SPRING