[SIMPLE] Tweening a Model without welding/complicated methods!

This is the MOST easiest way out there, and it requires NO WELDING or some other complicated method.

This also won’t require you to tween every single property of the model.

This uses i,v in pairs.

Without further ado, let’s get started.

First off, you will have to insert a ServerScript, a.k.a a Script in your model.
This is basically how it’s going to work:
Basically mass-selects all the movable properties of the model and moves their position. Yep. Easy as that, nothing as stupid as welding.
Paste this to your script:

local waittime = 10 -- How long will this tween(animation) last?
local moveXstuds = 0 -- How many studs in X axis should it move?
local moveYstuds = 27 -- How many studs in Y axis should it move?
local moveYstuds2 = 0 -- How many studs in Z axis should it move?

local TweenService = game:GetService("TweenService") -- Referencing the TweenService

local tweenInfo = TweenInfo.new(
	waittime, -- Time
	Enum.EasingStyle.Sine, -- EasingStyle
	Enum.EasingDirection.Out, -- EasingDirection
	0, -- RepeatCount (when less than zero the tween will loop indefinitely)
	false, -- Reverses (tween will reverse once reaching its goal)
	0 -- DelayTime
)


function ascend()
	for i,v in pairs(script.Parent:GetDescendants()) do
		if v:IsA("Union") or v:IsA("MeshPart") or v:IsA("Part") or v:IsA("CornerWedgePart") or v:IsA("WedgePart") or v:IsA("TrussPart") then
			local tween = TweenService:Create(v, tweenInfo, {Position = v.Position + Vector3.new(moveXstuds,moveYstuds,moveZstuds)})
			tween:Play()
		end
	end
end

function descend()
	for i,v in pairs(script.Parent:GetDescendants()) do
		if v:IsA("Union") or v:IsA("MeshPart") or v:IsA("Part") or v:IsA("CornerWedgePart") or v:IsA("WedgePart") or v:IsA("TrussPart") then
			local tween = TweenService:Create(v, tweenInfo, {Position = v.Position - Vector3.new(moveXstuds,moveYstuds,moveZstuds)})
			tween:Play()
		end
	end
end

wait(1)
ascend()
wait(waittime)
descend()


Customize the script and you should be able to successfully tween a model.

This was my first tutorial on the DevForum, please let me know if I made any mistakes.

3 Likes

I would somewhat discourage people from doing this. While it might have no effect over short distances and one off movements, this is no different from :SetPrimaryPartCFrame(). Float errors will pile up because there isn’t a static offset between individual parts, meaning parts of your model will eventually move apart far enough to create “cracks”.

There is a good reason welds exist in Roblox and other engines (though under different names). If you really want to avoid using them, you could use the first argument of :GetBoundingBox() as a reference point. Then iterate over each part, store the offset in a table, then reapply the offset each frame. The issue being questionable efficiency and tweens no longer being applicable.

6 Likes

Hi!

Before I begin, I’d like to congratulate you on your first community resources post. I’m glad that you’ve found another way of tweening a model. It is indeed useful to know this method exists for informative purposes, although I unfortunately regret to inform you that it’s not as performant as welding or :SetPrimaryPartCFrame() method.

Creating a lot of tweens is not very performance friendly I’m afraid, compared to creating only one tween when working with welds. The letter can be quite a pain in the neck if we take a manual approach, although it can be done via code, thus being a pretty safe and fast solution.

How to weld parts via script and do the same as above code? Take a look at the following code. It’s commented.

local TweenService = game:GetService("TweenService")

-- Settings -------------
local DURATION = 8

local x_axis = 20
local y_axis = 0
local z_axis = 50
-------------------------

local rootPart = script.Parent.RootPart -- The part we will be tweening (root part).

local tweenInf = TweenInfo.new(
	DURATION, -- tween duration
	Enum.EasingStyle.Linear, 
	Enum.EasingDirection.Out,
	0, -- repeat count (repeat count < 0 --> infinite loop)
	false, -- reverse (tween will reverse the process)
	0 -- delay time
)
-- Only create one tween for selected root part.
local tween = TweenService:Create(
	rootPart,
	tweenInf,
	{CFrame = rootPart.CFrame + Vector3.new(x_axis, y_axis, z_axis)}
)

-- Welding:
local function weldPartsTogether()
	local weld = Instance.new("WeldConstraint") -- Create a new weld.
	local _weld
	--[[
		:GetDescendants() creates a list of all
		descendants in hierarchy of the model.
	]]
	for _, part in pairs(script.Parent:GetDescendants()) do
		if (part:IsA("BasePart")) then
			if (part.Name == "RootPart") then
				part.Anchored = true; continue;
			end
			part.Anchored = false
			
			_weld = weld:Clone()		-- 1) Copy the weld.
			_weld.Part0 = rootPart		-- 2) Connect one end to root part.
			_weld.Part1 = part			-- 3) Connect other end to the new part.
			_weld.Parent = rootPart		-- 4) Place that weld inside the root part.
		end
	end
	--[[
		After the process is complete, remove the
		original as we don't really need it anymore.
	]]
	weld:Destroy()
end

wait(1)
weldPartsTogether()
tween:Play()

(I removed the descend function to save some space.)

What about your code? For informative purposes, here is the above code with some minor corrections.

Use :IsA(“BasePart”) instead of manually checking every so called sub-class.

if (v:IsA("BasePart")) then
-- continue
end

Instead of changing position, rather tween CFrames.

-- Direct:
{CFrame = CFrame.new(values here)}
-- According to world coordinates:
{CFrame = rootPart.CFrame + Vector3.new(coordinates here)}
-- According to local part's axis (orientation):
{CFrame = rootPart.CFrame * CFrame.new(values here)}

I realize that at the time I was writing this answer @nooneisback already answered you. I completely agree. Welds exist for various reasons and are indispensable. Among all their other uses, they help you carry out accurate tweening.

:SetPrimaryPartCFrame() is very useful method as well, although it has a small vulnerability, that being the fact that anchored parts slowly start drifting away from each other over longer use and and fast movements.

I didn’t know about :GetBoundingBox() idea, although, as you already said, it is very questionable approach, which leads us to rather rely on the above two methods, preferably welds in most cases.

2 Likes

Here’s what I was talking about.

local function MoveBy(modelMain,offMove,spdMove)
	local cfOrigin = modelMain:GetBoundingBox()
	local posOrigin = cfOrigin.Position
	cfOrigin = CFrame.new(posOrigin)

	local tabOffsets = {}

	for _,part in ipairs(modelMain:GetDescendants()) do
		if part:IsA("BasePart") then
			tabOffsets[part] = part.CFrame-posOrigin
		end
	end

	local posTarget = posOrigin+offMove
	local dirTarget = posTarget-posOrigin
	local distTarget = dirTarget.Magnitude
	dirTarget = dirTarget.Unit

	local distCurr = 0

	local con; con = game:GetService("RunService").Heartbeat:Connect(function(timeDelta)
		local timeDelta = timeDelta * workspace:GetPhysicsThrottling()/100
		distCurr += spdMove*timeDelta
		if distCurr>=distTarget then
			distCurr = distTarget
			con:Disconnect()
		end

		local posCurr = posOrigin + dirTarget*distCurr
		for part,cfOffset in pairs(tabOffsets) do
			part.CFrame = cfOffset + posCurr
		end
	end)
end

@nooneisback thank you for the code, I now realize what you were talking about before. It’s useful to know, although, as you said, performance aspect is questionable.

We are allocating a new table with all its content, calculating a relatively big amount of values, connecting a unique RunService event etc.

On the other hand, TweenService also runs complex processes behind it. Nevertheless, it is probably best to stick to tweening rather than writing custom functions, because Roblox services will almost always perform better, faster, often as privileged code, and with less memory involved.

I don’t think your code can be notably any better. Perhaps replacing RunService connection with repeat loop would almost negligibly free up some memory:

local RunService = game:GetService("RunService")

repeat
	timeDelta = RunService.Heartbeat:Wait()
	print(timeDelta)
until false

… although again, the difference is almost negligible.

I learned something new, thanks!

I don’t think not using welds or the good old :SetPrimaryPartCFrame() is easier than this?? It isn’t that hard to just get a auto weld script or a weld plugin or memorize this simple function:

local TweenService = game:GetService("TweenService")

function TweenModel(Model,Info,CFrame)
	local CFrameValue = Instance.new("CFrameValue")
	CFrameValue.Value = Model:GetPrimaryPartCFrame()
	
	local Tween = TweenService:Create(CFrameValue,Info,{Value = CFrame})
	Tween:Play()
	
	CFrameValue.Changed:Connect(function()
		Model:SetPrimaryPartCFrame(CFrameValue.Value)
	end)
	
	Tween.Completed:Connect(function()
		CFrameValue:Destroy()
	end)
end

or a even simpler function with welds

local TweenService = game:GetService("TweenService")
local Model = script.Parent --// Assuming the script is inside a model

--// Everything is supposedly unanchored (except for PrimaryPart) and welded to PrimaryPart
function TweenModel(Model,Info,CFrame)
	TweenService:Create(Model.PrimaryPart,TweenInfo.new(10),{CFrame = CFrame}):Play()
end

@RealExoctic yes, this is how :SetPrimaryPartCFrame can be used, although there is an issue with this approach. The method is known to drift values from correct one over active use time.

I found these two great posts by @Corecii!

Post 1: Best way to smoothly move a model? - #3 by reteach

Post 2: Best way to make parts rotate constantly without lag? - #5 by Corecii

This is their gif showing the problem: GIF | Gfycat.

Welds seem to be the best practice. :SetPrimaryPartCFrame() of course does result in similar outcomes. Perhaps replace .Changed connection with a loop:

repeat
	Model:SetPrimaryPartCFrame(CFrameValue.Value)
	RunService.Heartbeat:Wait()
until Tween.Completed

Otherwise, neat code block!

2 Likes