Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Services
- local Players = game:GetService("Players")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local UserInputService = game:GetService("UserInputService")
- local RunService = game:GetService("RunService")
- local TweenService = game:GetService("TweenService")
- local Debris = game:GetService("Debris")
- -- References to model folders used as tools (Arrows, Balls, Sliders)
- local models = ReplicatedStorage:WaitForChild("Models")
- local arrowsFolder = models:WaitForChild("Arrows")
- local sizeBallsFolder = models:WaitForChild("SizeBalls")
- local SlidersFolder = models:WaitForChild("Sliders")
- -- Player and camera references
- local player = Players.LocalPlayer
- local camera = workspace.CurrentCamera
- -- State variables
- local currentPart = nil -- The part currently being edited
- local isEditing = false -- Whether the player is in edit mode
- local mouseHeartbeatConnection -- Connection used for continuously following the mouse
- -- Tool instances and their drag connections
- local arrows = {}
- local balls = {}
- local sliders = {}
- local arrowDragConnections = {}
- local ballDragConnections = {}
- local sliderDragConnections = {}
- -- Removes existing tools from workspace and disconnects events, except for the currently active tool
- local function cleanupOldTools(currentTool)
- if currentTool ~= arrows then
- for i, arrow in pairs(arrows) do
- if arrow and arrow.Parent then
- arrow:Destroy()
- end
- end
- arrows = {}
- for i, connection in pairs(arrowDragConnections) do
- connection:Disconnect()
- end
- arrowDragConnections = {}
- end
- if currentTool ~= balls then
- for i, ball in pairs(balls) do
- if ball and ball.Parent then
- ball:Destroy()
- end
- end
- balls = {}
- for i, connection in pairs(ballDragConnections) do
- connection:Disconnect()
- end
- ballDragConnections = {}
- end
- if currentTool ~= sliders then
- for i, slider in pairs(sliders) do
- if slider and slider.Parent then
- slider:Destroy()
- end
- end
- sliders = {}
- for i, connection in pairs(sliderDragConnections) do
- connection:Disconnect()
- end
- sliderDragConnections = {}
- end
- end
- -- Makes the currentPart follow the mouse by raycasting from the screen to the world
- local function followMouse()
- cleanupOldTools()
- if mouseHeartbeatConnection then
- mouseHeartbeatConnection:Disconnect()
- end
- mouseHeartbeatConnection = RunService.Heartbeat:Connect(function()
- if not isEditing or not currentPart then return end
- local mousePos = UserInputService:GetMouseLocation()
- local unitRay = camera:ViewportPointToRay(mousePos.X, mousePos.Y)
- local raycastParams = RaycastParams.new()
- raycastParams.FilterType = Enum.RaycastFilterType.Exclude
- raycastParams.FilterDescendantsInstances = {currentPart}
- local result = workspace:Raycast(unitRay.Origin, unitRay.Direction * 10000, raycastParams)
- if result then
- local surfaceNormal = result.Normal
- local partSize = currentPart.Size
- local offsetDistance = math.max(
- math.abs(surfaceNormal.X) * partSize.X,
- math.abs(surfaceNormal.Y) * partSize.Y,
- math.abs(surfaceNormal.Z) * partSize.Z
- ) / 2
- -- Offset to keep the part slightly above the surface
- currentPart.Position = result.Position + (surfaceNormal * offsetDistance)
- end
- end)
- end
- -- Handle input from keyboard and mouse
- UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
- if gameProcessedEvent then return end
- -- Press F to enter editing mode and create a transparent part
- if input.KeyCode == Enum.KeyCode.F then
- isEditing = true
- local part = Instance.new("Part")
- part.Parent = workspace
- part.Anchored = true
- part.Transparency = 0.5
- part.CanCollide = false
- currentPart = part
- if mouseHeartbeatConnection then
- mouseHeartbeatConnection:Disconnect()
- end
- -- Make the part follow the mouse cursor
- mouseHeartbeatConnection = RunService.Heartbeat:Connect(function()
- if not isEditing or not currentPart then return end
- local mousePos = UserInputService:GetMouseLocation()
- local unitRay = camera:ViewportPointToRay(mousePos.X, mousePos.Y)
- local raycastParams = RaycastParams.new()
- raycastParams.FilterType = Enum.RaycastFilterType.Exclude
- raycastParams.FilterDescendantsInstances = {part}
- local result = workspace:Raycast(unitRay.Origin, unitRay.Direction * 10000, raycastParams)
- if result then
- local surfaceNormal = result.Normal
- local partSize = currentPart.Size
- local offsetDistance = math.max(
- math.abs(surfaceNormal.X) * partSize.X,
- math.abs(surfaceNormal.Y) * partSize.Y,
- math.abs(surfaceNormal.Z) * partSize.Z
- ) / 2
- currentPart.Position = result.Position + (surfaceNormal * offsetDistance)
- end
- end)
- end
- -- Press 2 to spawn arrows for moving the part
- if input.KeyCode == Enum.KeyCode.Two then
- if not isEditing or not currentPart then return end
- cleanupOldTools(arrows)
- if mouseHeartbeatConnection then
- mouseHeartbeatConnection:Disconnect()
- end
- -- Clone arrow models from ReplicatedStorage
- for i, v in pairs(arrowsFolder:GetChildren()) do
- local arrowClone = v:Clone()
- arrowClone.Parent = workspace
- table.insert(arrows, arrowClone)
- end
- -- Position arrows around the part and connect drag events
- for i, arrow in pairs(arrows) do
- local arrowAxis = arrow:GetAttribute("Axis")
- local axisGap = Vector3.new(0, 0, 0)
- if arrowAxis == "X" then axisGap = Vector3.new(-4, 0, 0)
- elseif arrowAxis == "Y" then axisGap = Vector3.new(0, 10, 0)
- elseif arrowAxis == "Z" then axisGap = Vector3.new(0, 0, 7)
- end
- local offset = (currentPart.Size / 2) * axisGap
- arrow.Position = currentPart.Position + offset
- -- Setup drag behavior
- local dragDetector = arrow:FindFirstChild("DragDetector")
- if dragDetector then
- local dragStartConnection = dragDetector.DragStart:Connect(function()
- arrow:SetAttribute("InitialOffset", arrow.Position - currentPart.Position)
- end)
- local dragConnection = dragDetector.DragContinue:Connect(function()
- if currentPart then
- local initialOffset = arrow:GetAttribute("InitialOffset")
- if initialOffset then
- currentPart.Position = arrow.Position - initialOffset
- -- Update other arrows to follow the new position
- for j, otherArrow in pairs(arrows) do
- if otherArrow ~= arrow and otherArrow.Parent then
- local otherAxis = otherArrow:GetAttribute("Axis")
- local otherAxisGap = Vector3.new(0, 0, 0)
- if otherAxis == "X" then otherAxisGap = Vector3.new(-4, 0, 0)
- elseif otherAxis == "Y" then otherAxisGap = Vector3.new(0, 10, 0)
- elseif otherAxis == "Z" then otherAxisGap = Vector3.new(0, 0, 7)
- end
- local otherOffset = (currentPart.Size / 2) * otherAxisGap
- otherArrow.Position = currentPart.Position + otherOffset
- end
- end
- end
- end
- end)
- table.insert(arrowDragConnections, dragStartConnection)
- table.insert(arrowDragConnections, dragConnection)
- end
- end
- end
- -- Press 1 to make the part follow the mouse again
- if input.KeyCode == Enum.KeyCode.One then
- if not isEditing or not currentPart then return end
- followMouse()
- end
- -- Press 3 to spawn balls to resize the part
- if input.KeyCode == Enum.KeyCode.Three then
- if not isEditing or not currentPart then return end
- cleanupOldTools(balls)
- if mouseHeartbeatConnection then
- mouseHeartbeatConnection:Disconnect()
- end
- -- Clone and position resizing balls
- for i, v in pairs(sizeBallsFolder:GetChildren()) do
- local ballClone = v:Clone()
- ballClone.Parent = workspace
- table.insert(balls, ballClone)
- end
- for i, ball in pairs(balls) do
- local ballAxis = ball:GetAttribute("Axis")
- local axisGap = Vector3.new(0, 0, 0)
- if ballAxis == "X" then axisGap = Vector3.new(-2, 0, 0)
- elseif ballAxis == "Y" then axisGap = Vector3.new(0, 5, 0)
- elseif ballAxis == "Z" then axisGap = Vector3.new(0, 0, 3.5)
- end
- local offset = (currentPart.Size / 3) * axisGap
- ball.Position = currentPart.Position + offset
- local dragDetector = ball:FindFirstChild("DragDetector")
- if dragDetector then
- local dragStartConnection = dragDetector.DragStart:Connect(function()
- ball:SetAttribute("InitialBallPosition", ball.Position)
- ball:SetAttribute("InitialPartSize", currentPart.Size)
- ball:SetAttribute("InitialPartPosition", currentPart.Position)
- end)
- local dragConnection = dragDetector.DragContinue:Connect(function()
- if currentPart then
- local initialBallPos = ball:GetAttribute("InitialBallPosition")
- local initialPartSize = ball:GetAttribute("InitialPartSize")
- local initialPartPos = ball:GetAttribute("InitialPartPosition")
- if initialBallPos and initialPartSize and initialPartPos then
- local deltaPos = ball.Position - initialBallPos
- local newSize = initialPartSize
- if ballAxis == "X" then
- local delta = math.abs(deltaPos.X) * 2
- if deltaPos.X > 0 then delta = -delta end
- newSize = Vector3.new(math.max(0.1, initialPartSize.X + delta), initialPartSize.Y, initialPartSize.Z)
- elseif ballAxis == "Y" then
- local delta = math.abs(deltaPos.Y) * 2
- if deltaPos.Y < 0 then delta = -delta end
- newSize = Vector3.new(initialPartSize.X, math.max(0.1, initialPartSize.Y + delta), initialPartSize.Z)
- elseif ballAxis == "Z" then
- local delta = math.abs(deltaPos.Z) * 2
- if deltaPos.Z < 0 then delta = -delta end
- newSize = Vector3.new(initialPartSize.X, initialPartSize.Y, math.max(0.1, initialPartSize.Z + delta))
- end
- currentPart.Size = newSize
- end
- end
- end)
- table.insert(ballDragConnections, dragStartConnection)
- table.insert(ballDragConnections, dragConnection)
- end
- end
- end
- -- Press 4 to spawn sliders to rotate the part
- if input.KeyCode == Enum.KeyCode.Four then
- if not isEditing or not currentPart then return end
- cleanupOldTools(sliders)
- if mouseHeartbeatConnection then
- mouseHeartbeatConnection:Disconnect()
- end
- for i, v in pairs(SlidersFolder:GetChildren()) do
- local circleClone = v:Clone()
- circleClone.Parent = workspace
- table.insert(sliders, circleClone)
- end
- for i, slider in pairs(sliders) do
- local axis = slider:GetAttribute("Axis")
- local axisGap = Vector3.new(0, 0, 0)
- if axis == "X" then axisGap = Vector3.new(-2, 0, -4)
- elseif axis == "Y" then axisGap = Vector3.new(1, 0, 3)
- elseif axis == "Z" then axisGap = Vector3.new(-2, 8, 2.5)
- end
- local offset = (currentPart.Size / 3) * axisGap
- slider.Position = currentPart.Position + offset
- local dragDetector = slider:FindFirstChild("DragDetector")
- if dragDetector then
- local dragStartConnection = dragDetector.DragStart:Connect(function()
- slider:SetAttribute("InitialPartRotation", currentPart.CFrame)
- slider:SetAttribute("InitialSliderPosition", slider.Position)
- slider:SetAttribute("LastMousePosition", UserInputService:GetMouseLocation())
- end)
- local dragConnection = dragDetector.DragContinue:Connect(function()
- if currentPart then
- local initialRotation = slider:GetAttribute("InitialPartRotation")
- local lastMousePos = slider:GetAttribute("LastMousePosition")
- if initialRotation and lastMousePos then
- local currentMousePos = UserInputService:GetMouseLocation()
- local mouseDelta = currentMousePos - lastMousePos
- local rotationSensitivity = 0.01
- local rotationAmount = 0
- if axis == "X" then
- rotationAmount = -mouseDelta.Y * rotationSensitivity
- currentPart.CFrame = initialRotation * CFrame.Angles(rotationAmount, 0, 0)
- elseif axis == "Y" then
- rotationAmount = mouseDelta.X * rotationSensitivity
- currentPart.CFrame = initialRotation * CFrame.Angles(0, rotationAmount, 0)
- elseif axis == "Z" then
- rotationAmount = (mouseDelta.X + mouseDelta.Y) * rotationSensitivity * 0.5
- currentPart.CFrame = initialRotation * CFrame.Angles(0, 0, rotationAmount)
- end
- end
- end
- end)
- table.insert(sliderDragConnections, dragStartConnection)
- table.insert(sliderDragConnections, dragConnection)
- end
- end
- end
- -- Left mouse button to finalize and place the part
- if input.UserInputType == Enum.UserInputType.MouseButton1 then
- if currentPart then
- isEditing = false
- currentPart.Transparency = 0
- currentPart.CanCollide = true
- cleanupOldTools()
- if mouseHeartbeatConnection then
- mouseHeartbeatConnection:Disconnect()
- end
- currentPart = nil
- end
- end
- end)
Advertisement
Add Comment
Please, Sign In to add comment