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

Well you guys seemed to like the tutorial so heres a Part 2. ¯\_(ツ)_/¯ (thanks for 33 likes!!!)

If you just came, you’ll have to first see the original post to continue else it might get confusing for you, check it out here.

Also this tutorial is a bit long might take you a day or two to complete this. (also sorry if it’s a bit complex and confusing for some people, tried explain it as much as I can)

Objectives:

  1. Recoil, Muzzle flash and sounds
  2. Bobble & Swaying
  3. Hit detection
  4. Bullet replication
  5. Aiming
  6. Reload

Changes regarding the previous post

So in the projectile module I forgot to put local Loop and dt * 60. here is the editted version:

image

(srry)

Before continuing, you need to have some knowledge on:

  1. Raycasting (Hit detection)
  2. Remote events (Server replication)
  3. Tween Service (Aiming)
  4. Lerping (Aiming)

Step 1: Recoil

We’re going to use this SpringModule.rbxm (1.3 KB) to make recoil, bobble and swaying.

When your done downloading the spring module, put the module inside ReplicatedStorage and require it in LocalHandler.

image

We will be using 3 functions from the SpringModule:

  1. SpringModule.new() (creates a new spring function, btw you can put params into the brackets but I just put them as default)
  2. Spring:shove(Vector3.new()) (sets the goal of the spring)
  3. Spring:update(dt) (updates the spring)

Alright so the concept is when the guns shoots we want to shove the spring. This might get confusing but if you understand how it works it’ll be easy.

So first let’s create a new spring function

image

So now with this variable we’re going to do RecoilSpring:shove(Vector3.new(1, 0, 0) every time the gun fires, you can change the 1 to any number you like.

image

Now we’re going to update the spring, so we will need to update it every frame. So instead of creating a new RenderStepped we can just use the existing one. So let’s pass in RecoilSpring as a parameter

image

Then in the MainModule, we’re going to make a param for the RecoilSpring. After that we’re gonna update the spring: local UpdatedRecoilSpring = RecoilSpring:update(dt).

After we get the updated spring, we will apply it to the camera and viewmodel so it has a bounce / recoil effect anddd

wow… thats pretty lame . Let’s do some adjustments.

  • First we’re gonna bump the recoil
  • Second we’re gonna make random sideways recoil and screen-shake
  • Third we’re gonna make it so it comes back down.

If you’re experienced and know how this spring module works maybe you can make bullet patterns!

then update on the MainModule…

That’s more like it!

Step 2: Muzzle Flash and Sounds

So for the muzzle flash, I’m just going to use a particle emitter from the toolbox. When you’re done finding the perfect muzzle flash for your gun. Go ahead and put it inside your barrel and set ‘Enabled’ to false (aka disabling). We will just be using :Emit() to emit them.

Tip: Turn on LockedToPart so the particles stick to the part :wink:

image

Alright, sounds… I sometimes have a mental crisis when determining where I want to put the gun’s SFX but for this tutorial I’ll make a folder inside GunComponents called ‘Sounds’ and put all of the gun’s SFX in there.

Also make sure your SFX has ‘PlayOnRemove’ set to true

image

Alright so the concept is when the gun fires, we will get every particle emitter inside the barrel and :Emit() then the sound will get cloned and parent to workspace (because sound origin is cringe) and play it.

Works beautifly!

Step 3: Bobble and swaying

I’m just going to make a simple bobble and swaying using springs. So lets create 2 new spring variables and put the spring in the .update parameter

image

In a nutshell, we’re shoving the spring according to the player’s velocity with the bobble formula then we apply that to the HumanoidRootPart.

I underlined everything new that is added

Here is our bobbing

For swaying I won’t be going indepth with this one. But in a nutshell it gets the mouse “velocity” and apply the offset to the viewmodel.

Here is the swaying. (quite beautiful innit?)

Step 4: Hit detection

If you use FastCast you can skip this step!

Alright so the way I did my hit detection is by using raycasting. It’s hard to explain it by words but it’s like putting your hand out and walking blindly, when your hand hits something you stop. We’re raycasting to the bullet’s future position. If it hits something we destroy the bullet, disconnect the loop and do something about it. (lol)

First we’re gonna raycast to the bullet’s future position time 1.5 just to be safe.

We’re gonna check if ‘Hit’ hit something and then check if it hit a player or a wall.

Let’s make a remote event to damage the player. Make a remote event and then put it in ReplicatedStorage, I named the remote event “Damage” to keep it simple.

image

So let’s make a variable for the remote event, I named the remote event ‘Damage’ and then pass it through .cast().

Now in the MainModule lets fire the remote event whenever it hits a player. Also we’re gonna handle the else statement later after we’re done with this damage thing.

DISCLAIMER: please DO NOT pass the damage as a parameter for server side damage, im doing this to keep it simple for beginners. (also when you’re getting a bit more advance in making fps frameworks you will need to start integrating sanity checks to make sure your server side is safe, for example another invisible bullet cast to make sure it’s actually valid)

So we’re gonna pass in the victim and the damage.

image

Now make a server script inside a ServerScriptService. We’re gonna handle remote events signals inside the server script. I named it ‘ServerHandler’

image

So lets make .OnServerEvent event for the damage. I know we already checked if “Hit” was valid but I’ll just check it on the server just to be safe.

image

anddd

Alright time to put some code on the else statement. Basically we will disconnect the loop then destroy the bullet. You can make it so an impact sound plays or make bullet holes, but I’m just going destroy the bullet nothing fancy.

image

Honestly you can stop reading the tutorial here, but let’s make bullet replication so other players can see the bullets.

Step 5: Bullet replication

Basically the concept it, when we fire a bullet it will tell the server that we casted a bullet. Then the server will tell all clients (except you) to cast a bullet.

Client > Server > All Client

First let’s make a remote event. I’ll just name it “Fire”

image

Before we proceed, let’s make some changes to the .cast() function. The changes will be important for bullet replication.

So first instead of passing the GunModel, we will directly pass the origin Vector3.

image

and we will change inside the LocalHandler

Alright let’s continue. So as I was saying, we we will make a variable for the “Fire” remote event, I called it ‘Fire’.

We will do :FireServer() every time we fire the gun and then we will pass the origin and the end position as the parameter.

Then on the server we will make a variable for the remote event and do .OnServerEvent.

image

Also we’re gonna use the same remote event to do :FireAllClient. We’re gonna pass the original client that sent the signal, the origin and the end position.

image

Now let’s go back to the LocalHandler and do a .OnClientEvent for the “Fire” remote event.

image

We’re gonna make sure that we’re not replicating our own bullets so lets add an if statement check if the client is us.

image

Now we’re gonna use the same .cast() to replicate our bullets but set the remote event parameter to nil since it’s just a fake bullet.

image

Now let’s make some little changes to the .cast() function. Basically we will check if the remote event is nil or not, if it’s not nil then damage the player else just destroy the bullet.

image

anddddd

Step 6: Aiming

Alright so there are multiple ways but I’m going to do it how I do it. First let’s make a value to be our alpha value and make sure it is a NumberValue not an IntegerValue

image

So how this will work is upon aiming we will Tween the “AimAlpha” value to 1 and then lerp the Sight to the viewmodel’s camera. Alright alright, you may not understand but just follow along.

Now we will create a new function called .Aim(). We will pass in 3 parameters, ToAim, viewmodel and the gun. As I said earlier we will tween the Alpha value to 1 if we’re aiming and to 0 if we’re un-aiming. Oh yeah don’t forget to play the Tween after being created.

Now we will lerp the Sight to the Camera. So in the .update() we will pass a new parameter which is the gun model.

Now in the LocalHandler we want to detect when the client or the player is holding the right mouse or not. If the player is clicking the mouse we will aim, if the player is holding it we will fire the .Aim() function.

image

Alright now it works, we’re done… or have we…?

Now you might see a problem, if your gun has a scope you should notice that when you aim and shoot it wouldn’t damage the player because your mouse is detecting the scope. My scope doesn’t have a mounted scope so Ill have to simulate one:

(recorded 16/8/2024 sorry if its not consistent)

Now a solution for this is to make our own custom MouseModule, now you would make a seperate module for this but I’m just going to do a simple version of it.

So first let’s make the function module.GetMouse(). We will pass in 2 parameters: Distance and RaycastParams

What we’re doing here basically is raycasting from the origin (or the camera) do it’s set distance if it hit something we will return the Position else we will return the max distance. Won’t explain much here, but it basically uses some funny functions and raycasting.

Now let’s replace the :GetMouse() with this brand new function

Now let’s test

Step 7: Reload

Honestly I didn’t feel like making this sections as I thought it was simple to implement. (That also explains for the sudden change of the theme color, since I’m not using Rojo right now.) Anyways let’s get on to it

The concept:

We will have an ammo variable containing how many ammo we have currently, before firing we will check if ammo is 0 or not, if it is 0 reject the shot and for every shot we will subtract 1 from it. If the player wants to reload we will reset the ammo variable

image

So now let’s implement the reloading mechanic

Let’s say we want to play a reload animation when player reloads, and refill our ammo variable only when the animation end. So just put your reload animation in the AnimationsFolder. (You can reuse the old equip animation to make a reload animation).

image

Now for the .reload() function.

image


image

Now for the result.

Note: you can add more to this like SFX, and screen shake effects but I’m trying to keep it as simple as possible.

We’re finished hooray!!! I hope you guys learned something from this tutorial once you know the basics and requirements to make an FPS framework, you can start advancing to make more complex FPS frameworks!

If you have any questions you can join the discord server

318 Likes

This is an amazing guide well done

12 Likes

Great guide, but you had stated in your previous guide you’d teach us how to aim down sights. What happened to that?

10 Likes

It’ll be a long section. I’d have to teach you guys on how to make custom Mouse Module because using :GetMouse().Hit.Position wouldn’t work. I’ll probably consider into making one in the near future.

8 Likes

Alright, good to hear about that not being forgotten.

4 Likes

The tutorial is useful. But Please Use Code Bricks. It will look better. It will be good if you use it.

7 Likes

Camera Bob?
I always get stuck here when I’m making a fps framework, Anyways nice tutorial! :>

3 Likes

I am currently here, and have no muzzle flash or sound, I’m not sure whats wrong and none of the print statements are running that I put in?

local GunModel = game.ReplicatedStorage:WaitForChild("MP5")
local Viewmodel = game.ReplicatedStorage:WaitForChild("Viewmodel")
local animsFolder = game.ReplicatedStorage:WaitForChild("MP5Anims")
local mainModule = require(game.ReplicatedStorage.MainModule)
local springModule = require(game.ReplicatedStorage.SpringModule)
local recoilSpring = springModule.new()
local player = game.Players.LocalPlayer
Viewmodel.Parent = game.Workspace.Camera
mainModule.weldgun(GunModel)

game:GetService("RunService").RenderStepped:Connect(function(dt)
	mainModule.update(Viewmodel, dt, recoilSpring)
end)

mainModule.equip(Viewmodel, GunModel, animsFolder.Hold)

local IsPlayerHoldingMouse
local CanFire = true
local delay = 0.1

game:GetService("RunService").Heartbeat:Connect(function(dt)
	if IsPlayerHoldingMouse then
		if CanFire then
			CanFire = false
			
			recoilSpring:shove(Vector3.new(1, 0, 0))
			
			coroutine.wrap(function()
				print("wrap one")
				for i, v in pairs(GunModel.Components.ShootyShootPart:GetChildren()) do
					if v:IsA("ParticleEmitter") then
						print(v.." is v")
						v:Emit()
					end
				end
				
				local fireSound = GunModel.Components.Sounds.Fire:Clone()
				
				fireSound.Parent = game.Workspace
				fireSound.Parent = nil
				fireSound:Destroy()
			end)

			coroutine.wrap(function()
				
				wait(0.2)
				
				recoilSpring:shove(Vector3.new(-4, math.random(-2, 4), -15))
			end)
			mainModule.cast(GunModel, game.Players.LocalPlayer:GetMouse().Hit.Position, 60, player)
			GunModel:GetChildren().CFrame = CFrame.new(0, -3, 0)
			
			wait(delay)
			CanFire = true
		end
	end
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)

this is my explorer2021-05-06 19_12_12-Weaponhandler - Roblox Studio

12 Likes

You have to call the coroutine like this:

coroutine.wrap(function()

end)()
8 Likes

Very nice, but I recommend using code blocks

4 Likes

just finished the tutorial, but im coming into a bug with the hit detection, its only detecting hits that are within a few studs of me, not just humanoid either anything it hits.

3 Likes

Could you provide a video showing this? I’ll have to fix this if it’s a mistake on my part. Thanks!

4 Likes

I can’t currently because I am away from my pc, but when I get back I will be sure to record one!

3 Likes

This could be because I wrote something wrong, and if you want me to show you the code I canrobloxapp-20210501-1256503.wmv (1.2 MB)

3 Likes

Like you did last time, would you mind adding pictures of the finished scripts? or code blocks?

3 Likes

Well I didn’t provide the finished script because it will be too long but here:

script.pdf (181.4 KB)

Also regarding the video you sent, I couldn’t really see the problem. Could you point out the problem?

4 Likes

also if you watch the video again, if I back up, it doesnt damage the humanoid, when I get closer it does damage

4 Likes

Correct me if I’m wrong but, I don’t see any Humanoid / NPC in the video?

4 Likes

oh im terribly sorry I sent the wrong cliprobloxapp-20210508-1632553.wmv (4.0 MB) , this should be the correct one, I added large parts where the bullet hits so you can tell the hit detection better

3 Likes

Huh, that’s pretty weird, can you send the .cast() function over? Thanks.

4 Likes