I created a unique way of loading any model part-by-part using TweenService!
The parts follow along the root of the model as it slowly locks onto its target orientation.
You can use the module here: https://www.roblox.com/library/6671199113/Loader-Module
Take a sample place here: Loader.rbxl (72.7 KB)
SAMPLE CODE
local Loader = require(game:GetService("ReplicatedStorage").Loader)
local model = workspace.Model
local modelLoading = Loader.Create(model)
modelLoading:Load()
SOURCE CODE
--// Services and Variables \\--
local TweenService = game:GetService("TweenService")
local Heartbeat = game:GetService("RunService").Heartbeat
local Loader = {}
Loader.__index = Loader
local SETTINGS = {
SPIN_INCREMENT = 8, -- Increasing this will slow down the model's spinning speed
MOVE_SPEED = 1, -- Number of seconds for part to lock into position
MIN_OFFSET = 10, -- Minimum number of studs that the part will be offset by
MAX_OFFSET = 20, -- Maximum number of studs that the part will be offset by
-- A random offset is taken between MIN_OFFSET and MAX_OFFSET
MIN_ROT_OFFSET = 2, -- Minimum rotational increment
MAX_ROT_OFFSET = 8, -- Maximum rotational increment
-- A random rotational offset is taken between MIN_ROT_OFFSET and MAX_ROT_OFFSET
}
-- Creates a new instance
local function create(instance, parent, name, properties)
local object = Instance.new(instance)
if properties then
for propertyName, propertyValue in pairs(properties) do
object[propertyName] = propertyValue
end
end
if name then object.Name = name end
if parent then object.Parent = parent end
return object
end
-- Returns random integer (+/-) between min and max
local function rand(min, max)
local number = math.random(min, max)
return math.random(2) == 1 and number * -1 or number
end
--// Create New Class \\--
function Loader.Create(model)
-- Detect the middle of the model and set the primary part
local orientation, size = model:GetBoundingBox()
local box = create("Part", model, "Box",
{Position = orientation.Position, Anchored = true, CanCollide = false, Transparency = 1}
)
model.PrimaryPart = box
-- Class creation
local CloneData = {}
setmetatable(CloneData, Loader)
CloneData.Model = model
CloneData.PrimaryPart = box
CloneData.NeededTweens = 0
CloneData.Origin = box.CFrame
-- Weld original parts to box (middle of the model)
-- Index clones in dictionary (referenced by original part)
CloneData.Clones = {}
for _, child in ipairs(model:GetDescendants()) do
-- Delete existing welds
if child:IsA("Weld") then
child:Destroy()
end
-- Index clones, weld parts, and set original model to be invisible
if child:IsA("BasePart") and child ~= box then
local clone = child:Clone()
clone.Anchored = true
CloneData.Clones[child] = clone
create("WeldConstraint", box, nil, {Part0 = child, Part1 = box})
child.Anchored = false
child.Transparency = 1
child.CanCollide = false
CloneData.NeededTweens += 1
end
end
return CloneData
end
--// Load Model \\--
function Loader:Load()
-- Create empty model identical to original
local model = create("Model", workspace, self.Model.Name)
local updateConnections = {}
-- Variables to keep track of completed tweens
local currentTween = nil
local completedTweens = 0
local primaryPart = self.PrimaryPart
-- Spins the original model
local spinStart = tick()
local spinConnection; spinConnection = Heartbeat:Connect(function()
-- If all tweens are completed (disconnections, cancel spinning, and destroy original model)
if self.NeededTweens == completedTweens then
spinConnection:Disconnect()
currentTween:Cancel()
TweenService:Create(
primaryPart,
TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{CFrame = self.Origin}
):Play(); wait(1)
for _, updateConnection in ipairs(updateConnections) do
updateConnection:Disconnect()
end
return
end
-- Spin model by increment radians every 0.25 seconds
if tick() - spinStart < 0.25 then return end spinStart = tick()
currentTween = TweenService:Create(
primaryPart,
TweenInfo.new(0.25, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
{CFrame = primaryPart.CFrame * CFrame.Angles(0, math.pi/SETTINGS.SPIN_INCREMENT, 0)}
); currentTween:Play()
end)
-- Variables for offsets
local minOff, maxOff = SETTINGS.MIN_OFFSET, SETTINGS.MAX_OFFSET
local minRot, maxRot = SETTINGS.MIN_ROT_OFFSET, SETTINGS.MAX_ROT_OFFSET
for part, clone in pairs(self.Clones) do
-- Variables for properties and offsets
local transparency = clone.Transparency
local size = clone.Size
local sizeX, sizeY, sizeZ = size.X, size.Y,size.Z
local offsetX, offsetY, offsetZ = rand(minOff, maxOff), rand(minOff, maxOff),rand(minOff, maxOff)
local rotX, rotY, rotZ = math.pi/rand(minRot, maxRot), math.pi/rand(minRot, maxRot), math.pi/rand(minRot, maxRot)
-- Set up the clone part (invisible, size 0, and offsetted, parented to created model)
clone.Transparency = 1
clone.Size = Vector3.new(0, 0, 0)
clone.CFrame *= CFrame.new(offsetX, offsetY, offsetZ) * CFrame.Angles(rotX, rotY, rotZ)
clone.Parent = model
-- Update the size, offset, and transparency using proportions based on increment (tweened)
local increment = create("NumberValue", nil, nil, {Value = 0.01})
local updateConnection; updateConnection = Heartbeat:Connect(function()
local value = increment.Value
local targetSize = Vector3.new(sizeX * value, sizeY * value, sizeZ * value)
local targetOffset = CFrame.new(offsetX-(offsetX*value), offsetY-(offsetY*value), offsetZ-(offsetZ*value))
local targetRotation = CFrame.Angles(rotX-(rotX*value), rotY-(rotY*value), rotZ-(rotZ*value))
clone.Size = targetSize
clone.Transparency = (transparency ~= 0) and 1 - (transparency * value) or 1 - value
clone.CFrame = part.CFrame * targetOffset * targetRotation
end)
table.insert(updateConnections, updateConnection)
-- Tween the increment value to 1 and destroy on completion
-- Add 1 to completedTweens, all connections disconnect when all tweens for each part is completed
coroutine.resume(coroutine.create(function()
local tween = TweenService:Create(
increment,
TweenInfo.new(SETTINGS.MOVE_SPEED, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{Value = 1}
); tween:Play()
tween.Completed:Connect(function()
increment:Destroy()
completedTweens += 1
end)
end))
-- Wait increment before next part moves
Heartbeat:Wait()
end
end
return Loader
Let me know what you guys think! Enjoy!