New Option for Positioning Objects

This may only be something that would useful for builders, but I know I would find myself using this feature quite often. Right now you have a few options to move objects: you could drag them, move them by typing in position coordinates, or by using the move tool’s arrows, which of course makes you use the mouse. My suggestion is that there’s some sort of way you can use certain keys (possibly the arrow keys) to move parts or models in your game. This would be nice for devs who are constantly on the move and working from laptops, or for people who just have an extremely unsteady hand. Of course this could still follow the move increment feature in studio. So if you have your lock set to one stud, for example, then when you pressed the left arrow it could move by one stud to the left. If it’s set to free, then possibly it move by .01 stud or even .001 stud. Although, using arrow keys could cause problems as they’re also used for moving around studio, perhaps you could provide some suggestions as to how it could work. Again, this mostly would help builders, but I believe it would be a useful feature to see in studio.

7 Likes

This feature is usually called ‘nudge’ fwiw

1 Like

here;s a plugin I started hacking together a few weeks ago and haven’t had time to pickup again. it will increment movements like you’re talking about, but i didn’t add keyboard control (don’t know how yet). it also does rotations but only on the y axis, but making the functions for x and z are the same as y. was also going to make a button to reset rotation to (0,0,0) and a textbox for custom number and a few other things i don’t remember right now. i just don’t have time to finish it anytime real soon, so if anyone would like to take the useful parts and make something pretty with it and publish it, i would be delighted. :slight_smile: btw it works as is, it’s just got a minimal ugly gui and missing a few functions to be Publlsh worthy

local hasLoaded = plugin:GetSetting("pluginHasLoaded")
if not hasLoaded then
	plugin:SetSetting("pluginHasLoaded", true)
end
 

local toolbar = plugin:CreateToolbar("increment Move Selected")
 

local button = toolbar:CreateButton(
	"move by increment",
	"by johnnygadget",
	"http://www.roblox.com/asset/?id=145723965"
)


local gui = Instance.new('ScreenGui',game:GetService('CoreGui'))
gui.Name = 'move increments'
gui.Enabled = false



local Frame = Instance.new("Frame")
local up = Instance.new("TextButton")
local down = Instance.new("TextButton")
local left = Instance.new("TextButton")
local right = Instance.new("TextButton")
local back = Instance.new("TextButton")
local forth = Instance.new("TextButton")
local size1 = Instance.new("TextButton")
local size2 = Instance.new("TextButton")
local size3 = Instance.new("TextButton")
local size4 = Instance.new("TextButton")
local size5 = Instance.new("TextButton")
local size6 = Instance.new("TextButton")
local rotate1 = Instance.new("TextButton")
local rotate01 = Instance.new("TextButton")
local rotate15 = Instance.new("TextButton")
local rotate225 = Instance.new("TextButton")
local rotate5 = Instance.new("TextButton")
local zero = Instance.new("TextButton")



Frame.Parent = gui
Frame.BackgroundColor3 = Color3.new(1, 1, 1)
Frame.Position = UDim2.new(0.0103734434, 0, 0.0223642178, 0)
Frame.Size = UDim2.new(0.149377599, 0, 0.690095842, 0)

up.Name = "up"
up.Parent = Frame
up.BackgroundColor3 = Color3.new(1, 1, 1)
up.Position = UDim2.new(0.37538898, 0, 0.273148149, 0)
up.Rotation = -90
up.Size = UDim2.new(0.20854944, 0, 0.0694444478, 0)
up.Style = Enum.ButtonStyle.Custom
up.Font = Enum.Font.SourceSans
up.FontSize = Enum.FontSize.Size14
up.Text = ">"
up.TextSize = 14
up.TextWrapped = true

down.Name = "down"
down.Parent = Frame
down.BackgroundColor3 = Color3.new(1, 1, 1)
down.Position = UDim2.new(0.37538898, 0, 0.347222209, 0)
down.Rotation = 90
down.Size = UDim2.new(0.20854944, 0, 0.0694444478, 0)
down.Style = Enum.ButtonStyle.Custom
down.Font = Enum.Font.SourceSans
down.FontSize = Enum.FontSize.Size14
down.Text = ">"
down.TextSize = 14
down.TextWrapped = true

left.Name = "left"
left.Parent = Frame
left.BackgroundColor3 = Color3.new(1, 1, 1)
left.Position = UDim2.new(0.23635605, 0, 0.42592594, 0)
left.Rotation = 180
left.Size = UDim2.new(0.20854944, 0, 0.0694444478, 0)
left.Style = Enum.ButtonStyle.Custom
left.Font = Enum.Font.SourceSans
left.FontSize = Enum.FontSize.Size14
left.Text = ">"
left.TextSize = 14
left.TextWrapped = true

right.Name = "right"
right.Parent = Frame
right.BackgroundColor3 = Color3.new(1, 1, 1)
right.Position = UDim2.new(0.50051868, 0, 0.42592594, 0)
right.Size = UDim2.new(0.20854944, 0, 0.0694444478, 0)
right.Style = Enum.ButtonStyle.Custom
right.Font = Enum.Font.SourceSans
right.FontSize = Enum.FontSize.Size14
right.Text = ">"
right.TextSize = 14
right.TextWrapped = true

back.Name = "back"
back.Parent = Frame
back.BackgroundColor3 = Color3.new(1, 1, 1)
back.Position = UDim2.new(0.44490549, 0, 0.578703701, 0)
back.Rotation = 45
back.Size = UDim2.new(0.20854944, 0, 0.0694444478, 0)
back.Style = Enum.ButtonStyle.Custom
back.Font = Enum.Font.SourceSans
back.FontSize = Enum.FontSize.Size14
back.Text = ">"
back.TextSize = 14
back.TextWrapped = true
back.Rotation = 45

forth.Name = "forth"
forth.Parent = Frame
forth.BackgroundColor3 = Color3.new(1, 1, 1)
forth.Position = UDim2.new(0.26416263, 0, 0.523148119, 0)
forth.Rotation = 225
forth.Size = UDim2.new(0.20854944, 0, 0.0694444478, 0)
forth.Style = Enum.ButtonStyle.Custom
forth.Font = Enum.Font.SourceSans
forth.FontSize = Enum.FontSize.Size14
forth.Text = ">"
forth.TextSize = 14
forth.TextWrapped = true
forth.Rotation = 225

size1.Name = "size1"
size1.Parent = Frame
size1.BackgroundColor3 = Color3.new(1, 1, 1)
size1.Position = UDim2.new(0.0417098887, 0, 0.0833333358, 0)
size1.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
size1.Font = Enum.Font.SourceSans
size1.FontSize = Enum.FontSize.Size14
size1.Text = ".01"
size1.TextSize = 14
size1.TextWrapped = true

size2.Name = "size2"
size2.Parent = Frame
size2.BackgroundColor3 = Color3.new(1, 1, 1)
size2.Position = UDim2.new(0.31977582, 0, 0.0833333358, 0)
size2.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
size2.Font = Enum.Font.SourceSans
size2.FontSize = Enum.FontSize.Size14
size2.Text = ".05"
size2.TextSize = 14
size2.TextWrapped = true

size3.Name = "size3"
size3.Parent = Frame
size3.BackgroundColor3 = Color3.new(1, 1, 1)
size3.Position = UDim2.new(0.611745059, 0, 0.0833333358, 0)
size3.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
size3.Font = Enum.Font.SourceSans
size3.FontSize = Enum.FontSize.Size14
size3.Text = ".1"
size3.TextSize = 14
size3.TextWrapped = true

size4.Name = "size4"
size4.Parent = Frame
size4.BackgroundColor3 = Color3.new(1, 1, 1)
size4.Position = UDim2.new(0.0417098887, 0, 0.175925925, 0)
size4.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
size4.Font = Enum.Font.SourceSans
size4.FontSize = Enum.FontSize.Size14
size4.Text = ".2"
size4.TextSize = 14
size4.TextWrapped = true

size5.Name = "size5"
size5.Parent = Frame
size5.BackgroundColor3 = Color3.new(1, 1, 1)
size5.Position = UDim2.new(0.31977582, 0, 0.175925925, 0)
size5.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
size5.Font = Enum.Font.SourceSans
size5.FontSize = Enum.FontSize.Size14
size5.Text = ".5"
size5.TextSize = 14
size5.TextWrapped = true

size6.Name = "size6"
size6.Parent = Frame
size6.BackgroundColor3 = Color3.new(1, 1, 1)
size6.Position = UDim2.new(0.611745059, 0, 0.175925925, 0)
size6.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
size6.Font = Enum.Font.SourceSans
size6.FontSize = Enum.FontSize.Size14
size6.Text = "1"
size6.TextSize = 14
size6.TextWrapped = true

rotate1.Name = "rotate1"
rotate1.Parent = Frame
rotate1.BackgroundColor3 = Color3.new(1, 1, 1)
rotate1.Position = UDim2.new(0.40319559, 0, 0.722222209, 0)
rotate1.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
rotate1.Font = Enum.Font.SourceSans
rotate1.FontSize = Enum.FontSize.Size14
rotate1.Text = "1*"
rotate1.TextSize = 14
rotate1.TextWrapped = true

rotate01.Name = "rotate01"
rotate01.Parent = Frame
rotate01.BackgroundColor3 = Color3.new(1, 1, 1)
rotate01.Position = UDim2.new(0.0834197775, 0, 0.722222209, 0)
rotate01.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
rotate01.Font = Enum.Font.SourceSans
rotate01.FontSize = Enum.FontSize.Size14
rotate01.Text = ".1*"
rotate01.TextSize = 14
rotate01.TextWrapped = true

rotate15.Name = "rotate15"
rotate15.Parent = Frame
rotate15.BackgroundColor3 = Color3.new(1, 1, 1)
rotate15.Position = UDim2.new(0.0973230749, 0, 0.842592597, 0)
rotate15.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
rotate15.Font = Enum.Font.SourceSans
rotate15.FontSize = Enum.FontSize.Size14
rotate15.Text = "15*"
rotate15.TextSize = 14
rotate15.TextWrapped = true

rotate225.Name = "rotate225"
rotate225.Parent = Frame
rotate225.BackgroundColor3 = Color3.new(1, 1, 1)
rotate225.Position = UDim2.new(0.40319559, 0, 0.842592597, 0)
rotate225.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
rotate225.Font = Enum.Font.SourceSans
rotate225.FontSize = Enum.FontSize.Size14
rotate225.Text = "22.5*"
rotate225.TextSize = 14
rotate225.TextWrapped = true

rotate5.Name = "rotate5"
rotate5.Parent = Frame
rotate5.BackgroundColor3 = Color3.new(1, 1, 1)
rotate5.Position = UDim2.new(0.70906812, 0, 0.722222209, 0)
rotate5.Size = UDim2.new(0.23635605, 0, 0.0740740746, 0)
rotate5.Font = Enum.Font.SourceSans
rotate5.FontSize = Enum.FontSize.Size14
rotate5.Text = "5*"
rotate5.TextSize = 14
rotate5.TextWrapped = true

zero.Name = "zero"
zero.Parent = Frame
zero.BackgroundColor3 = Color3.new(0.72549, 0.72549, 0.72549)
zero.Position = UDim2.new(0.36148572, 0, 0.944444418, 0)
zero.Size = UDim2.new(0.29196921, 0, 0.0416666679, 0)
zero.Font = Enum.Font.SourceSans
zero.FontSize = Enum.FontSize.Size14
zero.Text = "global"
zero.TextSize = 14
zero.TextWrapped = true




local on = false
local moveamount = .05
local global = true


 

local function rotatepartglobal(part,deg)
	if part then
		part.CFrame = part.CFrame * CFrame.Angles(0,math.rad(deg),0) 
	end
end

local function rotatepartlocal(part,deg)
	if part then
		part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.Angles(0,math.rad(deg),0))
	end
end

local function rotatemodelglobal(model,deg)
	if model then
		model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.Angles(0,math.rad(deg),0))
	end
end

local function rotatemodellocal(model,deg)
	if model then
		model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.Angles(0,math.rad(deg),0)))
	end
end

local function movepartglobal(part,direction)
	if part then
		if direction == 'up' then
			part.CFrame = part.CFrame * CFrame.new(0,moveamount,0) 
		elseif direction == 'down' then
			part.CFrame = part.CFrame * CFrame.new(0,-moveamount,0) 
		elseif direction == 'left' then
			part.CFrame = part.CFrame * CFrame.new(-moveamount,0,0) 
		elseif direction == 'right' then
			part.CFrame = part.CFrame * CFrame.new(moveamount,0,0) 
		elseif direction == 'back' then
			part.CFrame = part.CFrame * CFrame.new(0,0,-moveamount) 
		elseif direction == 'forth' then
			part.CFrame = part.CFrame * CFrame.new(0,0,moveamount) 
		end
	end
end

local function movepartlocal(part,direction)
	if part then
		if direction == 'up' then
			part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.new(0,moveamount,0))
		elseif direction == 'down' then
			part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.new(0,-moveamount,0)) 
		elseif direction == 'left' then
			part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.new(-moveamount,0,0)) 
		elseif direction == 'right' then
			part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.new(moveamount,0,0))
		elseif direction == 'back' then
			part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.new(0,0,-moveamount))
		elseif direction == 'forth' then
			part.CFrame = part.CFrame:toObjectSpace(part.CFrame * CFrame.new(0,0,moveamount))
		end
	end
end

local function movemodelglobal(model,direction)
	
	if model then
		local pp = model.PrimaryPart
		if not pp then 
			print('Model has no primary part') 
			return 
		else
			if direction == 'up' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.new(0,moveamount,0))
			elseif direction == 'down' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.new(0,-moveamount,0)) 
			elseif direction == 'left' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.new(-moveamount,0,0)) 
			elseif direction == 'right' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.new(moveamount,0,0))
			elseif direction == 'back' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.new(0,0,-moveamount))
			elseif direction == 'forth' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame * CFrame.new(0,0,moveamount))
			end
		end
	end
end

local function movemodellocal(model,direction)
	if model then
		local pp = model.PrimaryPart
		if not pp then 
			print('Model has no primary part') 
			return 
		else
			if direction == 'up' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.new(0,moveamount,0)))
			elseif direction == 'down' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.new(0,-moveamount,0)))
			elseif direction == 'left' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.new(-moveamount,0,0)))
			elseif direction == 'right' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.new(moveamount,0,0)))
			elseif direction == 'back' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.new(0,0,moveamount)))
			elseif direction == 'forth' then
				model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:toObjectSpace(model.PrimaryPart.CFrame * CFrame.new(0,0,-moveamount)))
			end
		end
	end
end





local function checktyperotate(thing,direction)
	if thing == nil then return end
	for i, v in pairs(thing) do
		if v:IsA('Part') then
			if global == true then
				rotatepartglobal(v,direction)
			else
				rotatepartlocal(v,direction)
			end
		elseif v:IsA('Model') then
			if global == true then
				rotatemodelglobal(v,direction)
			else
				rotatemodellocal(v,direction)
			end
		end
	end
end




local function checktype(thing,direction)
	if thing == nil then return end
	for i, v in pairs(thing) do
		if v:IsA('Part') then
			if global == true then
				movepartglobal(v,direction)
			else
				movepartlocal(v,direction)
			end
		elseif v:IsA('Model') then
			if global == true then
				movemodelglobal(v,direction)
			else
				movemodellocal(v,direction)
			end
		end
	end
end


local function rotateprocess(deg)
	local p = game.Selection:Get()
	if p then
		for i, v in pairs(p) do
			checktyperotate(p,deg)
		end
	end
end

local function moveprocess(way)
	local p = game.Selection:Get()
	if p then
		for i, v in pairs(p) do
			checktype(p,way)
		end
	end
end


zero.MouseButton1Down:connect(function()  
	if global == true then
		global = false
		zero.Text = 'local'
	else
		global = true
		zero.Text = 'global'
	end
end)

up.MouseButton1Down:connect(function()  
	moveprocess('up')
end)

down.MouseButton1Down:connect(function()  
	moveprocess('down')
end)

left.MouseButton1Down:connect(function()  
	moveprocess('left')
end)

right.MouseButton1Down:connect(function()  
	moveprocess('right')
end)

back.MouseButton1Down:connect(function()  
	moveprocess('back')
end)

forth.MouseButton1Down:connect(function()  
	moveprocess('forth')
end)

size1.MouseButton1Down:connect(function()  
	moveamount = .01
end)
size2.MouseButton1Down:connect(function()  
	moveamount = .05
end)
size3.MouseButton1Down:connect(function()  
	moveamount = .1
end)
size4.MouseButton1Down:connect(function()  
	moveamount = .2
end)
size5.MouseButton1Down:connect(function()  
	moveamount = .5
end)
size6.MouseButton1Down:connect(function()  
	moveamount = 1
end)

rotate01.MouseButton1Down:connect(function()  
	rotateprocess(.01)
end)

rotate1.MouseButton1Down:connect(function()  
	rotateprocess(1)
end)
rotate5.MouseButton1Down:connect(function()  
	rotateprocess(5)
end)
rotate15.MouseButton1Down:connect(function()  
	rotateprocess(15)
end)
rotate225.MouseButton1Down:connect(function()  
	rotateprocess(22.5)
end)

button.Click:connect(function()

	plugin:Activate(false)
	gui.Enabled = true

end)
2 Likes

here’s the plugin. just move to roblox plugins folder
incrementer.rbxmx (15.1 KB)

I honestly think that the there should be an option to activate a movement sequence and move objects according to your camera angle, much like the way blender works. Naturally be given the options to use the axis keys on your keyboard (X, Y, Z) to lock to a specific axis.

Another good idea for the drag tool is to have an option to move an object to a requested direction according to your increment value by simply clicking an arrow, allowing you to do extremely precise and fine movements very easily.

If there was a way to interact with the current given tools using plugins, I would have made a feature like that long ago.
Unless it is possible, but I’m just not aware of it… .-.

4 Likes

this is what the code i posted will do, i just haven’t added the option for user value

If you urgently need this functionality, you can use Building Tools by F3X: https://www.roblox.com/library/144950355/Building-Tools-by-F3X-Plugin

You can drag the parts roughly to where you want them, then perfectly align them with others using snap points (i.e. corners, edge midpoints, face centroids). Just hold R to see & drag snap points in the Move or Resize Tools—gets rid of the need to eyeball :tada:

Snapping in the Move Tool:
https://i.gyazo.com/5af8aff72876e15299981bfe9083e0a6.gifv

Snapping in the Resize Tool:
https://i.gyazo.com/a5bb51950a6c11507193f74aa2ed083f.gifv

You can also use nudging (which applies a movement, resize, or rotation in the specified direction by the current increment), which is particularly helpful when dealing with very precise, tiny increments, or when you don’t want to use your mouse:

https://i.gyazo.com/159e2ff412e26d5fa428aab8270e83eb.gifv

You can use this feature through your keyboard’s right-side number pad:
8 & 2 are up and down along the Y axis, respectively
4 & 6 are left and right along the X axis, respectively
1 & 9 are back and forth along the Z axis, respectively

Also if you hate using the mouse at all, it’s probably worth noting that you can type a different increment by simply pressing the - (dash) key, or switch between axes modes by simply pressing the Enter key :open_hands: Hopefully this is helpful!

7 Likes

That’s a really useful plugin. I’ve used it before, and I do really like it. The only issue, for me personally, is the inability to use hotkeys to change the Axes and Increment values. By that I mean a given increment. Currently, I use Alt-1 to 3 to change between a 1 stud, 0.2 studs and no stud increment.

While hotkeys would make it a lot faster to switch between increments, the main issue is having to write down the increment value every time. Would be a lot more convenient to also have given increment values that can be set without having the need to re-write them every time. Believe me, I change increments a lot, and moving my hands from mouse to keyboard and back very frequently will sore my arms. :stuck_out_tongue:

1 Like

I made a plugin years ago to do this (before plugins were around). I know some people still use it. A couple years ago, I ported it to be an actual plugin.

It can move individual parts, groups of selected parts, or entire models. I still use it semi-frequently when I need to make little adjustments.

6496d18471ea9750ddd6b3dd5fba7cb82f7abdfa.png

2 Likes

Increment presets feel clunky to me compared to hotkeys for doubling/halving the increment.

The keys for that in studio build suite are 2 & 3.