Building system failing in certain directions

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? I want to create a “grid” building system, where blocks can be placed. [example in gyazo video linked below]

  2. What is the issue? The system “works” but only in certain directions. Not to mention that the blocks will be placed INSIDE of a lower block sometimes.

  3. What solutions have you tried so far? I have tried changing the snap() function, increasing the (0.5) to a (0.6) which fixed the problem, but only in the Y direction, so I changed it back. I tried changing the (0.5) values between each of them, but am still having trouble.

local mouse = game.Players.LocalPlayer:GetMouse()
local tool = script.Parent
toolEquipped = false
recentlyMoved = false

local posx
local posy
local posz

local blocks = {
	test = game.ReplicatedStorage.Blocks.BlockTemplate
}
mouse.TargetFilter = blocks.test

local gridSize = 4

local function snap()
	posx = math.floor(mouse.Hit.X / gridSize + 0.5) * gridSize
	posy = math.floor(mouse.Hit.Y / gridSize + 0.5) * gridSize
	posz = math.floor(mouse.Hit.Z / gridSize + 0.5) * gridSize
end

tool.Equipped:Connect(function()
	toolEquipped = true
	snap()
	local pos1 = Vector3.new(posx, posy, posz)
	blocks.test.Parent = workspace
	blocks.test.Position = pos1
	
	mouse.Move:Connect(function()
		if toolEquipped == true then
			snap()
			posV = Vector3.new(posx, posy, posz)
			blocks.test.Parent = workspace
			blocks.test.Position = posV
			print("moved")
		end
	end)
	
	mouse.Button1Down:Connect(function()
		local a = blocks.test:Clone()
		a.Parent = workspace
		a.Position = posV
		a.Transparency = 0
		a.CanCollide = true
	end)
end)

tool.Unequipped:Connect(function()
	toolEquipped = false
	blocks.test.Parent = game.ReplicatedStorage.Blocks
end)

Here is a video detailing what happens in studio: https://gyazo.com/ed812b49bb1ba058b342cb5882489fad

Also, yes, I am aware that I am not using remote events, this is just for testing purposes, after I get this working then I will implement the events.

2 Likes

If you’re looking to create something like Minecraft’s building system, couldn’t you just take the mouse’s target’s position, then add the block’s size times the mouse’s normal?

Here’s what I wrote quickly:

local players = game:GetService('Players')
local localPlayer: Player = players.LocalPlayer
local mouse = localPlayer:GetMouse()

local userInputService = game:GetService('UserInputService')

local part = Instance.new('Part')
part.Transparency = 0.5
part.CanCollide = false
part.Anchored = true
part.Parent = workspace
part.Size = Vector3.new(4,4,4)
local filter = {
	part
}
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = filter
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist

mouse.Move:Connect(function()
	local raycastResult = workspace:Raycast(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 1000, raycastParams)
	if raycastResult then
		part.Position = (raycastResult.Instance.Position) + (part.Size * raycastResult.Normal)
	end
end)

userInputService.InputBegan:Connect(function(input: InputObject)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		local clonedPart = part:Clone()
		clonedPart.Parent = workspace
		clonedPart.CanCollide = true
		clonedPart.Transparency = 0
	end
end)

1 Like

The problem with this is that, while it does what you show in the video, if I put my mouse over a larger part than the block size, the block goes to the center of that part instead of where my mouse is

Yeah, you’d have to do some checks to see if your mouse’s target is an existing block, and if not, snap it to the grid based on the ray’s position.

How would I do that? I am struggling. If you can put it in words what I need to do then maybe I could try it.

So basically when you use workspace:Raycast(), it returns an object called “RaycastResult”.

So, when we cast our ray, raycastResult is our result:

local raycastResult = workspace:Raycast(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 1000, raycastParams)

So, take the raycastResult’s Instance property and check if it has some attribute, like an easily identifiable name

if raycastResult.Name == 'Block' then
    part.Position = (raycastResult.Instance.Position) + (part.Size * raycastResult.Normal)
else
    -- if the mouse's instance isn't a block, then snap it to the grid.
    -- here is where you'd snap it
end
1 Like

the “snap it to the grid” is what I am having trouble with. I can use math.floor + 0.5 to round it, but how do I guarantee that it’s on the grid?

Ohh my bad, basically you’d just do something like math.round(x * 4) / 4 where x is your position on the axis and 4 would be replaced with whatever your grid size is.

I have done something like what I assume what you meant

	mouse.Move:Connect(function()
		if toolEquipped == true then
			snap()
			local raycastParams = RaycastParams.new()
			raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			raycastParams.FilterDescendantsInstances = {blocks.test, tool, player.Character}
			
			local raycastResult = workspace:Raycast(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 1000, raycastParams)
			
			if raycastResult.Instance.Name == "BlockTemplate" then
				posV = (raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)
				blocks.test.Position = posV
			else
				local posVX = math.round(posx * gridSize) / gridSize
				local posVY = math.round(posy * gridSize) / gridSize
				local posVZ = math.round(posz * gridSize) / gridSize
				posV = Vector3.new(posVX, posy, posVZ)
			end
		end
	end)

however end up with this: https://gyazo.com/cf6e2e1a6a15908830dc3ddd4fb71e85

I think that’s happening either because of floating point errors or because of your parts’ positions.

Anyway, I think you’re going to have to manually check each normal to get your desired position:

You also might have to switch X and Z axes as I can never get them right lol

if raycastResult.Normal == raycastResult.Instance.CFrame.LookVector or raycastResult == -raycastResult.Instance.CFrame.LookVector then -- pretty sure this is the Z axis, if not then switch LookVector with RightVector
    local posVX = math.round(posx * gridSize) / gridSize
    local posVY = math.round(posy * gridSize) / gridSize
    local posVZ = ((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).Z
    posV = Vector3.new(posVX, posy, posVZ)
elseif raycastResult.Normal == raycastResult.Instance.CFrame.UpVector or raycastResult.Normal == -raycastResult.Instance.CFrame.UpVector then
    local posVX = math.round(posx * gridSize) / gridSize
    local posVY = ((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).Y
    local posVZ = math.round(posZ * gridSize) / gridSize
    posV = Vector3.new(posVX, posy, posVZ)
elseif raycastResult.Normal == raycastResult.Instance.CFrame.RightVector or raycastResult.Normal == -raycastResult.Instance.CFrame.UpVector
    local posVX = ((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).X
    local posVY = math.round(posY * gridSize) / gridSize
    local posVZ = math.round(posZ * gridSize) / gridSize
    posV = Vector3.new(posVX, posy, posVZ)
end

Still having trouble, but im going to try to troubleshoot the code to see what is going on. I do not understand normals probably as I should, but reading your code has given me an idea of what I need to do. If I need some help still, I’ll reply here

1 Like

The Normal property of a raycast result just returns the directional vector of the normal (face) of the part.

I have determined (through the great debug tool known as PRINT) that for some reason, the X and Z (yes, i changed LookVector to RightVector and it starts bugging out) values do not change on two of the three values (I have edited the Y one to suit me, as it works like it should as I have it) if I am holding my mouse over a part that is not “blocktemplate”

	mouse.Move:Connect(function()
		if toolEquipped == true then
			snap()
			local raycastParams = RaycastParams.new()
			raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			raycastParams.FilterDescendantsInstances = {blocks.test, tool, player.Character}
			
			local raycastResult = workspace:Raycast(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 300, raycastParams)
			
			if raycastResult.Instance.Name == "BlockTemplate" then
				posV = (raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)
				blocks.test.Position = posV
			else
			if raycastResult.Normal == raycastResult.Instance.CFrame.RightVector or raycastResult == -raycastResult.Instance.CFrame.RightVector then
				local posVX = ((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).X
				local posVY = math.round(posy * gridSize) / gridSize
				local posVZ = math.round(posz * gridSize) / gridSize
				posV = Vector3.new(posVX, posVY, posVZ)
				blocks.test.Position = posV
				print(posV, "1")
			elseif raycastResult.Normal == raycastResult.Instance.CFrame.UpVector or raycastResult == -raycastResult.Instance.CFrame.UpVector then
				local posVX = math.round(posx * gridSize) / gridSize
				local posVY = math.round(posy * gridSize) / gridSize + 4--((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).Y
				local posVZ = math.round(posz * gridSize) / gridSize
				posV = Vector3.new(posVX, posVY, posVZ)
				blocks.test.Position = posV
				print(posV, "2")
			elseif raycastResult.Normal == raycastResult.Instance.CFrame.LookVector or raycastResult == -raycastResult.Instance.CFrame.LookVector then
				local posVX = math.round(posx * gridSize) / gridSize
				local posVY = math.round(posy * gridSize) / gridSize
				local posVZ = ((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).Z
				posV = Vector3.new(posVX, posVY, posVZ)
				blocks.test.Position = posV
				print(posV, "3")
				end
			end
		end
	end)

For some reason, in

if raycastResult.Normal == raycastResult.Instance.CFrame.RightVector or raycastResult == -raycastResult.Instance.CFrame.RightVector then
				local posVX = ((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).X
				local posVY = math.round(posy * gridSize) / gridSize
				local posVZ = math.round(posz * gridSize) / gridSize
				posV = Vector3.new(posVX, posVY, posVZ)
				blocks.test.Position = posV
				print(posV, "1")

the X value does not change if I hold my mouse over a part that is not “blocktemplate” (It is the same with the Z part, the Z value does not change if I hold my mouse over a part that is not “blocktemplate”)

Here is a video showing what happens (if i click, the block gets placed INSIDE of the brown part): https://gyazo.com/800cf2d64e08d84b56d298e36b604481

Ah shoot, I think it’s because I forgot to add the .Normal property when checking the negative direction vectors, so switch raycastResult == with raycastResult.Normal ==

There is no change, it has the same outcome

Weird, I’ll try it out tomorrow and let you know if I come up with anything. Send me a pm in like 16ish hours if nobody else responds or if you don’t come up with a solution as I might forget

End code looks like

mouse.Move:Connect(function()
		if toolEquipped == true then
			snap()
			local raycastParams = RaycastParams.new()
			raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			raycastParams.FilterDescendantsInstances = {blocks.test, tool, player.Character}
			
			local raycastResult = workspace:Raycast(mouse.UnitRay.Origin, mouse.UnitRay.Direction * 300, raycastParams)
			
			if raycastResult.Instance.Name == "Wood" then
				posV = (raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)
				blocks.test.Position = posV
			else
				if raycastResult.Normal == raycastResult.Instance.CFrame.LookVector then
					local posVX = math.round(posx * gridSize) / gridSize
					local posVY = math.round(posy * gridSize) / gridSize
					local posVZ = math.round(posz * gridSize) / gridSize - 4
					posV = Vector3.new(posVX, posVY, posVZ)
					blocks.test.Position = posV
				elseif raycastResult.Normal == -raycastResult.Instance.CFrame.LookVector then
					local posVX = math.round(posx * gridSize) / gridSize
					local posVY = math.round(posy * gridSize) / gridSize
					local posVZ = math.round(posz * gridSize) / gridSize
					posV = Vector3.new(posVX, posVY, posVZ)
					blocks.test.Position = posV
				elseif raycastResult.Normal == raycastResult.Instance.CFrame.UpVector or raycastResult.Normal == -raycastResult.Instance.CFrame.UpVector then
					local posVX = math.round(posx * gridSize) / gridSize
					local posVY = math.round(posy * gridSize) / gridSize + 4--((raycastResult.Instance.Position) + (blocks.test.Size * raycastResult.Normal)).Y
					local posVZ = math.round(posz * gridSize) / gridSize
					posV = Vector3.new(posVX, posVY, posVZ)
					blocks.test.Position = posV
				elseif raycastResult.Normal == raycastResult.Instance.CFrame.RightVector then
					local posVX = math.round(posx * gridSize) / gridSize - 4
					local posVY = math.round(posy * gridSize) / gridSize
					local posVZ = math.round(posz * gridSize) / gridSize
					posV = Vector3.new(posVX, posVY, posVZ)
					blocks.test.Position = posV
				elseif raycastResult.Normal == -raycastResult.Instance.CFrame.RightVector then
					local posVX = math.round(posx * gridSize) / gridSize 
					local posVY = math.round(posy * gridSize) / gridSize
					local posVZ = math.round(posz * gridSize) / gridSize
					posV = Vector3.new(posVX, posVY, posVZ)
					blocks.test.Position = posV
				end
			end
		end
	end)

If you can’t tell, I solved the problem by separating the “or” statements that you made into their own “if” statements, which allowed me to add or subtract the correct gridsize amount for every axis, so blocks dont get placed inside of terrain and are placed where I want them to be.

Special thanks to: you, cody/7z99 for helping me get this far in the first place
&
kingerman88 for helping me realize this

1 Like