Hi, I’m currently trying to make a building system but I ran into a problem recently. Sometimes when I place I start to place down a build the build will go inside of something instead of resting on the side or the top of it. Here’s what I mean:
As you can see it works until a go to a certain angle and that it starts to phase through the other blocks. I don’t know why this is happening but if somebody could help me that would be great.
Code:
local mouseRay = mouse.UnitRay
local rayCastParams = RaycastParams.new()
rayCastParams.FilterType = Enum.RaycastFilterType.Blacklist
rayCastParams.FilterDescendantsInstances = {GhostItem, player.Character}
local castRay = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 500, rayCastParams)
local ignoreList = {GhostItem, player.Character}
if castRay then
local PosX = math.floor(castRay.Position.X / GridSize) * GridSize + GhostItem.PrimaryPart.Size.X/2
local PosY = math.floor(castRay.Position.Y / GridSize) * GridSize + GhostItem.PrimaryPart.Size.Y/2
local PosZ = math.floor(castRay.Position.Z / GridSize) * GridSize + GhostItem.PrimaryPart.Size.Z/2
GhostItem:SetPrimaryPartCFrame(CFrame.new(PosX,PosY,PosZ))
end
Since you’re using math.floor, the blocks will tend to go in in the negative axis direction.
math.floor(castRay.Position.X / GridSize + 0.5)may alleviate the issue somewhat, but I doubt it.
A more proper solution would use the RaycastResult.Normal value as well, to place the block in the correct adjacent grid space.
Ideally, you would figure out which grid space the block you’re pointing at is on, and then figure out which direction the face you clicked is facing, and get the adjacent block like that.
A hackier solution that may not work for all edge cases would be to just add the normal vector to your hit position (and also maybe round instead of floor? Dunno):
I was searching around and the thought of putting castRay.Normal into the code popped up and that might help or solve the issue. Though I don’t know how I would include castRay.Normal into the code to actually make it work. The code you provided just produced the same issue.
Calculate the position on a grid “edge” that you’re closest to
Figure out which axis direction the normal is in
Offset your position based on that.
Something like this (untested but the comments should push you in the right direction):
-- returns 0 if x is 0, 1 if x > 0, -1 if x < 0
local function sign(x)
if (x == 0) then return 0; end
if (x > 0) then return 1; end
return - 1;
end
-- get the axis-aligned unit vector that vec is closest to
function SnapToAxis(vec)
local lx = math.abs(vec.X)
local ly = math.abs(vec.Y)
local lz = math.abs(vec.Z)
if (lx > ly and lx > lz) then
return Vector3.new(sign(vec.X), 0, 0);
elseif (ly > lx and ly > lz) then
return Vector3.new(0, sign(vec.Y), 0);
else
return Vector3.new(0, 0, sign(vec.Z));
end
end
-- Given a grid size, part size, mouse position, and hit normal, return the
-- center position of the part in the adjacent cell.
-- Note: This will align parts by their centers. That may or may not be what you
-- want.
local function GetPlacementPosition(gridSize, partSize, position, normal)
-- 1. find the grid corner that the mouse is closest to
local gridCorner = 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
)
-- 2. get the axis that the surface we're touching is closest to
local snappedNormal = SnapToAxis(normal)
-- 3. from the corner, move the part outwards in the direction of the axis
-- by half its size on that axis
return gridCorner + partSize * snappedNormal / 2
end
edit: Instead of using the Normal to snap, it might make more sense to use (mouse position - part hit position). They’d be the same for simple parts, but it might be useful if you start placing whole models or more complex parts.
Even with the notes, I’m extremely confused on where I would call these functions in my script. I barely even know what half of that means, I’m only comfortable with basic math functions. Could you dumb it down a little bit for me to understand it? (I’m really sorry )
No problem, it’s a bunch of lines of code to dump on you.
You would just call the main function here:
if castRay then
local pos = GetPlacementPosition(GridSize, GhostItem.PrimaryPart.Size, castRay.Position, castRay.Normal)
GhostItem:SetPrimaryPartCFrame(CFrame.new(pos))
end
sign(x) is pretty straightforward, just “return the sign (+1, -1, or 0) of this number”.
SnapToAxis just returns a vector 3, aligned to one of the six axis directions, that the given vector is the closest to pointing to. Basically like “if the x component of this vector is the longest one, we’re closest to the x-axis, so use the sign of the X component to figure out if its +X or -X, and return (1, 0, 0) or (-1, 0, 0).”
GetPlacementPosition is basically the same code you have – round to the nearest grid point – and then it just nudges the position in the direction of the normal.
Although this is solved I have one more issue, the part doesn’t rotate correctly. This happens because in the new code you use CFrame.new and for it to be able to rotate it can not use CFrame.new. Here’s the old code for it to keep the rotation whenever the CFrame changes:
if castRay and GhostItem then
local PosX = math.floor(castRay.Position.X / GridSize) * GridSize + GhostItem.PrimaryPart.Size.X/2
local PosY = math.floor(castRay.Position.Y / GridSize) * GridSize + GhostItem.PrimaryPart.Size.Y/1.5
local PosZ = math.floor(castRay.Position.Z / GridSize) * GridSize + GhostItem.PrimaryPart.Size.Z/2
local primaryCf = GhostItem:GetPrimaryPartCFrame()
GhostItem:SetPrimaryPartCFrame(primaryCf-primaryCf.Position+Vector3.new(PosX, PosY, PosZ))
end
And here’s the new code:
if castRay then
local pos = GetPlacementPosition(GridSize, GhostItem.PrimaryPart.Size, castRay.Position, castRay.Normal)
GhostItem:SetPrimaryPartCFrame(CFrame.new(pos))
local primaryCf = GhostItem:GetPrimaryPartCFrame()
GhostItem:SetPrimaryPartCFrame(GhostItem.PrimaryPart.CFrame + Vector3.new(primaryCf-primaryCf.Position))
end
This code will produce the issue below:
Because the whole part moving code is wrapped inside of a Mouse.Move function whenever the mouse moves it will set the parts orientation back to normal orientation because in the new code you used CFrame.new. How would I fix this issue?
Edit: After a lot more time than it should’ve been, I fixed it.