If you’re asking about how to do the raycasts reliably, here’s a method I commonly use:
- Keep the arrow anchored, with CanCollide, CanTouch, and CanQuery set to false. We won’t use the arrow’s velocity to change its position, and instead do things through code.
- Once the arrow is fired, we spawn it into the world with a velocity. This velocity will not be modified by the default physics engine because the the arrow is anchored.
- Every physics step, we do a raycast to check and see if something’s been hit. If not, wait for the next physics step.
Here’s a code sample of how you would do something like this. Please let me know about any potential questions you have!
local RunService = game:GetService("RunService")
local arrowTemplate: BasePart = script.Arrow
local function applyPhysics(velocity: Vector3, deltaTime: number): (Vector3)
local newVelocity: Vector3 = velocity * (1 - 0.05 * deltaTime)
-- the above line applies drag to the object
-- drag is represented by '0.05'. The arrow will lose 5% of its speed every second
-- feel free to change/remove the drag!
newVelocity += Vector3.new(0, -workspace.Gravity * deltaTime, 0)
-- the above line will apply gravity to the object, similar to the physics engine
return newVelocity
end
local function spawnArrow(character: Model, position: Vector3, velocity: Vector3): (RaycastResult?)
local arrow: BasePart = arrowTemplate:Clone()
arrow.CFrame = CFrame.new(position, position + velocity) -- the second parameter makes the arrow face the direction it's going
arrow.Velocity = velocity -- we will change this value manually
-- these are the RaycastParams for the raycast
local params: RaycastParams = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
params.FilterDescendantsInstances = {character, arrow}
-- loop while the arrow isn't in the void.
-- add extra parameters if you'd like!
local deltaTime: number = 0.017 -- the first step value
-- deltaTime will also be set by RunService.Stepped, but we want to cast right now instead of waiting for the next physics step
while arrow.Position.Y > workspace.FallenPartsDestroyHeight do
local result: RaycastResult = workspace:Raycast(arrow.Position, arrow.Velocity * deltaTime)
-- multiplying the velocity by 'deltaTime' returns the distance travelled by the arrow
-- in 'deltaTime' amount of time (seconds)
if result then
-- we hit something!
-- return the RaycastResult
-- if you'd like to know what you hit, do 'result.Instance'
return result
else
-- we didn't hit something
-- lets move the arrow, and modify its velocity as well
-- the following order is important!
arrow.Position += arrow.Velocity * deltaTime
arrow.Velocity = applyPhysics(arrow.Velocity, deltaTime)
-- ...and a lazy way of rotating the arrow
arrow.CFrame = CFrame.new(arrow.Position, arrow.Position + arrow.Velocity)
-- finally, we go back to the start of the loop and wait for the next physics step
deltaTime = RunService.Stepped:Wait()
end
end
-- the 'while' loop exited without hitting something
return nil
end
There are more efficient ways of doing this, but the above code sample is easier to understand. If you’d like the efficient option, I can send you the Lua file I’ve written a few weeks ago.