Beta feature play testing, "Aerodynamics" with floating ballons

After trying to help someone make floating ballons here, I kinda got caught up in the idea and spent most of yesterday playing around with it. Being farily content with the results I figured I’d post a little guide on how to do it. This was a Saturday afternoon project so it’s far from perfect but I thought it was fun so here it is.

The script below with the described rig makes floating ballons the player can grab and drag along with them. With enough ballons held, (currently 5) the air density will be increased and the player will start to float away for however many seconds floatTime is set to. If time runs out you can quickly click a ballon to release it then click it again to start the air density timer over again.

The ballons could look better and they do spin about themselves quite quickly at times and they snap to the player in an rather abrupt manner but they work for a quick example of what Studio’s Beta feature “Aerodynamics” can do. Not sure what (if anything) happens to free (unheld ballons) , they should eventually come down but they do rise really quickly when not held, may need to Destroy/respawn if Y > 1000 or something.

Requires the currently Beta Studio feature “Aerodynamics” to be enabled as of posting. File>Beta Features, place a checkmark in “Aerodynamics” and restart Studio if prompted.

Setting game.Workspace.AirDensity to 0.02 seems to be a good starting point.
Optional: Provide a small vector (10, 0, 0) to game.Workspace.GlobalWind to get carried away by the wind when floating.

Save this script as a “Script” in ServerScriptService folder, put ballon models in a Workspace folder named “Ballons”.

Ballon model heirarchy as follows:

-Ballon model (named YellowBallon)
–Ballon part (named Ballon)
—Attachment (named BallonRopeAttach)
—Rope Constraint (named RopeConstraint)
—Weight part (named Weight)
----Attachment (named WeightRopeAttach)

Settings for ballon rig are as follows:
Ballon part: shape ball, size 4, preferably a color that’s not red, CustomPhysicalProperties enabled and Density set to 0.0195, position 10 units up along Y.
Ballon Attachment: position one stud below the ballon part along Y.
Rope Constraint: set Attachment0 to the BallonRopeAttch, set Attachment1 to the WeightRopeAttach. Rope.Length 10, Rope.Restitution 0.9.
Weight part: position 4-6 studs below the ballon part, shape ball, size 1,1,1, transparency 1, CustomPhysicalProperties enabled and Density set to 0.07

Create the ballon model then duplicate it for as many ballons as you need. Offset duplicates by some amount, clicks may not register correctly if ballons are in the same position. The comments should explain it all but if you have any quesions ask away.

Enjoy!

--the script
local ballons = game.Workspace.Ballons:GetChildren() --get all the ballon models in the Workspace.Ballons folder
local players = game:GetService("Players") --get the current players
local heldColor = Color3.fromRGB(255, 0, 0) --the color red... the ballon color will change to this when held
local startingAirDensity = game.Workspace.AirDensity --original air density value
local tempAirDensity = 0.5 --value to increase the air density to, a higher value = faster rise
local floatTime = 20 --duration in seconds, air density is increased during this time
local heldBallonsToTrigger = 5 --number of ballons that need to be held before triggering the air density change

local function SetAirDensity(density: number) --function to change the Air Density
	game.Workspace.AirDensity = density --set the new density
end

for _, ballon in ballons do --for each model in ballons
	
	--print("Found Model: " .. ballon.Name) --print the name of the model
	local bp = ballon.Ballon --get the ballon part
	bp:SetAttribute("OriginalColor", bp.Color) --create an attribute to save the ballons current color
	local cd = Instance.new("ClickDetector") --create a click detector
	cd.Parent = ballon --parent the click dectector to the model
	
	cd.MouseClick:Connect(function(player) --when the ballon is clicked
		local char = game.Workspace:FindFirstChild(player.Name) --get the player character
		local playerBC = char:GetAttribute("BallonCount") --get the player ballon count, can be nil
		if playerBC == nil or playerBC <= 0 then --if it doesn't exist or is less or equal to 0
			char:SetAttribute("BallonCount", 1) --create the attribute and set it to 1
			playerBC = 1
		else --if the attribute exists
			playerBC += 1 --increment the player ballon count by 1
			char:SetAttribute("BallonCount", playerBC) --set the attribute to the new ballon count
		end
		--print("Ballon Count is: " .. playerBC)
		local held = ballon:GetAttribute("Held") --get the Held attribute from the ballon
		local rp = bp.RopeConstraint --get the rope constraint on the ballon part
		if held then --if true the ballon is currently being held
			ballon:SetAttribute("Held", false) --set the Held attribute to false
			local weight = bp.Weight --get the ballons weight part
			local weldWeight = weight.WeldConstraint --get the weldconstraint
			weldWeight:Destroy() --destroy the weldconstraint
			weight.CFrame = CFrame.new(bp.Position + Vector3.new(0, -3, 0)) --move the weight back under the ballon
			weight.CanTouch = true --disable CanTouch
			weight.CanCollide = true --disable CanCollide
			weight.Massless = false --enable Massless
			rp.Attachment1 = weight.WeightRopeAttach --set rope attachment1 back to the weight
			rp.Length = 10 --reset rope length
			bp.Color = bp:GetAttribute("OriginalColor") --change the ballon back to it's original color
			playerBC -= 1 --reduce ballon count
			char:SetAttribute("BallonCount", playerBC)
			return --stop here
		end
		
		--if we're here, the ballon was not being held so grab it
		local wrist = char.RightHand.RightGripAttachment --get the characters RightGripAttachment
		ballon:SetAttribute("Held", true) --set the Held attribute to true
		rp.Attachment1 = wrist --attach the ballon string to the players right hand
		rp.Length = 15 --makes the rope a bit longer
		bp.Color = heldColor --change the ballon color to the heldColor
		local weight = bp.Weight --get the ballons weight part, we're going to "hide" it inside the ballon 
		weight.CFrame = CFrame.new(bp.Position) --move the weight into the middle of the ballon
		local weldWeight = Instance.new("WeldConstraint", weight) --create a weldconstraint and parent to weight
		weldWeight.Part0 = weight --set the first weld to the weight part
		weldWeight.Part1 = bp --set the second weld to the ballon part
		weight.CanTouch = false --disable CanTouch, it's inside the ballon, nothing should touch it
		weight.CanCollide = false --disable CanCollide, again inside ballon.
		weight.Massless = true --enable massless so the weight doesn't affect the ballon
		
		if playerBC >= heldBallonsToTrigger then --if holding more than heldBallonsToTrigger ballons
			--print("Increasing AirDensity for 20 seconds.")
			SetAirDensity(tempAirDensity) --increase the air density
			task.wait(floatTime) --time in seconds the air density will be increased for.
			SetAirDensity(startingAirDensity) --return the air density back to what it was
			--print("AirDensity returned to normal.")
		end
	end)
end

1 Like

As a rᐈᙏᐅrcᐈ for development, this belongs in #resources . This is cool tho.


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- Function to attach a balloon to a player
local function attachBalloon(player)
    local character = player.Character
    if character and character:FindFirstChild("HumanoidRootPart") then
        local balloon = ReplicatedStorage:WaitForChild("Balloon"):Clone()
        balloon.Parent = character

        -- Position the balloon above the player's head
        balloon:SetPrimaryPartCFrame(character.HumanoidRootPart.CFrame * CFrame.new(0, 5, 0))

        -- Attach the balloon to the player's character
        local weld = Instance.new("WeldConstraint")
        weld.Part0 = balloon.PrimaryPart
        weld.Part1 = character.HumanoidRootPart
        weld.Parent = balloon.PrimaryPart

        -- Make the balloon float
        local bodyPosition = Instance.new("BodyPosition")
        bodyPosition.MaxForce = Vector3.new(0, 5000, 0) -- Adjust as needed
        bodyPosition.Position = character.HumanoidRootPart.Position + Vector3.new(0, 10, 0)
        bodyPosition.Parent = balloon.PrimaryPart
    end
end

-- Attach balloon to player when they join the game
Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Delay to ensure character is fully loaded
        wait(1)
        attachBalloon(player)
    end)
end)