Bullets have a "mind of their own"

Hello

I’m not entirely sure how to explain this, but I’ve had a lot of trouble with my gun framework raycasting in old camera positions that should be updated to a new area.

https://gyazo.com/dca874cfa9dc25dc77fc1767e96758a1
https://gyazo.com/e054eb4bc868c45c56f2ac6423d54773

I know this occurs when you move your camera really fast, and thereby causing the script to update from an older position, but I wanted to know how you can keep it more precise to where the camera is pointed instead.

Here is the code:

               local CamPos = Cam.CFrame.Position
				local Direction = Cam.CFrame.LookVector
				local chosenInacc = Vector3.new(math.random(-Diverge, Diverge), math.random(-Diverge,Diverge), math.random(-Diverge,Diverge))*currInaccuracy*.005
				Direction += chosenInacc
				local Ray = workspace:Raycast(CamPos, Direction * 500, Params)
				
				if Ray then
					local Result = Ray.Instance
					local ResultPos
					if Result.Transparency == 1 then
						Params.FilterDescendantsInstances = {Params.FilterDescendantsInstances, Result}
						Ray = workspace:Raycast(Ray.Position, Direction * 1500, Params)
						if Ray then
							Result = Ray.Instance
						else
							Fire:FireServer(Ray.Position)
							return
						end
					end
					ResultPos = Ray.Position
					Fire:FireServer(Result, ResultPos)
				end
				recoilCF = recoilCF:Lerp(CFrame.Angles(math.rad(Recoil),0,0),.03)
				VMAnims.Shoot:Play()
				NormalAnims.Shoot:Play()
				vModel.Handle.A0.Spark:Emit(1)

This script is on the client, but it was recently changed from server validation in the hopes that I could rectify this problem. I’m hoping that I can move back to server validation if anyone can provide a better method for raycasting bullets.

2 Likes

Sounds more like an latency issue, you can try confirming this by manipulating the replication lag setting in the studio emulator and see how the performance differs:

ScreenShot_20220715124855

The actual hit registration and bullet mechanics should stay on the server for security, but the visuals should be client-sided, which should mitigate the visual lag in addition to removing some processing burdens from the server.
You will need to figure out a way to split the visuals from the bullet handler, such as having each client visually simulate the bullet upon request via a remote, but the player that’s shooting should immediately start simulating their own bullet

4 Likes

That does seem to be the issue:

https://gyazo.com/65a3b54ab85c49185762b9c24ada272c

So if it is a replication issue, then do you think sending a :FireAllClients() remote for visual purposes would be more legitimate than to have the server try to build the bullet tracer instead? I can show the code here as well:

if Current.Value <= 0 then return end
	if Char:GetAttribute("BulletCooldown") == true then return end

	Model.Handle["Shoot"..math.random(1,3)]:Play()
	Current.Value = Current.Value - 1
	if ResultPos then
		Bullet.Position = ResultPos
		Model.Beam.Attachment1 = Bullet.A1
		if Result then
			if Result.Parent:FindFirstChild("Humanoid") or Result:FindFirstAncestorOfClass("Model") then -- Person
				local Hum2
				if Result:FindFirstAncestorOfClass("Model"):GetAttribute("Class") then --the bullet hit an item
					Hum2 = Result:FindFirstAncestorOfClass("Model").Parent:FindFirstChild("Humanoid")
				else
					Hum2 = Result.Parent:FindFirstChild("Humanoid")
				end

				if Hum2 then
					local Char = Hum2.Parent
					local Player2 = game.Players[Char.Name]
					local Damage = Weapon:GetAttribute("Damage")
					local DamageRes = Hum:GetAttribute("DamageRes")
					if not DamageRes then
						DamageRes = 0
					end
					if Result.Name == "Head" then
						RegisterHit(Player, Player2, Hum2, Damage*2, Weapon)
					else
						RegisterHit(Player, Player2, Hum2, Damage - (Damage * (DamageRes/100)), Weapon)
					end
					Player.Hit.Value = Player.Hit.Value + 1

					Bullet:FindFirstChild("Hit"..math.random(1,4)):Play()
				end
			end
		end
	end
	Model.Handle.A0.Spark:Emit(1)
	Char:SetAttribute("BulletCooldown", true)
	task.wait(Weapon:GetAttribute("Cooldown"))
	Model.Beam.Attachment1 = nil
	Char:SetAttribute("BulletCooldown", false)

This is what the server receives. Result and ResultPos are given from the client’s ray, the rest is parameters passed within a function for sound/visual/reloading purposes.

What could I remove from here, or at least add to keep the bullets more accurate?

1 Like

The server should be responsible for all of the important stuff, including the raycast itself for hit registration. The client only needs to tell the server where they’re firing at, and the server is the one that does all of the sanity checks (where are they, what gun they’re using, etc.) and math involved.

Using this could be problematic; the shooter is supposed to immediately start simulating their own bullets’ visuals without the server telling them to do so. When you fire all clients, it might cause the bullet to visually duplicate on the shooter’s screen. You should manually fire each client excluding the shooter themselves since they already have the bullet.

As for actual replication, the server should just tell each client the origin and the direction of the bullet; basic information to recreate the bullet without knowing anything else.

Also unrelated note:

Save yourself some slack and use Luau’s compound operator: variable += 1
Syntax - Luau

1 Like

I’m not having much luck. It may have some subtle changes but I still think it’s not right.

Switching to server validation and then having visual/audio concerns on the client:
https://gyazo.com/e56dfa47c0235217663ce3e1d9a5bd21

if Current.Value <= 0 then return end
	if Char:GetAttribute("BulletCooldown") == true then return end
	local chosenInacc = Vector3.new(math.random(-Diverge, Diverge), math.random(-Diverge,Diverge), math.random(-Diverge,Diverge))*currInaccuracy*.005
	Direction += chosenInacc
	local Ray = workspace:Raycast(Pos, Direction * 500, Params)
	--Model.Handle["Shoot"..math.random(1,3)]:Play()
	Current.Value = Current.Value - 1
	if Ray.Instance then
		local Result = Ray.Instance
		if Result.Transparency == 1 then
			Params.FilterDescendantsInstances = {Params.FilterDescendantsInstances, Result}
			Ray = workspace:Raycast(Ray.Position, Direction * 1500, Params)
			if Ray then
				Result = Ray.Instance
			else
				return
			end
		end
		local ResultPos = Ray.Position
		RS.SentBullet:FireAllClients(Bullet, Model.Handle["Shoot"..math.random(1,3)], Model.Beam, ResultPos, Result, Model)
		if Result.Parent:FindFirstChild("Humanoid") or Result:FindFirstAncestorOfClass("Model") then -- Person
			local Hum2
			if Result:FindFirstAncestorOfClass("Model"):GetAttribute("Class") then --the bullet hit an item
				Hum2 = Result:FindFirstAncestorOfClass("Model").Parent:FindFirstChild("Humanoid")
			else
				Hum2 = Result.Parent:FindFirstChild("Humanoid")
			end

			if Hum2 then
				local Char = Hum2.Parent
				local Player2 = game.Players[Char.Name]
				local Damage = Weapon:GetAttribute("Damage")
				local DamageRes = Hum:GetAttribute("DamageRes")
				if not DamageRes then
					DamageRes = 0
				end
				if Result.Name == "Head" then
					RegisterHit(Player, Player2, Hum2, Damage*2, Weapon)
				else
					RegisterHit(Player, Player2, Hum2, Damage - (Damage * (DamageRes/100)), Weapon)
				end
				Player.Hit.Value += 1
			end
		end
	else
		local ResultPos = Direction * 1500
		RS.SentBullet:FireAllClients(Bullet, Model.Handle["Shoot"..math.random(1,3)], Model.Beam, ResultPos, nil, Model)
	end
	Char:SetAttribute("BulletCooldown", true)
	task.wait(Weapon:GetAttribute("Cooldown"))
	Char:SetAttribute("BulletCooldown", false)

This is the previous server-validated script, except I took anything related to setting bullet positioning/tracing and audio and had the client handle it. There’s already a previous function in my code that makes the worldmodel muted and invisible to the client, so FireAllClients() isn’t too much of a hassle.

i forget

1 Like

Did you do this? It looks like you didn’t since there’s a noticeable desync between the muzzle flash and the tracer in the recording you showed
ScreenShot_20220715140813
ScreenShot_20220715140819

I see what you mean, but how does that fix the accuracy issue?

For reference there is an invisible bullet part that is set once the ray has reached a position. That’s what ResultPos is.

All of the clients are now responsible for setting that bullet position themselves, in the FireAllClients() function. They set it to ResultPos.

I’m a bit confused by what you mean. Should the shooter make their own ray and have the tracer follow that ray, and then the clients make their own from the server, without having any Bullet part exist? How could this fix the issue?

That is an odd thing that is happening, usually I will try to use some way to predict the new lookvector either by checking the characters rotation velocity or using mouse delta to figure out how fast its going in order to compensate for it.

The issue with the visual inaccuracy is caused by latency. We already went over that. We’re trying to avoid the latency, and to do so we have to skip the process of sending remotes and receiving them. Upon firing, the shooter’s client will immediately start simulating the visual aspect of the bullet on their own while at the same time telling the server that they’ve fired a gun. The server then does the actual hitreg mumbo jumbo and tells everyone else in the game that they’ve fired the gun so they can all visually replicate the bullet as well.

2 Likes

Thanks for the help. The client now handles the visual shooting themselves and only other clients get told by the server to do their own bullets:
https://gyazo.com/e6761b23725faf3b2c065cdce6096e65

I’m still very unsure if this is entirely fixing the problem with server-sided raycasting (aka I’m not entirely sure I can fix this latency issue entirely or if there are other factors), but seeing as the server no longer deals with the tracer, I’d have to playtest with others to see.

Nevertheless thanks for your help. It’s my first time making an fps so I don’t know all of the tricks and whatnot