Why am I seeing this behavior when raycasting a CFramed line?

I’m experiencing inconsistency in the following code. I have a tool that fires a brick in the direction of the players mouse position. A local script passes the mouse’s position to the server and then the server uses the below code to fire the brick towards the mouse’s position, while also raycasting to detect any collisions.

local BEG = Part.CFrame.p
local DIR = (Pos-BEG).Unit*6 --Pos is the mouse's position
local raycastResult1 = workspace:Raycast(BEG,DIR,raycastParams)
--CommonModule.DrawRaycast(BEG,DIR)
local raycastResult2 = workspace:Raycast(Part.CFrame*CFrame.new(0,1,0).p,DIR,raycastParams)
--CommonModule.DrawRaycast(Part.CFrame*CFrame.new(0,1,0).p,DIR,raycastParams)
local INT = raycastResult and raycastResult.Position or BEG+DIR
local DIS = (BEG-INT).Magnitude
Part.CFrame = CFrame.new(BEG,INT)*CFrame.new(0,0,-DIS/2)

--Raycast hit detection comes next but doesn't affect the issue

The DrawRaycast function that is called is what I’m using to visualize the raycast. It’s code is as follows:

local MID = BEG+DIR/2
local Part = Instance.new("Part")
Part.Anchored = true
Part. CanCollide = false
Part.Transparency = .5
Part.Size = Vector3.new(1,1,DIR.Magnitude)
Part.CFrame = CFrame.new(MID,BEG)
Part.Parent = workspace

This is the result from the above code being looped:

image

On the first raycast, you can see raycast1 is centered and raycast2 is offset 1 stud above. The issue is every raycast2 after is in the same position as raycast1. What am I doing wrong here? Any suggestions would be appreciated!

Thanks!
Romul

1 Like

complicated isn’t it romul? unfortunately i can’t help :pensive:

Maybe the fact that your 2nd one is at 0,1,0? What if you make that 0,0,0

If raycastResult2 was set to 0,0,0 then it would always cast along the middle line. The expected behavior is that a raycast should cast along the center and the offset of 0,1,0.

This question is worded in a confusing way. What is the exact result you are expecting?

I spent a few minutes recreating your code in Studio, where partA represents your raycast representation of cast2 and partB represents mouse position.

local RunService = game:GetService("RunService")

local p : {} do
	p = {
		A = workspace.PointA; -- equivalent of part position
		B = workspace.PointB; -- equivalent of mouse position
	}
end

local castP = workspace.Cast

local function draw() : nil
	local BEG = p.A.CFrame.p
	local DIR = ((((p.B.Position) - (p.A.CFrame.p)).unit)*6)
	local MID = BEG+DIR/2
	
	castP.Size = Vector3.new(1, 1, DIR.magnitude)
	castP.CFrame = CFrame.new(MID,BEG)
	castP.Parent = workspace
end

RunService.Heartbeat:Connect(draw)

My code should be equivalent to your part positioning. Is this the same behavior that your raycast2 rep presents? If so, what do you need to be different?

It needs to cast from +1 stud above where it is now? At the same vector direction?

Ah, sorry, misread that.
What determines your raycastParams?

Thanks for helping take a look at this. I was not at my computer with Studio access so that was freehand code. Let me post the full handling.

First, the local script fires the player’s mouse position to the server event. That event runs the below code to create a part and move it between the origin and mouse’s position while creating raycasts to detect objects within its path.

module.Projectile = function(Player,Pos)
	local Char = Player.Character
	local Part = game.ReplicatedStorage.Part:Clone()
	Part.Parent = workspace
	game.Debris:AddItem(Part,10)
	Part.CFrame = Char.HumanoidRootPart.CFrame*CFrame.new(0,2,-4)*CFrame.Angles(math.rad(0),math.rad(90),math.rad(0))
	Part.CFrame = CFrame.new(Part.CFrame.p,Pos)
	local DB = false
	local STEP = false
	local COUNT = 0
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
	raycastParams.FilterDescendantsInstances = {workspace.PLAYERS}
	game:GetService("RunService").Heartbeat:Connect(function()
		if not DB and Part then
			COUNT = COUNT + 1
			if COUNT >= 250 then
				DB = true
				Part:Destroy()
			end
			local BEG = Part.CFrame.p
			local DIR = (Pos-BEG).Unit*6
			local raycastResult1 = workspace:Raycast(BEG,DIR,raycastParams)
			CommonModule.DrawRaycast(BEG,DIR)
			local raycastResult2 = workspace:Raycast(Part.CFrame*CFrame.new(0,1,0).p,DIR,raycastParams)
			CommonModule.DrawRaycast(Part.CFrame*CFrame.new(0,1,0).p,DIR)
			local INT = raycastResult and raycastResult.Position or BEG+DIR
			local DIS = (BEG-INT).Magnitude
			Part.CFrame = CFrame.new(BEG,INT)*CFrame.new(0,0,-DIS/2)*CFrame.Angles(math.rad(90),math.rad(0),math.rad(0))
			local Hit
			local HitPos
			if raycastResult1 and raycastResult1.Instance then
				Hit = raycastResult1.Instance
				HitPos = raycastResult1.Position
			elseif raycastResult2 and raycastResult2.Instance then
				Hit = raycastResult2.Instance
				HitPos = raycastResult2.Position
			end
		end
	end)
end

I have another module set up for drawing the raycast to visualize it as you see in the original post. That script is as below:

module.DrawRaycast = function(BEG,DIR)
	local Mid = BEG+DIR/2
	local Part = Instance.new("Part")
	Part.Anchored = true
	Part.CanCollide = false
	Part.Transparency = .5
	Part.Size = Vector3.new(1,1,DIR.Magnitude)
	Part.CFrame = CFrame.new(Mid,BEG)
	Part.Parent = workspace
	--game.Debris:AddItem(Part,5)
end

The goal is to have the original/center position’s raycast as well as the offset raycast fired. In the image, the top and bottom part should continuously spawn on top and bottom until the raycast is stopped.

Please let me know if I need to clarify anything further. Again, thanks!!

It was difficult to try and debug this code because there were nil instances and syntax errors (I assume these are fixed on your side).

That said, I wrote you a simple system that I assume will solve your use-case. I believe the problem you are running into in your original code is that you overcomplicated your CFrame/Vector math. You can direct your part in the correct direction by using CFrame.lookAt / (magnitude)/2.

My system works differently than yours does, using modules to create projectile objects and visualize them. Taking arguments: Player, and optional EndPos and Yield timer, and setting self.Player so you can index it if you need to. I did not FilterDescendants but you could add it.

System

Projectile class module:

local CastFrom = Vector3.new(0, 0, 0)
local Visualizer = require(script.Visualizer)

local Projectile = {}
Projectile.__index = Projectile
Projectile.Yield = .1 -- Default yield
Projectile.EndPosition = Vector3.new(0, 0, 0) -- Default EndPosition

function Projectile.new(Player, EndPos, ...)
	local p = {}
	setmetatable(p, Projectile)
	
	if EndPos then
		p.EndPosition = EndPos
	end
	
	if ... then
		p.Yield = ...
	end
	
	p.Player = Player
	
	return p
end

function Projectile:Cast()
	local End = self.EndPosition
	
	self.Visual1 = Visualizer:BuildVisual(CastFrom, End)
	self.Visual2 = Visualizer:BuildVisual(CastFrom + Vector3.new(0, 1, 0), End)
	
	local Ray1 = workspace:Raycast(CastFrom, (CastFrom - End), RaycastParams.new())
	local Ray2 = workspace:Raycast(CastFrom + Vector3.new(0, 1, 0), ((CastFrom + Vector3.new(0, 1, 0)) - End), RaycastParams.new())
	
	return Ray1, Ray2
end

function Projectile:Complete()
	self.Visual1:Destroy()
	self.Visual2:Destroy()
	self = nil
end

return Projectile

Visualizer module:

local Visualizer = {}

function Visualizer.BuildVisual(self, Origin, To)
	local Part = Instance.new("Part")
	Part.Parent = workspace
	Part.Size = Vector3.new(1, 1, (Origin - To).magnitude)
	Part.CFrame = (CFrame.lookAt(Origin, To) + (Origin + To)/2)
	Part.Anchored = true
	Part.Transparency = .5
	
	return Part
end

return Visualizer

Server-sided OnEvent:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Projectile = require(ReplicatedStorage.Projectile)

function ProjectileObject(Player, EndPosition, ...)
	local p = Projectile.new(Player, EndPosition)
	
	local Hit, Hit2 = p:Cast()
	
	if Hit then
		print(Hit.Instance.Name .. "hit by default cast.")
	end
	
	if Hit2 then
		print(Hit2.Instance.Name .. "hit by offset cast.")
	end
	
	task.wait(p.Yield)
	p:Complete()
end

ReplicatedStorage.DrawEvent.OnServerEvent:Connect(ProjectileObject)

Is this result reasonable for what you’re trying to achieve? (still a little confused)

To debug your system, take a look at the vector math inside Visualizer and see where it differs from yours.