How to smoothly rotate a part with mouse position

I’m creating a Rubiks game, that is basically a stationary camera facing a cube. I want the player to be able to rotate the cube to look around it, I don’t know how to smoothly do that.

I’m moving the cube by changing it’s primary part CFrame. This is simply done by changing the cube’s LookVector to face where the mouse is. This means that every time I start to rotate the cube, it is based on the default orientation and not its current orientation, so I can’t rotate to the backside.

local function rotateCube()
	local connection
	connection = RunService.Heartbeat:Connect(function()
		if not Movement["Mouse"] then connection:Disconnect() end
		print("moving")
		local ray = workspace.CurrentCamera:ScreenPointToRay(Mouse.X, Mouse.Y)
		ray = Ray.new(ray.Origin, ray.Direction*100)
		local part, position = workspace:FindPartOnRayWithIgnoreList(ray, {Cube})
		
		Cube:SetPrimaryPartCFrame(CFrame.new(Cube.PrimaryPart.Position, position))
	end)
end

How can I rotate the cube from its current orientation rather than CFrame’s default orientation?

9 Likes

I see what you’re doing, you want it to be like you’re dragging it left and right.

As you know, a part’s front face will always face towards the CFrame orientation value. That means in your current script it points the front to your current mouse position, and because you’re manipulating the mouse on a 2D plane, it can never rotate 360.

I am not a master of CFrame math and stuff, and not really good at thinking up code while I type, so my fix held together by tears and bandaids would to make the cube weld and unweld to a part which uses your script to face the mouse.

The cube welds to the… Let’s call movePart. The movePart faces your camera finding the proper CFrame orientation and because the cube is welded to it, it moves and also faces it. Then this invisible movePart breaks the weld and orientates itself to the default orientation then re-welds the cube. Now it seems like the cube is being dragged around and its not overly complex. I am not really sure how well this will work with your Rubix Cube if it is multiple parts or whatnot.

Anyway, sorry for the long and confusing reply, but you should get the jist of what you need to do now. Good luck.

1 Like

Hmm, I tried what you suggested and it does help some, but I now think the problem is with how I determine how the cube is to be rotated. Since I’m changing the cubes’ LookVector to face the mouse position, if I release the cube on the right side of the screen and then start dragging again from the left, there’s a quick snap to change sides that’s rather disorienting.

Okay, I figured it out with a fair amount of help from @Evercreeper. I used his idea of welding/unwelding a part so the rotations aren’t limited to the CFrame’s default, but I also had to rework how the cube is rotated to account for the differences in where the mouse is when first dragging.

The code very quickly got more complicated as I had to anchor and unachor the multiple parts of the cube for the welds to work, and calculate the mouse offsets, but the end result is the smooth rotation I was looking for.

-- Custom get mouse position. Used to ignore the cube so it's rotations don't influence the movement
local function getMousePosition(offset)
	offset = offset or Vector2.new(0, 0)
	local ray = Camera:ScreenPointToRay(Mouse.X - offset.X, Mouse.Y - offset.Y)
	ray = Ray.new(ray.Origin, ray.Direction*100)
	local part, position = workspace:FindPartOnRayWithIgnoreList(ray, {Cube, workspace.Baseplate})
	return position
end

local function rotateCube()
	local connection
	local offset = Vector2.new(Mouse.X-Camera.ViewportSize.X/2, Mouse.Y-Camera.ViewportSize.Y/2) -- calculate mouse position from center
	-- Weld parts together
	Cube.MovePart.CFrame = CFrame.new(Cube.MovePart.Position, getMousePosition(offset)) -- reset orientation, but face new offset position for seemless grabbing
	local welds = Instance.new("Folder", Cube)
	for _, pt in pairs(Cube:GetDescendants()) do
		if pt:IsA("BasePart") then
			local weld = Instance.new("WeldConstraint", welds)
			weld.Part0 = pt
			weld.Part1 = Cube.MovePart
			pt.Anchored = false
		end
	end
	-- Move cube
	connection = RunService.Heartbeat:Connect(function()
		if not Movement["Mouse"] then 
			-- movement ended, destroying welds and such
			connection:Disconnect()
			for _, pt in pairs(Cube:GetDescendants()) do
				if pt:IsA("BasePart") then
					pt.Anchored = true
				end
			end
			welds:Destroy()
		end
		local position = getMousePosition(offset)
		Cube.MovePart.CFrame = CFrame.new(Cube.MovePart.Position, position)
	end)
end
1 Like

That looks epic! Can’t wait to play the game, tell me when it’s released :ok_hand:

1 Like

Yikes, I’ll have to update this because I found a major problem with my previous “solution”.

There were a few problems actually. The weld/unweld idea slowly caused inaccuracies, and the parts no longer lined up neatly. Also, my rotating system was really bad and I just didn’t like how it felt. Because of these problems, I actually restarted this project from scratch, and now I’m actually happy with the results.

Because of no longer needing to worry about welding the parts together, the code has also become much more manageable. For the dragging, instead of rotating the part via a vector in 3D space found from a 2D vector, I decided to keep the calculations in 2D, and apply the rotation via CFrame.Angles(). The movement itself is still used with the cube’s PrimaryPart, but what made this work over the original code is that changing the orientation of the PrimaryPart won’t move the rest of the model with it. This means I can constantly rotate the PrimaryPart back to default orientation, so all movements are relative to the cube’s current rotation. Everything is now handled with UserInputService so there’s no need for connections with RunService anymore. Overall, everything in this system is far better.

Here’s the relevant code, commented slightly better than before.

local ButtonsHeld = {} -- Tracks buttons being held. Used to know when dragging
local LastMousePos = nil  -- Used to calculate how far mouse has moved

UserInputService.InputChanged:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	if input.UserInputType == Enum.UserInputType.MouseMovement then -- runs every time mouse is moved
		if ButtonsHeld["MouseButton2"] then -- makes sure player is holding down right click
			local CurrentMousePos = Vector2.new(Mouse.X,Mouse.Y)
			local change = (CurrentMousePos - LastMousePos)/5 -- calculates distance mouse traveled (/5 to lower sensitivity)
			
			-- The angles part is weird here because of how the cube happens to be oriented. The angles may differ for other sections
			Cube:SetPrimaryPartCFrame(Cube:GetPrimaryPartCFrame() * CFrame.Angles(0,math.rad(change.X),-math.rad(change.Y)))
			
			LastMousePos = CurrentMousePos
			-- This line makes everything possible. The PrimaryPart's orientation DOES NOT move the rest of the model with it. 
			Cube.PrimaryPart.Orientation = Vector3.new(0, 0, 0)
		end
	end
end)


UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	if input.UserInputType == Enum.UserInputType.MouseButton2 then -- player starts dragging
		LastMousePos = Vector2.new(Mouse.X,Mouse.Y)
		ButtonsHeld["MouseButton2"] = true
	end
end)

UserInputService.InputEnded:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	if input.UserInputType == Enum.UserInputType.MouseButton2 then -- player stops dragging
		ButtonsHeld["MouseButton2"] = nil
		LastMousePos = nil
	end
end)
25 Likes

this what im looking for im struggling to find a pre made script!!

like im trying to re create the clickable R in super nostalgia zone game hub place!!

this part im struggling at to make the part inside the viewportframe to be clickable!!

1 Like
Cube.PrimaryPart.CFrame = Cube.PrimaryPart.CFrame:ToWorldSpace(CFrame.Angles(0,math.rad(change.X),-math.rad(change.Y)))

wouldn’t be better?

Doing it that way would only effect the PrimaryPart of the model, and not move the rest of the model along with it. SetPrimaryPartCFrame move the model with it, keeping all other parts in their relative CFrame’s to the PrimaryPart’s global CFrame.