I doubt my method is at all the best, but here’s how I came up with my solution a while ago:
To start off, I created a Folder
in Workspace
to hold all of the blocks that were placed down. Each time a block is placed, wouldn’t you guess it, it’s placed inside this folder. So now when the player’s mouse hovers over some BasePart in workspace, I can sort of use a cheat code when I know they’re hovering over a block.
I have this module script here to store some common info on the blocks, but you could just make it a variable if you really wanted to
return {
GridSize = 4, -- might want to change this to fit your needs (in studs)
MaxPlaceDistance = 40, -- unsurprisingly also in studs
Normals = {
[Enum.NormalId.Back] = Vector3.new(0, 0, 1),
[Enum.NormalId.Front] = Vector3.new(0, 0, -1),
[Enum.NormalId.Bottom] = Vector3.new(0, -1, 0),
[Enum.NormalId.Top] = Vector3.new(0, 1, 0),
[Enum.NormalId.Left] = Vector3.new(-1, 0, 0),
[Enum.NormalId.Right] = Vector3.new(1, 0, 0),
}
}
Now that we have these constants set up, here comes the magic.
If we’re hovering over a block, which we’d know because it’s parent would be that Blocks Folder:
- Use the mouse.TargetSurface and the
Normals
table we created to get the direction to offset the block towards
If we’re hovering over a regular part:
- Do a little “mathy math” to find out where the block should be based on where the mouse is hitting
The function I used for this looked like so:
-- Variable meanings if they aren't super obvious (for the devforum):
-- block -> the actual block that's following my mouse
-- placing -> a bool to define if I'm currently placing a block or not
-- mouse -> player:GetMouse()
-- GRID_SIZE -> 4
-- BlockGlobals -> the module I used, but you could just make it a variable
local function snapGrid()
if block and placing and mouse.Target then
if mouse.Target.Parent ~= blocksFolder then
local vec3 = Vector3.new(
math.floor(mouse.Hit.p.X / GRID_SIZE + 0.5) * GRID_SIZE,
mouse.Target.Position.Y + mouse.Target.Size.Y/2 + GRID_SIZE/2,
math.floor(mouse.Hit.p.Z / GRID_SIZE + 0.5) * GRID_SIZE
)
block.Position = block.Position:lerp(vec3, 0.69)
else
local targetPos = mouse.Target.Position + Vector3.new(1, 1, 1) * GRID_SIZE * BlockGlobals.Normals[mouse.TargetSurface]
block.Position = block.Position:lerp(targetPos, 0.69)
end
end
end
And basically just run this snapGrid
function on a RenderStepped
loop and at best you get some semi-optimized prototyping functionality that works like a charm:
https://gyazo.com/d39083c796b356d96e0a90136c4e9501
Side note: this is a pretty old idea of mine and I’m sure someone with a degree of “all things that you shouldn’t do because there are simpler solutions if you really take the time to think about it” will come along and correct me, but this approach works.