I don’t think that’s exactly what I meant, but alright.
I made a breakdown of how to implement realistic aircraft physics, as well as an example of its implementation. I think its got everything you need to understand how to approach something like this, and how to actually make it work. I hope it helps: Creating Realistic Plane Physics
Hey all. Thank you for all the replies, I apologize for being late here. Tried to extend my break.
I did it and it was a good start. I made it in a way the drag would only apply on the wide area and not the edges, locally. It did not only simulate the air resistance, but tilting the plate causes it to fall towards the lowest point as well.
I think you overexplained it, but it was amazing. I had to read it several times within the timespan away from the forum since my brain wasn’t pairing with. A bit rough there, but I appreciate the effort you put here.
Tested the place. It’s good, but I found strange behaviors. Some of the scripts aren’t explained, I went for the Physics
script . I’m unfamiliar to ModuleScripts, so I don’t recognize some of the language.
I’ve had taken a break from Roblox developing for too long, way before I made this topic. Looking at those numbers and calculations scared me.
I assume that BodyVelocity
is not ideal for “realistic” aerodynamics, and BodyForce
will do the job. Since they work by applying constant forces, they are known to send objects to beyond lightyears. And this is where drag force is important.
I’ve tried testing it in a single BasePart to see if they work, while also experimenting different approaches and combinations.
@Imperiector told me to get CL from AoA, but I don’t feel like writing the lookup table. So, I had to ask ChatGPT’s opinion—knowing most of my questions for Ro-deving resulted in failure
local airDensity = 1.2
local CL_alpha = 4.5 -- Depends on wing types, GPT said ¯\_(ツ)_/¯ (F-15 Eagle)
local CL_0 = 0.2 -- CL at zero AoA
local S = 2 -- "The reference area S can be any number"
local Q = airDensity * 0.5 * velocity.Magnitude^2 -- Dynamic pressure
function calculateLift(Q, AoA)
local CL = (CL_alpha * AoA) + CL_0 -- GPT's formula
local lift = CL * AoA * Q * S
return lift
end
I’ve printed out the CL while the part goes in different directions, and… random, numbers. Somewhere between -13 to 7, I am clueless. I’ve made sure that AoA was correct.
Even worse with the lift. Went from negative tiny decimals to randomized, uneven numbers.
I am unable to calculate lift-induced drag without CL.
For the lift equation, you dont want to directly use the AoA to calculate the lift force. The AoA is used to calculate the lift coefficient. To clarify, AoA in simulations should only be how much the plane’s nose(cord line) deviates from the velocity vector ON THE PITCH AXIS. This means we are not concerned with the yaw axis AoA. In my physics module i use this function:
function Physics.calculateLift(velocity, liftPower, AOA)
-- Lift = 0.5 * velocity^2 * liftcoefficient * liftpower(hand tuned)
-- A curve sin(3x) derives the Lift Coefficient outputting values from 1 to -1 depending on the AOA.
-- Only values from -60 to 60 aoa will output coefficiencients from -1-1 beyond is the stall region, where coefficient stays 0.1
local liftCoefficient = math.clamp(math.sin(math.rad(3 * AOA)), -60, 60)
local lift = 0.5 * velocity.Magnitude^2 * liftCoefficient * liftPower
return lift/1000
end
It might sound complicated, but its essentially using the AoA to output a value from -1 to 1, that will determine the lift force. Planes are designed to go up when they face up, and down when they face down. The lift force can be positive, or negative, and its strength is dependent on angle of attack. The lift coefficient in this scenario is calculated by a sin(3x) curve.
You can see how when we provide an angle of attack(lets say 30) to the sin(3x) equation, its giving us a value of 1. This means at 30 the lift force is at its maximum. You can also see that at -30 (30 degrees BELOW the velocity vector) AoA we get maximum NEGATIVE AoA. This way, the lift force will be pointing down when we are flying down, and pointing up when we fly up. Past 30 degrees, the lift force starts to fall off, and at 60 degrees, we are stalling, since the plane no longer generates lift.
So, to summarize, the lift equation IRL is Lift = CoefficientLift * wingArea * 0.5 * airdesnity * Velocity^2. Our equation is simplified, we ignore airdensity along with wing Area, and replace it with a custom tuned liftPower variable that can be changed for different planes. We effectively get: 0.5 * V^2 * LiftCoefficient * liftPower. Our lift coefficient will make the lift force positive when we are pointing our nose above the velocity vector, and negative when we point below it. This is how i calculated my AoA:
-- Oncoming wind is opposite to the direction of the velocity vector
-- The AOA is calculated by getting the angle that the look vector deviates from the velocity vector
if velocity.Magnitude > 0 then
-- Project the velocity vector onto the plane's vertical plane
local projectedVelocity = velocity.Unit - CF.RightVector * velocity.Unit:Dot(CF.RightVector)
-- Calculate the angle between the projected velocity and look vector
local dotProduct = math.clamp(CF.LookVector:Dot(projectedVelocity.Unit), -1, 1)
local cross = CF.LookVector:Cross(projectedVelocity.Unit)
local sign = cross:Dot(CF.RightVector) > 0 and -1 or 1 -- Check if the aoa is pos/neg, if nose is above/below velocity vector
-- Convert to degrees and apply neg or pos, clamp from -90 to 90
AOA = math.clamp(math.deg(math.acos(dotProduct)) * sign, -90, 90)
else AOA = 0 end -- To avoid NaN AoAs
Sometimes we divide by 0 when our velocity is 0, and we get NaN AoA values, which breaks literally everything, so we have checks to see if we are above 0km/s. Velocity is a 3d vector pointing in the direction of travel the plane is moving in, and its MAGNITUDE is the speed it is traveling in that direction. When we say “if velocity.Magnitude > 0 then”, we are pretty much saying “only calculate the AoA when we are moving”. This way our physics functions that include AoA dont start crashing and printing errors.
Finally, in our AoA calculations, we are also outputting negative AoA. AoA is always say to be “the angle between the chord line of an airfoil (like an aircraft wing) and the direction of the relative wind”. Oncoming relative is wind is of course talking about the direction we are traveling. Its really an overcomplication that makes it sound so much more scary than it actually is, and its pretty much saying, oncoming wind is directly opposite to our direction of travel(velocity vector). The angle between the chordline(direction the nose points for simplicity), and the velocity vector is our angle of attack(on the pitch axis). In my AoA calculation, im just isolating that vertical pitch axis, and finding the angle between our velocity vector, and our lookvector(look vector is the direction our nose is pointing).
Used your lift equation and it works! But I’m afraid that it is not done, because I’ve moved onto the next problem: drag. I am concerned being unable to observe the 100% functionality of the lift as the drag turns out a warden.
Again, I used @Imperiector’s formula, which was obscure to me. At first, I thought it had something to do with liftPower
, in which is baked into lift-induced drag. I removed that, and the problem occurs to be more frequent.
The drag force, the parallel of thrust, somewhat goes to tremendously large numbers. It seemed fine in the first 2-3 seconds, but it went the other way around after.
local CD = 0.01 -- Drag coefficient
local S = 2 -- "Any number"
local wingAR = 3 -- ¯\_(ツ)_/¯
local oswald = 0.7 -- "Generally less than 1"
local Q = airDensity * 0.5 * velocity.Magnitude^2 -- Dynamic pressure
function calculateDrag(pressure, CL, AoA)
local CLAoA = CL * AoA
local CD_i = CLAoA^2 / (math.pi * wingAR * oswald) -- Lift-induced drag
local drag = (CD * CD_i) * pressure * S
return drag
end
I would like for more assistance from @Imperiector, as I am unsure if either the formula was wrong, or something else.
Besides, the lift is currently perfect. Thank you for the correction @xPoIarity.
Your velocity is a constant number which is the issue.
For videogame purposes you can ignore all other constants other than velocity and end up with this, a simple model where a plane accelerates until it reaches terminal velocity.
This basically makes the assumption that your plane is a flying sphere with constant area, and assumes the air density is constant. You can ignore @Imperiector realism for this.
Ultimately all it does is it will prevent a plane from infinitely accelerating and allow the plane to deaccelerate.
To make a simple plane model from the above I would add angular velocity to turn the plane with roll that is inversely proportional to speed. (More speed harder to turn)
And an anti gravity forces that scales to body weight to simulate lift once it reaches a certain speed.
I’m certain that I’ve made a drag simulation with a fixed coefficient and it works fine.
I tried using your method, it works, but I can’t seem to adjust the speed limit. I tried putting a * CD
, it caused infinite drag power. While being a simple formula, it is less realistic without Li drag.
No, it’s ran in a loop to keep it relative to time. Didn’t write like that.
Update: I don’t know what I’ve done with the modifications. I don’t know which formula to rely on, and now the lift itself is printing out huge values. I don’t know what’s causing this, lift, drag. Could be both. I’m lost right now.
function calculateLift(velocityMag, AoA)
AoA = math.deg(AoA)
local CL = math.clamp(math.sin(math.rad(3 * AoA)), -60, 60)
return 0.5 * velocityMag^2 * CL * liftPower, CL
end
function calculateDrag(velocity, pressure, CL, AoA)
local CLAoA = CL * AoA
local CD_i = CLAoA^2 / (math.pi * wingAR * oswald)
return (CD + CD_i) * pressure * S
end
local cF = pt.CFrame
local velocity = pt.AssemblyLinearVelocity
local velMag = velocity.Magnitude
if velocity.Magnitude == 0 then velMag = 0.001 end -- Prevent nan
local localVelocity = cF:VectorToObjectSpace(velocity)
local AoA = math.atan2(-localVelocity.Y, -localVelocity.Z)
local windex = cF * CFrame.Angles(-AoA, 0, 0) * CFrame.Angles(0, -AoS, 0)
local dir_lift = windex.UpVector
local dir_side = windex.RightVector
local dir_drag = -windex.LookVector
local pressure = airDensity * 0.5 * velMag^2
local lift, CL = calculateLift(velMag, AoA)
local drag = calculateDrag(velocity, pressure, CL, AoA)
pt.Lift.Force = dir_lift * lift
pt.Drag.Force = dir_drag * drag
My sticks collapsed.
Drag force is not opposite to thrust, its actually opposite to velocity(your direction of travel). The reason your lift is outputting huge values is most likely because your drag force is massive. This is most likely because your drag force is turning around, and facing the direction your moving, which means that it will propel you forward as you move, which the calculation would then add in(since we are using velocity^2) which will increase the same drag thats propelling your forward. Its a feedback loop that happens when your drag force isn’t facing the right way, causing your lift to become huge as well, since it incorportates v^2. Here’s my drag equation:
function Physics.calculateDrag(velocity, dragPower, altitude)
if velocity.Magnitude == 0 then return Vector3.zero end -- Dont return NaN drag
-- Drag = 0.5 * velocity^2 * airdensity * dragCoefficient
local dragCoefficient = dragPower * velocity.Magnitude
local airDensity = 1.2 * math.exp(-altitude / 25000) -- Simplified exponential decay
local dragForce = 0.5 * velocity.Magnitude^2 * airDensity * dragCoefficient
return dragForce/1000
end
Similar thing again, its just a simplified version of the realistic drag equation. You’ll see how im just considering our velocity.magnitude twice, once in the drag equation, and once in the coefficient. The coefficient of drag isn’t something thats fully understood in aviation, but its this extra variable thats measured in flight tests. In your simulation, its just a way to apply your drag power tuning variable until it feels right. You can actually use AoA to calculate the drag coefficient, which is slightly more realistic, and i actually did a while ago, but i gave that up for just a tuning variable(it was a bit problematic and messed with the drag direction). Again, our drag equation is derived from the real equation, but simplified. We ignore wing area, replaced with our dragPower tuning variable, increased to account for the drag coefficient(I found 0.001 to be good assuming you multiply it by velocity magnitude). Just dont forget to apply in the direction OPPOSITE TO VELOCITY(AssemblyLinearVelocity).
self.drag.Force = drag * -velocity.Unit -- The Drag force is opposite to Velocity
Also, check your AoA calculation.
Try getting a part to represent the direction of your velocity, and the direction of your nose to see if the AoA output is making sense. Your incorporting two axes (pitch AND yaw axes) into your AoA value, which might ruin things. It could also cause massive lift values if its wrong. Here’s mine, it works properly as far as i know:
-- Oncoming wind is opposite to the direction of the velocity vector
-- The AOA is calculated by getting the angle that the look vector deviates from the velocity vector
if velocity.Magnitude > 0 then
-- Project the velocity vector onto the plane's vertical plane
local projectedVelocity = velocity.Unit - CF.RightVector * velocity.Unit:Dot(CF.RightVector)
-- Calculate the angle between the projected velocity and look vector
local dotProduct = math.clamp(CF.LookVector:Dot(projectedVelocity.Unit), -1, 1)
local cross = CF.LookVector:Cross(projectedVelocity.Unit)
local sign = cross:Dot(CF.RightVector) > 0 and -1 or 1 -- Check if the aoa is pos/neg, if nose is above/below velocity vector
-- Convert to degrees and apply neg or pos, clamp from -90 to 90
AOA = math.clamp(math.deg(math.acos(dotProduct)) * sign, -90, 90)
else AOA = 0 end -- To avoid NaN AoAs
We are isolating the pitch axis from the yaw axis, and measuring the angle between. At the end, we are clamping to 90 and -90 AoA, and checking whether the plane’s look vector is below/above its direction of travel to see if its negative or positive AoA. Positive AoA is when the nose is above the Velocity Vector. Negative AoA is when the nose is below the Velocity vector. This way, our lift force will output NEGATIVE lift when we are pointing the nose below vel vec, and positive lift when we are pointing the nose above.
Also, your calculating a pressure variable which does not make sense. If your looking to incorporate some things @Imperiector is mentioning, make sure to get your basic forces working first so you dont get weird behavior that you cant explain. After that, you can add all the extra behavior you want.
I believe that windex.dir_drag
is a modified version of -velocity.Unit
that involves AoA.
Checked the AoA again, and it was the issue. After a takeoff, it repeatedly switched from positive to negative, which explained why it looked wobbly up-down before getting nullified.
I switched to your drag formula. It’s printing out huge numbers without the part even moving.
I went back to AoA calculation, which also uses your method, it does the same behavior. I notice that the wobble effect started from minuscule decimals, which gradually scales up and causes the large numbers.
Windex is using the cframe to find the relative airflow from the pitch AoA and Angle of Sideslip so you can apply drag in the opposite direction. Yes its a modified version, but it shouldn’t be causing the problem. If its happening regardless of the method you use to calculate AoA, that means something else is causing the weird AoA. What are you using as your plane? Is it a single mesh, or a rig with many parts? Also, how are you handling gravity? If you can, share a video of the wobble behavior.
I use a frictionless WedgePart, and my jet model with wheels and suspensions installed. Server-sided. This is how I set the constraints up for both models:
Thrust is relative to Attachment, while Drag and Lift to world. AlignOrientation is for testing AoA, and just how airplanes pitch up to lift.
WedgePart
Last time, it works for the first few seconds, where it manages to takeoff, then wobbles and disappear after. The values outputted large numbers. I used @Imperiector’s drag formula which worked for me.
Today, I gave another try with both drag models. Both WedgeParts disintegrated even without thrust. As how I remember Roblox physics is, there’s like a tiny amount of velocity on unanchored parts when the simulation starts.
I tried to print out both drag and lift value to check for large numbers. What’s weird is, there is no large numbers, other than decimals and decimals. It’s last print output when the WedgePart disappears is 0.
There was a Roblox update recently, and that’s when it started to happen even with @Imperiector’s drag formula. This is definitely something to do with their engine.
I cannot share you a clip of it’s wobbly motion, as the WedgePart itself disappears into thin air before doing anything.
Rigged Jet
Jet is pitched upwards 5 degrees.
Not far different, except it doesn’t immediately fling with @Imperiector’s formula.
I’ve tried with both suspensions enabled and disabled, they react the same way.
The worst of all, I did barely any modifications to the code and when I started to clip them, they don’t seem to react anymore. It’s my horrible part of programming when it comes to not knowing the root of an issue.
Latest version of my script:
local pt = script.Parent
local airDensity = 1.2
local liftPower = 100
local CD = 0.01
local S = 2 -- ¯\_(ツ)_/¯
local wingAR = 3
local oswald = 0.7
function calculateLift(velocity: Vector3, AoA)
AoA = math.deg(AoA)
print(AoA)
local CL = math.clamp(math.sin(math.rad(3 * AoA)), -60, 60)
return 0.5 * velocity.Magnitude^2 * CL * liftPower, CL
end
function calculateDrag(velocity: Vector3, pressure, CL, AoA)
local CLAoA = CL * AoA
local CD_i = CLAoA^2 / (3.14 * wingAR * oswald)
return (CD + CD_i) * pressure * S
end
function calculateAOA(velocity, CF)
local AoA
if velocity.Magnitude > 0 then
-- Project the velocity vector onto the plane's vertical plane
local projectedVelocity = velocity.Unit - CF.RightVector * velocity.Unit:Dot(CF.RightVector)
-- Calculate the angle between the projected velocity and look vector
local dotProduct = math.clamp(CF.LookVector:Dot(projectedVelocity.Unit), -1, 1)
local cross = CF.LookVector:Cross(projectedVelocity.Unit)
local sign = cross:Dot(CF.RightVector) > 0 and -1 or 1 -- Check if the aoa is pos/neg, if nose is above/below velocity vector
-- Convert to degrees and apply neg or pos, clamp from -90 to 90
AoA = math.clamp(math.deg(math.acos(dotProduct)) * sign, -90, 90)
else AoA = 0 end -- To avoid NaN AoAs
return math.rad(AoA)
end
game["Run Service"].Heartbeat:Connect(function(dT)
local cF = pt.CFrame
local velocity = pt.AssemblyLinearVelocity
if velocity.Magnitude == 0 then velocity = Vector3.new(0, 0.001, 0) end
local localVelocity = cF:VectorToObjectSpace(velocity)
local AoA = calculateAOA(velocity, cF)--math.atan2(-localVelocity.Y, -localVelocity.Z)
local AoS = math.asin(localVelocity.X / localVelocity.Magnitude)
local windex = cF * CFrame.Angles(-AoA, 0, 0) * CFrame.Angles(0, -AoS, 0)
local dir_lift = windex.UpVector
local dir_side = windex.RightVector
local dir_drag = -windex.LookVector
local pressure = airDensity * 0.5 * velocity.Magnitude^2
local lift, CL = calculateLift(velocity, AoA)
local drag = calculateDrag(localVelocity, pressure, CL, AoA)
pt.Lift.Force = dir_lift * lift
pt.Drag.Force = dir_drag * drag
end)
I am unable to determine what really causes the issue. But if I really have to, I’m saying it’s the Roblox engine itself.
Dont use align orientation. If your using constraints to get your plane to rotate, use angularVelocity. Handle inputs 60 times a second, and set the target angular velocity to be the current input on pitch/yaw:
function Controls.handleControls(flightControl, plane, stats, gun, velocity, AOA, deltaTime)
local rollInput = 0
local pitchInput = 0
local yawInput = 0
local degToRad = math.pi/180
-- Process control inputs
if UIS:IsKeyDown(Enum.KeyCode.LeftShift) then throttle = math.min(throttle + 0.0025, 1) end
if UIS:IsKeyDown(Enum.KeyCode.LeftControl) then throttle = math.max(throttle - 0.0025, 0) end
if UIS:IsKeyDown(Enum.KeyCode.W) then pitchInput = -stats.TurnRate * 0.5 end
if UIS:IsKeyDown(Enum.KeyCode.S) then pitchInput = stats.TurnRate end
if UIS:IsKeyDown(Enum.KeyCode.A) then rollInput = stats.RollRate end
if UIS:IsKeyDown(Enum.KeyCode.D) then rollInput = -stats.RollRate end
if UIS:IsKeyDown(Enum.KeyCode.Q) then yawInput = stats.YawRate end
if UIS:IsKeyDown(Enum.KeyCode.E) then yawInput = -stats.YawRate end
rollInput = rollInput + (yawInput * 1.75) -- Roll when the plane yaws
local angularVelocity = Vector3.new(pitchInput * degToRad, yawInput * degToRad, rollInput * degToRad) -- Calculate base angular vel
flightControl.AngularVelocity = angularVelocity
end
The max torque you set on the AngularVelocity constraint will be how quick the plane responds to rotational input. Align orientation is a weird way to handle it, kind of like setting cframe directly. Also, set thrust relative to WORLD
To mention that I did these server-sided. I thought it had something to do with the wobble behavior. I decided to try it on my client, and that’s when I couldn’t find out.
The jet doesn’t want to get past the speed of lifting off. I adjusted the liftPower
, and the top speed is capped at when it tries to takeoff, noticed by a wobbly behavior.
It’s just a copy-pasted script, yet it works differently. This does not make sense.
If your plane cant take off, its because the gravity is too strong. That, or the forces are too weak relative to the mass of the plane they are effecting. The code im giving you relies on the mesh being the proper mass(you can change this by changing the density), proper game gravity(0 since i use a vector force to handle gravity separately), and a bit of fiddling with inertia. Roblox vector forces aren’t really proportional, so you have to manipulate the velocity vector directly to nudge it towards where the thrust is pointing. Heres what i did for that:
function Physics.alignVV(velocity, lookVector, throttle, plane, dt)
local velocityUnit = velocity.Unit
local lookVectorUnit = lookVector.Unit
local dotProduct = velocityUnit:Dot(lookVectorUnit)
local angleBetween = math.acos(math.clamp(dotProduct, -1, 1))
-- Scale correction angle with delta time to decouple from frame rate
local correctionAngle = math.clamp(0.1 + (throttle * (0.5 - 0.01)), 0.01, 0.5) * (dt/(1/60))
local correctionMagnitude = math.rad(correctionAngle)
-- If the angle is greater than the threshold, do the correction
if angleBetween > correctionMagnitude then
local correctionAxis = velocityUnit:Cross(lookVectorUnit)
local correctionDirection = correctionAxis.Unit * correctionMagnitude
-- Gradually change the velocity direction using the correction.
local correctedVelocity = velocityUnit:Lerp(lookVectorUnit, correctionMagnitude)
correctedVelocity = correctedVelocity * velocity.Magnitude
plane.AssemblyLinearVelocity = correctedVelocity
end
end
It just directly changes the direction of the velocity vector based on the current thrust, and interpolates to make it smooth.
Use airfoils, search them up there great they simulate lift and drag for a part it custom airfoil characteristics that’s the most realistic physics your gonna get, i only know 3 utilize airfoil physics, hostile skies, airx 2020 and Novus flight simulator
I’ve re-examined every calculation made. From AoA, coefficients, and stuff. They all make sense, except one thing: lift
.
The fling behavior is caused by the lift force. As I said before, it increases and decreases repeatedly from little decimals to large numbers. The liftCoefficient
is fine, so I doubt it’s something to do with the airspeed/velocity.
I use .Heartbeat
to run the physics. The RunService
constantly updates the force. I came up with the idea, that the latency between the updates causes the velocity to be not perfect, hence why the wobble behavior started from tiny decimals. However, that might not be the case.
That’s the server side. On the client side, I would say that it already tries to fling itself at low speeds, cancelled by the solid surface below, and declining it from exceeding said speed.
Knowing that the lift is the problem here, I thought of using the zero gravity formula, by multiplying .AssemblyMass
with .Gravity
, but this time is affected by my desired stallSpeed
. It didn’t go well, and is cancelled.
Other than lift, there’s still issue with drag. If it the part reaches zero velocity, it becomes non-existent. That’s when they check if it’s zero, it would be replaced with something like 0.001. My jet model has suspensions on, preventing this from happening since the velocity never reaches zero.
What do I do?
One time, I gave up my own physics simulation. I’ve went to the Marketplace and think of using existing aerodynamics model made.
I don’t understand how most are done, as they are done in different ways—in ways I’ve never seen. They don’t work as intended, either. That’s the con of free models, you don’t fully understand them, at least to me. Which is why I avoid using them. It’s all on me again.
Since I couldn’t do more to the script and constraints, I tried going after one passive stuff: physical properties.
I’ve noticed that @xPoIarity has their jets set with large mass, which is why I gave another try here.
I tried playing with .Mass
. There is quite significant change with the reactions of the aerodynamics. The heavier it gets, the longer it endures before eventually glitching out. Although it is not perfect, it could still be improved.
I thought it is time to shrink the world—giving the illusion of a large map, while preventing you from reaching extreme world distances.
However, my suspension chassis doesn’t tolerate this. The SpringConstraint causes it to fling off the world, not any different from the issue I’ve been experiencing. I’ve tried adjusting stuff, and it results in no difference.
I removed the SpringConstraint, and keep the PrismConstraint on, as it also plays as the wheel hinges. It wobbles a lot, but eventually eases off. Not until it gets forced to move, causing it to fling off.
Alright. No suspensions, no wheels. Just welds. I’ve tried this long before, that’s when I’m issuing about setting the speed value to 0.001 when it is read zero. It doesn’t do the job. It completely disappears when it has reached zero velocity. The springs and wheels is what keeps this from intervening.
I’ve always planned of shrinking it down to do the illusion thing, but these issues stopped the jet from demonstrating aerodynamics.
I couldn’t think of anything more wrong, either the active or passive stuffs. It is likely that I have failed to understand how most stuff works. But again, I’m sure it’s all the fault of Roblox physics.
I could not fix this, nor have I achieved functioning aerodynamics.