Roblox · Песочница · урок 2
Как в Minecraft: выбираешь блок, под курсором появляется полупрозрачный призрак по сетке, R поворачивает, клик ставит.
Синим — что добавляем на этом уроке.
ReplicatedStorage ├─ ObjectFolder (Folder) — образцы блоков │ └─ Wood, Stone, Glass, Fence (Model) — PrimaryPart = MainPart │ └─ MainPart (Part) — невидимая коробка вокруг, CanCollide выкл. └─ PlacementEvent (RemoteEvent) Workspace └─ Placed (Folder) — поставленные блоки ServerScriptService └─ PlacementServer (Script) StarterGui └─ PlacementGui (ScreenGui) — ResetOnSpawn выкл. ├─ Frame (Frame) — кнопки Wood, Stone, Glass, Fence └─ PlacementLocal (LocalScript)
Где: StarterGui → PlacementGui · LocalScript
Зачем: Призрак, сетка, поворот, клик.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- клавиатура
local UIS = game:GetService("UserInputService")
-- каждый кадр
local RunService = game:GetService("RunService")
-- этот игрок
local player = Players.LocalPlayer
-- куда указывает курсор
local mouse = player:GetMouse()
-- просим сервер поставить блок
local placementEvent = RS:WaitForChild("PlacementEvent")
-- образцы блоков
local objects = RS:WaitForChild("ObjectFolder")
-- кнопки блоков
local frame = script.Parent:WaitForChild("Frame")
-- шаг сетки — размер блока
local GRID = 4
-- полупрозрачный блок под курсором
local preview = nil
-- какой блок ставим
local currentName = nil
-- поворот в градусах
local rotation = 0
-- закончить размещение
local function Stop()
-- убираем призрак
if preview then preview:Destroy() end
-- ничего не ставим
preview = nil
-- блока нет
currentName = nil
-- конец функции
end
-- начать ставить блок
local function Start(name)
-- старый призрак убираем
Stop()
-- образец
local object = objects:FindFirstChild(name)
-- такого блока нет
if not object then return end
-- копия только у этого игрока
preview = object:Clone()
-- все детали
for _, part in ipairs(preview:GetDescendants()) do
-- деталь
if part:IsA("BasePart") then
-- полупрозрачная (MainPart так и остаётся невидимой)
part.Transparency = math.max(part.Transparency, 0.6)
-- сквозь
part.CanCollide = false
-- курсор её не замечает
part.CanQuery = false
-- конец проверки
end
-- конец перебора
end
-- показываем
preview.Parent = workspace
-- mouse.Hit смотрит сквозь призрак
mouse.TargetFilter = preview
-- запоминаем
currentName = name
-- конец функции
end
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- ничего не ставим
if not preview or not preview.PrimaryPart then return end
-- точка под курсором
local hit = mouse.Hit.Position
-- по сетке
local x = math.floor(hit.X / GRID + 0.5) * GRID
-- по сетке
local z = math.floor(hit.Z / GRID + 0.5) * GRID
-- стоит на поверхности, а не утоплен
local y = hit.Y + preview.PrimaryPart.Size.Y / 2
-- двигаем призрак
preview:PivotTo(CFrame.new(x, y, z) * CFrame.Angles(0, math.rad(rotation), 0))
-- конец функции
end)
-- нажали клавишу — подписка одна
UIS.InputBegan:Connect(function(input, processed)
-- печатаем в чате
if processed then return end
-- R
if input.KeyCode == Enum.KeyCode.R then
-- поворот на 45°
rotation = (rotation + 45) % 360
-- Q
elseif input.KeyCode == Enum.KeyCode.Q then
-- отмена
Stop()
-- клик по миру (клик по кнопке отсеян processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton1 and preview then
-- сервер решает, можно ли
placementEvent:FireServer(currentName, preview:GetPivot())
-- призрак убираем
Stop()
-- конец проверки
end
-- конец функции
end)
-- кнопки блоков
for _, button in ipairs(frame:GetChildren()) do
-- только кнопки
if button:IsA("TextButton") then
-- имя кнопки = имя блока
button.MouseButton1Click:Connect(function() Start(button.Name) end)
-- конец проверки
end
-- конец перебора
endГде: ServerScriptService · Script
Зачем: Ставит блок, если можно.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- теги
local CS = game:GetService("CollectionService")
-- клиент просит поставить
local placementEvent = RS:WaitForChild("PlacementEvent")
-- образцы
local objects = RS:WaitForChild("ObjectFolder")
-- куда ставим постройки
local placed = workspace:WaitForChild("Placed")
-- дальше ставить нельзя
local MAX_DISTANCE = 40
-- клиент прислал блок и место
placementEvent.OnServerEvent:Connect(function(player, name, cf)
-- пришёл мусор
if typeof(name) ~= "string" or typeof(cf) ~= "CFrame" then return end
-- образец
local sample = objects:FindFirstChild(name)
-- нет такого или слишком далеко
if not sample or player:DistanceFromCharacter(cf.Position) > MAX_DISTANCE then return end
-- копия
local object = sample:Clone()
-- на место
object:PivotTo(cf)
-- в мир — теги копируются, блок можно сломать
object.Parent = placed
-- конец функции
end)Запусти Play (или Run) и убедись: