Help with bullet trajectory

I recently found an open sourced gun system I want to use, I have modified, improved and fixed bugs on it, but theres a couple problem I dont know how to fix. The game is a third person shooter. The bullets used to be targeted to the players cursor, but because of some bugs, ive now changed it to go straight from the player, towards wherever they’re looking (the game has a custom shift lock). These are my problems. This might be confusing but let me know if you need me to explain further.

  1. Because the players body might be slightly turned due to animations and the shift lock, when the gun shoots, the bullets dont match up with the crosshair. What other way could I make the bullets go straight and match with the cursor without using the humanoidrootpart as the start location?

here are some screenshots to show you what I mean


the crosshair is centered btw

Edit: I forgot to add the scripts so here:
Script 1: BulletPosition

local module = {}

function module.Get(Character)
	local LocalPlayer = game.Players.LocalPlayer
	local RayParameters = RaycastParams.new()
	local RayCast = nil

	local screenCenter = workspace.CurrentCamera.ViewportSize / 2

	local rayOrigin = Character.HumanoidRootPart.Position -- + Vector3.new(0,0,-6)
	local cameraLook = workspace.CurrentCamera.CFrame.LookVector
	local rayDirection = (screenCenter.x - workspace.CurrentCamera.ViewportSize.x / 2) * workspace.CurrentCamera.CFrame.RightVector +
		(screenCenter.y - workspace.CurrentCamera.ViewportSize.y / 2) * workspace.CurrentCamera.CFrame.UpVector +
		workspace.CurrentCamera.CFrame.LookVector

	RayParameters.FilterDescendantsInstances = {Character}
	local raycastResult = workspace:Raycast(rayOrigin, rayDirection * 5000, RayParameters)

	if raycastResult then
		return raycastResult.Position, raycastResult.Instance, raycastResult.Normal
	else
		return rayOrigin + rayDirection * 2000, nil, nil
	end
end

return module

also theres another script used for handling the gun, let me know if you need that

you are using the humanoid root part position as the origin so the bullet shoots from the humanoid root part if you were to change it to the camera instead of the humanoid root part it should shoot centered but this fix could come with some issues like being able to shoot around corners.

local rayOrigin = workspace.CurrentCamera.CFrame.Position

It used to be that before, but I changed it because if there is something/someone behind you, but infront of the camera, you can pretty much shoot backwards

Yea I assumed that could happen I’m looking for other ways to get the results you want

1 Like

This is actually a fairly simple fix. What you need to do is raycast from your mouse’s screen pos in world space and then find the unit vector pointed towards that location from your HRP.

This is a snippet from one of my Utils modules I’ve made:

function ClientUtils:InputToWorldPosition(input: InputObject, maxDepth: number?)
	maxDepth = maxDepth or DEFAULT_MAX_INPUT_DEPTH
	
	local inputPos = input.Position
	local unitRay = CAMERA:ScreenPointToRay(inputPos.X, inputPos.Y)
	local result = workspace:Raycast(unitRay.Origin, unitRay.Direction*maxDepth, RAYCAST_PARAMS)
	return result and result.Position or unitRay.Origin+unitRay.Direction*maxDepth
end

With that you can do something like:

-- pseudo code
UserInputService.InputEnded:Connect(function(input, process)
	if process then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end

	local inputPos = ClientUtils:InputToWorldPosition(input, 500)
	local bulletUnitVector = (inputPos-Character:GetPivot().Position).UnitVector

	-- do logic with the known bullet unit vector...
end)
1 Like

Thank you! But i am fairly new to this type of stuff so it is confusing, would you be able to show an example with my type of code?

Yea, do you know where they have the input handled? I hope it’s using UserInputService

I believe its this part in the GunHandler script:

function UserInput(Type, Input, Typing)
	if not EquipedTool.Equipped and not Character.Rig then
		return
	end
	if Type == "Started" then
		if Typing then
			return
		end
		if Input.KeyCode == Enum.KeyCode.Q then
			if ShoulderSide == "Right" then
				ShoulderSide = "Left"
				if script:GetAttribute("Aiming") then
					PlayerCamera:SetOffset(Vector3.new(PlayerCamera:GetOffset().X-(AimOffset.X*2),PlayerCamera:GetOffset().Y,PlayerCamera:GetOffset().Z))
				end
			elseif ShoulderSide == "Left" then
				ShoulderSide = "Right"
				if script:GetAttribute("Aiming") then
					PlayerCamera:SetOffset(Vector3.new(PlayerCamera:GetOffset().X+(AimOffset.X*2),PlayerCamera:GetOffset().Y,PlayerCamera:GetOffset().Z))
				end
			end
		elseif Input.KeyCode == Enum.KeyCode.F9 then
			if Barriers.ConsoleOpen then
				Barriers.ConsoleOpen = false
			else
				Barriers.ConsoleOpen = true
				if EquipedTool.Firing then
					EquipedTool.Firing = false
				end
			end
		elseif Input.KeyCode == Enum.KeyCode.X then
			if EquipedTool.Equipped and EquipedTool.Module.Bullet.ToggleFiringMode and not EquipedTool.Firing and not EquipedTool.Reloading then
				if EquipedTool.Model:GetAttribute("ToggleMode") == false then
					EquipedTool.Model:SetAttribute("ToggleMode",true) 
					EquipedTool.Module.Bullet.FiringMode = "Semi"
					EquipedTool.Model.Handle.Mode:Play()
					Events.MainEvent:FireServer("ReplicateSound","Handle","Mode")
				else
					EquipedTool.Model:SetAttribute("ToggleMode",false)
					EquipedTool.Module.Bullet.FiringMode = "Auto"
					EquipedTool.Model.Handle.Mode:Play()
					Events.MainEvent:FireServer("ReplicateSound","Handle","Mode")
				end
			end
		elseif Input.KeyCode == Enum.KeyCode.V then
			if EquipedTool.Equipped and not EquipedTool.Firing and not EquipedTool.Reloading then
				EquipedTool.Model.Handle.Mode:Play()
				Events.MainEvent:FireServer("ReplicateSound","Handle","Mode")
				if EquipedTool.Model:GetAttribute("Safety") == false then
					EquipedTool.Model:SetAttribute("Safety",true)
				else
					EquipedTool.Model:SetAttribute("Safety",false)
				end
			end
		elseif Input.KeyCode == Enum.KeyCode.R then
			if EquipedTool.Equipped and (not EquipedTool.Reloading and EquipedTool.Model:GetAttribute("AmmoInMag") ~= EquipedTool.Module.Bullet.Capacity) then
				GunHandler.Reload()
			end
		elseif Input.UserInputType == Enum.UserInputType.MouseButton2 then
			if not EquipedTool.Reloading and not script:GetAttribute("Aiming") and not Barriers.MenuOpened then
				GunHandler.Aim(true)
			end
		elseif Input.UserInputType == Enum.UserInputType.MouseButton1 then
			if EquipedTool.Equipped and not EquipedTool.Reloading and script:GetAttribute("Aiming") and EquipedTool.Model:GetAttribute("Safety") == false then
				GunHandler.PreFire()
			end
		end
	elseif Type == "Ended" then
		if Input.UserInputType == Enum.UserInputType.MouseButton2 then
			if not EquipedTool.Reloading and script:GetAttribute("Aiming") then
				GunHandler.Aim(false)
				if EquipedTool.Module.Effects.LoopedFireAudio then
					AnimationHandler:Stop(EquipedTool.Animations,"SpinAnimation")
					EquipedTool.Model.Handle.AttachmentEmitter.Fire:Stop()
					EquipedTool.Model.Handle.AttachmentEmitter.WindLoop:Stop()
					Events.MainEvent:FireServer("ReplicateSound","AttachmentEmitter","Stop")
				end
			end
		elseif Input.UserInputType == Enum.UserInputType.MouseButton1 then
			if EquipedTool.Firing then
				EquipedTool.Firing = false
				if EquipedTool.Module.Effects.LoopedFireAudio then
					AnimationHandler:Stop(EquipedTool.Animations,"SpinAnimation")
					EquipedTool.Model.Handle.AttachmentEmitter.Fire:Stop()
					EquipedTool.Model.Handle.AttachmentEmitter.WindLoop:Stop()
					Events.MainEvent:FireServer("ReplicateSound","AttachmentEmitter","Stop")
				end
			end
		end
	elseif Type == "Changed" then
		return
	end
end

Let me know if im wrong, thanks.

If this is a tool I believe they’re using Activated, this looks like it’s just for replicating sounds sadly.

Oh i think this is it:

function GunHandler.Shoot()
	if EquipedTool.Model:GetAttribute("AmmoInMag") == 0 then
		if EquipedTool.Module.Effects.LoopedFireAudio then
			AnimationHandler:Stop(EquipedTool.Animations,"SpinAnimation")
			EquipedTool.Model.Handle.AttachmentEmitter.Fire:Stop()
			EquipedTool.Model.Handle.AttachmentEmitter.WindLoop:Stop()
			Events.MainEvent:FireServer("ReplicateSound","AttachmentEmitter","Stop")
		end
		local Empty = EquipedTool.Model.Handle.Empty:Clone()
		Empty.Parent = EquipedTool.Model.Handle
		Empty:Play()
		Services.Debris:AddItem(Empty,Empty.TimeLength)
		Events.MainEvent:FireServer("ReplicateSound","Handle","Empty")
		return
	end
	EquipedTool.Model:SetAttribute("MuzzleSmoke",(EquipedTool.Model:GetAttribute("MuzzleSmoke") + EquipedTool.Module.Effects.MuzzleAdd))
	AnimationHandler:Stop(EquipedTool.Animations, "RecoilAnimation",-1)
	AnimationHandler:Play(EquipedTool.Animations, "RecoilAnimation",-1)
	local Attachment = EquipedTool.Model.Handle.AttachmentEmitter
	if EquipedTool.Module.Effects.EjectChamberSmoke then
		EquipedTool.Model.Handle.GasEject.ParticleEmitter:Emit(math.random(5,10))
		Events.MainEvent:FireServer("ReplicateGas","ParticleEmitter")
	end
	if EquipedTool.Module.Effects.EjectShells then
		Events.MainEvent:FireServer("ReplicateShell","Ejection",EquipedTool.Module.Bullet.Casing,EquipedTool.Model.Handle.Ejection.WorldCFrame)
		if UserSettings():GetService("UserGameSettings").SavedQualityLevel.Value >= 3  then
			local ShellClone = GunAssets.Casings[EquipedTool.Module.Bullet.Casing]:Clone()
			ShellClone.Parent = workspace.Ignore
			ShellClone.CFrame = EquipedTool.Model.Handle.Ejection.WorldCFrame
			ShellClone.Velocity = (ShellClone.CFrame * CFrame.Angles(math.rad(math.random(-2,2)),math.rad(math.random(-2,2)),math.rad(math.random(-2,2)))).LookVector * 35
			local AngularVelocity = Instance.new("BodyAngularVelocity")
			AngularVelocity.Parent = ShellClone
			AngularVelocity.MaxTorque = Vector3.new(math.huge, math.huge, math.huge)
			AngularVelocity.P = math.huge
			AngularVelocity.AngularVelocity = Vector3.new(math.random(), math.random(), math.random()) * 70
			Services.Debris:AddItem(AngularVelocity,0.6)
			Services.Debris:AddItem(ShellClone,8)
		end
	end
	if EquipedTool.Module.Effects.LoopedFireAudio then
		AnimationHandler:Play(EquipedTool.Animations,"SpinAnimation")
		if not EquipedTool.Model.Handle.AttachmentEmitter.Fire.IsPlaying then
			Attachment.Fire:Play()
			Attachment.WindLoop:Play()
			Events.MainEvent:FireServer("ReplicateSound","AttachmentEmitter","Play")
		end
	else
		local Sound = EquipedTool.Model.Handle.AttachmentEmitter.Fire:Clone()
		Sound.Parent = Attachment
		Sound:Play()
		Services.Debris:AddItem(Sound,4)
	end
	Attachment.Flash:Emit(8)
	Attachment.SmokeSides:Emit(8)
	Events.MainEvent:FireServer("ReplicateEffect","AttachmentEmitter",EquipedTool.Module.Effects.LoopedFireAudio)
	PlayerCamera:Shake(RandomVector(EquipedTool.Module.Recoil.Min, EquipedTool.Module.Recoil.Max))
	for I = 1, EquipedTool.Module.Bullet.NumberOfShots do
		local ReturnInstance,ReturnedPosition,Normal,Finished = nil,nil,nil,false
		local Params = RaycastParams.new()
		local MousePosistion, MouseHit, MouseNormal = MousePosition.Get(Character.Rig,"Shoot")
		local mdist = (MousePosistion - Attachment.WorldPosition).Magnitude
		if EquipedTool.Module.Bullet.Spread  then
			MousePosistion += Vector3.new(math.random(-100,100)*mdist/2000,math.random(-100,100)*mdist/2000,math.random(-100,100)*mdist/2000)
		end
		Ignore = {Character.Rig,workspace.Ignore}
		Params.FilterDescendantsInstances = Ignore
		local Amr = nil
		while not Finished do
			local Raycast = workspace:Raycast(Attachment.WorldPosition, (MousePosistion - Attachment.WorldPosition) * 5000, Params)
			if not Raycast or ((Raycast.Instance.CanCollide and Raycast.Instance.Name ~= "Armour") or (Raycast.Instance.Parent:FindFirstChild("Humanoid")) and Raycast.Instance.Name ~= "HumanoidRootPart") then
				Finished = true
				if Raycast then
					ReturnInstance,ReturnedPosition,Normal = Raycast.Instance,Raycast.Position,Raycast.Normal
					CreateEffect("Particle",Raycast.Instance,Raycast.Position,Raycast.Normal)
					Events.MainEvent:FireServer("ReplicateTracer",ReturnInstance,ReturnedPosition,Normal,nil)
				else
					ReturnedPosition = Attachment.WorldPosition + (MousePosistion - Attachment.WorldPosition) * 5000
				end
			else
				if Raycast.Instance.Name == "Armour" then
					local Level,Health = Raycast.Instance:GetAttribute("Level"),Raycast.Instance:GetAttribute("Health")
					if EquipedTool.Module.Bullet.ArmourPenValue >= Level and Health > 0 then
						Amr = Health
						CreateHitmarker("ArmourThrough")
					elseif EquipedTool.Module.Bullet.ArmourPenValue <= Level and Health > 0 then
						Finished = true
						ReturnInstance,ReturnedPosition,Normal = Raycast.Instance,Raycast.Position,Raycast.Normal
						CreateHitmarker("ArmourStop")
					end
					CreateEffect("Particle",Raycast.Instance,Raycast.Position,Raycast.Normal)
					Events.MainEvent:FireServer("ReplicateTracer",Raycast.Instance,Raycast.Position,Raycast.Normal,nil)
					Events.MainEvent:FireServer("Armour",Raycast.Instance,EquipedTool.Module.Damages.Torso,EquipedTool.Module.Bullet.ArmourPenValue)
				end
				if Raycast.Instance.Transparency ~= 1 and Raycast.Instance.Name ~= "Armour" then
					CreateEffect("Particle",Raycast.Instance,Raycast.Position,Raycast.Normal)
					Events.MainEvent:FireServer("ReplicateTracer",Raycast.Instance,Raycast.Position,Raycast.Normal,nil)
				end
				table.insert(Ignore,Raycast.Instance)
				Params.FilterDescendantsInstances = Ignore
			end
		end	


		coroutine.resume(coroutine.create(function()
			local NewTracer = GunAssets.Tracers.Tracer1:Clone()
			NewTracer.Parent = workspace.Ignore
			NewTracer.Position = Attachment.WorldPosition
			NewTracer.BodyVelocity.Velocity = (ReturnedPosition - Attachment.WorldPosition).Unit * 2000
			NewTracer.Trail.Transparency = NumberSequence.new{
				NumberSequenceKeypoint.new(0, 1), 
				NumberSequenceKeypoint.new(1, 1),
			}
			Events.MainEvent:FireServer("ReplicateTracer",ReturnInstance,ReturnedPosition,Normal,Attachment)
			if ReturnInstance and Normal then
				if (ReturnedPosition - Attachment.WorldPosition).Magnitude/2000 >= 0.045 and NewTracer then
					task.delay(0.045,function()
						NewTracer.Trail.Transparency = NumberSequence.new{
							NumberSequenceKeypoint.new(0, 0), 
							NumberSequenceKeypoint.new(1, 0),
						}
					end)
				end
				Services.Debris:AddItem(NewTracer,math.max(0.045,(ReturnedPosition - Attachment.WorldPosition).Magnitude/2000))
			elseif not ReturnInstance then
				if NewTracer then
					task.delay(0.045,function()
						NewTracer.Trail.Transparency = NumberSequence.new{
							NumberSequenceKeypoint.new(0, 0), 
							NumberSequenceKeypoint.new(1, 0),
						}
					end)
				end
				Services.Debris:AddItem(NewTracer,2)
			end
			if FoundHumanoid.ReturnHumanoid(ReturnInstance) then
				local Damage = nil
				if ReturnInstance.Name == "Head" then
					Damage = EquipedTool.Module.Damages.Head
				elseif ReturnInstance.Name == "UpperTorso" or ReturnInstance.Name == "LowerTorso" then
					Damage = EquipedTool.Module.Damages.Torso
				else
					Damage = EquipedTool.Module.Damages.Base
				end
				if Amr ~= nil then
					Damage -= math.sqrt(Amr)
				else
					CreateHitmarker("Person",ReturnInstance.Name)
				end
				Events.MainEvent:FireServer("DamageReplication",FoundHumanoid.ReturnHumanoid(ReturnInstance),Damage,Attachment,MousePosistion,Ignore,ReturnInstance)
			end
		end))
	end
end

Actually I just realized I don’t need this LOL… one second

1 Like

Let me know if this works and if you have any questions:

local module = {}

function module.Get(Character)
	local LocalPlayer = game.Players.LocalPlayer
	local RayParameters = RaycastParams.new()
	local RayCast = nil
	local MaxDepth = 5000
	
	RayParameters.FilterDescendantsInstances = {Character}
	
	-- get the mouse's WorldPosition(center of camera at all times I hope LOL)
	local mouseOrigin = workspace.CurrentCamera.CFrame.Position
	local mouseDirection = workspace.CurrentCamera.CFrame.LookVector
	local raycastResult = workspace:Raycast(mouseOrigin, mouseDirection * MaxDepth, RayParameters)
	-- if the result was nil, we still want to cast towards where we were aiming 
	-- so we default to our `mouseOrigin+mouseDirection*MaxDepth`
	local mouseWorldPos = raycastResult and raycastResult.Position or mouseOrigin+mouseDirection*MaxDepth
	
	-- raycast from HumanoidRootPart to our mouseWorldPos
	local rootPos = Character.HumanoidRootPart.Position
	local rootUnitVector = (mouseWorldPos-rootPos).Unit
	-- dist from rootPos to mouseWorldPos(may need to add 1 stud just incase...)
	local dist = (rootPos-mouseWorldPos).Magnitude
	-- raycast from our rootpart towards the rootUnitVector we calculated with the dist to the mouseWorldPos...
	local characterRaycastResult = workspace:Raycast(rootPos, rootUnitVector*dist, RayParameters)
	
	-- basically what you had before except we swapped it for the second raycast result...
	if characterRaycastResult then
		return characterRaycastResult.Position, characterRaycastResult.Instance, characterRaycastResult.Normal
	else
		return rootPos + rootUnitVector * dist, nil, nil
	end
end

return module

What if he just used the weapon as a origin then use the camera hit as direction?

He doesn’t want them to be able to peak hence why it’s origin is the root part… good shout tho.

Thank you! But unfortunately it did not work, anything behind the player but infront of the camera will still be shot backwards.

So the gun is shooting backwards? I might’ve messed up my vector math lol

Only if the crosshair is on an object behind the player

Oh you’ll probably have to offset the initial camera origin by the lookvector a little bit… I’m not the greatest when it comes to gun systems so I wouldn’t know the best thing to do for 3rd person stuff.

I’ll try, but thanks a lot for helping!!! I really appreciate it! :smile: :smiling_imp:

you could use dot product to check if the mouse world pos is behind the player actually… idk if this is the BEST solution but it would work as well…