You can do this relatively easily with raycasting. I wrote a response to someone about a week ago but I can’t find it rn.
Basically what you’d do is cast a ray from the mouse and use the normal returned from the ray to find the block’s “goal” position:
Here’s a very bare-bones block placement system:
local players = game:GetService('Players')
local userInputService = game:GetService('UserInputService')
local block = workspace.Block
local displayPart = block:Clone()
displayPart.Parent = workspace
displayPart.CanCollide = false
displayPart.Transparency = 0.5
local localPlayer = players.LocalPlayer
local mouse = localPlayer:GetMouse()
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.FilterDescendantsInstances = {localPlayer.Character; displayPart}
local function updateDisplayBlock()
local mouseUnitRay = mouse.UnitRay
local ray = workspace:Raycast(mouseUnitRay.Origin, mouseUnitRay.Direction * 1000, raycastParams)
if ray then
local normal: Vector3 = ray.Normal -- this returns the mouse hit's face
local hitPart: BasePart = ray.Instance -- this is the part that our mouse is touching
local displayPosition = hitPart.Position + normal * displayPart.Size -- this is where the magic happens
displayPart.Position = displayPosition
end
end
userInputService.InputBegan:Connect(function(inputObject: InputObject, gameProcessedEvent)
if gameProcessedEvent then
return
end
if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
local newDisplayPart = displayPart:Clone()
newDisplayPart.Parent = workspace
newDisplayPart.Transparency = 0
newDisplayPart.CanCollide = true
updateDisplayBlock()
end
end)
mouse.Move:Connect(updateDisplayBlock)