Bodycam System v3

I’m currently making a bodycam system that take as much math as possible to make the most realistic movement, I need some feedback about what kind of movement I should add to make the game more realistic

-Log-
unrecord style sway
camera adapt with body movement
dynamic and accurate footstep
slower paste than the last version
side aim with laser instead of iron sight

4 Likes

Looks nice. I can see it being used by mil sims and other RP games.

1 Like

I’ll make a hardcore zombie game like G&B using this system:)
it’s pretty wasteful to use this in a roleplay game

1 Like

I think you could use it for a story-driven bodycam shooter. I don’t recall the last time I saw one on ROBLOX, let alone one that’s got it’s own narrative.

2 Likes

Cool!

I immediately noticed the lack of acceleration/deceleration when moving. Player moves from 0-16 then 16-0 almost instantaneously. Would be a lot better if there’s even just 0.3 seconds of smoothing.

yeah, that’s a great Idea!
I’ll add it on the next update

how’d you get the shake like when walking. Each footstep followed with a little shake in the camera. I still have trouble with that till date. Nice work though!

first I change the camera part follow in basecamera module to Head but in my case I change it to my torso, and I have a created my own super accurate foot step system that check up to every single leg, and with that every time it detect a step, it’ll play the footstep sound and add a little shake using spring module, and that other I using noise to add a little bit more static motion but the main stuff is the foot step indicator and the basecamera module

here’s my accurate footstep system

if we need help in those things then Im free to help!

1 Like

I’m just stuck currently with the spring module. I’ve tried to replicate a shooting recoil. The main problem is that my game is simulated from a helmet camera and adding camera shake would just omit realism. I’m not sure how to maneuver it and I’ve tried making arms shake etc. Nothing seems to work These little details is what I lack overall. BTW great footstep system it works well! I’m using something called EasyFirstPerson instead of a viewmodel as I’m new to it.

local userInputService = game:GetService("UserInputService")
local runService = game:GetService("RunService")
local tweenService = game:GetService("TweenService")
local lighting = game:GetService("Lighting")


local tool = script.Parent.Parent

local animationsFolder = script.Parent:WaitForChild("Animations")
local remotesFolder = script.Parent:WaitForChild("Remotes")

local mouse = game.Players.LocalPlayer:GetMouse()
local camera = game.Workspace.CurrentCamera
local Spring = require(game:GetService("ReplicatedStorage"):WaitForChild("Spring"))

-- Create springs for camera shake
local positionSpring = Spring.new(Vector3.zero, 1, 2) -- Spring for position
local rotationSpring = Spring.new(Vector3.zero, 1, 2) -- Spring for rotation

local equipped = false
local reloading = false
local checkingAmmo = false
local currentHumanoid = nil
local currentCharacter = nil
local currentGui = nil
local wasUnequipped = false

local idleAnim = nil
local shootAnim = nil
local adsshoot = nil
local equipAnim = nil
local reloadAnim = nil
local sprintAnim = nil
local walkAnim = nil
local aimAnim = nil
local AmmoCheck = nil

local fullAuto = true
local fireRate = 0.1
local shooting = false
local sprinting = false
local semiDebounce = false
local walking = false
local aiming = false

local currentCrosshair = nil

local CameraShaker = require(game.ReplicatedStorage.CameraShaker)

local camera = game.Workspace.CurrentCamera

local camShake = CameraShaker.new(Enum.RenderPriority.Camera.Value, function(shakeCf)
	camera.CFrame = camera.CFrame * shakeCf
end)

camShake:Start()

-- Add more variation to the shoot animation
local randomSpeed = math.random(7, 15) / 10  -- Random speed between 0.7x and 1.5x
local randomTimePosition = math.random(0, 20) / 100  -- Random start position between 0 and 0.2 seconds


local swayStrength = 0.02  -- Adjust the sway strength
local swaySpeed = 5 

--Shoot Replicated--
-- Inside the Shoot event handler
remotesFolder.Shoot.OnClientEvent:Connect(function(maxAmmo, ammo)
	if aiming == true then
		adsshoot:Play(randomTimePosition, randomSpeed)
	else
		shootAnim:Play(randomTimePosition, randomSpeed)
	end

	-- Apply recoil impulses to the springs
	positionSpring:Impulse(Vector3.new(0, 0.1, -0.2)) -- Adjust values for desired recoil
	rotationSpring:Impulse(Vector3.new(0.05, 0, 0)) -- Adjust values for desired recoil

	-- Add the following lines to enable the blur effect
	local blur = lighting:FindFirstChild("SHoot")
	if blur then
		blur.Enabled = true
		wait(0.1)  -- Adjust the duration of the blur effect as needed
		blur.Enabled = false
	end

	-- Ammo Gui Animation
	if currentGui ~= nil then
		tweenService:Create(currentGui.Holder.Ammo, TweenInfo.new(0.1), {Size = UDim2.new(0.89, 0, 0.325, 0)}):Play()
		currentGui.Holder.Ammo.Text = tostring(ammo).."/"..tostring(maxAmmo)
		wait(0.1)
		if currentGui ~= nil then
			tweenService:Create(currentGui.Holder.Ammo, TweenInfo.new(0.1), {Size = UDim2.new(0.877, 0, 0.304, 0)}):Play()
		end
	end
end)

local RunService = game:GetService("RunService")




--Reload Replicated--
remotesFolder.Reload.OnClientEvent:Connect(function(reloadTime, maxAmmo)
	wasUnequipped = false
	reloading = true
	reloadAnim:Play(0, 10)
	script.Parent.Sounds.ReloadSound:Play()
	wait(reloadTime)
	if currentGui ~= nil and wasUnequipped == false then
		currentGui.Holder.Ammo.Text = tostring(maxAmmo).."/"..tostring(maxAmmo)
	end
	reloading = false
end)

--Equip Handler--
tool.Equipped:Connect(function()
	equipped = true
	script.Parent.Sounds.EquipSound:Play()
	userInputService.MouseIconEnabled = false
	currentCrosshair = script.Parent.CrosshairGui:Clone()
	currentCrosshair.Parent = game.Players.LocalPlayer.PlayerGui
	currentHumanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
	currentCharacter = tool.Parent
	idleAnim = currentHumanoid:LoadAnimation(animationsFolder.Idle)
	idleAnim.Priority = Enum.AnimationPriority.Movement
	shootAnim = currentHumanoid:LoadAnimation(animationsFolder.Shoot)
	adsshoot = currentHumanoid:LoadAnimation(animationsFolder.Adsshoot)
	equipAnim = currentHumanoid:LoadAnimation(animationsFolder.Equip)
	reloadAnim = currentHumanoid:LoadAnimation(animationsFolder.Reload)
	sprintAnim = currentHumanoid:LoadAnimation(animationsFolder.Sprint)
	walkAnim = currentHumanoid:LoadAnimation(animationsFolder.walking)
	aimAnim = currentHumanoid:LoadAnimation(animationsFolder.Aiming)
	AmmoCheck = currentHumanoid:LoadAnimation(animationsFolder.AmmoCheck)
	equipAnim:Play(0, 100)
	idleAnim:Play(0.5, 1)
end)

--Unequip Handler--
tool.Unequipped:Connect(function()
	shooting = false
	aiming = false
	sprinting = false
	walking = false
	equipped = false
	reloading = false
	checkingAmmo = false
	wasUnequipped = true
	
	wait()
	userInputService.MouseIconEnabled = true
	currentHumanoid = nil
	currentCharacter = nil
	idleAnim:Stop()
	shootAnim:Stop()
	equipAnim:Stop()
	reloadAnim:Stop()
	adsshoot:Stop()
	sprintAnim:Stop()
	walkAnim:Stop()
	idleAnim = nil
	AmmoCheck = nil
	shootAnim = nil
	equipAnim = nil
	aimAnim = nil
	adsshoot = nil
	reloadAnim = nil
	sprintAnim = nil
	walkAnim = nil
	currentCrosshair:Destroy()
	currentCrosshair = nil
	currentGui = nil
	currentHumanoid.WalkSpeed = 4
	
	script.Parent.Sounds.EquipSound:Stop()
	script.Parent.Sounds.ReloadSound:Stop()
end)




--Input Handler--
userInputService.InputBegan:Connect(function(input, gameProcess)
	if equipped == true then
		if gameProcess then
			return
		end

		if input.UserInputType == Enum.UserInputType.MouseButton1 then --Shoot
			if fullAuto == true and semiDebounce == false then --If NOT semi
				print("Client")
				semiDebounce = true
				shooting = true
				while shooting == true do
					remotesFolder.Shoot:FireServer(mouse.Hit.p, tool.Handle.Muzzle.WorldPosition, tool.Handle.CFrame, tool.Handle.Muzzle.WorldCFrame.LookVector)


					--camShake:Shake(CameraShaker.Presets.Deagle2)
					wait(fireRate)
				end
				
				wait(fireRate)
				semiDebounce = false
			else --If IS semi
				if semiDebounce == false then
					semiDebounce = true
					remotesFolder.Shoot:FireServer(mouse.Hit.p, tool.Handle.Muzzle.WorldPosition, tool.Handle.CFrame, tool.Handle.Muzzle.WorldCFrame.LookVector)


					--camShake:Shake(CameraShaker.Presets.Deagle2)
					wait(fireRate)
					semiDebounce = false
				end
			end
			
		end
		
		
		if input.KeyCode == Enum.KeyCode.R and reloading == false then --Reload
			remotesFolder.Reload:FireServer()
		end
		
		if input.KeyCode == Enum.KeyCode.T then
			-- Perform the raycast
			
		end

	

		if input.KeyCode == Enum.KeyCode.LeftShift and currentHumanoid.MoveDirection.magnitude > 0 then
			sprinting = true
			if sprinting == true then
				sprintAnim:Play(0)
				currentHumanoid.WalkSpeed = 10
			end
			
		end
		
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			aiming = true
			if aiming == true then
				local ReplicatedStorage = game:GetService("ReplicatedStorage")
				local CollectionService = game:GetService("CollectionService")
				local TweenService = game:GetService("TweenService")
				local RunService = game:GetService("RunService")

				local ScopeParallax = require(ReplicatedStorage.ScopeParallax)
				ScopeParallax.SetEnabled(true)
				aimAnim:Play(0.5,7)
				currentHumanoid.WalkSpeed = 2
			else
				currentHumanoid.WalkSpeed = 4
			end

		end
		
		if input.KeyCode == Enum.KeyCode.G then
			checkingAmmo = true
			
			if checkingAmmo == true then
				AmmoCheck:Play()

				tool.Parts.Mag.Attachment.BillboardGui.Enabled = true
				wait(2.5)
				tool.Parts.Mag.Attachment.BillboardGui.Enabled = false

			end
			
		end

		
		if input.KeyCode == Enum.KeyCode.V then --Reload
			tool.Parts.Pointer.SpotLight.Enabled = not tool.Parts.Pointer.SpotLight.Enabled
		end
	end
end)

userInputService.InputEnded:Connect(function(input, gameProcess)
	if equipped == true then
		if gameProcess then
			return
		end

		if input.UserInputType == Enum.UserInputType.MouseButton1 then --End of shoot
			shooting = false
		end
		
		
		if input.KeyCode == Enum.KeyCode.LeftShift then
			sprinting = false
			sprintAnim:Stop(0.5, 2)
			-- Stop the sprint animation here if needed
		end
		
		if input.KeyCode == Enum.KeyCode.G then
			checkingAmmo = false
			AmmoCheck:Stop(0.5, 2)
			-- Stop the sprint animation here if needed
			
		end
		
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			aiming = false
			aimAnim:Stop()
			

		end
	end
	
	
end)



runService.RenderStepped:Connect(function()
	-- Apply the spring values to the camera
	local camera = workspace.CurrentCamera
	local recoilOffset = CFrame.new(positionSpring.Position) * CFrame.Angles(rotationSpring.Position.X, rotationSpring.Position.Y, rotationSpring.Position.Z)
	camera.CFrame = camera.CFrame * recoilOffset

	-- Arm and character pointing towards mouse stuff
	if equipped == true and mouse.Hit.Position ~= nil and currentCharacter ~= nil then
		local rightX, rightY, rightZ = currentCharacter.Torso["Right Shoulder"].C0:ToEulerAnglesYXZ()
		currentCharacter.Torso["Right Shoulder"].C0 = (currentCharacter.Torso["Right Shoulder"].C0 * CFrame.Angles(0, 0, -rightZ)) * CFrame.Angles(0, 0, math.asin((mouse.Hit.p - mouse.Origin.p).unit.y))
		tweenService:Create(currentCharacter.Torso["Right Shoulder"], TweenInfo.new(0.2), {C0 = (currentCharacter.Torso["Right Shoulder"].C0 * CFrame.Angles(-rightX, 0, -rightZ)) * CFrame.Angles(0, 0, math.asin((mouse.Hit.p - mouse.Origin.p).unit.y))}):Play()
		local leftX, leftY, leftZ = currentCharacter.Torso["Left Shoulder"].C0:ToEulerAnglesYXZ()
		currentCharacter.Torso["Left Shoulder"].C0 = (currentCharacter.Torso["Left Shoulder"].C0 * CFrame.Angles(0, 0, -leftZ)) * CFrame.Angles(0, 0, math.asin((-mouse.Hit.p - -mouse.Origin.p).unit.y))
		tweenService:Create(currentCharacter.Torso["Left Shoulder"], TweenInfo.new(0.2), {C0 = (currentCharacter.Torso["Left Shoulder"].C0 * CFrame.Angles(-rightX, 0, -leftZ)) * CFrame.Angles(0, 0, math.asin((-mouse.Hit.p - -mouse.Origin.p).unit.y))}):Play()
	end

	-- Crosshair updater
	if equipped == true and currentCrosshair ~= nil then
		currentCrosshair.Crosshair.Position = UDim2.fromOffset(mouse.X, mouse.Y)
	end
end)

Above is the client for my gun script; The shoot remote is where shooting is fired.

local sensitivity = 2 -- how quick/snappy the sway movements are. Don't go above 2
local swaysize = 1 -- how large/powerful the sway is. Don't go above 2
local includestrafe = false -- if true the fps arms will sway when the character is strafing
local includewalksway = false -- if true, fps arms will sway when you are walking
local includecamerasway = false -- if true, fps arms will sway when you move the camera
local includejumpsway = false -- if true, jumping will have an effect on the viewmodel
local headoffset = Vector3.new(0,0,0) -- the offset from the default camera position of the head. (0,1,0) will put the camera one stud above the head.
local firstperson_arm_transparency = 0 -- the transparency of the arms in first person; set to 1 for invisible and set to 0 for fully visible.
local firstperson_waist_movements_enabled = false -- if true, animations will affect the Uppertorso. If false, the uppertorso stays still while in first person (applies to R15 only)
local CAMERA_HEIGHT = 1.3--0.69 for helmet camera
local CAMERA_DEPTH = -1.6  --was -0.8
local uis = game:GetService("UserInputService")
local runservice = game:GetService("RunService")
local tweenservice = game:GetService("TweenService")
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
repeat runservice.Heartbeat:Wait() until script.Parent:IsA("Model") -- yield until character
local character = player.Character
local rootpart = character:WaitForChild("HumanoidRootPart")
local humanoid = character:WaitForChild("Humanoid")
local aimoffset = script:WaitForChild("AimOffset") -- a property for other scripts to use to influence the viewmodel offset (such as a gun aim system)
local torso
local roothip
local lowertorso
local oldc0
local leftshoulder
local rightshoulder
local larm
local rarm
local armparts = {}
local rigtype = nil
local isrunning = false
local armsvisible = true
local armtransparency = firstperson_arm_transparency
local isfirstperson = true
local sway = Vector3.new(0,0,0)
local walksway = CFrame.new(0,0,0)
local strafesway = CFrame.Angles(0,0,0)
local jumpsway = CFrame.new(0,0,0)
local jumpswaygoal = Instance.new("CFrameValue")
local viewmodel = Instance.new("Model")
local fakeroot = Instance.new("Part")
viewmodel.Name = "Viewmodel"
fakeroot.Name = "HumanoidRootPart"
fakeroot.CanCollide = false
fakeroot.CanTouch = false
fakeroot.Anchored = true
fakeroot.Transparency = 1
fakeroot.Parent = viewmodel
local faketorso = Instance.new("Part")
faketorso.Name = "Torso"
faketorso.CanCollide = false
faketorso.CanTouch = false
faketorso.Transparency = 1
faketorso.Parent = viewmodel
viewmodel.PrimaryPart = fakeroot
viewmodel.WorldPivot = fakeroot.CFrame+fakeroot.CFrame.UpVector*3
viewmodel.Parent = nil
local fakelowertorso = nil
local waistclone = nil
local leftshoulderclone = nil
local pos = nil
local rightshoulderclone = nil
local roothipclone = nil
local oldcpos = nil
camera.CameraType = Enum.CameraType.Scriptable
   rigtype = "R6"
   torso = character:WaitForChild("Torso")
   -- add the arms
   table.insert(armparts, character:WaitForChild("Right Arm"))
   table.insert(armparts, character:WaitForChild("Left Arm"))
   roothip = rootpart:FindFirstChildOfClass("Motor6D")
   oldc0 = roothip.C0
   leftshoulder = torso:WaitForChild("Left Shoulder")
   rightshoulder = torso:WaitForChild("Right Shoulder")
   faketorso.Size = torso.Size
   fakeroot.Size = rootpart.Size
   faketorso.CFrame = fakeroot.CFrame
   roothipclone = roothip:Clone()
   roothipclone.Parent = fakeroot
   roothipclone.Part0 = fakeroot
   roothipclone.Part1 = faketorso
   leftshoulderclone = leftshoulder:Clone()
   leftshoulderclone.Name = "LeftShoulderClone"
   leftshoulderclone.Parent = torso
   leftshoulderclone.Part0 = torso
   rightshoulderclone = rightshoulder:Clone()
   rightshoulderclone.Name = "RightShoulderClone"
   rightshoulderclone.Parent = torso
   rightshoulderclone.Part0 = torso
   larm = character:WaitForChild("Left Arm")
   rarm = character:WaitForChild("Right Arm")

if firstperson_arm_transparency >= 1 then
   armsvisible = false
end


local function visiblearms(bool)
   if armsvisible then
   	local castshadow = not bool
   	for i, part in ipairs(armparts) do
   		part.LocalTransparencyModifier = armtransparency
   		part.CastShadow = castshadow
   	end
   end
end

local function enableviewmodel()
   isfirstperson = true
   viewmodel.Parent = workspace.CurrentCamera
   rightshoulderclone.Enabled = true
   leftshoulderclone.Enabled = true
   leftshoulder.Enabled = false
   rightshoulder.Enabled = false
   rightshoulderclone.Part1 = rarm
   rightshoulderclone.Part0 = faketorso
   rightshoulderclone.Parent = faketorso
   leftshoulderclone.Part1 = larm
   leftshoulderclone.Part0 = faketorso
   leftshoulderclone.Parent = faketorso
   armtransparency = firstperson_arm_transparency
   for i, v in pairs(character:GetChildren()) do
   	if v:IsA("Accessory") then
   		v:WaitForChild("Handle").LocalTransparencyModifier = 0
   	end
   	if v:IsA("Part") or v:IsA("MeshPart") then
   		v.LocalTransparencyModifier = 0
   	end
   end
end


local function disableviewmodel()
   isfirstperson = false
   viewmodel.Parent = nil
   rightshoulderclone.Enabled = false
   leftshoulderclone.Enabled = false
   viewmodel.Parent = nil
   leftshoulder.Parent = torso
   leftshoulder.Part0 = torso
   leftshoulder.Part1 = larm
   rightshoulder.Parent = torso
   rightshoulder.Part0 = torso
   rightshoulder.Part1 = rarm
   leftshoulder.Enabled = true
   rightshoulder.Enabled = true
   armtransparency = 0
   visiblearms(false)
   for i, v in pairs(character:GetChildren()) do
   	if v:IsA("Accessory") then
   		v:WaitForChild("Handle").LocalTransparencyModifier = 0
   	end
   	if v:IsA("Part") or v:IsA("MeshPart") then
   		v.LocalTransparencyModifier = 0
   	end
   end
end

local function checkfirstperson()
   enableviewmodel()
end



uis.MouseDeltaSensitivity = 0.5
local playerchanged_con = nil
playerchanged_con = player.Changed:Connect(function(property)
   if property == "CameraMaxZoomDistance" or property == "CameraMode" then
   	if player.CameraMaxZoomDistance <= 0.5 or player.CameraMode == Enum.CameraMode.LockFirstPerson then
   		enableviewmodel()
   	end
   end
end)

if (game.StarterPlayer.CameraMode == Enum.CameraMode.LockFirstPerson) or (game.StarterPlayer.CameraMaxZoomDistance <= 0.5) then
   enableviewmodel() 
end

game:GetService("RunService").RenderStepped:connect(function()
   checkfirstperson()
   if isfirstperson == true then
   	visiblearms(true)
   	local delta = uis:GetMouseDelta()
   	rightshoulderclone.Transform = rightshoulder.Transform
   	leftshoulderclone.Transform = leftshoulder.Transform
   	--finalCF is the calculation for the position where the viewmodel should be
   	local finalcf = (camera.CFrame*walksway*jumpsway*strafesway*CFrame.Angles(math.rad(sway.Y*swaysize),math.rad(sway.X*swaysize)/10,math.rad(sway.Z*swaysize)/2))+(camera.CFrame.UpVector*(-1.7-(headoffset.Y+(aimoffset.Value.Y))))+(camera.CFrame.LookVector*(headoffset.Z+(aimoffset.Value.Z)))+(camera.CFrame.RightVector*(-headoffset.X-(aimoffset.Value.X)+(-(sway.X*swaysize)/75)))			
   	viewmodel.PrimaryPart.CFrame = viewmodel.PrimaryPart.CFrame:Lerp(CFrame.new(finalcf.Position, Vector3.new(mouse.Hit.Position.X, mouse.Hit.Position.Y, mouse.Hit.Position.Z)),0.9)		
   	if character:FindFirstChildWhichIsA("Tool") then --if a tool is equipped, enabled the viewmodel otherwise regular arms
   		if camera.CameraType == Enum.CameraType.Scriptable then
   			enableviewmodel()
   		end
   	else
   		if camera.CameraType == Enum.CameraType.Scriptable then
   			disableviewmodel()
   		end
   	end
   	if camera.CameraType == Enum.CameraType.Scriptable then	 --basically is a tool equipped
   		local divisor = 0.075 --how smooth the camera turning is, the lower the number the smoother and slower but the more buggy it is but the higher it is the less buggy but the less control of the camera the player has
   		if oldcpos then
   			local supamagnitude = (oldcpos-pos).Magnitude
   			divisor = supamagnitude*0.09
   			if divisor > 0.075 or divisor == 0 and supamagnitude < 2 then
   				divisor = 0.075
   			end				
   		end
   		local targetPos = pos or character.Camera_Part.Position -- Use fallback if pos is nil
   		camera.CFrame = camera.CFrame:Lerp(CFrame.new(character.Camera_Part.Position, targetPos), divisor)

   		--camera.CFrame = CFrame.lookAt(camera.CFrame.Position, camera.CFrame.Position + Vector3.new(camera.CFrame.LookVector.X,-0.15,camera.CFrame.LookVector.Z)) --prevent the weird movement tilt
   		
   	end
   	oldcpos = pos
   end
end)



mouse.Move:Connect(function()
   if mouse.Target then
   	pos = mouse.Hit.Position --if mouse moved then change pos, this is to only make the camera/viewmodel move when the player wants it to
   end
end)

humanoid:GetPropertyChangedSignal("MoveDirection"):Connect(function()
   if mouse.Target then
   	pos = mouse.Hit.Position --if player then change pos, otherwise pos is a set spot in workspace that you could walk up to and the camera would bug out
   end
end)

and this is my EasyFirstPerson which has been edited. It would be of great help if you could provide me with some assistance!

1 Like

My way to do it is to shove the recoil spring in the shoot function, and I didn’t use easy first person so I don’t know how to work with it, about the camera recoil cframe, I just make another spring and only shove it on the with random number for each axis and add it to the camera rotation

1 Like

ok i’ll try that. Lyk how it goes.

This bodycam system looks INSANELY GOOD AND REALISTIC FOR A ROBLOX GAME! Great job on it! How long did it take to create this bodycam system? Will you use it in anything?

If I include the failed project, that gives me what I need to make this. It took about 6 months, but I only made this in 3 weeks

(Sorry for the late response)