Designing an FPS Framework: Beginner’s guide [PART 2]

I will try to do some tests thanks!

hey dude, amazing tutorial, but when my ammo goes to zero i cant shoot anymore, can u help me?
obraz_2023-11-28_211232949

I know it’s been several months, but I just saw this and forgot to reply (mb), I’m sure you’ve figured it out by now but I still wanted to answer for the people who has the same problem.

instead of input.UserInputType it should be input.KeyCode

(haven’t been on the forums for ages)

1 Like

Kind of curious, is there a way to return the gun to the starting shooting position with the spring module you used? I’m currently having a rough time figuring that out. Since my recoil is only going up and not returning

this is what it looks like, i’ve tried using lerp to do it but it does not work for me, it could be my bad scripting skills since I’m not really good at it

There’s multiple ways you can do that, one way is to simply shove it in the opposite direction too or like someone already mentioned here:

1 Like

Hey there, I was using this tutorial but i seem to have a problem. When ever I move my mouse to the side of my screen, it shoots correctly, but the bullets dont delete and keep going. Aswell as I dont see the bullet when i shoot normally (aka in the middle of my screen or first person) unless I get very close to a dummy. Heres my code:

LOCAL SCRIPT:

local GunModel = game.ReplicatedStorage:WaitForChild(“Apple_Pistol”) – your gun
local Viewmodel = game.ReplicatedStorage:WaitForChild(“Viewmodel”) – the viewmodel!!
local AnimationsFolder = game.ReplicatedStorage:WaitForChild(“ApplePistol_Animations”)

– our main module
local MainModule = require(game.ReplicatedStorage.MainModule)
local SpringModule = require(game.ReplicatedStorage.SpringModule)

Viewmodel.Parent = game.Workspace.Camera
MainModule.weldgun(GunModel)

local RecoilSpring = SpringModule.new()
local BobbleSpring = SpringModule.new()
local SwayingSpring = SpringModule.new()

game:GetService(“RunService”).RenderStepped:Connect(function(dt)
MainModule.update(Viewmodel, dt, RecoilSpring, BobbleSpring, SwayingSpring, GunModel)
end)

MainModule.equip(Viewmodel, GunModel, AnimationsFolder[“Hold(Right)”])

local IsPlayerHoldingMouse
local CanFire = true
local Delay = 0.1

local Damage = game.ReplicatedStorage.Damage
local Fire = game.ReplicatedStorage.Fire

Fire.OnClientEvent:Connect(function(client, origin, endposition)
if client ~= game.Players.LocalPlayer then
MainModule.cast(origin, endposition, 5, nil)
end
end)

game:GetService(“RunService”).Heartbeat:Connect(function(dt)
if IsPlayerHoldingMouse then
if CanFire then
CanFire = false

        RecoilSpring:shove(Vector3.new(0, 0, 10)) -- Vector3.new(up, sideways, shake)

        coroutine.wrap(function()

            for i, v in pairs(GunModel.Parts.Barrel:GetChildren()) do
                if v:IsA("ParticleEmitter") then
                    v:Emit()
                end
            end

			local FireSound = GunModel.Parts.Sounds.Fire:Clone()

            FireSound.Parent = game.Workspace
            FireSound.Parent = nil
            FireSound:Destroy()
        end)()

        coroutine.wrap(function()

            wait(0.2)

            RecoilSpring:shove(Vector3.new(0, 0, -10))
        end)()

        local CastParams = RaycastParams.new()
        CastParams.IgnoreWater = true
        CastParams.FilterType = Enum.RaycastFilterType.Blacklist
        CastParams.FilterDescendantsInstances = {Viewmodel, game.Players.LocalPlayer.Character}


        local Mouse = MainModule.GetMouse(1000, CastParams)

        MainModule.cast(GunModel.Parts.Barrel.Position, Mouse, 5, Damage)
		Fire:FireServer(GunModel.Parts.Barrel.Position, Mouse)
        
        wait(Delay)
        CanFire = true
    end
end

end)

game:GetService(“UserInputService”).InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then

    IsPlayerHoldingMouse = true

elseif input.UserInputType == Enum.UserInputType.MouseButton2 then

    MainModule.aim(true, Viewmodel, GunModel)

end

end)

game:GetService(“UserInputService”).InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then

    IsPlayerHoldingMouse = false

elseif input.UserInputType == Enum.UserInputType.MouseButton2 then

    MainModule.aim(false, Viewmodel, GunModel)

end

end)

MODULE SCRIPT:

local module = {}

local function GetBobbing(addition)
return math.sin(tick() * addition * 1.3) * 0.5
end

function module.update(viewmodel, dt, RecoilSpring, BobbleSpring, SwayingSpring, gun)
viewmodel.HumanoidRootPart.CFrame = game.Workspace.Camera.CFrame

local Bobble = Vector3.new(GetBobbing(10),GetBobbing(5),GetBobbing(5))
local MouseDelta = game:GetService("UserInputService"):GetMouseDelta()

local Character = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()

BobbleSpring:shove(Bobble / 10 * (Character.HumanoidRootPart.Velocity.Magnitude) / 10)
SwayingSpring:shove(Vector3.new(-MouseDelta.X / 500, MouseDelta.Y / 200, 0))

local UpdatedRecoilSpring = RecoilSpring:update(dt)
local UpdatedBobbleSpring = BobbleSpring:update(dt)
local UpdatedSwaySpring = SwayingSpring:update(dt)

gun.Parts.Sight.CFrame = gun.Parts.Sight.CFrame:Lerp(viewmodel.HumanoidRootPart.CFrame, game.ReplicatedStorage.Values.AimAlpha.Value)

viewmodel.HumanoidRootPart.CFrame = viewmodel.HumanoidRootPart.CFrame:ToWorldSpace(CFrame.new(UpdatedBobbleSpring.Y, UpdatedBobbleSpring.X, 0))
viewmodel.HumanoidRootPart.CFrame *= CFrame.new(UpdatedSwaySpring.X, UpdatedSwaySpring.Y, 0)

viewmodel.HumanoidRootPart.CFrame *= CFrame.Angles(math.rad(UpdatedRecoilSpring.X) * 2, 0, 0)
game.Workspace.Camera.CFrame *= CFrame.Angles(math.rad(UpdatedRecoilSpring.X), math.rad(UpdatedRecoilSpring.Y), math.rad(UpdatedRecoilSpring.Z))

end

function module.weldgun(gun)
local Main = gun.Handle

for i, v in ipairs(gun:GetDescendants()) do
    if v:IsA("MeshPart") and v ~= Main then
        local NewMotor = Instance.new("Motor6D")
        NewMotor.Name = v.Name
        NewMotor.Part0 = Main
        NewMotor.Part1 = v
        NewMotor.C0 = NewMotor.Part0.CFrame:inverse() * NewMotor.Part1.CFrame -- Dont worry about this, it's just for repositioning
        NewMotor.Parent = Main
    end
end

end

function module.equip(viewmodel, gun, hold)
local GunHandle = gun.Handle
local HRP_Motor6D = viewmodel:WaitForChild(“HumanoidRootPart”).Handle
gun.Parent = viewmodel
HRP_Motor6D.Part1 = GunHandle
local Hold = viewmodel.AnimationController:LoadAnimation(hold)
Hold:Play()
end

function module.cast(origin, endposition, velocity, damage)

local Bullet = game.ReplicatedStorage.Apple:Clone()
Bullet.Size = Vector3.new(0.1, 0.1, 5)
Bullet.Anchored = true
Bullet.CanCollide = false
Bullet.Parent = game.Workspace

Bullet.CFrame = CFrame.new(origin, endposition)

local Loop

Loop = game:GetService("RunService").RenderStepped:Connect(function(dt)

    local Hit = workspace:Raycast(Bullet.Position, Bullet.CFrame.LookVector * velocity * 1.5)

    if Hit then
		if Hit.Instance.Parent:FindFirstChild("Humanoid") and damage ~= nil then
			
            damage:FireServer(Hit.Instance.Parent, 10)

        else

            Loop:Disconnect()
            Bullet:Destroy()
            
		end
    end

    Bullet.CFrame *= CFrame.new(0, 0, -velocity * (dt * 60))
end)

end

function module.aim(toaim, viewmodel, gun)
if toaim then
game:GetService(“TweenService”):Create(game.ReplicatedStorage.Values.AimAlpha, TweenInfo.new(1), { Value = 1 }):Play()
else
game:GetService(“TweenService”):Create(game.ReplicatedStorage.Values.AimAlpha, TweenInfo.new(1), { Value = 0 }):Play()
end
end

function module.GetMouse(Distance, CastParams)
local MouseLocation = game:GetService(“UserInputService”):GetMouseLocation()
local UnitRay = game:GetService(“Workspace”).Camera:ViewportPointToRay(MouseLocation.x, MouseLocation.y)

local origin = UnitRay.Origin
local endp = UnitRay.Direction * Distance
local Hit = game:GetService("Workspace"):Raycast(origin, endp, CastParams)

if Hit then
    return Hit.Position
else
    return UnitRay.Origin + UnitRay.Direction * Distance
end

end

return module

I have tried alot of things, pretty stupid it doesnt work still.

hey, sorry for the really late reply.

can you please provide a video showing what actually happens. and can you please reformat your code i can’t really read it that way

Everything is fixed, but thank you anyways! I actually did make a video but “new devfourm users cant post videos” which I thought was stupid. To be fair I am new to the DevFourm and thats why the code is formatted that way, I just copy and pasted lol! Thanks again :smiley:

yello is there a possible way where i can learn to edit the sway and bobbing in depth?
i was able to add gun sway, breathing sway, and bobbing sway but it look pretty lifeless

1 Like

hey! so basically, swaying, bobbing is something that you have to play around with and tweak to get something you’re satisfied with, try playing with the camera position and rotation, gun position and rotation or the viewmodel position and rotation.

use desmos to come up with a bobbing formula that suits your need, heres a pretty useful formula that a kind person made.

tweak the variables a little to get what you need. add some noise to your bobble to give that random effect

for swaying, experiment with rotational sway, its a lot to get into and I myself was obsessed with getting the perfect bobble and swaying effect, and thankfully i achieved it just right for my own needs,

some other details that i want to mention is, try adding an idle bobble, where it would still bobble without you moving, be subtle with your bobble, dont make it too much or else itll look weird.

image

most importantly, take inspiration from other games and see if you can recreate something similar

hello can you tell me the idea on how to free aim thing?

hey, so basically free aim is a not too difficult to implement, its basically permanent swaying in a nutshell.

you basically have a free_aim vector2 variable, and add mouse_delta to it accordingly, clamping the values so it doesnt exceed a certain amount.

free_aim: vector2

loop
    free_aim += mouse_velocity
    free_aim = clamp_free_aim(free_aim)
    viewmodel.CFrame *= CFrame.Angles(free_aim.x, free_aim.y, 0)
    -- (1) free_aim -= free_aim * 0.01
end

of course you can extend it more, like adding springs to give it a more natural effect, making it returning to the center (by subtracting a little off the free_aim variable as shown in (1))

hi again can you give me more detail on the code?

how do i setup the vector2 properly because i never work with it
how to get mouse velocity?
and what clamp_free_aim do

hey, right so basically i dont really have a straight forward code ready and show, my current code is a jumbled mess that i myself dont even understand, but ill try and provide a solid foundation for you.

a vector2 works just like a vector3, but it only has 2 axis, you can also use a vector3 it doesnt really matter tbh

if you followed the tutorial, mouse velocity is basically GetMouseDelta()

just a dummy function to say that you should clamp the values so it doesnt exceed a certain amount, use math.clamp

local currentFreeAimAngle: Vector2

-- definitely not optimized js a sample code
local function clamp(v1, v2, v3)
    local x, y = v1.x, v1.y
    if v1.x < v2.x then x=v2.x end
    if v1.x > v3.x then x=v2.x end

    if v1.y < v2.y then x=v2.y end
    if v1.y > v3.y then x=v2.y end
    return Vector2.new(x, y)
end

loop
    local rawMouseInput = mouseDelta -- "velocity" of mouse
    local new = currentFreeAimAngle + rawMountInput
    currentFreeAimAngle = clamp(new, lowerBound, upperBound)
    -- apply the rotation to the viewmodel with the set free aim angle
    viewmodel.CFrame *= CFrame.Angles(currentFreeAimAngle.y, currentFreeAimAngle.x, 0)
end

currentFreeAimAngle = math.clamp(new, Minimum, Maximum)

there’s error that i couldnt find a way to properly solove it
the new variable should have the output as number and i checked it using print() which came out as number

but in the clamp function the x or new is return as vector2 for some reason

the output:

you cant use math.clamp, thats only for numbers. thats why i made a special function for it

oh how silly i am thanks g!
charlimit

you’re giving the function a number instead of a vector2