Help with Arrow physics and arch

You can write your topic however you want, but you need to answer these questions:

  1. I want to create realistic arrow physics with arch.

  2. I cannot figure out how to exactly make it work, considering that I want to make it extremely realistic (making the arrow velocity from where it is placed on)

  3. I have checked whatever I could find, I am pretty bad in physics so most of the times I couldn’t understand it, but I have tried bezier curves, .Velocity, lerping method and now I am trying ApplyImpulse, even though I have gotten somewhat of a curved movement of the arrow itself, it still kind of doesn’t work properly and sometimes just falls to the ground + I do not know how to make the arrow even arch and face the direction it is falling to.

Current Script:

tool.Activated:Connect(function()
	local Arrow = game:GetService("ReplicatedStorage"):FindFirstChild("Parts"):FindFirstChild("BowParts"):FindFirstChild("Arrow"):Clone()
	local Velocity = 25
	local End = Arrow.CFrame.LookVector * Velocity
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Blacklist
	params.FilterDescendantsInstances = {character,character:GetChildren(),Arrow,tool,tool:GetChildren(),character:GetDescendants(),}
	Arrow.Parent = workspace
	Arrow.CFrame = tool:FindFirstChild("Middle").A.WorldCFrame * CFrame.Angles(math.rad(90),0,0)
	Arrow:ApplyImpulse(End)
	repeat
		local RayHit = workspace:Raycast(Arrow.End.WorldPosition,Arrow.End.WorldCFrame.YVector * 5,params)
		if RayHit then
			Arrow.Anchored = true
		end
		task.wait(.05)
	until Arrow.Anchored == true
	
end)
1 Like

The easiest would be to use the library FastCast. It’s designed specifically for ranged weapons:

You can use FastCast if you want, but if you want to make your own I can give you a quick physics lesson for moving arrow projectiles .

Firstly, we are assuming there is no air resistance (windy conditions, altitude, etc). This means there will be no acceleration in the X/Z direction, but there will be deceleration (due to gravity) in the Y direction.

We can get deceleration due to gravity like this, where dt is the change in time:

decelerationDueToGravity = Vector3.new(0, workspace.Gravity * dt, 0)

And we simply subtract this from the arrow’s current velocity, then move the arrow:

decelerationDueToGravity = Vector3.new(0, workspace.Gravity * dt, 0)
currentArrowVelocity = currentArrowVelocity - decelerationDueToGravity
Arrow.Position = Arrow.Position + currentArrowVelocity

But we also want the arrow to face where it is going, therefore we predict the next position of the arrow, and slightly modify the code:

decelerationDueToGravity = Vector3.new(0, workspace.Gravity * dt, 0)
currentArrowVelocity = currentArrowVelocity - decelerationDueToGravity
currentArrowPosition = Arrow.Position + currentArrowVelocity
nextArrowVelocity = currentArrowVelocity - decelerationDueToGravity
nextArrowPosition = currentArrowPosition + currentArrowVelocity

Arrow.CFrame = CFrame.lookat(currentArrowPosition, nextArrowPosition)

You can put this in a Heartbeat or Stepped loop and add your raycasting.

1 Like

So, Something like this?

tool.Activated:Connect(function()
	local Arrow = game:GetService("ReplicatedStorage"):FindFirstChild("Parts"):FindFirstChild("BowParts"):FindFirstChild("Arrow"):Clone()
	local Velocity = 25
	local End = Arrow.CFrame.LookVector * Velocity
	local params = RaycastParams.new()
	character = script.Parent
	params.FilterType = Enum.RaycastFilterType.Blacklist
	params.FilterDescendantsInstances = {character,character:GetChildren(),Arrow,tool,tool:GetChildren(),character:GetDescendants(),workspace:FindFirstChild("Izrui"):GetChildren()}
	Arrow.Parent = workspace
	Arrow.CFrame = tool:FindFirstChild("Middle").A.WorldCFrame * CFrame.Angles(math.rad(90),0,0)
	Arrow:ApplyImpulse(End)
	game:GetService("RunService").Heartbeat:Connect(function(dt)
		decelerationDueToGravity = Vector3.new(0, workspace.Gravity * dt, 0)
		currentArrowVelocity = Arrow.Velocity - decelerationDueToGravity
		currentArrowPosition = Arrow.Position + currentArrowVelocity
		nextArrowVelocity = currentArrowVelocity - decelerationDueToGravity
		nextArrowPosition = currentArrowPosition + currentArrowVelocity

		Arrow.CFrame = CFrame.lookAt(currentArrowPosition, nextArrowPosition)
	end)
	
end)

Although, I think this kind of gives a problem considering that the part is just nonexistent after a second of me spawning it with applyimpulse, maybe I am applying the impulse wrongly?

Nvm, I think I kindof made it work, but it’s too quick for some reason, it moves in extreme speeds, on top of that, it does not go to the way the arrow is actually facing.
This is the code:

tool.Activated:Connect(function()
	local Arrow = game:GetService("ReplicatedStorage"):FindFirstChild("Parts"):FindFirstChild("BowParts"):FindFirstChild("Arrow"):Clone()
	local Velocity = 15
	local End = Arrow.CFrame.LookVector * Velocity
	local params = RaycastParams.new()
	character = script.Parent
	params.FilterType = Enum.RaycastFilterType.Blacklist
	params.FilterDescendantsInstances = {character,character:GetChildren(),Arrow,tool,tool:GetChildren(),character:GetDescendants(),workspace:FindFirstChild("Izrui"):GetChildren()}
	Arrow.Parent = workspace
	Arrow.CFrame = tool:FindFirstChild("Middle").A.WorldCFrame * CFrame.Angles(math.rad(90),0,0)
	game:GetService("RunService").Heartbeat:Connect(function(dt)
		decelerationDueToGravity = Vector3.new(0, workspace.Gravity * dt, 0)
		currentArrowVelocity = End - decelerationDueToGravity
		currentArrowPosition = Arrow.Position + currentArrowVelocity
		nextArrowVelocity = currentArrowVelocity - decelerationDueToGravity
		nextArrowPosition = currentArrowPosition + currentArrowVelocity

		Arrow.CFrame = CFrame.lookAt(currentArrowPosition, nextArrowPosition)
	end)
end)

Gif: Important - Roblox Studio (gyazo.com)

You declared End before setting the Arrow’s CFrame. It should be the other way round. You can also fiddle with the velocity to alter the speed, and it would be more preferable to rotate the arrow upwards when you create it for a better arch.

Concerning the speed, its a mistake on my part, I forgot to mention that you should multiply velocity by dt as well. Also, you should scrap End and replace it with currentArrowVelocity.

I will clean up your code a bit:

tool.Activated:Connect(function()
	local Arrow = game:GetService("ReplicatedStorage"):FindFirstChild("Parts"):FindFirstChild("BowParts"):FindFirstChild("Arrow"):Clone()
    Arrow.CFrame = tool:FindFirstChild("Middle").A.WorldCFrame * CFrame.Angles(math.rad(90),0,0) -- I don't know which component rotates the arrow upwards, but rotate the arrow upwards so that it initially has movement upwards.
    Arrow.Parent = workspace
	local power = 5 -- configure this until you have a good speed
	local currentArrowVelocity = Arrow.CFrame.LookVector * power
    local decelerationDueToGravity = Vector3.new(0, workspace.Gravity, 0)
	local params = RaycastParams.new()
	character = script.Parent
	params.FilterType = Enum.RaycastFilterType.Blacklist
	params.FilterDescendantsInstances = {character,character:GetChildren(),Arrow,tool,tool:GetChildren(),character:GetDescendants(),workspace:FindFirstChild("Izrui"):GetChildren()}

	game:GetService("RunService").Heartbeat:Connect(function(dt)
		currentArrowVelocity = currentArrowVelocity - decelerationDueToGravity*dt
		currentArrowPosition = Arrow.Position + currentArrowVelocity*dt

        -- Add hit detection code here

        local nextArrowVelocity = currentArrowVelocity - decelerationDueToGravity*dt
		local nextArrowPosition = currentArrowPosition + nextArrowVelocity*dt
		Arrow.CFrame = CFrame.lookAt(currentArrowPosition, nextArrowPosition)
	end)
end)
1 Like

Yeah, this is the perfect thing, although, the hit detection is kind of not working right, there’s some sort of delay on it even though it detects the parts with the raycast, it sometimes goes through the part that should be detected and detect the other part + on top of that, even when it detects, it doesn’t instantly stop the movement in which case sometimes hides the arrow or makes it look like it nearly penetrated through. Is there no other way? I could maybe use task.wait, I heard it’s like deltatime and use another type of loop, maybe repeat until or even lerp maybe. Also, sometimes it just gets stuck in the air (I disconnect the heartbeat when it gets touched).

Gif: Important - Roblox Studio (gyazo.com)

Have you disconnected the Heartbeat event when the arrow hits?

Yeah, that’s what I was doing, apparently, when I disconnect, it somehow affects the rest of the spawning arrows too somehow. Like, if one arrow hits before a new one spawns, then the newer one gets stuck in air.

As for the code itself, it’s basically this:

local tool = script.Parent
tool.Equipped:Connect(function()
	character = script.Parent
end)

tool.Activated:Connect(function()
	local Arrow = game:GetService("ReplicatedStorage"):FindFirstChild("Parts"):FindFirstChild("BowParts"):FindFirstChild("Arrow"):Clone()
	Arrow.CFrame = tool:FindFirstChild("Middle").A.WorldCFrame * CFrame.Angles(math.rad(120),0,0) -- I don't know which component rotates the arrow upwards, but rotate the arrow upwards so that it initially has movement upwards.
	Arrow.Parent = workspace
	local power = 100 -- configure this until you have a good speed
	local currentArrowVelocity = Arrow.CFrame.LookVector * power
	local decelerationDueToGravity = Vector3.new(0, workspace.Gravity, 0)
	local params = RaycastParams.new()
	character = script.Parent
	params.FilterType = Enum.RaycastFilterType.Blacklist
	params.FilterDescendantsInstances = {character,character:GetChildren(),Arrow,tool,tool:GetChildren(),character:GetDescendants(),workspace:FindFirstChild("Izrui"):GetChildren()}
	task.spawn(function()
		ArrowMovement = game:GetService("RunService").Heartbeat:Connect(function(dt)
			currentArrowVelocity = currentArrowVelocity - decelerationDueToGravity*dt
			currentArrowPosition = Arrow.Position + currentArrowVelocity*dt
			local nextArrowVelocity = currentArrowVelocity - decelerationDueToGravity*dt
			local nextArrowPosition = currentArrowPosition + nextArrowVelocity*dt
			Arrow.CFrame = CFrame.lookAt(currentArrowPosition, nextArrowPosition)
			local RayHit = workspace:Raycast(Arrow.End.WorldPosition,Arrow.End.WorldCFrame.YVector * 2,params)
			if RayHit then
				if RayHit.Instance ~= Arrow then
					print(RayHit.Instance)
					Arrow.Anchored = true
					ArrowMovement:Disconnect()
				elseif not RayHit then
					Arrow.Touched:Connect(function(part)
						if part ~= Arrow then
							Arrow.Anchored = true
							ArrowMovement:Disconnect()

						end
					end)
				end
			end
		end)
	end)
end)

(My script might look a bit messed up ngl with all of those ends and stuff)

Alright so, I figured it out and it works now. I made the raycast hitbox outside of the heartbeat and it worked perfectly, but sometimes it doesn’t detect the hit and go through the ground for infinitely, in which case I have to end up disconnecting the heartbeat and stuff, but thanks a lot. The bug itself doesn’t occur that often and I can use it + it works perfectly, so I am very thankful for the solution you gave + on top of that the short physics lesson that was also helpful.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.