Hello,
I have followed a tutorial to create a grid based block building system. It works great, however, it does not work properly on terrain. The issue I am having is on terrain, it sometimes follows the grid and sometimes it doesn’t. For example, if I try to place a block, it will be off the grid and will not work. If I move the mouse around, I can find a spot that does snap to the grid properly and from there I can place the block. Here are some examples (sorry for the weird contrast):
Here is when it is off grid:
Notice the highlighted text in console. The value after Preview Position is where the preview block is currently showing up. I cannot place the block here.
In this image, notice that the highlight text is now rounded properly. I can place the block here.
This rounding issue does not occur on regular parts. Only on terrain. Does anyone have any ideas? I will link the entire code used.
Local/Tool script:
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local blockTemplate = ReplicatedStorage:WaitForChild("Block")
local placeBlockEvent = ReplicatedStorage:WaitForChild("PlaceBlock")
local PlacementValidator = require(ReplicatedStorage:WaitForChild("PlacementValidator"))
local camera = workspace.CurrentCamera
local player = game.Players.LocalPlayer
local char = player.Character
local tool = script.Parent.Parent
local castParams = RaycastParams.new()
castParams:AddToFilter(char)
local preview = nil
local function preparePreviewPart(part)
preview = blockTemplate:Clone()
preview.Transparency = 0.5
preview.CanCollide = false
preview.CanQuery = false
preview.Parent = workspace
preview.CastShadow = false
preview.Anchored = true
end
local function renderPreview()
local mouseLocation = UserInputService:GetMouseLocation()
local unitRay = camera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
local cast = workspace:Raycast(unitRay.Origin, unitRay.Direction * 1000, castParams)
if cast and preview then
local snappedPosition = PlacementValidator.SnapToGrid(cast.Position)
local normalCF = CFrame.lookAlong(cast.Position, cast.Normal)
local relativeSnapped = normalCF:PointToObjectSpace(snappedPosition)
local xVector = normalCF:VectorToWorldSpace(Vector3.xAxis * -math.sign(relativeSnapped.X))
local yVector = normalCF:VectorToWorldSpace(Vector3.yAxis * -math.sign(relativeSnapped.Y))
local cf = CFrame.fromMatrix(snappedPosition, xVector, yVector, cast.Normal)
preview.Position = cf:PointToWorldSpace(blockTemplate.Size/2)
print('Cast Position ' ..tostring(cast.Position)..' Snapped Position '..tostring(snappedPosition)..' Preview Position '..tostring(preview.Position))
end
end
tool.Equipped:Connect(function()
preparePreviewPart(blockTemplate)
RunService:BindToRenderStep("Preview", Enum.RenderPriority.Camera.Value, renderPreview)
end)
tool.Unequipped:Connect(function()
RunService:UnbindFromRenderStep("Preview")
preview:Destroy()
preview = nil
end)
tool.Activated:Connect(function()
placeBlockEvent:FireServer(preview.Position)
end)
Server Script:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local placeBlockEvent = ReplicatedStorage.PlaceBlock
local blockTemplate = ReplicatedStorage.Block
local PlacementValidator = require(ReplicatedStorage.PlacementValidator)
local function placeBlock(player, position)
if not PlacementValidator.IsWithinMaxDistance(player, position) or not PlacementValidator.IsWithinGrid(position) then
return
end
local block = blockTemplate:Clone()
block.Position = position
block.Parent = workspace
end
placeBlockEvent.OnServerEvent:Connect(placeBlock)
Module Script:
local MAX_DISTANCE = 25
local GRID_SIZE = 4
local PlacementValidator = {}
function PlacementValidator.IsWithinMaxDistance(player, position)
local playerPos = player.Character and player.Character:GetPivot().Position
if not playerPos then
return false
end
print("Passed Max Distance Check")
return (position - playerPos).Magnitude <= MAX_DISTANCE
end
function PlacementValidator.SnapToGrid(pos,)
return Vector3.new(
pos.X // GRID_SIZE,
pos.Y // GRID_SIZE,
pos.Z // GRID_SIZE
) * GRID_SIZE
end
function PlacementValidator.IsWithinGrid(pos)
local blockPos = PlacementValidator.SnapToGrid(pos) + Vector3.one * GRID_SIZE/2
print("Passed Grid Check")
return blockPos:FuzzyEq(pos)
end
return PlacementValidator