Puzzle Assembly Script

I am currently trying to make a script that assembles puzzle pieces.
The goal is to make them all become a single model when they are in the correct position.
It doesn’t matter if they are tilted or not, they would just all need to be at least half a unit (0.5) close to each other in the correct place.

Here’s an example.
From this

To this:

I am not a very experienced programmer but I’ve tried to work around it and make the player place them in a specific board and detect if the puzzle piece was touching the right part (which would be kinda like a “coordinate” of some sort) but that didn’t work either.

Can anyone help me with this?

First you need a way to determine which pieces are your connected neighbors.

How are you creating the puzzle in the first place? Is it manually authored, or procedurally generated?

Since I’m not very experienced I built the puzzle using Roblox 4x4x1 parts and then proceeded to turn them into union by adding half a cylinder to one of the pieces and remove the other half on the neighbor piece.

Since you already know where each piece should be, you can manually somehow store each piece’s correct position (could be in Vector3 form), and then check if it’s close enough.

You could parent a Vector3Value (named “CorrectPosition” or something similar) to each piece and save the piece’s correct position there.
Then, when you want to check if all pieces are placed correctly, loop through all pieces and get the distance between their current position and their correct position:

local correctPosition = piece.CorrectPosition.Value
local position = piece.Position
local distance = (correctPosition - position).Magnitude --Calculates the distance

if distance < 0.5 then --Change the distance to whatever suits you
    --Piece is close enough to the correct position!

Let me know if you need help with the actual coding part of this. :slight_smile:

Hi there!
Thank you so much for the suggestion!
I tried making a “positions” model so I could check if the position between Piece1(Union in the Puzzle Model) and P1(Part in the Positions Model) but something is not working properly, I think the value of “distance” is not being assigned and therefore being a nil value.
Also the while was just to test if the distance thing worked which it didn’t.


Note: The puzzle unions are sub-divided in groups (Column1, 2 … up until Column6)

No worries, that’s great info.

It looks like your parts are arranged in a grid, as well. That’s good!

First, make a grid in code, too. Usually this is done as array of columns. You did most of the work manually, but you could also just put all your parts in one model and call a function like this one “Gridify” that sorts things into columns/rows (the context here was someone had a bunch of parts that weren’t organized into a grid at all):

So assuming you do that you now should have a table (either returned from Gridify, or a similar function you write that works with your pre-sorted columns, or manually written in a script) that looks something like this:

local grid = ...

grid = {
  { Piece1,  Piece5,  Piece9,  Piece13 },
  { Piece2,  Piece6,  Piece10, Piece14 },
  { Piece3,  Piece7,  Piece11, Piece15 },
  { Piece4,  Piece8,  Piece12, Piece16 }


grid["numRows"] = #grid
grid["numCols"] = #grid[1]

Now that things are in a grid the problem becomes very easy since you can access a part by its row/column index: grid[7][12] would give us the puzzle piece in row 7, column 12.

For instance we can write this function to return a list of parts neighboring a given position:

local function GetNeighbors(grid, row, col)
  -- builds up an array of parts we're neighboring to the N/S/E/W
  local neighbors = {}
  if row > 1 then table.insert(neighbors, grid[row - 1][col]) end
  if row < grid.numRows then table.insert(neighbors, grid[row + 1][col]) end
  if col > 1 then table.insert(neighbors, grid[row][col - 1]) end
  if col < grid.numCols then table.insert(neighbors, grid[row][col + 1]) end
  return neighbors

And then, for instance, we can see if all parts are close to all their neighbors by looping through all rows:

local function IsClose(part1, part2)
  return (part1.Position - part2.Position).Magnitude < 5

local function IsGridCloselyPacked(grid)
  for row = 1, grid.numRows do -- look at each "row" array
    for col = 1, grid.numCols do -- look at each part inside that row
      -- look through its neighbors
      for _, neighbor in pairs(GetNeighbors(grid, row, col)) do
        -- check that each neighbor is close to us
        if not IsClose(grid[row][col], neighbor) then
          return false -- one wasn't close
  return true -- all are close

So that IsGridCloselyPacked function is basically what you’re looking for—it returns true/false, answering the question “is every puzzle piece close to the puzzle pieces above, below, left, and right of it?”

(We’re duplicating computation by checking a lot of stuff twice, for simplicity’s sake, but that’s fine)

The problem is that you’re assigning the distance only once at the start! Recalculate the distance in the loop, otherwise it won’t change as you move the piece.

while true do
    distance = (correctPosition - position).Magnitude
    if distance <= 0.5 then
        print("Close enough!")

Thank you so much for such a detailed explanation!
My goal with this is to have a model that has some special features to it (being able to assemble) so I don’t know much about programming yet. I have no idea where I have to put all the scripts you created for me…
Also when the IsGridCloselyPacked function returned true I would like it to assemble the puzzle either by attaching the pieces to each other or by “spawning” the solid model instead (which I assume it’s easier).

Sorry for the late response

The script can go anywhere, for example inside the model or in ServerScriptStorage

Just as long as you figure out that grid

You could call IsGridCloselyPacked in a loop, like once a second, or you could call it whenever the player moves a piece, or whatever

As for assembling it—spawning the finished model is easy, just delete all the pieces and replace it with the finished picture

Or you could be fancy and tween each piece to its final position, that would look neat.