So... I looked back into long-ago questions and I saw swimming in the air... and I made this LocalScript in StarterPlayerScripts.
wait() local player = game:GetService("Players").LocalPlayer wait(2) spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) player.Character.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, true) for i,v in pairs(Enum.HumanoidStateType:GetEnumItems()) do if v ~= Enum.HumanoidStateType.Swimming then player.Character.Humanoid:SetStateEnabled(v, false) end end wait() spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) wait() spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) wait() spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) for _, part in pairs(game:GetService("Players").LocalPlayer.Character:GetChildren()) do if part:IsA("BasePart") then local F = Instance.new("BodyForce", part) F.Force = Vector3.new(0, 196.2, 0) * part:GetMass() elseif part:IsA("Accoutrement") then local F = Instance.new("BodyForce", part.Handle) F.Force = Vector3.new(0, 196.2, 0) * part.Handle:GetMass() end end--]]
The issue is, the BodyForces cause swimming in the air to be more like... well... swimming in the air; there's not enough friction.
How do I modify air friction here?
I changed the code to this
wait() local player = game:GetService("Players").LocalPlayer player.Character.Humanoid.WalkSpeed = 20 wait(2) spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) player.Character.Humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, true) for i,v in pairs(Enum.HumanoidStateType:GetEnumItems()) do if v ~= Enum.HumanoidStateType.Swimming and v ~= Enum.HumanoidStateType.None then player.Character.Humanoid:SetStateEnabled(v, false) end end wait() spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) wait() spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) wait() spawn(function() while true do player.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Swimming) game:GetService("RunService").RenderStepped:Wait() end end) -- helper function so we can use a single bodyforce in rootpart -- instead of individual bodyforces per part local function getSummedMass(model) local mass = 0 for _, v in pairs(model:GetDescendants()) do if v:IsA('BasePart') and v.ClassName ~= 'Terrain' then mass = mass + v:GetMass() elseif v:IsA("Accoutrement") then mass = mass + v.Handle:GetMass() end end return mass end -- in a loop, RenderStep recommended while true do local rootPart = player.Character.HumanoidRootPart local force = Instance.new("BodyForce", rootPart) local mass = getSummedMass(player.Character) -- no need to convert mass to kg but for reference 1kg = 8000 rbx local p = mass * 1.225 local v = rootPart.Velocity -- a cube has a drag coefficient of 1.05 so we will use that local Cd = 1.05 -- when air 'swimming' with top of head oriented with velocity there is an area of about 3 square studs local A = 3^2 local DragForce = (.5 * p) * (v.Magnitude^2) * Cd * A local GravityOffset = Vector3.new(0, mass * workspace.Gravity, 0) local ActingForce = (v * DragForce) + GravityOffset force.Force = ActingForce game:GetService("RunService").RenderStepped:Wait() end
However, it simply just poofs the whole character goodbye, and triggers a blackscreen. (as in, the character's parts and accessories/hats just go poof, and a never-ending blackscreen pops up, and the character doesn't recover)
So err....
Roblox doesn't model drag force due to air resistance.
A simple model is Drag Force = 1/2 p v^2 Cd A where:
p is the fluid density (at sea level air has a density of 1.225 kg/m^3)
v is the velocity magnitude of the object relative to the fluid (just the objects velocity for the sake of keeping things simple)
Cd is the drag coefficient of the object's current cross section
A is the cross section area.
In code this looks a bit like:
-- helper function so we can use a single bodyforce in rootpart -- instead of individual bodyforces per part local function getSummedMass(model) local mass = 0 for _, v in pairs(model:GetDescendants()) do if v:IsA('BasePart') and v.ClassName ~= 'Terrain' then mass = mass + v:GetMass() end end return mass end -- in a loop, RenderStep recommended local rootPart = character.HumanoidRootPart local mass = getSummedMass(character) -- no need to convert mass to kg but for reference 1kg = 8000 rbx local p = mass * 1.225 local v = rootPart.Velocity -- a cube has a drag coefficient of 1.05 so we will use that local Cd = 1.05 -- when air 'swimming' with top of head oriented with velocity there is an area of about 3 square studs local A = 3^2 local DragForce = (.5 * p) * (v.Magnitude^2) * Cd * A local GravityOffset = Vector3.new(0, mass * workspace.Gravity, 0) local ActingForce = (v * DragForce) + GravityOffset rootPart.BodyForce.Force = ActingForce
If you're not satisfied with a constant cross section surface area; you can work through how I did it. I fire a bunch of rays from a flat plane, determine if there's an intersection with the object, then sum surface areas of each individual triangle formed by truthy intersections. I do this 180 times to capture a surface area for every angle in 2D.
Hope this helps!