Gun Bullets Delayed

Tweens are not really a good fit for projectiles if you intend to add exploit protection or bullet drop at some point. Regardless, all you’d need to do is instead of feeding 0.1 as the 1st argument for tweeninfo, you do ((ray.Origin - endPosition).Magnitude / RAY_LENGTH) * 0.1.

Also yeah the code could be buggy, as mentioned in the last post it was untested.

What do you suppose I use then? I don’t really want exploiters in my game.

Just make a line with a part like discussed in my other reply. It will be a line, from the barrel of your gun, to where you fired, and it is more or less a prerequisite to visualizing where your projectile will go once we give it travel time.

There are 2 major problems with the tween approach you are using: first, it’s impossible to create bullet dropoff with something like a tween as it eases from A to B in a straight line. Second, you fire the gun with a raycast, then the tween follows the path the raycast took after the fact. That means if you hit someone and they move away, the projectile will look as if it’s hitting thin air since that’s where your code thinks the projectile should stop.

If you were to stop using raycasts in favor of a tween to avoid the 2nd problem, you get 2 more problems: Tweens don’t actually go in a line, they “skip” to the position they think it should be. A sufficiently fast projectile would phase right through things like that. And the other problem is because part of anti exploit involves needing to instantly recreate the path a projectile took which is impossible with a tween due to the first problem, and also because tweens are tied to your frame rate (you can’t tell them to scrub ahead to a specific point or anything like that).

Oh okay, some issues I am facing however is that, if the result is nil, I still want the line to show so that the bullet feels realistic and second problem, if the distance is greater than the range, it should go to the allowed amount of range, for example, if the player shoots a player very far away, further than the allowed range, I still want the bullet to travel till that range and then get destroyed, after it exceeds the range value.

If a raycast not hitting anything gives you no result instead of no instance, just replace

if result.Instance then

with

if result then

All the sample code I provided already takes what you said into consideration.

I changed if result.Instance then with if result then and still doesn’t work, also the sample code you provided doesn’t work, its shoots above my head. Here is the code I have so far:

-- local tweenService = game:GetService("TweenService")
local RANGE = 20
local RAY_LENGTH = 300
local camera = workspace.CurrentCamera
local gun = script.Parent
local player = game.Players.LocalPlayer
local fired = script.Parent:WaitForChild("GunFired")

local function gunActivated()
	local screenCenterCoords = camera.ViewportSize / 2
	local ray = camera:ViewportPointToRay(screenCenterCoords.X, screenCenterCoords.Y)
	
	local gunModel = script.Parent:WaitForChild("Handle")

	local function fireGun() -- I don't know why I need ray.Unit, I just used it because it felt important.
		local char = player.Character or player.CharacterAdded:Wait()
		local Bullet = Instance.new("Part")
		Bullet.Name = "Bullet"
		Bullet.Anchored = true
		Bullet.CanCollide = false
		local rayCastParams = RaycastParams.new()
		rayCastParams.FilterDescendantsInstances = {char, gunModel, Bullet}
		rayCastParams.FilterType = Enum.RaycastFilterType.Exclude
		local result = workspace:Raycast(ray.Origin, ray.Direction * RAY_LENGTH, rayCastParams)
		if result then
			
			local endPosition = result.Position
			
			local lineCFrame = CFrame.new(ray.Origin, endPosition) * CFrame.new(0, 0, -result.Distance/2)
			
			Bullet.CFrame = lineCFrame
			
			Bullet.Parent = workspace
			if RANGE > result.Distance then
				Bullet.Size = Vector3.new(0.1, 0.1, (ray.Origin - endPosition).Magnitude)
			else
				Bullet.Size = Vector3.new(0.1, 0.1, (ray.Origin - endPosition).Magnitude) -- Part I am confused about and don't know how to fix..
			end
		end
	end
	fireGun()
end

gun.Activated:Connect(gunActivated)

It doesn’t shoot above my head on my end, it shoots from the center of the screen.
Also you are not following the same format as in the sample code. If a result exists, you calculate the endposition and midposition one way. If it is nil, you do it the other way. Then you run the actual line creation code outside of either checks using those values since that code doesn’t need to know how the hitscan went, just where the line should be and what length it should have.
Right now you’re just aborting if the raycast hits nothing.
Checking for range is unnecessary too. Again the sample code you were provided already took those things into consideration. Experimenting is good, though.

Okay I tried putting in ur code differently this time, now it shoots sideways instead. Here is the code, tell me if I put it wrong somehow:

-- local tweenService = game:GetService("TweenService")
local RANGE = 20
local RAY_LENGTH = 300
local camera = workspace.CurrentCamera
local gun = script.Parent
local player = game.Players.LocalPlayer
local fired = script.Parent:WaitForChild("GunFired")

local function gunActivated()
	local screenCenterCoords = camera.ViewportSize / 2
	local ray = camera:ViewportPointToRay(screenCenterCoords.X, screenCenterCoords.Y)
	
	local gunModel = script.Parent:WaitForChild("Handle")

	local function fireGun() -- I don't know why I need ray.Unit, I just used it because it felt important.
		local char = player.Character or player.CharacterAdded:Wait()
		local Bullet = Instance.new("Part")
		Bullet.Name = "Bullet"
		Bullet.Anchored = true
		Bullet.CanCollide = false
		local rayCastParams = RaycastParams.new()
		rayCastParams.FilterDescendantsInstances = {char, gunModel, Bullet}
		rayCastParams.FilterType = Enum.RaycastFilterType.Exclude
		local result = workspace:Raycast(ray.Origin, ray.Direction * RAY_LENGTH, rayCastParams)
		
		local line = Instance.new("Part")
		line.Name = "Line"
		line.Anchored = true
		line.Size = Vector3.new(0.1, 0.1, 0.1)
		line.CanCollide = false
		local endPosition
		local midPosition

		if result then
			endPosition = result.Position
			midPosition = ray.Origin:Lerp(endPosition , 0.5)
		else
			endPosition = ray.Direction * RAY_LENGTH
			midPosition = ray.Origin + (endPosition / 2)
		end

		-- give line length
		line.Size = Vector3.new(0.1, 0.1, (ray.Origin - endPosition).Magnitude)
		
		local facingCFrame = CFrame.lookAt(ray.Origin, ray.Origin + ray.Direction)
		line.CFrame = CFrame.new(midPosition) * facingCFrame * CFrame.new(-facingCFrame.Position)
		-- mid pos + origin pos with facing angle - origin pos = mid pos with facing angle

		line.Parent = workspace
		
	end
	fireGun()
end

gun.Activated:Connect(gunActivated)

Order operation matters with cframes, had these backwards.

line.CFrame = CFrame.new(midPosition) * CFrame.new(-facingCFrame.Position) * facingCFrame

Okay now it works great! Thanks! Now, how would we go about creating the projectile that visually shows on the players screen?

The moment you fire the gun you want to send an event to the server, which then sends an event to every other client telling it to run the same “fireGun” function, although with the info from the ray we generated on our own client rather than from the other player’s client. This will effectively make the line appear from the other player’s head.

I’ll leave it up to you to figure how you want to do this.

Not trying to seem dumb here but uh, I can’t seem to figure out how to do that, I tried documentations and searching it up but idk how to do it…

Break it down into smaller parts. And if that doesn’t work, break it down into even smaller parts. If you’re trying to look up “how to replicate bullet across all clients” or something ultra-specific like that you won’t be able to find anything of value a lot of the time.
First of all, how do you send something to the server? You already know that, since you used to do fired:FireServer() previously.

How do to receive data on the server? Well, if you don’t know that, you can look into the documentation for remote event’s FireServer function which should point you in the right direction, along with some examples on the api.

Next, how to send something to every client? Well, you can also find information on that on the remoteevent api page. And keep in mind that the localscript inside of another player’s gun will not run, so you should not be using the fired remote that’s a child of the tool for replicating shots. It would be tedious to make those connections from a script that isn’t a child of the tool, after all.

Or you could disregard what I said and experiment some more. There is an extra method I haven’t mentioned that would allow you to get away with using the remote in the tool. I’ll show you how to do it later but for this I really want you to practice finding ways to make things work independently.

I am very lost and confused, I looked at the documentation 3 times and the best I could come up with was this:

-- local script
fired:FireServer()
	fireGun()
-- Server script
local fired = script.Parent:WaitForChild("GunFired")

fired.OnServerEvent:Connect(function(player)
	
	for _, v in game.Players:GetPlayers() do
		if v ~= player then
			fired:FireClient(v, player)
		end
	end
	
end)

Am I atleast close?

I think youre doing client to server to client replication, and yea I think that looks alright. You have to do onclientevent when the remote activates in a localscript though

Mostly correct. Make sure you’re passing the ray’s origin and direction so the server can also pass those on to the other clients. Just make sure the scripts responsible for making connections on the server and on the client actually run and connect to the correct remotes.
Also, as stated previously, localscripts in other player’s tools will not run. That means you cannot connect to the fired event of another player- that is, unless you replace your local script with a regular script and change the “RunContext” to “Client” which forcibly runs it.

Note that this means the localscript will also run from within the starterbackpack folder, so you’ll need to make sure within the script that the parent of the tool isn’t starterbackpack.

From there you can pull the fireGun function outside of the gunActivated function, connect the fired event to calling fireGun, and modify it to accept an “origin” and “direction” argument. You’d create the ray in gunActivated, send it to the server, and run the function with the ray which will then perform the raycast and create the line part.

Can you explain this part a little bit more for me? I don’t really understand this part too well…

1 Like

You know how the “fireGun” function is inside of the “gunActivated” function? Pull it out of there, like how gunActivated isn’t inside of another function.

Then you can give it arguments. If you don’t know how,

local function myFunction(arg1, arg2)
	print(arg1, arg2) -- prints "example", 1
end

myFunction("example", 1)

If you’re confused about something else you’ll need to elaborate

Is the part I am confused about, what would I give args to take in a “origin” and “direction” argument as I have two functions one, “firegun” and the other “gunActivated”

local function fireGun(origin, direction)
	-- replace ray.Origin and ray.Direction with the arguments
end

local function activateGun()
	local ray = --viewportpointtoray
	fired:FireServer(ray.Origin, ray.Direction)
	fireGun(ray.Origin, ray.Direction)
end