I’ve been experimenting with float curves in order to generate paths from 1 node to another such that I can tween a part between two nodes in a smoothly curving way. Kind of like a bezier curve with control points.
Example gif of what I’m trying to achieve:
The problem with this is that when I try to tween a part to show the path it takes, it is nothing at all what I’d like from it.
The blue line is the desired path that I want it to take,
The red line is the actual path that the part takes.
This is the most accurate visualization I could make.
I did follow a tutorial video on how to make float curves and tweening with them and expanded on what I learned to incorporate using angles so that it looks like the part is following a path and not just remaining static in one orientation.
I’ve tried changing the left and right tangents of both nodes to see if anything changed but visually they were almost the same. I’m not even sure if I’m using float curves the way they were intended and if I should move to a different curve generation like bezier curves.
Module script that generates the path:
local module = {}
module.__index = module
local TweenService = game:GetService("TweenService")
local part = game.ReplicatedStorage:WaitForChild("Part")
module.new = function(positions, rotations, speed)
local self = setmetatable({}, module)
self.Part = part:Clone()
self.Part.Position = positions[1]
self.Part.Parent = workspace
self.Curve = Instance.new("Vector3Curve")
self.CurveRot = Instance.new("EulerRotationCurve")
local curveX, curveY, curveZ = self.Curve:X(), self.Curve:Y(), self.Curve:Z()
local curveRotX, curveRotY, curveRotZ = self.CurveRot:X(), self.CurveRot:Y(), self.CurveRot:Z()
self.Time = 0
for index, position in positions do
if index > 1 then self.Time += (position - positions[index - 1]).Magnitude / speed end
-- positions
local curveKeyX = FloatCurveKey.new(self.Time, position.X, Enum.KeyInterpolationMode.Cubic)
curveKeyX.LeftTangent = math.rad(rotations[index].Y)
curveKeyX.RightTangent = math.rad(rotations[index].Y)
curveX:InsertKey(curveKeyX)
local curveKeyY = FloatCurveKey.new(self.Time, position.Y, Enum.KeyInterpolationMode.Cubic)
curveY:InsertKey(curveKeyY)
local curveKeyZ = FloatCurveKey.new(self.Time, position.Z, Enum.KeyInterpolationMode.Cubic)
curveZ:InsertKey(curveKeyZ)
curveKeyX.LeftTangent = math.rad(rotations[index].Y)
curveKeyX.RightTangent = math.rad(rotations[index].Y)
-- rotations
local curveRotKeyX = FloatCurveKey.new(self.Time, rotations[index].X, Enum.KeyInterpolationMode.Cubic)
curveRotX:InsertKey(curveRotKeyX)
local curveRotKeyY = FloatCurveKey.new(self.Time, rotations[index].Y, Enum.KeyInterpolationMode.Cubic)
curveRotY:InsertKey(curveRotKeyY)
local curveRotKeyZ = FloatCurveKey.new(self.Time, rotations[index].Z, Enum.KeyInterpolationMode.Cubic)
curveRotZ:InsertKey(curveRotKeyZ)
end
return self
end
module.Set = function(self, curveTime)
self.Part.Position = Vector3.new(table.unpack(self.Curve:GetValueAtTime(curveTime)))
self.Part.Orientation = Vector3.new(table.unpack(self.CurveRot:GetAnglesAtTime(curveTime)))
end
module.Tween = function(self, curveTime, easingStyle, easingDirection)
local alpha = curveTime / self.Time
local tween = TweenService:GetValue(alpha, easingStyle, easingDirection)
self.Part.Position = Vector3.new(table.unpack(self.Curve:GetValueAtTime(tween * self.Time)))
self.Part.Orientation = Vector3.new(table.unpack(self.CurveRot:GetAnglesAtTime(tween * self.Time)))
end
module.Destroy = function(self)
self.Part:Destroy()
self.Curve:Destroy()
end
return module
local script that does all the initializations.
local RunService = game:GetService("RunService")
local PathModule = require(game.ReplicatedStorage:WaitForChild("Path"))
local pathsFolder = workspace:WaitForChild("Paths")
local paths = {}
local function ChildAdded(model)
local positions = {}
local rotations = {}
for index,child in model:GetChildren() do
positions[tonumber(child.Name)] = child.Position
rotations[tonumber(child.Name)] = child.Orientation
end
paths[model] = PathModule.new(positions, rotations, 10)
end
local function ChildRemoved(model)
paths[model]:Destroy()
paths[model] = nil
end
local function Loop(deltaTime)
local serverTime = workspace:GetServerTimeNow()
for model, path in paths do
local loopTime = serverTime % path.Time
path:Tween(loopTime, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
end
end
for index, child in pathsFolder:GetChildren() do ChildAdded(child) end
pathsFolder.ChildAdded:Connect(ChildAdded)
pathsFolder.ChildRemoved:Connect(ChildRemoved)
RunService.Heartbeat:Connect(Loop)
I’m sorry for the lack of comments, I’m still getting into the habit of leaving comments on my scripts.