Roblox · Песочница · урок 11
Итог курса на одной странице: полная структура песочницы и финальные версии всех скриптов. Удобно сверить свою игру или собрать её заново.
Синим — что добавляем на этом уроке.
Workspace — StreamingEnabled выкл. ├─ Terrain (Terrain) — земля и холмы — копается лопатой ├─ Nature (Folder) — Tree, Rock: тег Breakable, атрибуты Tool, Drop, Respawn; MainPart → Health; HitsToBreak ├─ Placed (Folder) — поставленные блоки ├─ Workbench (Model) — Table → ProximityPrompt → OpenCraft ├─ AppleTree (Model) — Apples, PromptPart → ProximityPrompt → TakingFood ├─ House (Model) — Bed → BedPart → ProximityPrompt, BedScript ├─ Zombie (Model) — атрибуты Damage, WalkSpeed, SprintSpeed; EnemyScript └─ Waypoints (Folder) — точки патруля ReplicatedStorage ├─ ObjectFolder (Folder) — Wood, Stone, Glass, Fence — тег Breakable, MainPart, HitsToBreak ├─ Items (ModuleScript) ├─ PlacementEvent, CraftOpenEvent, SleepEvent, TimeEvent (RemoteEvent) └─ CraftFunction (RemoteFunction) ServerStorage ├─ Tools (Folder) — Pickaxe, Axe, Shovel └─ Food (Folder) — Apple ServerScriptService ├─ Inventory (ModuleScript) └─ InventoryServer, PlacementServer, BreakServer, CraftServer, HungerServer, DayNight (Script) StarterPack └─ Pickaxe, Axe (Tool) — Handle, Mining, Wear, ToolScript StarterPlayer → StarterCharacterScripts └─ Run, Crouch, Fly (LocalScript) StarterGui — все ResetOnSpawn выкл. ├─ InventoryGui (ScreenGui) — MainFrame → ItemFrame → Sample; InventoryLocal ├─ CraftGui (ScreenGui) — MainFrame → RecipesFrame, CraftFrame; CraftLocal ├─ HungerGui, StaminaGui (ScreenGui) — Background → Bar ├─ SleepGui (ScreenGui) — SleepFrame → Yes, No; SleepLocal └─ FaqGui (ScreenGui) — OpenFaq, FaqFrame → ListFrame, InfoFrame; FaqScript Players → игрок (во время игры) — Inventory (IntValue-счётчики), Hunger
Где: ServerScriptService · ModuleScript
Зачем: Счётчики предметов.
-- таблица модуля
local Inventory = {}
-- папка инвентаря игрока
local function Folder(player)
-- создаёт InventoryServer
return player:FindFirstChild("Inventory")
-- конец функции
end
-- сколько у игрока предмета
function Inventory.Count(player, name)
-- инвентарь
local folder = Folder(player)
-- ячейка
local slot = folder and folder:FindFirstChild(name)
-- число или 0
return slot and slot.Value or 0
-- конец функции
end
-- добавить
function Inventory.Add(player, name, amount)
-- инвентарь
local folder = Folder(player)
-- нет инвентаря
if not folder then return end
-- ячейка
local slot = folder:FindFirstChild(name)
-- такого ещё нет
if not slot then
-- новая ячейка-счётчик
slot = Instance.new("IntValue")
-- имя = предмет
slot.Name = name
-- в инвентарь
slot.Parent = folder
-- конец проверки
end
-- прибавляем
slot.Value += amount
-- конец функции
end
-- забрать; false — не хватает
function Inventory.Take(player, name, amount)
-- не хватает
if Inventory.Count(player, name) < amount then return false end
-- ячейка
local slot = Folder(player)[name]
-- вычитаем
slot.Value -= amount
-- закончился — ячейка пропадает
if slot.Value <= 0 then slot:Destroy() end
-- получилось
return true
-- конец функции
end
-- отдаём модуль
return InventoryГде: ServerScriptService · Script
Зачем: Папка инвентаря.
-- игроки
local Players = game:GetService("Players")
-- игрок зашёл
local function Setup(player)
-- инвентарь
local folder = Instance.new("Folder")
-- имя — его ищут скрипты
folder.Name = "Inventory"
-- в игрока — клиент видит свои предметы
folder.Parent = player
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) 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
-- инвентарь
local Inventory = require(game:GetService("ServerScriptService"):WaitForChild("Inventory"))
-- клиент прислал блок и место
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
-- блока нет в инвентаре — не ставим
if not Inventory.Take(player, name, 1) then return end
-- копия
local object = sample:Clone()
-- на место
object:PivotTo(cf)
-- в мир — теги копируются, блок можно сломать
object.Parent = placed
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Разрушение и добыча.
-- теги: все ломаемые объекты помечены «Breakable»
local CS = game:GetService("CollectionService")
-- игроки
local Players = game:GetService("Players")
-- тег
local TAG = "Breakable"
-- через сколько секунд вырастает дерево или камень
local RESPAWN = 60
-- инвентарь
local Inventory = require(game:GetService("ServerScriptService"):WaitForChild("Inventory"))
-- настраиваем один ломаемый объект
local function Setup(model)
-- образец в хранилище или уже настроен
if not model:IsDescendantOf(workspace) or model:GetAttribute("Ready") then return end
-- MainPart — невидимая коробка вокруг объекта
local main = model:IsA("Model") and model.PrimaryPart
-- сколько ударов выдержит
local hits = model:FindFirstChild("HitsToBreak")
-- объект собран неправильно
if not main or not hits then return end
-- копия — чтобы вырастить снова
local sample = model:Clone()
-- настроен
model:SetAttribute("Ready", true)
-- полная прочность
local maxHits = hits.Value
-- полоска прочности
local gui = main:FindFirstChild("Health")
-- пока не били — не видно
if gui then gui.Enabled = false end
-- инструмент → номер последнего засчитанного взмаха
local lastSwing = {}
-- чего-то коснулись
main.Touched:Connect(function(hit)
-- инструмент
local tool = hit:FindFirstAncestorOfClass("Tool")
-- идёт ли удар
local mining = tool and tool:FindFirstChild("Mining")
-- не удар или не тот инструмент
if not mining or not mining.Value or tool.Name ~= model:GetAttribute("Tool") then return end
-- номер взмаха
local swing = tool:GetAttribute("Swing")
-- этот взмах уже засчитан
if lastSwing[tool] == swing then return end
-- запоминаем
lastSwing[tool] = swing
-- удар
hits.Value -= 1
-- прочность инструмента
local wear = tool:FindFirstChild("Wear")
-- изнашивается
if wear then wear.Value -= 1 end
-- полоска есть
if gui then
-- показываем
gui.Enabled = true
-- сколько осталось
gui.Green.Size = UDim2.fromScale(math.max(hits.Value, 0) / maxHits, 1)
-- конец проверки
end
-- ещё держится
if hits.Value > 0 then return end
-- кто сломал
local player = Players:GetPlayerFromCharacter(tool.Parent)
-- добыча — в инвентарь
if player then Inventory.Add(player, model:GetAttribute("Drop") or model.Name, 1) end
-- где стоял
local parent = model.Parent
-- сломан
model:Destroy()
-- природный объект
if model:GetAttribute("Respawn") then
-- через минуту вырастет снова
task.delay(RESPAWN, function() sample:Clone().Parent = parent end)
-- конец проверки
end
-- конец функции
end)
-- конец функции
end
-- все, что уже есть
for _, model in ipairs(CS:GetTagged(TAG)) do Setup(model) end
-- новые: поставленные блоки, выросшие деревья
CS:GetInstanceAddedSignal(TAG):Connect(Setup)Где: ServerScriptService · Script
Зачем: Крафт.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- образцы инструментов
local SS = game:GetService("ServerStorage")
-- рецепты
local Items = require(RS:WaitForChild("Items"))
-- инвентарь
local Inventory = require(game:GetService("ServerScriptService"):WaitForChild("Inventory"))
-- запрос крафта
local craftFunction = RS:WaitForChild("CraftFunction")
-- инструменты, которые можно скрафтить
local tools = SS:WaitForChild("Tools")
-- игрок нажал «Скрафтить»
craftFunction.OnServerInvoke = function(player, item)
-- рецепт
local recipe = typeof(item) == "string" and Items.Recipes[item]
-- нет
if not recipe then return false, "Нет такого рецепта" end
-- проверяем каждый ингредиент
for name, amount in pairs(recipe) do
-- не хватает
if Inventory.Count(player, name) < amount then
-- ответ
return false, "Не хватает: " .. (Items.Names[name] or name)
-- конец проверки
end
-- конец перебора
end
-- забираем ингредиенты
for name, amount in pairs(recipe) do Inventory.Take(player, name, amount) end
-- это инструмент?
local tool = tools:FindFirstChild(item)
-- да
if tool then
-- в рюкзак
tool:Clone().Parent = player.Backpack
-- блок
else
-- в инвентарь
Inventory.Add(player, item, 1)
-- конец проверки
end
-- ответ
return true, "Создано: " .. (Items.Names[item] or item)
-- конец функции
endГде: ServerScriptService · Script
Зачем: Сытость.
-- игроки
local Players = game:GetService("Players")
-- раз в сколько секунд голод растёт
local FREQUENCY = 4
-- на сколько падает сытость
local DECREASE = 2
-- урон от голода, когда сытость 0
local STARVE_DAMAGE = 5
-- игрок зашёл
local function Setup(player)
-- сытость
local hunger = Instance.new("IntValue")
-- имя — его читают интерфейс и еда
hunger.Name = "Hunger"
-- сыт
hunger.Value = 100
-- в игрока
hunger.Parent = player
-- после возрождения — снова сыт
player.CharacterAdded:Connect(function() hunger.Value = 100 end)
-- пока игрок в игре
while player.Parent do
-- ждём
task.wait(FREQUENCY)
-- его Humanoid
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
-- жив
if humanoid and humanoid.Health > 0 then
-- ещё не голоден
if hunger.Value > 0 then
-- сытость падает
hunger.Value = math.max(hunger.Value - DECREASE, 0)
-- голоден
else
-- здоровье понемногу уходит
humanoid:TakeDamage(STARVE_DAMAGE)
-- конец проверки
end
-- конец проверки
end
-- конец цикла
end
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) endГде: ServerScriptService · Script
Зачем: Сутки.
-- освещение
local Lighting = game:GetService("Lighting")
-- часов за секунду: сутки — 8 минут
local STEP = 0.05
-- бесконечно
while true do
-- секунда
task.wait(1)
-- время идёт, после 24 — снова 0
Lighting.ClockTime = (Lighting.ClockTime + STEP) % 24
-- конец цикла
endГде: ReplicatedStorage · ModuleScript
Зачем: Названия и рецепты.
-- таблица модуля
local Items = {}
-- как предметы называются для игрока
Items.Names = {
-- блоки
Wood = "Дерево", Stone = "Камень", Glass = "Стекло", Fence = "Забор",
-- инструменты
Pickaxe = "Кирка", Axe = "Топор", Shovel = "Лопата",
-- конец таблицы
}
-- рецепты: предмет → что нужно
Items.Recipes = {
-- стекло из камня
Glass = {Stone = 2},
-- забор из дерева
Fence = {Wood = 2},
-- лопата
Shovel = {Wood = 2},
-- топор
Axe = {Wood = 2, Stone = 1},
-- кирка
Pickaxe = {Wood = 1, Stone = 2},
-- конец таблицы
}
-- отдаём модуль
return ItemsГде: StarterGui → InventoryGui → MainFrame · 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 Items = require(RS:WaitForChild("Items"))
-- предметы игрока
local inventory = player:WaitForChild("Inventory")
-- MainFrame — окно инвентаря
local frame = script.Parent
-- список ячеек
local itemFrame = frame:WaitForChild("ItemFrame")
-- шаблон ячейки
local sample = itemFrame:WaitForChild("Sample")
-- I — открыть или закрыть инвентарь
local TOGGLE_KEY = Enum.KeyCode.I
-- шаг сетки — размер блока
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()
-- I
elseif input.KeyCode == TOGGLE_KEY then
-- инвентарь открыть / закрыть
frame.Visible = not frame.Visible
-- клик по миру (клик по кнопке отсеян processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton1 and preview then
-- сервер решает, можно ли
placementEvent:FireServer(currentName, preview:GetPivot())
-- призрак убираем
Stop()
-- конец проверки
end
-- конец функции
end)
-- перерисовать ячейки инвентаря
local function Refresh()
-- старые ячейки
for _, card in ipairs(itemFrame:GetChildren()) do
-- убираем (шаблон оставляем)
if card:IsA("ImageButton") and card ~= sample then card:Destroy() end
-- конец перебора
end
-- каждый предмет
for _, slot in ipairs(inventory:GetChildren()) do
-- копия шаблона
local card = sample:Clone()
-- имя
card.Name = slot.Name
-- название по-русски
card.ItemName.Text = Items.Names[slot.Name] or slot.Name
-- сколько
card.Count.Text = "× " .. slot.Value
-- блок, который можно поставить
local object = objects:FindFirstChild(slot.Name)
-- картинка (Texture внутри блока)
local icon = object and object:FindFirstChild("Texture")
-- на кнопку
card.Image = icon and icon.Texture or ""
-- шаблон был скрыт
card.Visible = true
-- в список
card.Parent = itemFrame
-- клик — ставить этот блок
card.MouseButton1Click:Connect(function() Start(slot.Name) end)
-- конец перебора
end
-- конец функции
end
-- новый предмет
inventory.ChildAdded:Connect(function(slot)
-- число изменилось — перерисовать
slot.Changed:Connect(Refresh)
-- сразу
Refresh()
-- конец функции
end)
-- предмет закончился
inventory.ChildRemoved:Connect(Refresh)
-- предметы, что уже есть
for _, slot in ipairs(inventory:GetChildren()) do slot.Changed:Connect(Refresh) end
-- сразу при входе
Refresh()Где: StarterGui → CraftGui → MainFrame · LocalScript
Зачем: Меню крафта.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- рецепты и названия
local Items = require(RS:WaitForChild("Items"))
-- верстак позвал
local openEvent = RS:WaitForChild("CraftOpenEvent")
-- попросить сервер скрафтить
local craftFunction = RS:WaitForChild("CraftFunction")
-- окно крафта
local mainFrame = script.Parent
-- список рецептов
local recipesFrame = mainFrame:WaitForChild("RecipesFrame")
-- шаблон кнопки рецепта
local sample = recipesFrame:WaitForChild("Sample")
-- один рецепт
local craftFrame = mainFrame:WaitForChild("CraftFrame")
-- что сейчас крафтим
local selected = nil
-- открыть рецепт
local function Show(item)
-- запоминаем
selected = item
-- название
craftFrame.Object.Text = Items.Names[item] or item
-- строчки ингредиентов
local parts = {}
-- каждый ингредиент
for name, amount in pairs(Items.Recipes[item]) do
-- «Дерево × 2»
table.insert(parts, (Items.Names[name] or name) .. " × " .. amount)
-- конец перебора
end
-- список
craftFrame.Ingredients.Text = "Нужно: " .. table.concat(parts, ", ")
-- без старых сообщений
craftFrame.Message.Text = ""
-- прячем список
recipesFrame.Visible = false
-- показываем рецепт
craftFrame.Visible = true
-- конец функции
end
-- кнопка на каждый рецепт — сама
for item in pairs(Items.Recipes) do
-- копия шаблона
local button = sample:Clone()
-- имя
button.Name = item
-- название по-русски
button.Text = Items.Names[item] or item
-- шаблон был скрыт
button.Visible = true
-- в список
button.Parent = recipesFrame
-- клик — рецепт
button.MouseButton1Click:Connect(function() Show(item) end)
-- конец перебора
end
-- «Скрафтить» — подписка одна
craftFrame.CraftButton.MouseButton1Click:Connect(function()
-- ничего не выбрано
if not selected then return end
-- сервер проверит и скрафтит
local _, text = craftFunction:InvokeServer(selected)
-- «Создано» или «Не хватает»
craftFrame.Message.Text = text
-- конец функции
end)
-- назад к рецептам
craftFrame.BackButton.MouseButton1Click:Connect(function()
-- прячем рецепт
craftFrame.Visible = false
-- показываем список
recipesFrame.Visible = true
-- конец функции
end)
-- закрыть
mainFrame.CloseButton.MouseButton1Click:Connect(function() mainFrame.Visible = false end)
-- верстак позвал
openEvent.OnClientEvent:Connect(function()
-- сначала список
craftFrame.Visible = false
-- рецептов
recipesFrame.Visible = true
-- открываем
mainFrame.Visible = true
-- конец функции
end)Где: StarterGui → HungerGui · LocalScript
Зачем: Полоса сытости.
-- игроки
local Players = game:GetService("Players")
-- сытость
local hunger = Players.LocalPlayer:WaitForChild("Hunger")
-- полоса (растёт снизу вверх)
local bar = script.Parent:WaitForChild("Background"):WaitForChild("Bar")
-- обновить полосу
local function Update()
-- высота = сытость
bar.Size = UDim2.fromScale(1, math.clamp(hunger.Value / 100, 0, 1))
-- конец функции
end
-- изменилась
hunger.Changed:Connect(Update)
-- сразу
Update()Где: StarterGui → SleepGui → SleepFrame · LocalScript
Зачем: Сон.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- плавное затемнение
local TS = game:GetService("TweenService")
-- эффекты экрана
local Lighting = game:GetService("Lighting")
-- окно «Лечь спать?»
local frame = script.Parent
-- кровать спросила
local sleepEvent = RS:WaitForChild("SleepEvent")
-- сказать серверу «Да»
local timeEvent = RS:WaitForChild("TimeEvent")
-- сначала скрыто
frame.Visible = false
-- лёг ночью
sleepEvent.OnClientEvent:Connect(function()
-- спрашиваем
frame.Visible = true
-- конец функции
end)
-- «Да» — подписка одна
frame:WaitForChild("Yes").MouseButton1Click:Connect(function()
-- прячем окно
frame.Visible = false
-- свой эффект — даже если в Lighting уже есть другой
local fade = Instance.new("ColorCorrectionEffect")
-- имя
fade.Name = "SleepFade"
-- только у этого игрока
fade.Parent = Lighting
-- экран темнеет
local dark = TS:Create(fade, TweenInfo.new(2), {Brightness = -1})
-- засыпаем
dark:Play()
-- ждём
dark.Completed:Wait()
-- сервер ставит утро
timeEvent:FireServer()
-- спим
task.wait(1)
-- светлеет
local light = TS:Create(fade, TweenInfo.new(2), {Brightness = 0})
-- просыпаемся
light:Play()
-- ждём
light.Completed:Wait()
-- эффект больше не нужен
fade:Destroy()
-- конец функции
end)
-- «Нет»
frame:WaitForChild("No").MouseButton1Click:Connect(function() frame.Visible = false end)Где: StarterGui → FaqGui → FaqFrame · LocalScript
Зачем: Подсказки.
-- окно подсказок
local faqFrame = script.Parent
-- кнопка «?»
local openButton = faqFrame.Parent:WaitForChild("OpenFaq")
-- список тем (в методичке по ошибке FaqList)
local listFrame = faqFrame:WaitForChild("ListFrame")
-- текст темы
local infoFrame = faqFrame:WaitForChild("InfoFrame")
-- тема → подсказка; имя кнопки = ключ
local INFO = {
-- клавиши
Keys = "Shift — бег, C — присесть, F — полёт, I — инвентарь, R — повернуть блок, Q — отменить размещение.",
-- кирка
Pickaxe = "Кирка ломает камни и каменные блоки. Изнашивается: прочность видно по числу ударов.",
-- топор
Axe = "Топор рубит деревья, деревянные блоки и заборы. Срубленное дерево вырастает через минуту.",
-- крафт
Craft = "Подойди к верстаку и нажми E. Выбери рецепт — если ингредиентов хватает, предмет появится в инвентаре или рюкзаке.",
-- голод
Hunger = "Сытость падает со временем. На нуле уходит здоровье. Сорви яблоко с яблони и съешь его.",
-- сон
Sleep = "Ночью ляг на кровать: сон перемотает время до утра и восстановит здоровье. Прыжок — встать.",
-- противник
Enemy = "Зомби патрулирует лес и обходит стены. Заметив тебя, бежит быстрее. Держись подальше или построй укрытие.",
-- конец таблицы
}
-- «?»
openButton.MouseButton1Click:Connect(function()
-- открыть / закрыть
faqFrame.Visible = not faqFrame.Visible
-- конец функции
end)
-- закрыть
faqFrame:WaitForChild("CloseButton").MouseButton1Click:Connect(function() faqFrame.Visible = false end)
-- назад к темам
infoFrame:WaitForChild("BackButton").MouseButton1Click:Connect(function()
-- прячем текст
infoFrame.Visible = false
-- показываем список
listFrame.Visible = true
-- конец функции
end)
-- кнопки тем
for _, button in ipairs(listFrame:GetChildren()) do
-- только кнопки
if button:IsA("TextButton") then
-- клик по теме
button.MouseButton1Click:Connect(function()
-- заголовок
infoFrame.Object.Text = button.Text
-- текст
infoFrame.Info.Text = INFO[button.Name] or "Скоро здесь будет подсказка."
-- прячем список
listFrame.Visible = false
-- показываем текст
infoFrame.Visible = true
-- конец функции
end)
-- конец проверки
end
-- конец перебора
endГде: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Бег с выносливостью.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- каждый кадр
local RunService = game:GetService("RunService")
-- игроки
local Players = game:GetService("Players")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- полоса выносливости
local gui = Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("StaminaGui")
-- полоса
local bar = gui:WaitForChild("Background"):WaitForChild("Bar")
-- запас
local MAX = 150
-- тратится в секунду
local DRAIN = 30
-- восстанавливается в секунду
local REGEN = 20
-- бег
local RUN_SPEED = 25
-- шаг
local WALK_SPEED = 16
-- сейчас
local stamina = MAX
-- Shift зажат
local shift = false
-- бежим
local running = false
-- нажали
UIS.InputBegan:Connect(function(input, processed)
-- Shift
if not processed and input.KeyCode == Enum.KeyCode.LeftShift then shift = true end
-- конец функции
end)
-- отпустили
UIS.InputEnded:Connect(function(input)
-- Shift
if input.KeyCode == Enum.KeyCode.LeftShift then shift = false end
-- конец функции
end)
-- каждый кадр
RunService.Heartbeat:Connect(function(dt)
-- погиб или сидит — не бежим
if humanoid.Health <= 0 or character:GetAttribute("Crouching") then running = false return end
-- можно бежать
local want = shift and humanoid.MoveDirection.Magnitude > 0 and stamina > 0
-- бежим
if want then
-- тратим
stamina = math.max(stamina - DRAIN * dt, 0)
-- Shift отпущен
elseif not shift then
-- отдыхаем
stamina = math.min(stamina + REGEN * dt, MAX)
-- конец проверки
end
-- состояние сменилось
if want ~= running then
-- запоминаем
running = want
-- скорость меняем только в этот момент
humanoid.WalkSpeed = want and RUN_SPEED or WALK_SPEED
-- конец проверки
end
-- полная — полоску прячем
gui.Enabled = stamina < MAX
-- длина полосы
bar.Size = UDim2.fromScale(stamina / MAX, 1)
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Присед.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- анимации
local animator = humanoid:WaitForChild("Animator")
-- скорость сидя
local CROUCH_SPEED = 6
-- обычная скорость
local WALK_SPEED = 16
-- Animation внутри скрипта
local crouchAnim = script:WaitForChild("CrouchAnim")
-- дорожка анимации
local track = nil
-- своя анимация есть
if crouchAnim.AnimationId ~= "" then
-- загружаем
track = animator:LoadAnimation(crouchAnim)
-- поверх ходьбы и стояния
track.Priority = Enum.AnimationPriority.Action
-- по кругу
track.Looped = true
-- конец проверки
end
-- сидим?
local crouching = false
-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
-- нужна C и не в чате
if processed or input.KeyCode ~= Enum.KeyCode.C then return end
-- погибшие не приседают
if humanoid.Health <= 0 then return end
-- C переключает: сесть / встать
crouching = not crouching
-- сообщаем скрипту бега
character:SetAttribute("Crouching", crouching)
-- сели
if crouching then
-- медленно
humanoid.WalkSpeed = CROUCH_SPEED
-- камера ниже — видно и без анимации
humanoid.CameraOffset = Vector3.new(0, -1.5, 0)
-- анимация приседа
if track then track:Play(0.2) end
-- встали
else
-- обычная скорость
humanoid.WalkSpeed = WALK_SPEED
-- камера на место
humanoid.CameraOffset = Vector3.zero
-- выключаем анимацию
if track then track:Stop(0.2) end
-- конец проверки
end
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Полёт.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- каждый кадр
local RunService = game:GetService("RunService")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- камера — летим туда, куда смотрим
local camera = workspace.CurrentCamera
-- F — взлететь / приземлиться
local KEY = Enum.KeyCode.F
-- скорость полёта
local SPEED = 40
-- летим?
local flying = false
-- точка для сил
local attachment = root:FindFirstChild("RootAttachment") or Instance.new("Attachment", root)
-- держит скорость полёта
local move = Instance.new("LinearVelocity")
-- за центр
move.Attachment0 = attachment
-- сильнее гравитации
move.MaxForce = 1000000
-- пока висим
move.VectorVelocity = Vector3.zero
-- выключено до полёта
move.Enabled = false
-- в персонажа
move.Parent = root
-- поворачивает персонажа
local turn = Instance.new("AlignOrientation")
-- к заданному направлению
turn.Mode = Enum.OrientationAlignmentMode.OneAttachment
-- за центр
turn.Attachment0 = attachment
-- жёстко
turn.RigidityEnabled = true
-- выключено до полёта
turn.Enabled = false
-- в персонажа
turn.Parent = root
-- включить или выключить полёт
local function SetFlying(value)
-- запоминаем
flying = value
-- сила
move.Enabled = value
-- поворот
turn.Enabled = value
-- в полёте ноги не шагают
humanoid.PlatformStand = value
-- конец функции
end
-- нажали
UIS.InputBegan:Connect(function(input, processed)
-- F переключает
if not processed and input.KeyCode == KEY and humanoid.Health > 0 then SetFlying(not flying) end
-- конец функции
end)
-- погиб — падает
humanoid.Died:Connect(function() SetFlying(false) end)
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- не летим
if not flying then return end
-- камера
local cf = camera.CFrame
-- направление
local dir = Vector3.zero
-- вперёд — куда смотрит камера (и вверх-вниз тоже)
if UIS:IsKeyDown(Enum.KeyCode.W) then dir += cf.LookVector end
-- назад
if UIS:IsKeyDown(Enum.KeyCode.S) then dir -= cf.LookVector end
-- вправо
if UIS:IsKeyDown(Enum.KeyCode.D) then dir += cf.RightVector end
-- влево
if UIS:IsKeyDown(Enum.KeyCode.A) then dir -= cf.RightVector end
-- пробел — вверх
if UIS:IsKeyDown(Enum.KeyCode.Space) then dir += Vector3.new(0, 1, 0) end
-- одинаковая скорость в любую сторону
if dir.Magnitude > 0 then dir = dir.Unit end
-- летим
move.VectorVelocity = dir * SPEED
-- куда смотрит камера по горизонтали
local flat = Vector3.new(cf.LookVector.X, 0, cf.LookVector.Z)
-- персонаж смотрит туда же
if flat.Magnitude > 0.01 then turn.CFrame = CFrame.lookAt(Vector3.zero, flat) end
-- конец функции
end)Где: Pickaxe, Axe (StarterPack и ServerStorage.Tools) · Script
Зачем: Взмах и износ.
-- кирка или топор
local tool = script.Parent
-- взмах
local anim = script:WaitForChild("Animation")
-- звук взмаха
local swingSound = tool:WaitForChild("Handle"):WaitForChild("Swing")
-- BoolValue: true — идёт удар
local mining = tool:WaitForChild("Mining")
-- IntValue: прочность
local wear = tool:WaitForChild("Wear")
-- взмах идёт
local busy = false
-- клик
tool.Activated:Connect(function()
-- кто держит
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- взмах идёт или погиб
if busy or not humanoid or humanoid.Health <= 0 then return end
-- начали
busy = true
-- номер взмаха — один удар за взмах
tool:SetAttribute("Swing", (tool:GetAttribute("Swing") or 0) + 1)
-- звук
swingSound:Play()
-- взмах
if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
-- удар
mining.Value = true
-- длина взмаха
task.wait(0.4)
-- конец удара
mining.Value = false
-- можно снова
busy = false
-- конец функции
end)
-- прочность изменилась
wear.Changed:Connect(function(value)
-- сломался
if value <= 0 then tool:Destroy() end
-- конец функции
end)Где: ServerStorage → Tools → Shovel · LocalScript
Зачем: Лопата: клики.
-- игроки
local Players = game:GetService("Players")
-- лопата
local tool = script.Parent
-- просим сервер изменить ландшафт
local terraform = tool:WaitForChild("Terraform")
-- куда указывает курсор
local mouse = Players.LocalPlayer:GetMouse()
-- лопата в руках?
local equipped = false
-- взяли
tool.Equipped:Connect(function() equipped = true end)
-- убрали
tool.Unequipped:Connect(function() equipped = false end)
-- уже в руках
if tool.Parent and tool.Parent:FindFirstChildOfClass("Humanoid") then equipped = true end
-- ЛКМ
tool.Activated:Connect(function()
-- копать
terraform:FireServer("Dig", mouse.Hit.Position)
-- конец функции
end)
-- ПКМ
mouse.Button2Down:Connect(function()
-- насыпать землю
if equipped then terraform:FireServer("Raise", mouse.Hit.Position) end
-- конец функции
end)Где: ServerStorage → Tools → Shovel · Script
Зачем: Лопата: ландшафт.
-- игроки
local Players = game:GetService("Players")
-- лопата
local tool = script.Parent
-- команда от клиента
local terraform = tool:WaitForChild("Terraform")
-- размер ямы или холмика
local RADIUS = 3
-- дальше не дотянуться
local MAX_DISTANCE = 25
-- не чаще
local COOLDOWN = 0.3
-- время прошлого копка
local last = 0
-- клиент копает
terraform.OnServerEvent:Connect(function(player, action, position)
-- лопата не у него или мусор
if Players:GetPlayerFromCharacter(tool.Parent) ~= player or typeof(position) ~= "Vector3" then return end
-- слишком часто или далеко
if os.clock() - last < COOLDOWN or player:DistanceFromCharacter(position) > MAX_DISTANCE then return end
-- запоминаем
last = os.clock()
-- копать
if action == "Dig" then
-- шар воздуха — яма
workspace.Terrain:FillBall(position, RADIUS, Enum.Material.Air)
-- насыпать
elseif action == "Raise" then
-- шар земли — холмик
workspace.Terrain:FillBall(position, RADIUS, Enum.Material.Ground)
-- конец проверки
end
-- конец функции
end)Где: Workspace → Workbench → Table → ProximityPrompt · Script
Зачем: Верстак.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- ProximityPrompt верстака
local prompt = script.Parent
-- открыть меню крафта
local openEvent = RS:WaitForChild("CraftOpenEvent")
-- игрок нажал E у верстака
prompt.Triggered:Connect(function(player)
-- меню открывается только у него
openEvent:FireClient(player)
-- конец функции
end)Где: ServerStorage → Food → Apple · Script
Зачем: Яблоко.
-- яблоко
local tool = script.Parent
-- сколько сытости даёт
local foodValue = tool:WaitForChild("FoodValue")
-- хруст
local eatSound = tool:WaitForChild("Handle"):WaitForChild("Eat")
-- анимация еды
local anim = script:WaitForChild("Animation")
-- уже съедено
local eaten = false
-- клик — съесть
tool.Activated:Connect(function()
-- кто ест
local character = tool.Parent
-- игрок
local player = game.Players:GetPlayerFromCharacter(character)
-- Humanoid
local humanoid = character:FindFirstChildOfClass("Humanoid")
-- уже, не игрок или погиб
if eaten or not player or not humanoid or humanoid.Health <= 0 then return end
-- съедено
eaten = true
-- анимация
if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
-- хруст
eatSound:Play()
-- жуём
task.wait(1)
-- сытость
local hunger = player:FindFirstChild("Hunger")
-- сытость сервер считает сам, не выше 100
if hunger then hunger.Value = math.min(hunger.Value + foodValue.Value, 100) end
-- яблока больше нет
tool:Destroy()
-- конец функции
end)Где: Workspace → AppleTree → PromptPart → ProximityPrompt · Script
Зачем: Яблоня.
-- образец яблока
local SS = game:GetService("ServerStorage")
-- ProximityPrompt «Сорвать»
local prompt = script.Parent
-- яблоня: Prompt → PromptPart → AppleTree
local tree = prompt.Parent.Parent
-- яблоко-инструмент
local apple = SS:WaitForChild("Food"):WaitForChild("Apple")
-- через сколько секунд вырастут снова
local REGROW = 10
-- сорвали
prompt.Triggered:Connect(function(player)
-- яблоки на дереве (декор)
local apples = tree:FindFirstChild("Apples")
-- уже сорваны
if not apples then return end
-- яблоко в рюкзак
apple:Clone().Parent = player.Backpack
-- яблоки пропали с дерева
apples.Parent = nil
-- сорвать нечего
prompt.Enabled = false
-- растут
task.wait(REGROW)
-- снова на дереве
apples.Parent = tree
-- можно рвать
prompt.Enabled = true
-- конец функции
end)Где: Workspace → House → Bed → BedPart · Script
Зачем: Кровать.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- время суток
local Lighting = game:GetService("Lighting")
-- BedPart — где лежит персонаж
local part = script.Parent
-- «Лечь»
local prompt = part:WaitForChild("ProximityPrompt")
-- спросить игрока «Спать?»
local sleepEvent = RS:WaitForChild("SleepEvent")
-- игрок согласился
local timeEvent = RS:WaitForChild("TimeEvent")
-- кто лежит
local current = nil
-- сварка с кроватью
local weld = nil
-- подписка «прыжок — встать»
local jumpConnection = nil
-- сейчас ночь?
local function IsNight()
-- с 20 до 6
return Lighting.ClockTime >= 20 or Lighting.ClockTime < 6
-- конец функции
end
-- встать с кровати
local function StandUp()
-- никто не лежит
if not current then return end
-- отвариваем
if weld then weld:Destroy() end
-- отписываемся
if jumpConnection then jumpConnection:Disconnect() end
-- ничего нет
weld, jumpConnection = nil, nil
-- Humanoid
local humanoid = current:FindFirstChildOfClass("Humanoid")
-- снова управляем
if humanoid then humanoid.PlatformStand = false end
-- ставим рядом с кроватью
if current.Parent then current:PivotTo(part.CFrame + Vector3.new(0, 4, 0)) end
-- кровать свободна
current = nil
-- можно лечь снова
prompt.Enabled = true
-- конец функции
end
-- игрок нажал «Лечь»
prompt.Triggered:Connect(function(player)
-- персонаж
local character = player.Character
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- прошлый игрок вышел из игры — кровать свободна
if current and not current.Parent then StandUp() end
-- занято или погиб
if current or not humanoid or not root or humanoid.Health <= 0 then return end
-- лежит он
current = character
-- второй не ляжет
prompt.Enabled = false
-- персонаж «лежит», а не стоит
humanoid.PlatformStand = true
-- лёжа на кровати
root.CFrame = part.CFrame * CFrame.Angles(math.rad(90), 0, 0)
-- приварим к кровати
weld = Instance.new("WeldConstraint")
-- кровать
weld.Part0 = part
-- персонаж
weld.Part1 = root
-- в кровать
weld.Parent = part
-- нажал прыжок
jumpConnection = humanoid:GetPropertyChangedSignal("Jump"):Connect(function()
-- встаёт
if humanoid.Jump then StandUp() end
-- конец функции
end)
-- погиб в кровати — кровать освобождается
humanoid.Died:Once(function() if current == character then StandUp() end end)
-- ночью спросим «Спать?»
if IsNight() then sleepEvent:FireClient(player) end
-- конец функции
end)
-- игрок выбрал «Да»
timeEvent.OnServerEvent:Connect(function(player)
-- лежит не он или уже не ночь
if current ~= player.Character or not IsNight() then return end
-- утро
Lighting.ClockTime = 7
-- Humanoid
local humanoid = current:FindFirstChildOfClass("Humanoid")
-- выспался — здоровье полное
if humanoid then humanoid.Health = humanoid.MaxHealth end
-- конец функции
end)Где: Workspace → Zombie · Script
Зачем: Противник.
-- поиск пути в обход стен
local PathfindingService = game:GetService("PathfindingService")
-- игроки
local Players = game:GetService("Players")
-- противник
local enemy = script.Parent
-- Humanoid
local humanoid = enemy:WaitForChild("Humanoid")
-- центр
local root = enemy:WaitForChild("HumanoidRootPart")
-- точки патруля
local waypoints = workspace:WaitForChild("Waypoints")
-- замечает игрока ближе 40 studs
local SIGHT = 40
-- бьёт ближе 4 studs
local ATTACK_RANGE = 4
-- секунд между ударами
local COOLDOWN = 1
-- время прошлого удара
local lastAttack = 0
-- Animator
local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)
-- ходьба
local walk = Instance.new("Animation")
-- стандартная R15
walk.AnimationId = "rbxassetid://507777826"
-- загружаем
local walkTrack = animator:LoadAnimation(walk)
-- по кругу
walkTrack.Looped = true
-- скорость изменилась
humanoid.Running:Connect(function(speed)
-- идёт — шагает
if speed > 0.5 and not walkTrack.IsPlaying then walkTrack:Play() elseif speed <= 0.5 then walkTrack:Stop() end
-- конец функции
end)
-- движение считает сервер
pcall(function() root:SetNetworkOwner(nil) end)
-- настройки пути
local path = PathfindingService:CreatePath({
-- ширина противника
AgentRadius = 2,
-- рост
AgentHeight = 6,
-- умеет прыгать
AgentCanJump = true,
-- не лазает
AgentCanClimb = false,
-- вода «дорогая» — обходит, если можно
Costs = {Water = 100},
-- конец настроек
})
-- ближайший живой игрок в поле зрения
local function FindTarget()
-- пока никого
local best, bestHumanoid, bestDistance = nil, nil, SIGHT
-- все игроки
for _, player in ipairs(Players:GetPlayers()) do
-- персонаж
local character = player.Character
-- его Humanoid
local target = character and character:FindFirstChildOfClass("Humanoid")
-- его центр
local targetRoot = character and character:FindFirstChild("HumanoidRootPart")
-- жив
if target and targetRoot and target.Health > 0 then
-- расстояние
local distance = (targetRoot.Position - root.Position).Magnitude
-- ближе прошлого
if distance < bestDistance then best, bestHumanoid, bestDistance = targetRoot, target, distance end
-- конец проверки
end
-- конец перебора
end
-- центр, Humanoid и расстояние
return best, bestHumanoid, bestDistance
-- конец функции
end
-- идти по найденному пути
local function MoveAlong(destination, chasing)
-- строим путь
local ok = pcall(function() path:ComputeAsync(root.Position, destination) end)
-- путь не нашёлся
if not ok or path.Status ~= Enum.PathStatus.Success then
-- идём напрямик
humanoid:MoveTo(destination)
-- немного
task.wait(0.5)
-- потом снова подумаем
return
-- конец проверки
end
-- точки пути
for i, waypoint in ipairs(path:GetWaypoints()) do
-- тут надо прыгнуть
if waypoint.Action == Enum.PathWaypointAction.Jump then humanoid.Jump = true end
-- к точке
humanoid:MoveTo(waypoint.Position)
-- дошли
humanoid.MoveToFinished:Wait()
-- в погоне путь пересчитываем каждые 3 точки — игрок двигается
if chasing and i >= 3 then return end
-- в патруле заметил игрока — бросаем патруль
if not chasing and FindTarget() then return end
-- конец перебора
end
-- конец функции
end
-- пока жив
while humanoid.Health > 0 do
-- есть кто рядом?
local targetRoot, target, distance = FindTarget()
-- есть
if targetRoot then
-- погоня — быстрее (атрибут SprintSpeed)
humanoid.WalkSpeed = enemy:GetAttribute("SprintSpeed") or 18
-- рядом
if distance <= ATTACK_RANGE then
-- удар готов
if os.clock() - lastAttack >= COOLDOWN then
-- запоминаем
lastAttack = os.clock()
-- урон из атрибута Damage
target:TakeDamage(enemy:GetAttribute("Damage") or 10)
-- конец проверки
end
-- немного
task.wait(0.2)
-- далеко
else
-- бежим в обход препятствий
MoveAlong(targetRoot.Position, true)
-- конец проверки
end
-- никого
else
-- патруль — медленно
humanoid.WalkSpeed = enemy:GetAttribute("WalkSpeed") or 8
-- точки патруля
local points = waypoints:GetChildren()
-- есть
if #points > 0 then
-- случайная точка
local point = points[math.random(1, #points)]
-- идём к ней
MoveAlong(point:IsA("Model") and point:GetPivot().Position or point.Position, false)
-- точек нет
else
-- стоим
task.wait(1)
-- конец проверки
end
-- конец проверки
end
-- конец цикла
endЗапусти Play (или Run) и убедись: