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

UPDATE: Here are the scripts for you to compare with your own: fpsframeworkscripts.rbxm (5.8 KB). (apologies for not providing this earlier)

5 Likes

This acted as a very helpful tool for me. Haven’t found anything else like it. If possible, and you being a programmer and all, kindle advise the way forward when it comes to adding other weapons to the game. It however seems to be limited to only one gun.
Thanks.

3 Likes

Hey there, just a tip for your recoil. Rather than making it go up and return the way you’re doing it you should save a camera offset cframe variable and inverse the last frames offset then apply the current frames one. Sorry if this is a bad explanation but I’ll show you what I mean.

local camoffset = CFrame.new()

renderstepped:Connect(function()
    local newoffset = CFrame.Angles(spring.p.x,0,0)
    camera.CFrame = camera.CFrame * camoffset:Inverse() * newoffset
    camoffset = newoffset
end)

You will achieve a result like this:

9 Likes

Where and how should we put/use this exactly,
Not a programmer

2 Likes

Good point but I don’t see why you’re inversing it (unless you’re using a different spring module), for my system you can just edit the kick and the recoil to achieve this effect.

image

(sorry for low video quality had multiple programs running while recording)
https://gyazo.com/d854ad3825e7a25a1f2c12c8438b1c8b

The spring is a bit too hard here maybe soften it a bit if you’re copying this effect

3 Likes

I inverse it to get the raw camera cframe with no recoil etc applied to it. If I did it the way you did it there the camera would go up but it wouldn’t come back down again. Here’s an example of it if I was to do it the way you’re doing it:

3 Likes

you should put it before you’re setting the cframe of the viewmodel to the camera, or something similar depending on how you do it

2 Likes

Well I shove the opposite way so this dosen’t happen. Bumped up the recoil to show the effect:

https://gyazo.com/f4ded1ee602f405c8a52d37091792563

and it gives more control on the opposite recoil imo.

1 Like

I used to do it that exact way but I’ve since shied away from doing it that way

1 Like

I’m running into a issue with the camera bobbing

HumanoidRootPart is not a valid member of Model "Workspace.jespower2431"
The code itself: BobbleSpring:shove(Bobble / 10 * (Character.HumanoidRootPart.Velocity.Magnitude) / 10)
I’ve tried WaitForChild but that only removes the error and doesn’t fix it.
So; I need help, thanks in advance :smiley:

1 Like

I’m assuming the bobbing isn’t working if so, may I see the full code? Thanks.

Mainmodule:

local module = {}

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

function module.update(viewmodel, dt, RecoilSpring, BobbleSpring, SwayingSpring)
	viewmodel.HumanoidRootPart.CFrame = game.Workspace.Camera.CFrame
	
	local Bobble = Vector3.new(GetBobbing(10), GetBobbing(5), GetBobbing(5))
	
	local Character = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
	
	BobbleSpring:shove(Bobble / 10 * (Character.HumanoidRootPart.Velocity.Magnitude) / 10)
	-- // BobbleSpring:shove(Bobble / 10 * (Character:WaitForChild("HumanoidRootPart").Velocity.Magnitude) / 10)
	
	local UpdatedRecoilSpring = RecoilSpring:update(dt)
	local UpdatedBobbleSpring = BobbleSpring:update(dt)
	local UpdatedSwayingSpring = SwayingSpring:update(dt)
	
	viewmodel.HumanoidRootPart.CFrame = viewmodel.HumanoidRootPart.CFrame:ToWorldSpace(CFrame.new(UpdatedBobbleSpring.Y, UpdatedBobbleSpring.X, 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.GunComponents.Handle
	
	for i, v in ipairs(gun:GetDescendants()) do
		if v:IsA("BasePart") 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
			NewMotor.Parent = Main
		end
	end
end

function module.equip(viewmodel, gun, hold)
	local GunHandle = gun.GunComponents.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(gun, endposition, velocity)
	local gunBarrel = gun.GunComponents.Barrel
	
	local Bullet = Instance.new("Part")
	Bullet.Size = Vector3.new(1, 1, 5)
	Bullet.Anchored = true
	Bullet.CanCollide = false
	Bullet.BrickColor = BrickColor.White()
	Bullet.Material = Enum.Material.Neon
	Bullet.Parent = game.Workspace
	
	Bullet.CFrame = CFrame.new(gunBarrel.Position, endposition)
	
	local Loop
	
	Loop = game:GetService("RunService").RenderStepped:Connect(function(dt)
		Bullet.CFrame *= CFrame.new(0, 0, -velocity * (dt * 60))
		if (Bullet.Position - gunBarrel.Position).magnitude > 5000 then
			Bullet:Destroy()
			Loop:Disconnect()
		end
	end)
end

return module

LocalHandler:

local GunModel = game.ReplicatedStorage:WaitForChild("Groza") -- your gun
local Viewmodel = game.ReplicatedStorage:WaitForChild("Viewmodel") -- the viewmodel!!

local IsPlayerHoldingMouse
local CanFire = true
local Delay = 0.1

local AnimationsFolder = game.ReplicatedStorage:WaitForChild("Groza_Animations")

-- our mainmodule
local MainModule = require(game.ReplicatedStorage.MainModule)
local SpringModule = require(game.ReplicatedStorage.SpringModule)

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

Viewmodel.Parent = game.Workspace.Camera

-- alright first lets position the viewmodel

MainModule.weldgun(GunModel)
--//MainModule.equip(Viewmodel, GunModel)

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

game:GetService("UserInputService").InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		IsPlayerHoldingMouse = true
	end
end)

game:GetService("UserInputService").InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		IsPlayerHoldingMouse = false
	end
end)

game:GetService("RunService").Heartbeat:Connect(function(dt)
	if IsPlayerHoldingMouse then
		if CanFire then
			CanFire = false
			
			RecoilSpring:shove(Vector3.new(1, 0, 0))
			MainModule.cast(GunModel, game.Players.LocalPlayer:GetMouse().Hit.Position, 60)
			
			wait(Delay)
			CanFire = true
			
			RecoilSpring:shove(Vector3.new(3, math.random(-2, 2), 10)) -- Vector3.new(up, sideways, shake)
			
			coroutine.wrap(function()
				
				for i, v in pairs(GunModel.GunComponents.Barrel:GetChildren()) do
					if v:IsA("ParticleEmitter") then
						v:Emit()
					end
				end
				
				local FireSound = GunModel.GunComponents.Sounds.Fire:Clone()
				
				FireSound.Parent = game.Workspace
				FireSound.Parent = nil
				FireSound:Destroy()
			end)()
			
			coroutine.wrap(function()

				wait(0.2)

				RecoilSpring:shove(Vector3.new(-2.8, math.random(-1, 1), -10))
			end)()
		end
	end
end)

MainModule.equip(Viewmodel, GunModel, AnimationsFolder.HoldAnimation)
1 Like

You forgot to multiply addition: math.sin(tick() * addition * 1.3) * 0.5

3 Likes

It fixed it yes and no, the error is still there, but it works now, any fixes on the error? I feel much better with a clean console.

I’m assuming it’s HumanoidRootPart error, just simply put a :WaitForChild() for it and it should fix it.

That works half and half, it’s indeed errorless, but the hands space out for a few seconds, looks like I gotta implement a loading screen, but then it’s good for now, thanks!

Amazing guide, but I’m noticing that the bullet’s replication to the other clients does not work

Sometimes I noticed a issue (like forgetting to include something) and smashed my keyboard multiple times (don’t worry, it still works xD)

Make sure you follow the steps correctly and debug your code thoroughly and you could always ask for help. :+1:

1 Like

DISCLAIMER: please DO NOT pass the damage as a parameter for server side damage, im doing this to keep it simple for beginners

umm what does this mean i don’t understand it fully

It’s more of a security thing. If you pass damage and rely on it, exploiters could exploit this and put in an insane amount of damage and the server will accept it.

3 Likes