Grid placing system

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.

I mean something like BedWars or SkyWars where when you look in front of you, you can see a transparent block.

I made this for my game.

It’s a bit buggy but it does the job done.

Place a Local Script in a Tool.

Place a Normal script in the tool.

Place a Folder named “PartsFolder” in the workspace and place all the parts you want in the map.

Place a Folder named “PartsPreview” in the workspace for the prediction block.

Place a Remote event named “PlaceBlock” in the tool

This is the code for the Local Script:

local PlaceBlockEvent = script.Parent.PlaceBlock

local equipped = false
local uis = game:GetService("UserInputService")
local mouse = game.Players.LocalPlayer:GetMouse()
local character = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()
local cam = workspace.CurrentCamera


local function findClosestPart(preview)
	local lowestMagParts = {}
	local currentPart = nil
	local currentClosestDir = nil
	local currentClsoestmag = nil 
	for _,v in pairs(game.Workspace.PartsFolder:GetDescendants()) do
		if v:IsA("BasePart") then
			local magnitude1 = (character.PrimaryPart.Position - v.Position).Magnitude
			if magnitude1 <= 25 then
				table.insert(lowestMagParts, v)
			end
		end
	end
	local lowestMagnitude = nil
	for _,v in pairs(lowestMagParts) do
		if v:IsA("BasePart") then
			local mouse3DPos = character.PrimaryPart.Position + (mouse.Hit.Position - character.PrimaryPart.Position).Unit * (character.PrimaryPart.Position - v.Position).Magnitude
			local mag = (v.Position - mouse3DPos).Magnitude
			if not lowestMagnitude then
				lowestMagnitude = mag
				currentPart = v
				continue
			end
			if mag < lowestMagnitude then
				lowestMagnitude = mag
				currentPart = v
			end
		end
	end
	
		local surfaceIDs = {
			top = currentPart.Position + Vector3.new(0,25,0),
			bottom = currentPart.Position - Vector3.new(0,25,0),
			left = currentPart.Position - Vector3.new(25,0,0),
			right = currentPart.Position + Vector3.new(25,0,0),
			front = currentPart.Position - Vector3.new(0,0,25),
			back = currentPart.Position + Vector3.new(0,0,25),
		}



		for surfaceId, pos in pairs(surfaceIDs) do
			local mag = ((character.PrimaryPart.Position + (mouse.Hit.Position - character.PrimaryPart.Position).Unit * 25) - pos).Magnitude
			if not currentClsoestmag then
				currentClsoestmag = mag
				currentClosestDir = surfaceId
				continue
			end
			if mag < currentClsoestmag then
				currentClsoestmag = mag
				currentClosestDir = surfaceId
			end
		end
		if currentClosestDir ~= nil then
			table.clear(lowestMagParts)
			return currentPart, currentClosestDir
		end

end

local connection = nil

script.Parent.Equipped:Connect(function()
	if equipped == false then
		equipped = true
		local clonePreview = Instance.new("Part")
		clonePreview.Parent = game.Workspace:WaitForChild("PartsPreview")
		clonePreview.Name = script.Parent.Parent.Name.."Preview"
		clonePreview.Transparency = 1
		clonePreview.Size = Vector3.new(3,3,3)
		clonePreview.CanCollide = false
		clonePreview.Anchored = true
		local selection = Instance.new("SelectionBox", clonePreview)
		selection.Visible = true
		selection.Adornee = clonePreview
		selection.LineThickness = 0.05
		selection.Color3 = Color3.fromRGB(0,0,0)
		for _,v in pairs(game.Workspace.PartsPreview:GetChildren()) do
			if v ~= clonePreview then
				v:Destroy()
			end
		end
		mouse.TargetFilter = clonePreview
		task.defer(function()
			while wait() do
				if equipped == true then
					local mouseX,mouseY,mouseZ = mouse.Hit.X, mouse.Hit.Y, mouse.Hit.Z
					local mouseTarget = mouse.TargetSurface
					if mouse.Target ~= nil then
						if mouseTarget == Enum.NormalId.Top then
							clonePreview.CFrame = (CFrame.new(mouse.Target.Position + Vector3.new(0,3, 0))) --- the 3 is the size of the block
						elseif mouseTarget == Enum.NormalId.Bottom then
							clonePreview.CFrame = (CFrame.new(mouse.Target.Position - Vector3.new(0,3, 0))) --- the 3 is the size of the block
						elseif mouseTarget == Enum.NormalId.Left then
							clonePreview.CFrame = (CFrame.new(mouse.Target.Position - Vector3.new(3, 0,0))) --- the 3 is the size of the block
						elseif mouseTarget == Enum.NormalId.Right then
							clonePreview.CFrame = (CFrame.new(mouse.Target.Position + Vector3.new(3,0, 0))) --- the 3 is the size of the block
						elseif mouseTarget == Enum.NormalId.Front then
							clonePreview.CFrame = (CFrame.new(mouse.Target.Position - Vector3.new(0,0, 3))) --- the 3 is the size of the block
						elseif mouseTarget == Enum.NormalId.Back then
							clonePreview.CFrame = (CFrame.new(mouse.Target.Position + Vector3.new(0,0, 3))) --- the 3 is the size of the block
						end
					elseif mouse.Target == nil then
						local closestPart, direction = findClosestPart(clonePreview)
						if tostring(direction) == "top" then
							clonePreview.CFrame = (CFrame.new(closestPart.Position + Vector3.new(0,3, 0))) --- the 3 is the size of the block
						elseif tostring(direction) == "bottom" then
							clonePreview.CFrame = (CFrame.new(closestPart.Position - Vector3.new(0,3, 0))) --- the 3 is the size of the block
						elseif tostring(direction) == "left" then
							clonePreview.CFrame = (CFrame.new(closestPart.Position - Vector3.new(3,0,0))) --- the 3 is the size of the block
						elseif tostring(direction) == "right" then
							clonePreview.CFrame = (CFrame.new(closestPart.Position + Vector3.new(3,0, 0))) --- the 3 is the size of the block
						elseif tostring(direction) == "front" then
							clonePreview.CFrame = (CFrame.new(closestPart.Position - Vector3.new(0,0, 3))) --- the 3 is the size of the block
						elseif tostring(direction) == "back" then
							clonePreview.CFrame = (CFrame.new(closestPart.Position + Vector3.new(0,0, 3))) --- the 3 is the size of the block
						end
						closestPart = nil
						direction = nil
					end	
				end
			end
		end)

		connection = mouse.Button1Down:Connect(function()
			if equipped == true then
				for _,v in pairs(game.Workspace:WaitForChild("PartsFolder"):GetDescendants()) do
					if v:IsA("BasePart") then
						if clonePreview.Position == v.Position then
							return
						end
					end
				end

				local primaryPart = clonePreview.CFrame
				PlaceBlockEvent:FireServer(primaryPart)
			end

		end)
	end
end)

script.Parent.Unequipped:Connect(function()
	if equipped == true then
		equipped = false
		if workspace.PartsPreview:FindFirstChild(script.Parent.Parent.Name.."Preview") then
			local preview = workspace.PartsPreview:FindFirstChild(script.Parent.Parent.Name.."Preview")
			preview:Destroy()
		end
	end
	connection:Disconnect()
	connection = nil
end)
 

Here is the code for the Normal script:

local event = script.Parent.PlaceBlock
local part = game.ReplicatedStorage:WaitForChild("Wood Plank") ---- name of the part in the replicated storage you want to clone for the block placement system

event.OnServerEvent:Connect(function(plr, pos)
	local Clone = part:Clone()
	Clone.Parent = game.Workspace:WaitForChild("PartsFolder")
	Clone.CFrame = pos
end)

This is my first time making a reply so let me know if you have problems!

2 Likes

Also here’s a video clip of the tool. It’s a bit lag since I’m on a laptop

robloxapp-20220302-2138143.wmv (3.4 MB)

Thank you so much for your help! I’ve been looking around for something like this.

However, it does not seem to work. There is currently nothing in the output.

I’ve followed your directions and re-checked the script…

EDIT: Fixed the problem, i didnt have a handle lol

However, for some odd reason, I get this error.
image

Here’s where it’s causing the issue.

It seems to be happening when I take the mouse off the map.

Thanks anyways, you are a lifesaver!

1 Like