Asset Loader (OPEN-SOURCED)

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: Loader Module - Roblox
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! :slight_smile:

130 Likes

Omg wow Thats Awesome Bro! love it!

3 Likes

This is literally just amazing! Thank you for making it open sourced, lovely! :grinning_face_with_smiling_eyes: By the way, I think you should put this in #resources:community-resources

4 Likes

Thank you!!

I didn’t put it in resources because I didn’t feel like everyone would be using this collectively haha

4 Likes

Wow, that’s amazing!! And open source as well. :grin:

3 Likes

Doesn’t have to be used collectively. :slight_smile:
As it’s open-source and you are sharing the code, it can be moved over to #resources:community-resources, because really; that’s what it is.

3 Likes

Alright I’ll do that then :slight_smile: thank you and @EcoCrashed

1 Like

This is so cool!
I need to try this out on super large models.

2 Likes

This is awesome! But how much time did it take you to make it?

1 Like

do i have to make the model parts don’t collide?
edit: the model parts doesn’t go to the original position after completed

1 Like

This is it chief, this is it…

I love this, thank you for your time! I can already envision some good uses for this.

Feels like a Minecraft building time lapse.

4 Likes

Hey,
You made a great asset for the community

Good Job :+1: :wave:

2 Likes

Good project but I think it’ll be good to have like “finished” or “ended” sort of signal so we know when the tween thing ended and it might be better to use this on client for performances purpose.

If you want some assets for signal “class” I recommend you:
https://quenty.github.io/api/classes/Signal.html

2 Likes

Here’s a modified version that have a “Finished” event and client side Loader.rbxl (74.9 KB)

3 Likes

Thanks for linking my post! It is reccomended to use my newer signal class here:

I know the name is kinda misleading.

I have done benchmarking, and the new one is a lot faster, and is less bloated with features, making it more light-weight.

1 Like

Thank you! This will be really useful for me.

1 Like

Great! I can see so many uses for this I can’t even list them.

1 Like

Here is an example of the use of this (with a ton of parts):

1 Like

Wow, this looks great! Seeing it load part by part is pretty cool.

1 Like

This looks super cool and helpful. Thanks. :slightly_smiling_face: :+1:

1 Like