How to make a placement system like bedwars?

Title explains it all. My grid function:

local function SnapToGrid(position : Vector3, gridSize : number)
    return Vector3.new(
        math.floor(position.X / gridSize + 0.5) * gridSize,
        math.floor(position.Y / gridSize + 0.5) * gridSize,
        math.floor(position.Z / gridSize + 0.5) * gridSize
    );
end;

But the problem is that it doesn’t even work properly. (My grid size is 4)

2 Likes

What do you mean by not working properly? Please explain.

It’s just not working right. It’s colliding into the baseplate. I’ll try to give a video of what I mean.

You need to add half the part’s height to its Y-axis position so that it doesn’t clip into the floor.

Assuming you’re using the mouse’s world space location.

2 Likes

To the Y coordinate, you need to add half of the object’s hitbox’s Y size. This way, it won’t clip inside the baseplate.

You could use mouse.Target and add the size of the block to it.

intead of using math.floor(x + 0.5) use math.round(x)

1 Like

Yeah, using mouse.Hit.Position

Will give it a test when my class is over!

It worked properly, not after I changed the Baseplate size though.

If you’re changing other instances around the placed instance then you’ll need to dynamically reposition that placed instance accordingly.

Yeah, did that and it fixed it. Just one final question, which I’m pretty sure I need to use Raycasting which I’m really bad at, but. Every time I place down a block it the next one doesn’t want to go on top of it. Any solutions?

it the next one doesn’t want to go on top of it. Any solutions?

Mind rephrasing this?

It’s not going on top of the other block.

Hello. Sorry for interrupting, but I can’t seem to fix this problem at all.

sorry I’m here so late hopefully you’ll see this the problem is with the Raycast the ray cast position is directly on the surface of the part and since the raycast position is so close theres two options the grid function either makes it inside the block or outside the block so sadly you cant just use a simple grid system so you have to find the position of the block outside the block or grid you want to put it on so first we have to find the distance between the ray instance position and the ray position if we just get the position one will always stay the same and one could a position could be very high on the x y or z when we do need that

 local x, y, z = CFrame.new(ray.Position - ray.Instance.Position):GetComponents()

Then we get the absolute value of all the vectors of all of them because we need to find the highest value to know which axis to put the block on then we get the actual value of the vectors to position later we also put it until tables to find the values easier without making the code look too messy

local xabs = math.abs(x)
	local yabs = math.abs(y)
	local zabs = math.abs(z)
	local tableofvectors = {x,y,z}
	local tableofabsvectors = {xabs,yabs,zabs}

then we get the highest value of the absolute vectors to know the axis that will be changed by unpacking the table of absolute vectors

local max = math.max(table.unpack(tableofabsvectors))

After we make a variable which i call finder to find the axis we need using the value of max
just to make it clear we are NOT FINDING THE VALUE we are finding the index because the xyz is in order so now we know the axis

local finder = table.find(tableofabsvectors,max)

Then we go over and make another table then using the index we found using the finder we find the axis and the value we need then using the finder we make a new vector finding the index for a value in the table of tableofvectors then multiplying it by a value that fits your blocks my blocks are 3x3x3 and i put 2 as my value

	local Ltable = {[1] = 0,[2] = 0,[3] = 0}
	Ltable[finder] = tableofvectors[finder] * 2

Lasty we use to world space to set a new origin for our blocks so our blocks arent positioned near the position 0,0,0 in workspace using the Ltable then we return the cframe we get

	local cframe ray.Instance.CFrame:ToWorldSpace(CFrame.new(Vector3.new(Ltable[1],Ltable[2],Ltable[3])))
	
	return cframe

This is how you call this function and use it

				local newsnap = Snap(ray)
				part.CFrame = newsnap

This is the whole function

local function Snap(ray)
	--get the distance of the ray and the instance
	local x, y, z = CFrame.new(ray.Position - ray.Instance.Position):GetComponents()
	-- get the aboluste values for later
	local xabs = math.abs(x)
	local yabs = math.abs(y)
	local zabs = math.abs(z)
	--Make tables to keep coode clean
	local tableofvectors = {x,y,z}
	local tableofabsvectors = {xabs,yabs,zabs}
	--get the highest number out of the the three absoulte values of the vectors to get the axis
	local max = math.max(table.unpack(tableofabsvectors))
	--	print(max)
	--NOT getting the value but getting the index so later we can use that index to find the actual values so its not just one sided
	local finder = table.find(tableofabsvectors,max)
	--print(finder)
	--local vtable = {[1] = ray.Instance.Position.X,[2] = ray.Instance.Position.Y,[3] = ray.Instance.Position.Z}
	--vtable[finder] = tableofvectors[finder]
	--we make a table to plug into the vector3
	local Ltable = {[1] = 0,[2] = 0,[3] = 0}
	--we change the table because we dont know the actual value of the finder so we couldent change it before
	Ltable[finder] = tableofvectors[finder] * 2
	--	print(tableofvectors)
	--We make the orgin or you could say the 0,0,0 point in workspace the ray instance position (is not global and does not change anything it just gives a cframe)
	--if we did not change the orgin it would appear close the the spawnlocation in the workspace (the spawnlocation is close to the 0,0,0 point in workspace)
	local cframe = ray.Instance.CFrame:ToWorldSpace(CFrame.new(Vector3.new(Ltable[1],Ltable[2],Ltable[3])))
	
	return cframe
	
	--return {PosX,PosY,PosZ}
end

Sorry for the bump but since he linked another topic to this, I’m going to break down this convoluted grid system and show how it doesn’t work:

Only getting the position of the difference between the ray and its object (in other words, converted into object space, but not quite because rotation is not applied, should be corrected to PointToObjectSpace)

local objectSpaceVector = ray.Instance.CFrame:PointToObjectSpace(ray.Position)

What finder is, is the index of the longest axis

local Ltable = {[1] = 0,[2] = 0,[3] = 0}
Ltable[finder] = tableofvectors[finder] * 2

Ltable is just a Vector3 but it seems like he forgot
He then proceeded to change only the axis with the longest value, multiplied by 2 (2 is supposedly the snapping value)

local cframe = ray.Instance.CFrame:ToWorldSpace(CFrame.new(Vector3.new(Ltable[1],Ltable[2],Ltable[3])))	
return cframe

Basically, this is from the object’s relative back to the world’s relative. While he used CFrame, the rotation came back, since it is relative to the object, it would make the movement super weird.

The flaws:

  • It doesn’t work, this “snap” system just moves your part twice (or whatever your snapping distance is) the distance in the longest axis between your cursor and the object
  • If the part is rotated, it applies to the snapping vector (if the part is rotated 90 degrees on the Y axis, the movement of the Z axis will be swapped with the X axis
  • Any rotated plane is not going to work for this, this only work in the base axis plane (for example you place a table of different orientation, and now you want it to snap on the table’s surface instead of just the base coordinate system
  • Too convoluted, OP refused to use Vector3 and used table as an alternative for some reason

For an easier snapping system:

Vector3.new(math.round(posX * snap)/snap, math.round(posY * snap)/snap, math.round(posZ * snap)/snap)

snap is the number of studs, but inversed. For example, if you want the grid system to be 0.25 studs, snap is 1/0.25 = 4

To apply this snap to a specific plane, transform your part's CFrame to ObjectSpace, this will remove any rotation and position shifting (when the plane’s origin is like [-9.238, 122.828, 3.816]
Then apply the snapping and return it back to its WorldSpace

Here is an example code on one of my building system

-- Assuming raycastResult is not nil
local part = raycastResult.Instance
local pos = raycastResult.Position

local nPos = part.CFrame:PointToObjectSpace(pos) -- part.CFrame:Inverse() * pos
-- Applying the snapping formula
nPos = Vector3.new(math.round(nPos.X * snap)/snap, 0, math.round(nPos.Z * snap)/snap)
nPos = part.CFrame:PointToWorldSpace(nPos) -- part.CFrame * pos
return nPos

The Y value is 0 because I don’t want people to change the Y position.

Alternatively, if you have a system where the input is a CFrame instead of a Vector3 for some reason, consider using ToObjectSpace and ToWorldSpace to keep the rotation of 2 objects.
Or if you want to handle the rotation separately (like my system) but got a CFrame input, consider using CFrame.Position.

There may be situation where you want to snap to grid the Y axis too, like placing doors and windows on walls. Adjust the snapping formula to your needs.

Documentation

CFrame:PointToObjectSpace
CFrame:PointToWorldSpace
CFrame:ToObjectSpace
CFrame:ToWorldSpace