How can I move a model using TweenService

I have a model which can’t have a primary part and I’m wondering how can I move the whole model using TweenService.

I tried using MoveTo(), but it still didn’t work.

local function moveCar(car)
	local dest = workspace.Area.Despawn
	local carToMove = car
	
	local tween = TweenService:Create(car, TweenInfo.new(4.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out), {Position = Vector3.new(workspace.Area.Despawn.Position.X, car.Position.Y, workspace.Area.Despawn.Position.Z)})
end

local function spawnCar()
	local car = chooseCar()
	
	for i, v in pairs(Spawnables:GetChildren()) do
		if v.Name == car then
			local clone = v:Clone()
			local carInfoClone = carInfo:Clone()
			clone.Parent = workspace.SpawnedCars
			
			local newCF = CFrame.new(Vector3.new(workspace.Area:FindFirstChild("Road Spawn").Position.X, workspace.Area:FindFirstChild("Road Spawn").Position.Y + .5, workspace.Area:FindFirstChild("Road Spawn").Position.Z))
			moveCar(clone) 
			
			clone:PivotTo(newCF)
			
			carInfoClone.Parent = clone
			carInfoClone:FindFirstChild("MainFrame").CarName.Text = clone.Name
			carInfoClone:FindFirstChild("MainFrame").CarPrice.Text = "$"..tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Price").Value)
			carInfoClone:FindFirstChild("MainFrame").CarRarity.Text = tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Rarity").Value)
		end
	end
end

You will need to end up choosing a part which will be the primary part - you may not need to set it in the model property though.

The way to move a model using tween service is to have each object (except the primary part) unanchored and welded to the primary part. Then, you would use TweenService to adjust the CFrame (NOT Position) of the primary part. If done correctly, the whole model should move along with the primary part.

You can move a Model with 2 methods (there are more but i use these):

  • Using a PrimaryPart and Welding everything
  • Using PivotTo calls and doing a CFframe Interpolation, you would need to code the easing styles yourself. Its more computive too.
local styles = {
	linear = function(t)
		return 1 - (1 - t)^2
	end,
	easeOutQuad = function(t)
		return 1 - (1 - t)^2
	end,
}

local function moveModel(model, style, targetCFrame, duration)
	local startCFrame = model:GetPivot()
	local startTime = tick()
	local connection

	connection = game:GetService("RunService").Heartbeat:Connect(function()
		local t = (tick() - startTime) / duration
		if t >= 1 then t = 1 end
		local cframe = startCFrame:Lerp(targetCFrame, styles[style](t))
		model:PivotTo(cframe)
		if t >= 1 then
			connection:Disconnect()
		end
	end)

	return connection
end

Usage: moveModel(workspace.Car, "easeOutQuad", targetCframe, 2)

1 Like

I remember finding a good resource on this a while back. Post: Introduction to Tweening Models

					local function TweenModelToCFrame(model: Model, info: TweenInfo, result: CFrame)
						local cframe = Instance.new("CFrameValue") do
							cframe.Value = model:GetPivot()
						end

						cframe:GetPropertyChangedSignal("Value"):Connect(function()
							model:PivotTo(cframe.Value)
						end)

						local tween = TweenService:Create(cframe, info, {Value = result})
						tween:Play()

						tween.Completed:Wait()
						cframe:Destroy()
						return true -- ignore, but can be helpful if you want to wait until the tween is done
					end

Not sure if its me or not, when I used that method and i applied rotation and positioning really fast or in different times, it makes the parts shift in the model. Also here is the devforum post you are looking for:

I tend to go for the simplest solution. Use a CFrame Value instance.

local function tweenModel(model, info, targetCFrame)
    local temp = Instance.new("CFrameValue")
    temp.Value = model:GetPivot()
    local tween = tweenservice:Create(temp, info, {Value = targetCFrame})

    tween.Completed:Once(function() temp:Destroy() end)

    temp.Changed:Connect(function(newCFrame)
        model:PivotTo(newCFrame)
    end)
    return tween
end
3 Likes

This always worked for me.

I implemented the function into your code, so i think this could work.

local tweenService = game:GetService("TweenService")
local info = TweenInfo.new(4.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)

local function tweenModel(model, CF)
	local CFrameValue = Instance.new("CFrameValue")
	CFrameValue.Value = model:GetPrimaryPartCFrame()

	CFrameValue:GetPropertyChangedSignal("Value"):Connect(function()
		model:SetPrimaryPartCFrame(CFrameValue.Value)
	end)
	
	local tween = tweenService:Create(CFrameValue, info, {Value = CF})
	tween:Play()
	
	tween.Completed:Connect(function()
		CFrameValue:Destroy()
	end)
end

local function spawnCar()
	local car = chooseCar()
	
	for i, v in pairs(Spawnables:GetChildren()) do
		if v.Name == car then
			local clone = v:Clone()
			local carInfoClone = carInfo:Clone()
			clone.Parent = workspace.SpawnedCars
			
			local newCF = CFrame.new(Vector3.new(workspace.Area:FindFirstChild("Road Spawn").Position.X, workspace.Area:FindFirstChild("Road Spawn").Position.Y + .5, workspace.Area:FindFirstChild("Road Spawn").Position.Z))
			tweenModel(clone, newCF) 
			
			carInfoClone.Parent = clone
			carInfoClone:FindFirstChild("MainFrame").CarName.Text = clone.Name
			carInfoClone:FindFirstChild("MainFrame").CarPrice.Text = "$"..tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Price").Value)
			carInfoClone:FindFirstChild("MainFrame").CarRarity.Text = tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Rarity").Value)
		end
	end
end

Thank you everyone for the replies!! So for what I understand, I need to make a “hitbox” and weld every part of the model to that hitbox. Then set the primary part to the model and tween it?

This could work but is over engineered and not very efficient as it limits how you can build with the model. PivotTo works like set primary part cframe except without a part and every descendant will follow. Check out @Clxzed and my reply. It should work fine with a cframe value

And how do i set a primary part cframe without a part? I set the cframe to the spawning point or a random part from the model?

PivotTo uses the pivot point, there is no need for a primary part. By default the pivot point is the center of the model. You can edit this manually.

I personally use this approach, since it works right off the bat without any sort modifications nor limits set on the model you’re tweening.
Tho I rather use Heartbeat or such instead of value.Changed. not sure if it makes that big of a difference, but afaik .Changed doesn’t always fire with repeated property changes and imo just feel odd to use.

I tried using this method but the car is standing still. It is not moving, just spawning.

I never played the tween in the function, I return it in case you want to connect any completed signals of your own, so you have to manually play the tween. You can change this in the function if you don’t plan on using the tween instance

What exactly are you trying to achieve? A driveable car, or just some cutscene-type thing? Also, just saying “It didn’t work” doesn’t help. That means you did something wrong, in which case you should show us what you did.

My bad, I am trying to make those “Steal a car” type games, and i want to tween the car to the despawn area.

This is the code i used:

local function tweenModel(model, inf, targetCFrame)
	local temp = Instance.new("CFrameValue")
	temp.Value = model:GetPivot()
	local tween = tweenService:Create(temp, inf, {Value = targetCFrame})

	tween.Completed:Once(function() temp:Destroy() end)

	temp.Changed:Connect(function(newCFrame)
		model:PivotTo(newCFrame)
	end)
	return tween
end

local function spawnCar()
	local car = chooseCar()
	
	for i, v in pairs(Spawnables:GetChildren()) do
		if v.Name == car then
			local clone = v:Clone()
			local carInfoClone = carInfo:Clone()
			clone.Parent = workspace.SpawnedCars
			
			local newCF = CFrame.new(Vector3.new(workspace.Area:FindFirstChild("Road Spawn").Position.X, workspace.Area:FindFirstChild("Road Spawn").Position.Y + .5, workspace.Area:FindFirstChild("Road Spawn").Position.Z))
			tweenModel(clone, info, CFrame.new(Vector3.new(workspace.Area.Despawn.Position.X, workspace.Area:FindFirstChild("Road Spawn").Position.Y + .5, workspace.Area.Despawn.Position.Z)))
			
			clone:PivotTo(newCF)
			
			carInfoClone.Parent = clone
			carInfoClone:FindFirstChild("MainFrame").CarName.Text = clone.Name
			carInfoClone:FindFirstChild("MainFrame").CarPrice.Text = "$"..tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Price").Value)
			carInfoClone:FindFirstChild("MainFrame").CarRarity.Text = tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Rarity").Value)
		end
	end
end

I get no error, just the car standing still.

Change does always fire, infact improper use with immediate signals can crash your game (though this is fixed with the deferred signals update). Using changed also lets you skip an extra read since it’s given in the function (but this is micro optimization so really doesn’t matter). I would actually recommend against using heartbeat though because tweens update after heartbeat and will result in the loop reading the property of the previous frames tween update, this could potentially result in skipping a frame of the tween. However both methods work fine.

1 Like

Update, I tried the welding method and managed to move something… That something is the billboard gui above the car. It somehow moves that but the model is standing still inside the spawn area.

The code i used:

local function moveCar(car)
	if car then
		local primaryPart = car.PrimaryPart
		local tween = tweenService:Create(primaryPart, info, {Position = Vector3.new(workspace.Area.Despawn.Position.X, workspace.Area.Despawn.Position.Y, workspace.Area.Despawn.Position.Z)})
		
		tween:Play()
	end
end

local function spawnCar()
	local car = chooseCar()
	
	for i, v in pairs(Spawnables:GetChildren()) do
		if v.Name == car then
			local clone = v:Clone()
			local carInfoClone = carInfo:Clone()
			clone.Parent = workspace.SpawnedCars
			
			local newCF = CFrame.new(Vector3.new(workspace.Area:FindFirstChild("Road Spawn").Position.X, workspace.Area:FindFirstChild("Road Spawn").Position.Y + .5, workspace.Area:FindFirstChild("Road Spawn").Position.Z))
			
			clone:PivotTo(newCF)
			
			moveCar(clone)
			
			carInfoClone.Parent = clone
			carInfoClone:FindFirstChild("MainFrame").CarName.Text = clone.Name
			carInfoClone:FindFirstChild("MainFrame").CarPrice.Text = "$"..tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Price").Value)
			carInfoClone:FindFirstChild("MainFrame").CarRarity.Text = tostring(clone:FindFirstChild("CarStats"):FindFirstChild("Rarity").Value)
		end
	end
end

You have to unachor everything else except for the primarypart. And Weld everything to the primarypart. Also The solution with cframe values, if the tween doesnt finish and you call it again, it shifts the parts inside the model.