Awhile ago I made a “Model Tween Module” that you could use to tween the models properties (Scale, Position and Orientation). It was just a side project of mine and I didn’t think it would be that useful, that is why I thought of rewriting this module because the version I made back then is nowhere performant.
Anyway enough talk:
Here are video concepts:
The Code:
export type ModelTweenProperties = {
Scale: number?,
Position: Vector3?,
Orientation: Vector3?,
CFrame: CFrame?
}
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ModelTween = {}
local function createSignal()
local callbacks = {}
local signal = {}
function signal:Fire(...)
for _, callback in callbacks do
task.spawn(callback, ...)
end
end
function signal:Connect(callback: () -> ()) : RBXScriptConnection
table.insert(callbacks, callback)
return {
Connected = true,
Disconnect = function(self)
self.Connected = false
table.remove(callbacks, table.find(callbacks, callback))
end,
}
end
function signal:Once(callback: () -> ()) : RBXScriptConnection
local connection
connection = signal.Connect(function(...)
callback(...)
connection:Disconnect()
end)
table.insert(callbacks, connection)
return {
Connected = true,
Disconnect = function(self)
self.Connected = false
table.remove(callbacks, table.find(callbacks, connection))
end,
}
end
function signal:Wait()
local thread = coroutine.running()
local argList
local connection
connection = signal.Connect(function(...)
argList = {...}
connection.disconnect()
task.defer(function()
coroutine.resume(thread, table.unpack(argList))
end)
end)
return coroutine.yield()
end
return signal
end
local ModelGetMethods = {
Scale = function(model: Model)
return model:GetScale()
end,
Position = function(model: Model)
return model:GetPivot().Position
end,
Orientation = function(model: Model)
local sx, sy, sz, m00, m01, m02, m10, m11, m12, m20, m21, m22 = model:GetPivot():GetComponents()
local x = math.atan2(-m12, m22)
local y = math.asin(m02)
local z = math.atan2(-m01, m00)
return Vector3.new(x, y, z)
end,
CFrame = function(model: Model)
return model:GetPivot()
end,
}
local ModelSetMethods = {
Scale = function(model: Model, scale: number)
model:ScaleTo(scale)
end,
Position = function(model: Model, position: Vector3)
local modelPivot = model:GetPivot()
model:PivotTo(CFrame.lookAt(position, position + modelPivot.LookVector))
end,
Orientation = function(model: Model, orientation: Vector3)
local modelPivot = model:GetPivot()
model:PivotTo(CFrame.lookAt(modelPivot.Position) * CFrame.Angles(math.rad(orientation.X), math.rad(orientation.Y), math.rad(orientation.Z)))
end,
CFrame = function(model: Model, cframe: CFrame)
model:PivotTo(cframe)
end,
}
local lerpMethods = {
CFrame = function(cframe: CFrame, targetCframe: CFrame, alpha: number)
return cframe:Lerp(targetCframe, alpha)
end,
}
function ModelTween:Create(model: Model, tweenInfo: TweenInfo, properties: ModelTweenProperties) : Tween
local preTweenProperties = {}
local tweenThread: thread?
local tweenHandler: RBXScriptConnection?
local signal = createSignal()
return {
TweenInfo = tweenInfo,
PlaybackState = Enum.PlaybackState.Begin,
Completed = signal,
Cancel = function(tween)
if tweenHandler then
tweenHandler:Disconnect()
end
if tweenThread then
task.cancel(tweenThread)
end
tween.PlaybackState = Enum.PlaybackState.Cancelled
signal:Fire(tween.PlaybackState)
for propertyName, value in preTweenProperties do
ModelSetMethods[propertyName](model, value)
end
end,
Pause = function(tween)
if tweenHandler then
tweenHandler:Disconnect()
end
if tweenThread then
task.cancel(tweenThread)
end
tween.PlaybackState = Enum.PlaybackState.Paused
end,
Play = function(tween)
tween.PlaybackState = Enum.PlaybackState.Delayed
tweenThread = task.delay(tweenInfo.DelayTime, function()
tween.PlaybackState = Enum.PlaybackState.Playing
for propertyName, value in properties do
preTweenProperties[propertyName] = ModelGetMethods[propertyName](model, value)
end
local goingReverse = false
local repeatedTimes = 0
local startTime = os.clock()
tweenHandler = RunService.PostSimulation:Connect(function(_)
local currentTime = os.clock()
local deltaTime = currentTime - startTime
if deltaTime >= tweenInfo.Time then
if tweenInfo.Reverses and not goingReverse then
goingReverse = true
startTime = os.clock()
return
end
if tweenInfo.RepeatCount == -1 or repeatedTimes < tweenInfo.RepeatCount then
repeatedTimes += 1
goingReverse = false
startTime = os.clock()
return
end
if tweenInfo.Reverses then
for propertyName, value in preTweenProperties do
ModelSetMethods[propertyName](model, value)
end
else
for propertyName, value in properties do
ModelSetMethods[propertyName](model, value)
end
end
tween.PlaybackState = Enum.PlaybackState.Completed
signal:Fire(tween.PlaybackState)
tweenHandler:Disconnect()
return
end
local alpha = TweenService:GetValue(deltaTime / tweenInfo.Time, tweenInfo.EasingStyle, tweenInfo.EasingDirection)
for propertyName, value in properties do
local preValue = preTweenProperties[propertyName]
if goingReverse then
ModelSetMethods[propertyName](model, lerpMethods[propertyName] and lerpMethods[propertyName](value, preValue, alpha) or value + (preValue - value) * alpha)
else
ModelSetMethods[propertyName](model, lerpMethods[propertyName] and lerpMethods[propertyName](preValue, value, alpha) or preValue + (value - preValue) * alpha)
end
end
end)
end)
end,
}
end
return ModelTween
Code sample:
local modelTween = require(PATH_TO_MODULE)
local model = SOME_MODEL
local tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut, -1, true)
local modelTween = modelTween:Create(model, tweenInfo, {
Position = model:GetPivot().Position + Vector3.yAxis * 10,
Scale = 2
})
modelTween.Completed:Connect(function(playbackState: Enum.PlaybackState)
print(playbackState)
end)
modelTween:Play()
modelTween:Cancel()
modelTween:Play()
Link to the model
P.S
- While the returned object is of a type “Tween”, it does not have all the properties (such as Parent, Instance, ect.) it has the one needed for the tween to work.
- Consider the fact that while this is a optimized version its still not that performant and may cause lag when used on large models, aka with a lot of parts. Consider switching to meshes or other approaches if you experience issues.