Issue with multi tiled part placement

I am trying to make a part placement system for my custom tycoon game, and i am having the issue of only the base part moving even though they are linked together using weld constraints. Ths rises issues with multi part placement overlapping the second tile.

whole code (local script):

local remoteEvent = workspace.RemoteEvents.PlacePart
local gridSize = 4
local currentRotation = 0
local Clone
local isValidPlacement = true

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local screenGui = script.Parent.Parent.Parent

-- Function to snap to the grid
local function snapToGrid(position, gridSize, partSize)
	return Vector3.new(
		math.floor(position.X / gridSize + 0.5) * gridSize,
		math.floor(position.Y / gridSize) + partSize.Y / 2,
		math.floor(position.Z / gridSize + 0.5) * gridSize
	)
end

-- Function to check if a spot is available for all parts (including descendants)
local function isSpotAvailableForAllParts(parts)
	for _, part in ipairs(parts) do
		local region = Region3.new(
			part.Position - part.Size / 2 + Vector3.new(0.05, 0.05, 0.05),
			part.Position + part.Size / 2 - Vector3.new(0.05, 0.05, 0.05)
		)
		local partsInRegion = workspace:FindPartsInRegion3(region, nil, math.huge)

		for _, foundPart in ipairs(partsInRegion) do
			if workspace.Factories.Factory1.PlacedParts:IsAncestorOf(foundPart) then
				return false
			end
		end
	end
	return true
end

-- Function to get all descendants of a part
local function getAllDescendants(part)
	local descendants = {}
	for _, child in ipairs(part:GetDescendants()) do
		if child:IsA("BasePart") then
			table.insert(descendants, child)
		end
	end
	return descendants
end

-- Function to detect if a part is touching another part
local function isTouchingAnotherPart(part)
	local touchingParts = part:GetTouchingParts()
	for _, touchingPart in ipairs(touchingParts) do
		if workspace.Factories.Factory1.PlacedParts:IsAncestorOf(touchingPart) then
			return touchingPart
		end
	end
	return nil
end

-- Function to move all ancestors and descendants of a part
local function moveAncestorsAndDescendants(part)
	local targetPosition = Vector3.new(100, 0, 100)
	local partsToMove = {}

	-- Move all descendants
	local function collectDescendants(p)
		for _, child in ipairs(p:GetDescendants()) do
			if child:IsA("BasePart") then
				table.insert(partsToMove, child)
			end
		end
	end

	-- Collect ancestors and descendants
	local currentPart = part
	while currentPart.Parent and currentPart.Parent ~= workspace do
		table.insert(partsToMove, currentPart)
		currentPart = currentPart.Parent
	end

	-- Collect descendants of the original part
	collectDescendants(part)

	-- Move all parts (ancestors and descendants)
	for _, partToMove in ipairs(partsToMove) do
		partToMove.Position = targetPosition + Vector3.new(0, partToMove.Size.Y / 2, 0)
	end
end

-- Function to hide UI and start placing block
local function startPlacingBlock()
	screenGui.Enabled = false
	workspace.Factories.Factory1.Base.Texture.Transparency = 0.8

	if Block then
		Clone = Block:Clone()
		Clone.Orientation = Vector3.new(0,script.Parent.Parent.Parent.LastRot.Value,0)
		Clone.CanQuery = false
		Clone.CanCollide = false
		Clone.Transparency = 0.5
		Clone.BrickColor = BrickColor.new("Bright green")
		Clone.Parent = workspace

		-- Function to update block position
		local function updateBlockPosition()
			local targetPosition = mouse.Hit.p
			local targetSize = Clone.Size
			local snappedPosition = snapToGrid(targetPosition, gridSize, targetSize)

			Clone.Position = snappedPosition
			Clone.Orientation = Vector3.new(0, currentRotation, 0)

			local partsToCheck = getAllDescendants(Clone)
			table.insert(partsToCheck, Clone)

			-- Check if spot is available
			if isSpotAvailableForAllParts(partsToCheck) then
				isValidPlacement = true
				Clone.BrickColor = BrickColor.new("Bright green") -- Green for valid
			else
				-- Find the part it's touching
				local touchingPart = isTouchingAnotherPart(Clone)

				if touchingPart then
					-- Move ancestors and descendants
					moveAncestorsAndDescendants(touchingPart)
					isValidPlacement = true
					Clone.BrickColor = BrickColor.new("Bright green")
				else
					isValidPlacement = false
					Clone.BrickColor = BrickColor.new("Bright red") -- Red for invalid
				end
			end
		end

		game:GetService("RunService").RenderStepped:Connect(function()
			if Clone then
				updateBlockPosition()
			end
		end)

		mouse.Button1Down:Connect(function()
			if Clone and isValidPlacement then
				remoteEvent:FireServer({
					Position = Clone.Position,
					Orientation = Clone.Orientation,
					Name = Clone.Name
				})

				Clone:Destroy()
				Clone = nil
				screenGui.Enabled = true
				workspace.Factories.Factory1.Base.Texture.Transparency = 1
			end
		end)
	end
end

-- Rotate block by 90 degrees on "R" key press
local function rotateBlock()
	currentRotation += 90
	script.Parent.Parent.Parent.LastRot.Value += 90
	if Clone then
		Clone.Orientation = Vector3.new(0, currentRotation, 0)
	end
end

-- Connect "R" key to rotate the block
mouse.KeyDown:Connect(function(key)
	if key == "r" then
		rotateBlock()
	end
end)

-- Start block placement on button click
script.Parent.MouseButton1Click:Connect(startPlacingBlock)

but i believe the problem rests in this function:

    local targetPosition = Vector3.new(100, 0, 100)
    local partsToMove = {}

    -- Move all descendants
    local function collectDescendants(p)
        for _, child in ipairs(p:GetDescendants()) do
            if child:IsA("BasePart") then
                table.insert(partsToMove, child)
            end
        end
    end

    -- Collect ancestors and descendants
    local currentPart = part
    while currentPart.Parent and currentPart.Parent ~= workspace do
        table.insert(partsToMove, currentPart)
        currentPart = currentPart.Parent
    end

    -- Collect descendants of the original part
    collectDescendants(part)

    -- Move all parts (ancestors and descendants)
    for _, partToMove in ipairs(partsToMove) do
        partToMove.Position = targetPosition + Vector3.new(0, partToMove.Size.Y / 2, 0)
    end
end

Are the other parts anchored? - limit sadge

nope, they are anchored and canncollide is disabled, so the properties of the constraint is still working because its not falling through the baseplate

I’m not familiar with weld constraints, but if the parts are anchored, do they still obey the constraints? In other words, if the parts were unanchored, would they move with the cursor?

The issue here (I think) is that you are not moving your parts via physics, so the weld constraint will not update and instead just change the position relative to the part its welded to. I think normals welds might be able to solve this because they need to be directly adjacent to the other part, but I don’t recommend this.

Instead, since you are not moving the parts via physics, you should utilize models and CFrames for this case. Group the parts together in a model, and make the part you are moving the primary part. Then, set the CFrame of the model to the position you were setting before:

model:SetPivot(CFrame.new(targetPosition + Vector3.new(0, partToMove.Size.Y / 2, 0)))

I understand that rotations may be a problem here, and I am not sure that position above is correct, but I think you get what I am trying to get at here.

so i should make it a model instead? this is what ive been using and ive been considering using models

image

if the parts are anchored they no longer follow the constraints

1 Like

Yes, make it a model; that’s what I always use for my placing systems. Even better, you can set the primary part to a part that takes up your entire grid space so that your can align your entire model to the grid position very easily.

1 Like

thank you! i will try it now, this is my first part placement system like this

1 Like

legend, thanks for that it worked

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.