How to get the parts around a part?

Hello :D
I am making something on a grid where the state of a part changes based on the ones around it. However I don’t know how I would get the part next to it or nil if there is none. Any ideas?

1 Like

You could use workspace:GetPartBoundsInBox() with a size slightly larger than the size of your part. However, I wouldn’t necessarily recommend this approach. For efficiency and reliability, you could use your own data structures instead of physics queries.

If the parts are rectangular and the grid as a whole is a rectangle, you could use a simple 2D array of parts (array of arrays or one array with size length * width). If you use code to create the parts, you can add them to the 2D array when you create them.

In addition to this array, you could have a dictionary with parts as keys and grid indices as values (the value could be either a pair of indices or one index that can be converted to a pair of indices). When you add a part to the 2D array, you also add the part as a key and the indices/index as a value in this dictionary.

Assuming that you have an array of arrays of parts, a dictionary containing the two indices for each part as a Vector2 and a function for checking whether a pair of indices is valid, getting the neighbors could look like this:

local function getNeighbors(part: BasePart): {BasePart}
	local neighbors: {BasePart} = table.create(4)
	local indexVec: Vector2 = indicesForParts[part]
	local x: number, y: number = indexVec.X, indexVec.Y
	if areCoordsInBounds(x + 1, y) then
		table.insert(neighbors, partArray2D[x + 1][y]
	end
	if areCoordsInBounds(x, y + 1) then
		table.insert(neighbors, partArray2D[x][y + 1]
	end
	if areCoordsInBounds(x - 1, y) then
		table.insert(neighbors, partArray2D[x - 1][y]
	end
	if areCoordsInBounds(x, y - 1) then
		table.insert(neighbors, partArray2D[x][y - 1]
	end
	return neighbors
end
3 Likes

Some follow-up questions:

  • What does the : {BasePart} part of the code do?
  • Why are you doing the indexVec.X + indexVec.Y ?
  • How would all parts access the same table if variables are only per script?
  1. local function getNeighbors(part: BasePart): {BasePart}

The : {BasePart} thing is completely optional. It indicates type safety. Basically saying this function will return a table filled with base parts.

Or a simpler example, a function local function myfunction(a: number): boolean takes in one parameter which is a number and returns a boolean.

  1. local x: number, y: number = indexVec.X + indexVec.Y

I assume this is a typo since it doesnt really make any sense and it would create an error since y is nil. I assume its supposed to be local x: number, y: number = indexVec.X, indexVec.Y

oh ok, any ideas on the last question?

Yeah, that second one was indeed a typo. Idk what I was thinking when I wrote that plus there :sweat_smile:.

I’m not sure what you mean by your third question. If you need to access the tables from multiple scripts, you can have the tables in a module script.

I don’t really understand @RoBoPoJu’s answer (since he’s using custom data structures), so I’m going to give my own method that I would use.

local function getClosestPointOnPart(part : BasePart, position : Vector3) : Vector3
	-- gets closest point to a position in the bounds of a part
	return Vector3.new( math.clamp(position.X, part.Position.X - part.Size.X/2, part.Position.X + part.Size.X/2), math.clamp(position.Y, part.Position.Y - part.Size.Y/2, part.Position.Y + part.Size.Y/2), math.clamp(position.Z, part.Position.Z - part.Size.Z/2, part.Position.Z + part.Position.Z/2) )
end

local function getPartDistance(part1 : BasePart, part2: BasePart) : number
    local closestPoint1 : Vector3 = getClosestPointOnPart(part1, part2.Position)
    local closestPoint2 : Vector3 = getClosestPointOnPart(part2, closestPoint1)
    return (closestPoint2-closestPoint1).Magnitude
end

local threshold = 20 -- studs

local partsArray : {BasePart?} = {} -- this can be any part in workspace or whatever you want to check

function getCloseParts(part : BasePart) : {BasePart}
    local closeParts = {}
    for _, part2 in ipairs(partsArray) do
        if part2 == part then continue end -- dont need to check the part itself
        if getPartDistance(part, part2) <= threshold then
            table.insert(closeParts, part2)
        end
    end
end

Unable to test it rn, but it should work, theoretically.
If you want a simpler version that takes less time, you could just get the magnitude of the difference of the two parts’ positions to get the differences from their center, but then it wouldn’t count any large enough part as a neighbor.

Module scripts allow scripts to share data?
Would it work like this?

local module = {}
    module.parts = {}
    function module.addPart(part)
        module.parts.insert(part)
    end
    function module.getList()
        return module.parts
    end
return module

If there’s only one grid, the module could look like this for example. I haven’t tested this, though, so I may have made some mistakes in the code.

local PartStorage = {}

local numPartsInColumn: number = --something
local numPartsInRow: number = --something

local partArray2D: {{BasePart}} = table.create(numPartsInColumn)
local indicesForParts: {[BasePart]: Vector2} = {}

local function areCoordsInBounds(rowIndex: number, columnIndex: number): boolean
	return rowIndex >= 1 and rowIndex <= numPartsInColumn
		and columnIndex >= 1 and columnIndex <= numPartsInRow 
end

function PartStorage.addPart(part: BasePart, rowIndex: number, columnIndex: number): ()
	partArray2D[rowIndex][columnIndex] = part
	indicesForParts[part] = Vector2.new(rowIndex, columnIndex)
end

function PartStorage.getNeighbors(part: BasePart): {BasePart}
	local neighbors: {BasePart} = table.create(4)
	local indexVec: Vector2 = indicesForParts[part]
	local x: number, y: number = indexVec.X, indexVec.Y
	if areCoordsInBounds(x + 1, y) then
		table.insert(neighbors, partArray2D[x + 1][y]
	end
	if areCoordsInBounds(x, y + 1) then
		table.insert(neighbors, partArray2D[x][y + 1]
	end
	if areCoordsInBounds(x - 1, y) then
		table.insert(neighbors, partArray2D[x - 1][y]
	end
	if areCoordsInBounds(x, y - 1) then
		table.insert(neighbors, partArray2D[x][y - 1]
	end
	return neighbors
end

for rowIndex: number = 1, numPartsInColumn do
	partsArray2D[rowIndex] = table.create(numPartsInRow)
end

return PartStorage