How would I Use Raycasting for Particle Position?

Hello! I am currently trying to replicate a dust effect caused by helicopters when hovering near terrain. I originally tried to use a prismatic constraint with a particle emitter, but the part would keep getting stuck on terrain and other objects. It seems as if I need to use raycasting to deal with this.
(Below is a screenshot of the hierarchy and setup.)

I do not have any knowledge of how to use raycasting. I need the helicopter to raycast downwards on the Y axis to locate what part or terrain is below the helicopter, and the attachment with the particle emmiter should be brought to the y location of the raycast.

Here is how I would do it:

  1. Cast a ray downwards from the helicopter.
  2. Check to see if anything is hit.
  3. If anything is hit, position a part there and add a particle emitter (in my example, I re-use the same part and disable/enable the particle emitter).
  4. If something isn’t hit, disable the particle emitter.

Example (hide the part of course)
https://gyazo.com/9b0a51b4c7f91f7b779e810d979d817c

local MAX_DISTANCE = 20
local MAX_SMOKE_SIZE = 1

local Emitter = script.Parent.Emitter

local raycastParams = RaycastParams.new()
raycastParams.IgnoreWater = false -- Don't ignore water
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.FilterDescendantsInstances = {script.Parent.Emitter} -- Ignore the emitter and anything else we don't want here!

while (wait()) do
										-- Origin, Direction, RaycastParams
	local raycastResult = workspace:Raycast(script.Parent.Helicopter.Position, Vector3.new(0, -1, 0) * MAX_DISTANCE, raycastParams)
	
	if (raycastResult) then
		Emitter.Smoke.Enabled = true -- Enable smoke
		Emitter.Position = raycastResult.Position -- Set position of marker
		
		local Distance = (raycastResult.Position - script.Parent.Helicopter.Position).Magnitude -- Could use something like this to make smoke smaller when helicopter is higher up.
		Emitter.Smoke.Size = (Distance / MAX_DISTANCE) * MAX_SMOKE_SIZE
	else
		Emitter.Smoke.Enabled = false -- Disable the particle when we don't hit anything
	end
end

image

Here’s the documentation for the Raycast API in Roblox. WorldRoot | Roblox Creator Documentation.

So I think what you’d probably want to do here is create a RaycastParams | Roblox Creator Documentation object that uses the Enum.RaycastFilterType.Blacklist filter type and the helicopter is in the RaycastParams.FilterDescendantsInstances list so that the ray doesn’t hit the helicopter.

A quick example of how this could be done is:

local helicopter = script.Parent -- Assumes the script is within the helicopter model.
local floor_test_part = helicopter.FloorTestPart -- Some part that is placed where the ray test should start
-- and is oriented in the direction you want the ray to test in (top facing away from the ground.)
local particle_emitter_part = helicopter.SmokeEmitter

local maximum_ground_distance = 30 -- How far the ray will go to test for the floor

local ray_params = RaycastParams.new()
ray_params.FilterType = Enum.RaycastFilterType.Blacklist
ray_params.FilterDescendantsInstances = { helicopter }

--- Fires a ray directly downward from the floor_test_part CFrame.
-- @return RaycastResult for the ray or nil if the ray did not hit anything.
local function get_ground()
  local heli_position = floor_test_part.Position
  local floor_direction = (floor_test_part.CFrame * CFrame.new(0, -maximum_ground_distance, 0)).LookVector

  -- Perform the ray test to find the ground beneath the helicopter in the direction of the helicopter
  -- and not just straight down, since rotors don't blow air straight down on the Y-axis.
  return workspace:Raycast(heli_position, floor_direction, ray_params)
end

while wait(1) do
  local floor_ray_result = get_ground()
  if (floor_ray_result) then
    -- Position your particles here with floor_ray_result.Position
    particle_emitter_part.Position = floor_ray_result.Position;
    -- Could also angle it with the floor using floor_ray_result.Normal
  end
end

See RaycastResult | Roblox Creator Documentation for details on what a ray cast returns.

I haven’t tested this code but it should serve as a decent example of how you can do this.

Neither of those solutions seem to work, nothing happens ingame.

So in the first one, Helicopter is just the part where the ray should be sent from.

local raycastResult = workspace:Raycast(script.Parent.Helicopter.Position, Vector3.new(0, -1, 0) * MAX_DISTANCE, raycastParams)

Should be changed to

local raycastResult = workspace:Raycast(script.Parent.?????.Position, Vector3.new(0, -1, 0) * MAX_DISTANCE, raycastParams)

where ????? is the part that you want to emit the ray from.

alright it works, the only issue I have now is it casts the ray once and then stops. Is there a way to keep casting the ray over and over?

You can link the cast to RunService so that it checks per frame. Provided you aren’t spawning 50 helicopters you’ll likely be fine.

See RunService. I would recommend using Heartbeat so that your calculation happens after each physics simulation.

You can write this connection to a variable and disconnect it when the helicopter becomes inactive.

local RunService = game:GetService("RunService")
local Helicopter = {}
Helicopter.__index = Helicopter

function Helicopter.new(helicopterObj)
	local t = {}
	setmetatable(t, Helicopter)
	
	t.Root = helicopterObj or false
	t.Connection = nil

	-- Set up various helicopter states & functions.

	return t
end

function Helicopter:RoutineCheck()
	return function()
		-- Raycast
	end
end

An example of using this to build the connection would be this:

local Example = Helicopter.new()

Example.Connection = RunService.Heartbeat:Connect(Example:RoutineCheck()) -- Connect to RunService
Example.Connection:Disconnect() -- Disconnect from RunService

Whether or not my example system is similar to yours doesn’t matter. You can run the Raycast on RunService and disconnect it when it becomes inactive.