Quick actions

cmd+k|ctrl+k

Navigation

Languages

KSP_Track

Snippet info

Language

Lua

Visibility

public

Author

zen

Created

2024-01-01T17:51:27.601112Z

Updated

2024-08-25T22:28:38.716457Z

local TweenService = game:GetService("TweenService")

local Signal = {}
Signal.__index = Signal

-- Constructor
function Signal.new()
	return setmetatable({
		_bindable = Instance.new("BindableEvent"),

		_args = nil,
		_argCount = nil,

	},Signal)
end

function Signal:Fire(...)
	self._args = {...}
	self._argCount = select("#", ...)

	self._bindable:Fire()
end

function Signal:fire(...)
	return self:Fire(...)
end

function Signal:Connect(handler)
	if not (type(handler) == "function") then
		error(("connect(%s)"):format(typeof(handler)), 2)
	end

	return self._bindable.Event:Connect(function()
		handler(unpack(self._args or {}))
	end)
end

function Signal:connect(...)
	return self:Connect(...)
end

function Signal:Wait()
	self._bindableEvent.Event:Wait()
	assert(self._argData, "Missing argument data, likely due to :TweenSize/Position corrupting.")
	return unpack(self._args, 1, self._argCount)
end

function Signal:wait()
	return self:Wait()
end

function Signal:Destroy()
	if self._bindable then
		self._bindable:Destroy()
		self._bindable = nil
	end

	self._args = nil
	self._argCount = nil

	setmetatable(self, nil)
end

function Signal:destroy()
	return self:Destroy()
end

local Track = {}
Track.__index = Track

function Track.new(Animator : any, Transform : any, Character : Model, Sequence : KeyframeSequence)
	local NewTrack = setmetatable({}, Track)
	local TransformAmount = 0
	
	NewTrack.Animator = Animator
	NewTrack.KeyframeReached = Signal.new()
	NewTrack.OnFinished = Signal.new()
	
	NewTrack.Playing = false
	NewTrack.Looped = Sequence.Loop
	NewTrack.Smooth = false
	NewTrack.FiredEvent = false
	NewTrack.Speed = 1
	NewTrack.Character = Character
	NewTrack.Finished = 0
	
	for _ in Transform do 
		TransformAmount += 1
	end
	
	NewTrack.TransformAmount = TransformAmount
	
	NewTrack.Transform = Transform
	
	local SequenceSorted = {}
	
	for _, Object in Sequence:GetChildren() do 
		if Object:IsA("Keyframe") then
			table.insert(SequenceSorted, Object)
		end
	end
	
	table.sort(SequenceSorted, function(A, B)
		return A.Time < B.Time
	end)
	
	NewTrack.Keyframes = SequenceSorted
	
	return NewTrack
end

function Track:AdjustSpeed(Value : number)
	self.Speed = Value
end

function Track:Play(speed: number?)
	if self.Playing then return end
	if speed then
		assert(typeof(speed) == "number","Speed parameter must be a number")
	end
	self.Finished = 0
	self.Speed = speed or 1
	self.Playing = true
end

function Track:Reset()
	for Part1, Motor in self.Transform do 
		Motor.Index = 1
		Motor.Skip = 1
		Motor.Done = false
		Motor.Alpha = 0
	end
end

function Track:Stop()
	if not self.Playing then return end
	self.Finished = 0
	self.Playing = false
	self:Reset()
end

function Track:IsFinished()
	return self.Finished >= self.TransformAmount
end

function Track:_update(DeltaTime : number)
	
	if self.Playing then
		for _,Motor in self.Transform do
			if Motor.Object:IsA("Weld") and not Motor.Object.Enabled then
				Motor.Object.Enabled = true
			end
		end
	else
		for _,Motor in self.Transform do
			if Motor.Object:IsA("Weld") and Motor.Object.Enabled then
				Motor.Object.Enabled = false
			end
		end
	end
	
	if not self.Playing then return end
	
	local Keyframes = self.Keyframes
	
	self.FiredEvent = false
	
	for Name, Motor in self.Transform do 
		if Motor.Done then continue end
		
		local CurrentKeyframe = Keyframes[Motor.Index]
		local NextKeyframe = Keyframes[Motor.Index + Motor.Skip]
		if not NextKeyframe then continue end
		
		local CurrentPose = CurrentKeyframe:FindFirstChild(Name, true)
		if not CurrentPose then continue end
		
		local NextPose = NextKeyframe:FindFirstChild(Name, true)
		if not NextPose then Motor.Skip += 1 continue end
		
		Motor.Skip = 1
		Motor.Alpha += DeltaTime * self.Speed
		
		local Alpha = Motor.Alpha / (NextKeyframe.Time - CurrentKeyframe.Time)
		
		if Alpha >= 1 then
			if not self.FiredEvent then
				self.KeyframeReached:Fire(NextKeyframe.Name,Motor.Index + Motor.Skip)
				self.FiredEvent = true
			end
			
			Motor.Alpha = 0
			Motor.Index += 1
			
			if Motor.Index >= #Keyframes - 1 then
				self.Finished += 1
				Motor.Done = true
			end
		end
		
		local TweenedAlpha = TweenService:GetValue(
			math.clamp(Alpha, 0, 1),
			Enum.EasingStyle[CurrentPose.EasingStyle.Name],
			Enum.EasingDirection[CurrentPose.EasingDirection.Name]
		)
		
		if Motor.Object:IsA("Weld") then
			Motor.Object.C0 = (Motor.Default * CurrentPose.CFrame):Lerp(Motor.Default * NextPose.CFrame, TweenedAlpha)
		else
			if self.Smooth then
				Motor.Object.CFrame = Motor.Object.CFrame:Lerp((Motor.Default * CurrentPose.CFrame):Lerp(Motor.Default * NextPose.CFrame, TweenedAlpha),10 * DeltaTime)
			else
				Motor.Object.CFrame = (Motor.Default * CurrentPose.CFrame):Lerp(Motor.Default * NextPose.CFrame, TweenedAlpha)
			end
		end
	end
	
	if self:IsFinished() then
		self.OnFinished:Fire()
		if self.Looped then
			self.Finished = 0
			self:Reset()
		else
			self:Stop()
		end
	end
end

return Track
INFO