Grid placing system

I am attempting to create a voxel / block type game where you can place and remove blocks. Currently a block is 3x3x3 studs. The “grid” to place blocks would be 3 studs since the blocks are 3x3x3.

Think minecraft (not copying it). How would I make it so I can place blocks like that?
I’ve managed to make it possible to place blocks wherever the mouse is pointing, but of course this isn’t what I really want.

mouse.Button2Down:Connect(function()
	print("place")
	local block = game:GetService("ReplicatedStorage").block:Clone()
	block.Parent = workspace
	block.Position = mouse.Hit.Position
end)

“block” in ReplicatedStorage is just a 3x3x3 part.

6 Likes

mouse.Hit.Position gives a position with decimals and stuff. Not rounded at all. So you will need to round it. Now there is no default rounding function, but we do have math.floor() and we can use that to make a custom rounding function. math.floor(x) always rounds down, that doesn’t seem very usefull does it? math.floor(4.6) --> 4 But what if you add 0.5 to it? All of a sudden you get a rounding functions! math.floor(4.6+0.5) --> 5 math.floor(4.2+0.5) --> 4.
This only rounds down to 1 tho, not exactly what you want. But why not just devide the number by 3, round that, and then multiply it by 3 again. Now you have something which can round numbers to the nearest 3. math.floor(x/3)*3 Fill in any number for x and you will get the rounded version back.
Add in vectors and stuff and you get this function:

local function round(vector,grid)
	return vector3.new(
		math.floor(vector.X/grid+.5)*grid,
		math.floor(vector.Y/grid+.5)*grid,
		math.floor(vector.Z/grid+.5)*grid
	)
end

round(mouse.Hit.Position,3)

That should solve your question!

3 Likes
mouse.Button2Down:Connect(function()
	print("place")
	local block = game:GetService("ReplicatedStorage").block:Clone()
	block.Parent = workspace
	block.Position = round(mouse.Hit.Position,3)
end)

I’ve modified my code to fit your function, however it doesn’t seem to be functioning as expected.
https://gyazo.com/3721195c393b96b021fddc9cb62ed1e8

It seems to be placing it in a diagonal line.
The mouse’s position is in the center of the screen, where the crosshairs are.

i’ve made something similar,try you cursor.Target.Position

script.Parent.Activated:connect(function()
	if a == 1 and val.Value > 0 then
		for i = 1,#blocks do
			if (blocks[i].Name == cursor.Target.Name) then
				v = true
			end
		end
		if ((v) and cursor.Target.ClassName == "Part" and (script.Parent.Handle.Position - cursor.hit.p).magnitude <= 15) then
			val.Value = val.Value - 1
			player.PlayerGui.ScreenGui.TextBox.Text = val.Value			
			a = 0


			if cursor.Target.Position.X + 1.5 == cursor.Hit.X then
				X = math.ceil(cursor.Hit.X) -(math.ceil(cursor.Hit.X) % 3) + 3
			else
				X = math.ceil(cursor.Hit.X) -(math.ceil(cursor.Hit.X) % 3)
			end
			if ((cursor.Hit.Y%0.5) == 0) then
				if cursor.Target.Position.Y + 1.5 == cursor.Hit.Y then
					Y = math.ceil(cursor.Hit.Y) - (math.ceil(cursor.Hit.Y) % 3) + 3
				else
					Y = math.ceil(cursor.Hit.Y) - (math.ceil(cursor.Hit.Y) % 3)
				end
			else
				Y = math.ceil(cursor.Hit.Y) - (math.ceil(cursor.Hit.Y) % 3)
			end
			if cursor.Target.Position.Z + 1.5 == cursor.Hit.Z then
				Z = math.ceil(cursor.Hit.Z) -(math.ceil(cursor.Hit.Z) % 3) + 3
			else
				Z = math.ceil(cursor.Hit.Z) -(math.ceil(cursor.Hit.Z) % 3)
			end
			requestPartEvent:FireServer(X,Y,Z,"lucky")

			a = 1
		end
	end
end)

https://gyazo.com/d94978a2e6202daba877dac7c4f082a2

1 Like

Sorry if I’m missing something, but how should I modify your code to fit mine?

Try this

script.Parent.Activated:connect(function()
		if  (game.ReplicatedStorage.block.Handle- cursor.hit.p).magnitude <= 15) then
			if cursor.Target.Position.X + 1.5 == cursor.Hit.X then
				X = math.ceil(cursor.Hit.X) -(math.ceil(cursor.Hit.X) % 3) + 3
			else
				X = math.ceil(cursor.Hit.X) -(math.ceil(cursor.Hit.X) % 3)
			end
			if ((cursor.Hit.Y%0.5) == 0) then
				if cursor.Target.Position.Y + 1.5 == cursor.Hit.Y then
					Y = math.ceil(cursor.Hit.Y) - (math.ceil(cursor.Hit.Y) % 3) + 3
				else
					Y = math.ceil(cursor.Hit.Y) - (math.ceil(cursor.Hit.Y) % 3)
				end
			else
				Y = math.ceil(cursor.Hit.Y) - (math.ceil(cursor.Hit.Y) % 3)
			end
			if cursor.Target.Position.Z + 1.5 == cursor.Hit.Z then
				Z = math.ceil(cursor.Hit.Z) -(math.ceil(cursor.Hit.Z) % 3) + 3
			else
				Z = math.ceil(cursor.Hit.Z) -(math.ceil(cursor.Hit.Z) % 3)
			end
	
		
		end
	end
end)

this might be wrong,because its been awhile since i did something like this

1 Like

Just a quick fix, you have to distribute X, Y, and Z coordinates for each component of the new Vector3:

local function round(vector,grid) 
    return Vector3.new( 
        math.floor(vector.X/grid+.5)*grid, 
        math.floor(vector.Y/grid+.5)*grid, 
        math.floor(vector.Z/grid+.5)*grid 
    ) 
end 

round(mouse.Hit.Position,3)
3 Likes

Thank you! That fixed it, it’s definitely a nice, simple solution.

1 Like

Actually, this doesn’t work on two sides of the block. It places the block inside of the block I am pointing at.
https://gyazo.com/e4882c9a22bda629429caf14084316b8

Try getting the normal from mouse.Hit using a ray and adding that to the original position:

-- services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local player = Players.LocalPlayer
local block = ReplicatedStorage:WaitForChild("block")

-- how far you want the player to reach
local REACH = 100

local castParams = RaycastParams.new()

mouse.Button2Down:Connect(function()

	local unitRay = mouse.UnitRay

	castParams.FilterDescendantsInstances = { player.Character }

	-- automatically ignores the player's character
	
	local result = Workspace:Raycast(
		unitRay.Origin, unitRay.Direction * REACH,
		castParams
	)

	if result then
		local newBlock = block:Clone()
		newBlock.Position = round(result.Position + result.Normal * 1.5, 3)
		newBlock.Parent = Workspace
	end

end)
19 Likes

It works, finally! Thank you very much.

1 Like

Sorry for bumping, but is this a local script or normal? And where would I put this?

I’m making a similar system, but I don’t fully understand the Raycasting. Could you please explain how it works?

This should be a local script, but if you want everyone to see the blocks you place, you should fire a remote to the server instead of creating the block client-side.

@Smartysaur11, if you’re talking about how Workspace:Raycast works, you can look at the Intro to Raycasting tutorial on the dev hub or check the API reference.

2 Likes

Ty raycasting has always been one of the things that stumps me

1 Like

Sorry to stir upp this again but im working on a similiar problem and am interested.

So, the round() function. What is that? I searched up ‘round() roblox’ and the best result I got was a function returning math.floor(n + 0.5) where n is the number. But here we are passing n as a vector3??? That screws with my head. To add to that what is the second parameter for? To round it to multiples of 3? If so how would that function look?

Thanks!

If you look at some of the replies before my solution, you’ll see the round() function there.

And you guessed correctly on the second parameter :+1:

I guess I would rename the function to something like snapToGrid()? That could clear the confusion.

1 Like

Sorry, I’m a little dumb, but

Why are you multiplying the result’s normal with 1.5? Is it like (studs / 2)?

Sorry for bumping, but how would a Prediction Block system be made with this?
(EDIT: I’m still learning scripting btw)

It’s the exact same way. Just make a clone which will be the prediction block and make it transparent.