Semi-realistic helicopter dust effect

Hey together!

I came across this question

and wanted to try that out. It started out simple but I spent so much time into researching ideas (I am an absolute beginner) etc. that I decided to make this an own topic and release it - and I couldn’t find ANYONE who posted this, so hopefully one of you may find it helpful. For the dust particle I used this https://create.roblox.com/store/asset/14448568026/Dust-effect dust effect. To be honest, I don’t know how much or if anything I changed on that effect (my settings are posted below), but I don’t want to just rip it off so download it there and use it on your own.

Demo:

You will need:

  • A helicoper part or model
    • A child in that folder named “HeliEffectPart” and which is a part. For that I used a simple flat cube. I then made it invisible. This part will emit the dust later on. Important: Unanchor and disable canCollide etc. for the part or else it will disturb gameplay.
      • A child under “HeliEffectPart” which is the Dust and it has to be actually named “Dust”, it is a ParticleEmitter and I have the following settings for it:

I put everything into worldspace.

Now place a script named “Heli” (name does not matter) under the helicopter part/model (same folder than HeliEffectPart). Magic Code:

local RunService = game:GetService("RunService")
local heli = script.Parent -- The helicoper part or heli model, in my case it's just a part, a flat cube
local heliPosition = nil -- DO NOT CHANGE THIS
local lastPosition = heli.Position
local heliEffectPart = heli:WaitForChild("HeliEffectPart") -- The effect part, in my case a copy of the heli part and transparency set to 1
heliEffectPart:WaitForChild("Dust")
local distance = 0

local minEffectSize = 8 -- Minimum effect size of the dust effect
local maxEffectSize = 40 -- Maximum effect size
local effectSizeMultiplier = 1.7 -- Overrides maxEffectSize, play around to make effect bigger, should always be >= 1 !
local effectOpacityMultiplier = 1 -- Makes effect more "aggressive", should always be >= 1 ! Best used with decimals as max. Opacity is anyways 1
local maxDistance = 40 -- Max distance from heli to floor to show effect. If heli above, no effect


local function updateDustEffect()
	heliPosition = heli.Position
	
	-- Only update if heli actually moved, this is also why lastPosition has to be nil initially
	if lastPosition ~= heliPosition then
		local direction = Vector3.new(0, -100, 0) -- scanning downwards
		
		-- Since we have a HeliEffect part, we have to ignore that...
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {script.Parent}
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude -- ... by excluding children of the Heli script
		local result = workspace:Raycast(heliPosition, direction, raycastParams)

		-- If there is a hit
		if result then
			distance = result.Distance
		else
			distance = nil -- Set distance to nil if nothing was found, i.e. heli is too high or nothing below heli
		end
		-- Make sure distance is set and meets all requirements
		-- Adding the 0 just to make sure
		if distance and distance >= 0 and distance <= maxDistance then
			print("helipos: " .. tostring(heliPosition) .. " vs direction " .. tostring(direction) .. " vs distance " .. result.Distance)
			heliEffectPart.Position = result.Position -- This is moving the part of the effect which emits the dust
			local heliEffectPartPosition = heliEffectPart.Position
			-- Calculates effect size based on given parameters
			local size = (maxEffectSize * maxDistance - maxEffectSize * distance) / maxEffectSize * effectSizeMultiplier
			
			if size >= minEffectSize then
				print("Size: " .. size)
				-- Apply the scale to the part's Size
				heliEffectPart.Size = Vector3.new(size, 1, size)
				local sizeSequence = NumberSequence.new({
					NumberSequenceKeypoint.new(0, size / 3),  -- At 0 seconds, size is size / 3
					NumberSequenceKeypoint.new(1, size)  -- At 1 second, size is size
				})
				heliEffectPart.Dust.Size = sizeSequence
				if not heliEffectPart.Dust.Enabled then
					-- effect was disabled, enable it
					heliEffectPart.Dust.Enabled = true
				end
			else
				-- disable effect as effect size too low
				heliEffectPart.Dust.Enabled = false
			end
		-- Heli too high
		else
			heliEffectPart.Dust.Enabled = false
		end

		lastPosition = heliPosition
	end
end

RunService.Heartbeat:Connect(updateDustEffect)
  • The effects are only working on one cube, the HeliEffectPart. If you hover over other parts, it will abruptly be stopped and new particles emitted on that other part (as can be seen in the demo, hopefully). This maybe can be fixed by a Tween (I don’t know, never worked with it yet) and by having multiple smaller HeliEffectParts that emit in different directions (e.g. in front left, front right, back etc. of the heli)
  • Does not contain a heli
  • Was not tested with a heli yet
  • Is not optimal, I have no idea if it will lag or whatever
  • Calculation examples were my own ideas, I played around a bit, but therefore they may be suboptimal

All it does so far is emit dust-like particles on the “floor”. When the “heli” gets closer to the ground, the particles get bigger (and the HeliEffectPart grows, spreading more particles). This is for testing purposes so you can play around (and to answer the intial question of the OP). Also, I constantly changed the code to make it look better, initially it was just a smoke effect but now that I look at it I actually like it a little bit :sweat_smile:

I found this an interesting experiment. Have fun with it and let me know if that helped you! :slight_smile:

23 Likes

This is how it looks in an helicopter (sorry I couldn’t fly it properly :joy:)


You can see that it’s imperfect, especially when the surface height difference is high.

If you try adding the code to a helicopter, make sure to change the line
raycastParams.FilterDescendantsInstances = {script.Parent}
so that includes the whole model. Also, in best case attach the script and all parts to a part of the helicopter that flies with it (in my case I inserted it to the Engine). I changed it to
raycastParams.FilterDescendantsInstances = {script.Parent.Parent}

7 Likes

Please do not update this on RunService.Heartbeat and instead only shoot a Ray when Instance::Changed event is emitted from the main part (and if the surface can also move, consider listening to Changed on the hit part too, make sure this is always cleaned up!)

Of course this may not always be effective, but it is way less costly. To be even more sure you can fire a Ray every few seconds or even longer just to make sure a moving part has not intersected the original Ray between the emitter and the hit part.

Do not always shoot a Ray on Changed either, pay attention to os.time and only shoot a ray if the difference is great enough.

4 Likes

I’m not 100% sure if that would fit here, though of course my script for sure is super unoptimized. When you fly with the helicopter, the position constantly changes and the dust needs to be adjusted accordingly. I don’t think you can achieve that with checking for changes of the surfaces without raycasting constantly. Firing every few seconds will also not fit here at all. Though making this client side, adding a very small delay and using the change event is probably a better idea indeed! It was just a demo after all :slight_smile:

3 Likes

The dust should depend on the color of the floor as well as the material! It should also be fully client sided if it isn’t already.

It’s just a demo, that’s even like the first kinda word working script I ever did :skull: But yes, good ideas :+1:t3:

Doh! U hit a building, wouldnt your blades break, and the it should expode!

Well yes, but that’s not my heli :sweat_smile: Also it should fall down, rotate mid-air and then a big fire ball :joy:

1 Like

I tried this out, I just used on/off vs sizing to simplify, still need to make it client side:

9 Likes

Works at first when I tried it. Then I decide to make it work if the Rotors are active but then some kind of bug happened making it do this:

1 Like

Yes absolutely, it’s very unoptimized but to be honest still looked kinda cool in your video, how did you do that effect? That’s looks different from what I used and better even

Make sure to unanchor the HeliEffectPart and to disable cancollide for it :slight_smile:

It already was. The problem was the size changing

i set mine to no collisions, no query, and no cantouch, and also set massless

If its not anchored and no-collide, it could fall out of the workspace while its waiting for the rotors to turn.

I left mine anchored. (but with it anchored it can’t be inside the heli model which has to be unanchored)

Yeah, I’m not sure if i can even use this, I don’t know of a good way to optimize it.

I don’t want every client to have to animate the effect part for every helicopter, and I don’t want the server to have to do it either, so I’m kind of stuck.

I could make it where only the driver can see it but that would be kind of lame for everyone else.

just to clarify, you can keep using heartbeat if you want to, 1 single raycast running every tick
is not gonna explode the server nor client.

1 Like

That is a nice helicopter, where did you get it from?

Thanks

Can anyone share a working .rbxl file of this?

just with how they say to set it up.

This is how I tried but nothing emits …

Thanks
heli.rbxl (60.3 KB)

uh, i just ran your script and it works fine:
image

this configuration isn’t meant to ‘play’ you have to ‘run’ and use the dragger to move the ‘heli’ around.

this just demonstrates what it would do it if was attached to a heli model that moves.

you’ll have to weld this ‘heli’ part to your heli model then it would work. (and also configure a way to start and stop it based on the engine running or not)