
KeylessUniversal

--[[
StamperTool - LocalScript
Coloque este script em StarterPlayer > StarterPlayerScripts
O que mudou nessa versão:
1) CORRIGIDO: antes, ao clicar no botão, o jogo recalculava a posição
usando o mouse EM CIMA DO BOTÃO, então o bloco aparecia "atrás" dele.
Agora o preview PARA de se mover quando o mouse/dedo está sobre
qualquer parte da interface, então ele fica travado no último lugar
válido no mundo — e é ali que o bloco é colocado.
2) NOVO: uma barra de seleção de textura (terra, grama, pedra, areia,
neve, gelo, madeira, metal, vidro, lava, luz) — clique numa opção
pra escolher o tipo de bloco antes de colocar.
]]
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Workspace = game:GetService("Workspace")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local playerGui = player:WaitForChild("PlayerGui")
-- Se esse script já rodou antes (testou de novo sem parar o jogo, ou tem
-- uma cópia dele em outro lugar), remove a interface antiga primeiro.
-- Isso evita várias cópias transparentes empilhadas por cima uma da
-- outra — o que faria parecer que "tem algo na frente" bloqueando o
-- clique nos botões.
local oldGui = playerGui:FindFirstChild("StamperGui")
if oldGui then
oldGui:Destroy()
end
local oldPreview = Workspace:FindFirstChild("StamperPreview")
if oldPreview then
oldPreview:Destroy()
end
-- ================== CONFIGURAÇÕES ==================
local BLOCK_SIZE = Vector3.new(4, 4, 4)
local USE_GRID_SNAP = true
local GRID_SIZE = 4
local MAX_PLACE_DISTANCE = 100
-- =====================================================
-- ================== TIPOS DE BLOCO (TEXTURAS) ==================
local BLOCK_TYPES = {
{ Name = "Terra", Material = Enum.Material.Ground, Color = Color3.fromRGB(115, 82, 54) },
{ Name = "Grama", Material = Enum.Material.Grass, Color = Color3.fromRGB(74, 130, 51) },
{ Name = "Pedra", Material = Enum.Material.Slate, Color = Color3.fromRGB(128, 128, 128) },
{ Name = "Areia", Material = Enum.Material.Sand, Color = Color3.fromRGB(220, 200, 150) },
{ Name = "Neve", Material = Enum.Material.Snow, Color = Color3.fromRGB(255, 255, 255) },
{ Name = "Gelo", Material = Enum.Material.Ice, Color = Color3.fromRGB(200, 230, 255) },
{ Name = "Madeira", Material = Enum.Material.WoodPlanks, Color = Color3.fromRGB(130, 90, 50) },
{ Name = "Metal", Material = Enum.Material.Metal, Color = Color3.fromRGB(180, 180, 190) },
{ Name = "Vidro", Material = Enum.Material.Glass, Color = Color3.fromRGB(200, 230, 255), Transparency = 0.5 },
{
Name = "Lava", Material = Enum.Material.CrackedLava, Color = Color3.fromRGB(255, 80, 0),
Light = { Color = Color3.fromRGB(255, 100, 0), Range = 12, Brightness = 3 },
},
{
Name = "Luz", Material = Enum.Material.Neon, Color = Color3.fromRGB(255, 255, 200),
Light = { Color = Color3.fromRGB(255, 255, 200), Range = 16, Brightness = 5 },
},
}
local selectedType = BLOCK_TYPES[1]
-- Pasta pra guardar os blocos colocados
local placedFolder = Workspace:FindFirstChild("PlacedBlocks")
if not placedFolder then
placedFolder = Instance.new("Folder")
placedFolder.Name = "PlacedBlocks"
placedFolder.Parent = Workspace
end
-- ================== BLOCO FANTASMA (PREVIEW) ==================
local previewPart = Instance.new("Part")
previewPart.Name = "StamperPreview"
previewPart.Size = BLOCK_SIZE
previewPart.Anchored = true
previewPart.CanCollide = false
previewPart.CanQuery = false
previewPart.Transparency = 0.5
previewPart.Material = Enum.Material.ForceField
previewPart.Color = Color3.fromRGB(0, 255, 100)
previewPart.Parent = Workspace
local isValidPlacement = true
local camera = Workspace.CurrentCamera
local function snap(position)
if not USE_GRID_SNAP then
return position
end
return Vector3.new(
math.floor(position.X / GRID_SIZE + 0.5) * GRID_SIZE,
math.floor(position.Y / GRID_SIZE + 0.5) * GRID_SIZE,
math.floor(position.Z / GRID_SIZE + 0.5) * GRID_SIZE
)
end
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = { previewPart }
local isDeleteMode = false
local lastHitInstance = nil -- a peça (do mundo ou já colocada) que a mira está tocando agora
-- Sistema de "mira central": em vez de depender de onde o mouse/dedo está
-- tocando (que no celular é pouco confiável e conflita com a interface),
-- o preview segue sempre o CENTRO da câmera, tipo uma mira. Pra mirar,
-- o jogador só precisa girar a câmera (arrastar o dedo em qualquer lugar
-- da tela) — não precisa acertar um ponto exato na tela.
RunService.RenderStepped:Connect(function()
local character = player.Character
if not character then
return
end
raycastParams.FilterDescendantsInstances = { previewPart, character }
local origin = camera.CFrame.Position
local direction = camera.CFrame.LookVector * MAX_PLACE_DISTANCE
local result = Workspace:Raycast(origin, direction, raycastParams)
if result then
lastHitInstance = result.Instance
else
lastHitInstance = nil
end
if isDeleteMode then
-- modo apagar: a mira "abraça" o bloco colocado que está na frente,
-- em vez de mostrar um preview de bloco novo
local canDelete = lastHitInstance ~= nil and lastHitInstance:IsDescendantOf(placedFolder)
if canDelete then
previewPart.Size = lastHitInstance.Size + Vector3.new(0.2, 0.2, 0.2)
previewPart.CFrame = lastHitInstance.CFrame
previewPart.Color = Color3.fromRGB(255, 60, 60)
else
previewPart.Size = BLOCK_SIZE
local hitPosition = result and (result.Position + result.Normal * (BLOCK_SIZE.Y / 2))
or (origin + camera.CFrame.LookVector * MAX_PLACE_DISTANCE)
previewPart.CFrame = CFrame.new(snap(hitPosition))
previewPart.Color = Color3.fromRGB(120, 120, 120) -- nada pra apagar aqui
end
isValidPlacement = canDelete
return
end
previewPart.Size = BLOCK_SIZE
local hitPosition
if result then
-- assenta o bloco em cima da superfície atingida (funciona em
-- chão, parede ou teto, seguindo a normal da superfície)
hitPosition = result.Position + result.Normal * (BLOCK_SIZE.Y / 2)
isValidPlacement = true
else
-- nada na frente: mostra o preview no limite da distância máxima,
-- só como indicação visual, e marca como inválido
hitPosition = origin + camera.CFrame.LookVector * MAX_PLACE_DISTANCE
isValidPlacement = false
end
previewPart.CFrame = CFrame.new(snap(hitPosition))
previewPart.Color = isValidPlacement and selectedType.Color or Color3.fromRGB(255, 60, 60)
end)
-- ================== INTERFACE ==================
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "StamperGui"
screenGui.ResetOnSpawn = false
-- IgnoreGuiInset FICA DESLIGADO (false): assim o Roblox empurra nossa
-- interface pra baixo da barra de topo padrão (ícones de menu/chat),
-- em vez de ficar escondida atrás dela.
screenGui.IgnoreGuiInset = false
screenGui.DisplayOrder = -10 -- fica ATRÁS da interface/HUD do jogo, em vez de na frente
screenGui.Parent = playerGui
-- Mira central (mostra pra onde a câmera está apontando)
local reticle = Instance.new("Frame")
reticle.Name = "Reticle"
reticle.Size = UDim2.new(0, 8, 0, 8)
reticle.AnchorPoint = Vector2.new(0.5, 0.5)
reticle.Position = UDim2.new(0.5, 0, 0.5, 0)
reticle.BackgroundColor3 = Color3.new(1, 1, 1)
reticle.BackgroundTransparency = 0.3
reticle.ZIndex = 50
reticle.Parent = screenGui
local reticleCorner = Instance.new("UICorner")
reticleCorner.CornerRadius = UDim.new(1, 0)
reticleCorner.Parent = reticle
-- Botão de colocar bloco (canto superior direito — longe do joystick e do
-- botão de pulo do celular)
local placeButton = Instance.new("TextButton")
placeButton.Name = "PlaceButton"
placeButton.Size = UDim2.new(0, 150, 0, 55)
placeButton.Position = UDim2.new(1, -170, 0, 45)
placeButton.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
placeButton.TextColor3 = Color3.new(1, 1, 1)
placeButton.Font = Enum.Font.GothamBold
placeButton.TextSize = 20
placeButton.Text = "Colocar Bloco"
placeButton.AutoButtonColor = true
placeButton.ZIndex = 50
placeButton.Parent = screenGui
local placeCorner = Instance.new("UICorner")
placeCorner.CornerRadius = UDim.new(0, 12)
placeCorner.Parent = placeButton
-- Botão de alternar modo (Colocar / Apagar), logo abaixo do botão principal
local modeButton = Instance.new("TextButton")
modeButton.Name = "ModeButton"
modeButton.Size = UDim2.new(0, 150, 0, 45)
modeButton.Position = UDim2.new(1, -170, 0, 110)
modeButton.BackgroundColor3 = Color3.fromRGB(60, 60, 60)
modeButton.TextColor3 = Color3.new(1, 1, 1)
modeButton.Font = Enum.Font.GothamBold
modeButton.TextSize = 16
modeButton.Text = "Modo: Colocar"
modeButton.AutoButtonColor = true
modeButton.ZIndex = 50
modeButton.Parent = screenGui
local modeCorner = Instance.new("UICorner")
modeCorner.CornerRadius = UDim.new(0, 12)
modeCorner.Parent = modeButton
-- Barra de seleção de texturas: coluna vertical rolável no MEIO da tela,
-- lado esquerdo — essa é a única região que ficou livre em todos os
-- prints que você mandou (topo tem o HUD do jogo e ícones, lado direito
-- tem o placar, embaixo tem os botões Menu/Skins e os controles)
local textureFrame = Instance.new("ScrollingFrame")
textureFrame.Name = "TextureBar"
textureFrame.Size = UDim2.new(0, 140, 0, 320)
textureFrame.AnchorPoint = Vector2.new(0, 0.5)
textureFrame.Position = UDim2.new(0, 20, 0.5, 0)
textureFrame.ScrollingDirection = Enum.ScrollingDirection.Y
textureFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
textureFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
textureFrame.ScrollBarThickness = 6
textureFrame.ZIndex = 50
textureFrame.BackgroundColor3 = Color3.fromRGB(25, 25, 25)
textureFrame.BackgroundTransparency = 0.1
textureFrame.Parent = screenGui
local textureCorner = Instance.new("UICorner")
textureCorner.CornerRadius = UDim.new(0, 12)
textureCorner.Parent = textureFrame
local listLayout = Instance.new("UIListLayout")
listLayout.FillDirection = Enum.FillDirection.Vertical
listLayout.Padding = UDim.new(0, 8)
listLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
listLayout.Parent = textureFrame
local padding = Instance.new("UIPadding")
padding.PaddingLeft = UDim.new(0, 10)
padding.PaddingRight = UDim.new(0, 10)
padding.PaddingTop = UDim.new(0, 10)
padding.PaddingBottom = UDim.new(0, 10)
padding.Parent = textureFrame
local textureButtons = {}
local function refreshSelection()
for _, entry in ipairs(textureButtons) do
local isSelected = entry.blockType == selectedType
entry.stroke.Thickness = isSelected and 3 or 1
entry.stroke.Color = isSelected and Color3.fromRGB(0, 255, 100) or Color3.fromRGB(255, 255, 255)
end
end
for i, blockType in ipairs(BLOCK_TYPES) do
local button = Instance.new("TextButton")
button.Name = blockType.Name
button.LayoutOrder = i
button.Size = UDim2.new(0, 70, 0, 70)
button.BackgroundColor3 = blockType.Color
button.BackgroundTransparency = 0
button.AutoButtonColor = true
button.Text = blockType.Name
button.TextSize = 13
button.Font = Enum.Font.GothamBold
button.TextColor3 = Color3.new(1, 1, 1)
button.ZIndex = 51
button.TextStrokeTransparency = 0
button.Parent = textureFrame
local btnCorner = Instance.new("UICorner")
btnCorner.CornerRadius = UDim.new(0, 10)
btnCorner.Parent = button
local stroke = Instance.new("UIStroke")
stroke.Thickness = 1
stroke.Color = Color3.fromRGB(255, 255, 255)
stroke.Parent = button
-- .Activated funciona de forma confiável com clique E com toque,
-- inclusive dentro de uma ScrollingFrame (MouseButton1Click às vezes
-- falhava no celular se o dedo tremesse um pouco durante o toque)
button.Activated:Connect(function()
selectedType = blockType
refreshSelection()
end)
table.insert(textureButtons, { button = button, stroke = stroke, blockType = blockType })
end
refreshSelection()
-- ================== ALTERNAR MODO (COLOCAR / APAGAR) ==================
modeButton.Activated:Connect(function()
isDeleteMode = not isDeleteMode
if isDeleteMode then
modeButton.Text = "Modo: Apagar"
modeButton.BackgroundColor3 = Color3.fromRGB(120, 40, 40)
placeButton.Text = "Apagar Bloco"
placeButton.BackgroundColor3 = Color3.fromRGB(120, 40, 40)
else
modeButton.Text = "Modo: Colocar"
modeButton.BackgroundColor3 = Color3.fromRGB(60, 60, 60)
placeButton.Text = "Colocar Bloco"
placeButton.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
end
end)
-- ================== FUNÇÃO DO BOTÃO PRINCIPAL (COLOCAR OU APAGAR) ==================
placeButton.Activated:Connect(function()
if isDeleteMode then
-- só apaga se o que está na mira for um bloco que essa ferramenta colocou
if lastHitInstance and lastHitInstance:IsDescendantOf(placedFolder) then
lastHitInstance:Destroy()
end
return
end
if not isValidPlacement then
return
end
local newBlock = Instance.new("Part")
newBlock.Size = BLOCK_SIZE
newBlock.CFrame = previewPart.CFrame
newBlock.Anchored = true
newBlock.CanCollide = true
newBlock.Material = selectedType.Material
newBlock.Color = selectedType.Color
newBlock.Transparency = selectedType.Transparency or 0
newBlock.Parent = placedFolder
-- se o tipo escolhido tem luz (lava, bloco de luz), adiciona o PointLight
if selectedType.Light then
local light = Instance.new("PointLight")
light.Color = selectedType.Light.Color
light.Range = selectedType.Light.Range
light.Brightness = selectedType.Light.Brightness
light.Parent = newBlock
end
end)No comments yet for this script.



