How to make placement not go beyond boundaries?


function PlacementController:GetMousePoint(ignoreList)
    if mouse.Target ~= nil then
		local unitRay = camera:ScreenPointToRay(mouse.x, mouse.y)
		
		local raycastParams = RaycastParams.new()
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		raycastParams.FilterDescendantsInstances = ignoreList
		
		local ray = workspace:Raycast(unitRay.Origin, unitRay.Direction * range, raycastParams)
		
		if ray then
			return ray.Position, ray.Instance, ray.Normal
		end
	end
end
	
function Snap(position)
    PosX = math.floor(position.X / Grid + 0.5) * Grid -- Snaps
    PosY = position.Y -- Change this to your Y
    PosZ = math.floor(position.Z / Grid + 0.5) * Grid 
end
function PlacementController:Start()
    local ignoreList = {char, Sign}
    RunService.RenderStepped:Connect(function()
        local position, target, normal = self:GetMousePoint(ignoreList)
        
        if target == game.Workspace.Part  then
            Snap(position)
            Sign:SetPrimaryPartCFrame(Sign.PrimaryPart.CFrame:Lerp(CFrame.new(PosX,PosY, PosZ) * CFrame.new(0, 2, 0), 0.5))
        end
    end)

Placement is going beyond boundaries.

It seems like problem is because a position of a part represent a imaginary dot inside part’s center, how can I explain this is:

So you may want to use part.CFrame.lookVector and rightVector, they will return the position value of faces which is like this:

I am terribly sorry about my drawing, I’ve never been good at drawing.
For further information please check the API:
https://developer.roblox.com/en-us/api-reference/datatype/CFrame

I am confused how this will work. Cause you are just showing the problem I am having.

Use align position and create boxs which only it can collide with. Since you seem to be on a 2d grid you can use a aabb system to glitch it back in bounds

You could probably use math.clamp to solve this.

I am not a math person so can you explain how I can use math.clamp for this?

You have to keep it placementPart.size.X/2 away from either side. You should pretend that the baseplate is its size - placementPart.Size and then go from there. If you need it to handle rotated objects its a bit more tricky and you would need to essentially determine the smallest axis aligned box that would fit the whole thing for it first then do the same thing with that box.

  1. get the size of the placement pad
  2. get the size of the part
  3. pretend the placement pad size is placementPad.Size - part.Size
  4. check that the mouse point is withing the bounds of the placement parts pretendSize

Note that this would only work on a rectangular baseplate. Any other baseplate shapes would need a different method or an adjustment to this one.


math.clamp(Part.Size.X/2, Sign.PrimaryPart.X, Part.Size.X - Sign.PrimaryPart.X)

Am I doing the right? Part = Pad and Sign = the placement part

Take the X and Z position of where the part is supposed to go, then clamp those values.

PosX = math.clamp(Position.X, -(base.Position.X - (base.Size.X / 2)), (base.Position.X - (base.Size.X / 2))
PosZ = math.clamp(Position.Z, -(base.Position.Z - (base.Size.Z / 2)), (base.Position.Z - (base.Size.Z / 2))

Then set where the part moves on the X and Z axis to PosX and PosZ. Let me know if this works.

That is essentially it I think, it just does not factor in the size of the piece that is being placed. It will clamp it, but it will allow the middle of the piece to go all the way to the bounds letting the edges go past. To fix that you have to pretend that the base.Size = base.Size - placementPart.Size. Then divide that by 2

Expected ‘)’ (to close ‘(’ at line 47), got 'PosX

 PosX = math.clamp(Sign.PrimaryPart.X, -(Part.Position.X - (Part.Size.X / 2)), (Part.Position.X - (Part.Size.X / 2)
    PosX = math.clamp(Sign.PrimaryPart.Z, -(Part.Position.Z - (Part.Size.Z / 2)), (Part.Position.Z - (Part.Size.Z / 2)
    PosY = position.Y -- Change this to your Y

They simply forgot to close the last parenthesis. This is a small modification to their code that fixes the out of bounds error you would still get by factoring in the size of the part you are placing in the variable

local fakeSize = base.Size - placementPart.Size
PosX = math.clamp(Position.X, -(base.Position.X - (fakeSize / 2)), (base.Position.X - (fakeSize / 2)))
PosZ = math.clamp(Position.Z, -(base.Position.Z - (fakeSize / 2)), (base.Position.Z - (fakeSize / 2)))

 local fakeSize = Part.Size - Sign.PrimaryPart.Size
    PosX = math.clamp(Sign.PrimaryPart.Position.X, -(Part.Position.X - (fakeSize / 2)), (Part.Position.X - (fakeSize / 2)))
    PosZ = math.clamp(Sign.PrimaryPart.Position.Z, -(Part.Position.Z - (fakeSize / 2)), (Part.Position.Z - (fakeSize / 2)))

Vector 3 expected got number…

Here is a simplified version of the code, tell me if it throws another error. One thing to note is that I’m assuming the Sign.PrimaryPart.Size covers the entirety of the sign, otherwise this won’t work.

local fakeSize = (base.Size - placementPart.Size)/2
PosX = base.Position.X + math.clamp(Position.X-base.Position.X, -fakeSize.X, fakeSize.X)
PosZ = base.Position.Z + math.clamp(Position.Z-base.Position.Z, -fakeSize.Z, fakeSize.Z)

If it does error please copy the line it points to as having the error as well so I know exactly where it is.

edit: I did something dumb in the code, hopefully you haven’t copied it until now


Won’t move at all


local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Folder = ReplicatedStorage.GameClient
local Models = Folder.Models
local char = player.Character or player.CharacterAdded:Wait()
local Sign = Models.Sign:Clone()
local Grid = 0.5
local PosX
local PosY
local PosZ
local Part = game.Workspace.Part
Sign.Parent = game.Workspace
-- Customize:
local range = 250 -- Mouse object move range


function PlacementController:GetMousePoint(ignoreList)
    if mouse.Target ~= nil then
		local unitRay = camera:ScreenPointToRay(mouse.x, mouse.y)
		
		local raycastParams = RaycastParams.new()
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		raycastParams.FilterDescendantsInstances = ignoreList
		
		local ray = workspace:Raycast(unitRay.Origin, unitRay.Direction * range, raycastParams)
		
		if ray then
			return ray.Position, ray.Instance, ray.Normal
		end
	end
end
local base = Part
function Snap(position)

local fakeSize = (base.Size - Sign.PrimaryPart.Size)/2
PosX = base.Position.X + math.clamp(Sign.PrimaryPart.Position.X, -fakeSize.X, fakeSize.X)
PosZ = base.Position.Z + math.clamp(Sign.PrimaryPart.Position.Z, -fakeSize.Z, fakeSize.Z)
    PosY = position.Y -- Change this to your Y
    
end
function PlacementController:Start()
    local ignoreList = {char, Sign}
    RunService.RenderStepped:Connect(function()
        local position, target, normal = self:GetMousePoint(ignoreList)
        
        if target == game.Workspace.Part  then
            
            Snap(position)
            if PosX >= -99.5 then
                Sign:SetPrimaryPartCFrame(Sign.PrimaryPart.CFrame:Lerp(CFrame.new(PosX,PosY, PosZ) * CFrame.new(0, 2, 0), 0.5))
            end
            
        end
    end)
end


PosX = base.Position.X + math.clamp(Sign.PrimaryPart.Position.X, -fakeSize.X, fakeSize.X)

That first argument is supposed to be the target position, like the mouse position. Probably the pos passed into that function

PosX = base.Position.X + math.clamp(position, -fakeSize.X, fakeSize.X)

Same applies to PosZ
which appears to be the problem

Still won’t move. It’s stuck in the corner but somehow fits perfectly in the corner.

Can you show me the updated code? also I made a mistake you might have caught where I used position instead of position.X

PosX = base.Position.X + math.clamp(position.X, -fakeSize.X, fakeSize.X)

local fakeSize = (base.Size - Sign.PrimaryPart.Size)/2
PosX = base.Position.X + math.clamp(Sign.PrimaryPart.Position.X, -fakeSize.X, fakeSize.X)
PosZ = base.Position.Z + math.clamp(Sign.PrimaryPart.Position.Z, -fakeSize.Z, fakeSize.Z)
PosY = position.Y -- Change this to your Y
    ```
local fakeSize = (base.Size - Sign.PrimaryPart.Size)/2
PosX = base.Position.X + math.clamp(position.X, -fakeSize.X, fakeSize.X)
PosZ = base.Position.Z + math.clamp(position.Z, -fakeSize.Z, fakeSize.Z)
PosY = position.Y -- Change this to your Y