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.
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
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
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.