From f52eb81e69ccb3546d7ce6a963d8368081965227 Mon Sep 17 00:00:00 2001 From: min Date: Tue, 9 Jun 2026 02:50:38 +0300 Subject: [PATCH] =?UTF-8?q?feat(wiki):=20LangTabs=20=D0=B2=D0=BE=20=D0=B2?= =?UTF-8?q?=D1=81=D0=B5=2012=20=D1=81=D1=82=D0=B0=D1=82=D0=B5=D0=B9=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B0=20G=20(=D0=91=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D1=88=D0=B8=D0=B5=20=D1=81=D0=B8=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=D1=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit G1 NPC: scene.spawnNpc vs Model+Humanoid:MoveTo+BillboardGui G2 Инвентарь: game.inventory vs Tool в Backpack G3 Звук: game.sound.play vs Instance.new('Sound') + .Parent=Part для 3D G4 Камера: camera.cutscene vs CurrentCamera + Scriptable + TweenService G5 Beam/Trail: fx.beam vs Instance.new('Beam')+Attachment G6 Мультиплеер: game.players vs Players + Teams сервисы G7 Лидерборды: game.leaderstats vs leaderstats Folder + IntValue G8 Damage floaters: game.fx.damageFloater vs BillboardGui+TweenService G9 Инвентарь: game.items vs Backpack+leaderstats для подсчёта G10 Небо: game.scene.setSkybox vs Sky+Atmosphere в Lighting G11 Модалки: game.modal.dialog vs ScreenGui+Frame+TextLabel G12 Машины: game.scene.spawn('vehicle:car') vs VehicleSeat --- src/community/docsData.jsx | 785 ++++++++++++++++++++++++++++--------- 1 file changed, 607 insertions(+), 178 deletions(-) diff --git a/src/community/docsData.jsx b/src/community/docsData.jsx index 963887f..c32689f 100644 --- a/src/community/docsData.jsx +++ b/src/community/docsData.jsx @@ -2616,11 +2616,13 @@ end`}} <>

NPC (неигровой персонаж) — это житель твоей игры: - торговец, враг, проводник. Создаётся командой - game.scene.spawnNpc(модель, опции). + торговец, враг, проводник.

- {`// Создаём NPC по имени Боб + +

В JS используем game.scene.spawnNpc(модель, опции):

+ {`// Создаём NPC по имени Боб const bob = game.scene.spawnNpc('character-a', { x: 5, y: 0, z: 0, name: 'Боб', @@ -2628,35 +2630,84 @@ const bob = game.scene.spawnNpc('character-a', { speed: 3 }); -// Боб говорит реплику над головой (3 секунды) -bob.say('Привет, путник!', 3); - -// Боб идёт в точку (x = 10, z = 0) -bob.moveTo(10, 0);`} -

Что умеет NPC:

- - - - - - - - - - -
moveTo(x, z)идти в точку
follow('player')гнаться за игроком
stop()остановиться
say(текст, сек)реплика над головой
damage(n)нанести урон NPC
remove()убрать со сцены
onDeath(fn)что сделать при гибели
-

Пример — враг гонится за игроком:

- {`const enemy = game.scene.spawnNpc('character-b', { +bob.say('Привет, путник!', 3); // реплика над головой +bob.moveTo(10, 0); // идёт в точку`} +

Что умеет NPC:

+ + + + + + + + + + +
moveTo(x, z)идти в точку
follow('player')гнаться за игроком
stop()остановиться
say(текст, сек)реплика над головой
damage(n)нанести урон NPC
remove()убрать
onDeath(fn)при гибели
+

Пример — враг гонится за игроком:

+ {`const enemy = game.scene.spawnNpc('character-b', { x: 0, y: 0, z: 20, name: 'Враг', hp: 50, speed: 2 }); -enemy.follow('player'); // началась погоня - +enemy.follow('player'); enemy.onDeath(() => { game.ui.showText('Враг побеждён!', 2); - game.scene.spawnParticles('explosion', - enemy.position, { duration: 1 }); });`} + } + lua={<> +

+ В Lua NPC — это обычный Model с Humanoid внутри. + Движение делается через humanoid:MoveTo(point). + Реплики — через ChatService или BillboardGui. +

+ {`-- NPC модель должна лежать в Workspace. +-- Внутри Model должны быть Part'ы и Humanoid. +local npc = workspace:WaitForChild("Боб") +local humanoid = npc:WaitForChild("Humanoid") +local hrp = npc:WaitForChild("HumanoidRootPart") + +-- Реплика над головой через BillboardGui +local function say(text, duration) + local bg = Instance.new("BillboardGui") + bg.Size = UDim2.new(4, 0, 1, 0) + bg.StudsOffset = Vector3.new(0, 3, 0) + bg.Parent = npc.Head or hrp + local label = Instance.new("TextLabel", bg) + label.Size = UDim2.new(1, 0, 1, 0) + label.BackgroundTransparency = 1 + label.TextColor3 = Color3.new(1, 1, 1) + label.TextScaled = true + label.Text = text + task.delay(duration, function() bg:Destroy() end) +end + +say("Привет, путник!", 3) + +-- Идёт в точку +humanoid:MoveTo(Vector3.new(10, hrp.Position.Y, 0))`} +

Враг гонится за игроком:

+ {`local enemy = workspace:WaitForChild("Враг") +local humanoid = enemy.Humanoid +local Players = game:GetService("Players") + +-- Каждые 0.5 сек обновляем цель — позицию игрока +task.spawn(function() + while enemy.Parent do + local player = Players:GetPlayers()[1] + if player and player.Character then + local target = player.Character.HumanoidRootPart.Position + humanoid:MoveTo(target) + end + task.wait(0.5) + end +end) + +-- При гибели +humanoid.Died:Connect(function() + print("Враг побеждён!") +end)`} + } + /> ), }, @@ -2666,39 +2717,82 @@ enemy.onDeath(() => { body: ( <>

- Инвентарь — это сумка предметов внизу экрана. - Инструмент — предмет, который игрок берёт в руку: - меч, фонарик, лопата. + Инвентарь — сумка предметов. Инструмент — + предмет, который игрок берёт в руку: меч, фонарик.

- {`// Выдать игроку меч прямо в руку + + {`// Выдать игроку меч прямо в руку game.player.giveTool('sword', { name: 'Стальной меч', - equip: true // сразу взять в руку + equip: true }); // Ловим, когда игрок применил инструмент (ЛКМ) game.player.onToolUse((e) => { game.log('Игрок применил:', e.tool); });`} -

- Команды отдела game.inventory: - add(item) — добавить предмет, - remove(имя) — убрать, - has(имя) — есть ли предмет, - list() — список всех предметов. -

+

+ Команды game.inventory: add, + remove, has, list. +

+ } + lua={<> + {`-- В Roblox инструмент — это Tool-инстанс в Backpack игрока. +local Players = game:GetService("Players") +local player = Players.LocalPlayer + +-- Создаём меч +local sword = Instance.new("Tool") +sword.Name = "Стальной меч" +sword.RequiresHandle = false -- упрощённо без Handle-Part +sword.Parent = player.Backpack + +-- Сразу взять в руку (переложить в Character) +sword.Parent = player.Character + +-- Ловим применение (ЛКМ или активация) +sword.Activated:Connect(function() + print("Игрок применил меч!") +end)`} +

+ Инвентарь игрока = его Backpack (Roblox-сервис). + Чтобы посмотреть что есть: player.Backpack:GetChildren(). +

+ } + />

Пример — игра «ключ и сундук»:

- {`game.self.onInteract(() => { - // проверяем, есть ли у игрока ключ + {`game.self.onInteract(() => { if (game.inventory.has('Ключ')) { game.ui.showText('Сундук открыт!', 2); - game.inventory.remove('Ключ'); // ключ потрачен + game.inventory.remove('Ключ'); } else { game.ui.showText('Нужен ключ', 1.5); } -}, { text: 'Открыть', distance: 4 });`} +}, { text: 'Открыть', distance: 4 });`}
} + lua={{`local part = script.Parent +local prompt = Instance.new("ProximityPrompt") +prompt.ActionText = "Открыть" +prompt.MaxActivationDistance = 4 +prompt.Parent = part + +prompt.Triggered:Connect(function(player) + -- Ищем ключ в Backpack + local key = player.Backpack:FindFirstChild("Ключ") + if not key then + key = player.Character and player.Character:FindFirstChild("Ключ") + end + if key then + print("Сундук открыт!") + key:Destroy() -- ключ потрачен + else + print("Нужен ключ") + end +end)`}} + /> ), }, @@ -2707,38 +2801,59 @@ game.player.onToolUse((e) => { title: 'G3. Звук: свои звуки и 3D-позиционный звук', body: ( <> -

- Звук оживляет игру. Команда - game.sound.play(id, опции). -

+

Звук оживляет игру.

- {`// Готовые звуки-пресеты -game.sound.play('coin'); // звон монетки -game.sound.play('win'); // победа -game.sound.play('jump'); // прыжок -game.sound.play('hit'); // удар + +

В JS — команда game.sound.play(id, опции):

+ {`// Готовые звуки-пресеты +game.sound.play('coin'); +game.sound.play('win'); +game.sound.play('jump'); +game.sound.play('hit'); // Свой загруженный звук, потише game.sound.play('sound_1', { volume: 0.7 });`} -

- Пресеты: jump, pickup, - win, lose, click, - hit, coin. -

-

- 3D-звук — если указать опцию at, - звук пойдёт из точки в мире: чем дальше игрок, тем тише. -

- {`// Звук костра — слышен только когда подходишь близко -game.sound.play('sound_2', { +

+ Пресеты: jump, pickup, + win, lose, click, + hit, coin. +

+

+ 3D-звук — опция at привязывает + звук к точке в мире, тише с расстоянием. +

+ {`game.sound.play('sound_2', { at: { x: 0, y: 1, z: 0 }, - loop: true // звук повторяется по кругу + loop: true });`} + } + lua={<> +

В Lua используется Sound-инстанс:

+ {`-- Простой звук — играет везде одинаково +local sound = Instance.new("Sound") +sound.SoundId = "rbxassetid://9120386436" -- свой ID +sound.Volume = 0.7 +sound.Parent = workspace +sound:Play()`} +

+ 3D-звук — родителем ставим Part в мире. + Sound автоматически становится позиционным. +

+ {`-- Звук костра — слышен близко +local campfire = workspace.Костёр +local sound = Instance.new("Sound") +sound.SoundId = "rbxassetid://..." +sound.RollOffMaxDistance = 30 -- метры до полной тишины +sound.Looped = true +sound.Parent = campfire -- родитель = Part → 3D-звук +sound:Play()`} + } + /> - Звук в играх обязателен — игра без звука кажется - «мёртвой». Но не запускай длинную музыку в самом начале: - это скучно и тормозит старт. Звуки вешай на события: - прыжок, попадание, победа. + Звук обязателен — игра без звука кажется «мёртвой». + Но не запускай длинную музыку в начале — это тормозит старт. + Звуки вешай на события: прыжок, попадание, победа. ), @@ -2748,29 +2863,65 @@ game.sound.play('sound_2', { title: 'G4. Камера: FOV, привязка, катсцены', body: ( <> -

Отдел game.camera управляет видом игрока:

- - - - - - - - -
setFov(градусы)угол обзора — больше «шире» видно
shake(сила, сек)тряска камеры (взрыв, удар)
focusOn(ref)навести камеру на объект
cutscene(точки, опции)пролёт камеры по точкам
reset()вернуть камеру игроку
-

Пример — облёт уровня при старте игры:

- {`// камера плавно пролетает через три точки + +

В JS — отдел game.camera:

+ + + + + + + + +
setFov(градусы)угол обзора
shake(сила, сек)тряска камеры
focusOn(ref)навести на объект
cutscene(точки, опции)пролёт камеры
reset()вернуть игроку
+ {`// Облёт уровня при старте game.camera.cutscene([ { x: 0, y: 20, z: -30 }, { x: 0, y: 15, z: 0 }, { x: 0, y: 10, z: 30 } -], { segDuration: 2 }); // 2 секунды на отрезок +], { segDuration: 2 }); -// когда облёт закончится — отдать камеру игроку game.onCutsceneDone(() => { game.ui.showText('Поехали!', 2); });`} + } + lua={<> +

В Lua — стандартный Roblox Camera через Workspace.CurrentCamera:

+ + + + + + + +
camera.FieldOfView = 90угол обзора
camera.CameraType = Enum.CameraType.Scriptableотключить авто-следование
camera.CFrame = CFrame.new(pos, look)поставить камеру
TweenService:Create(camera, ...)плавный пролёт
+ {`local TweenService = game:GetService("TweenService") +local camera = workspace.CurrentCamera + +-- Отключаем авто-следование за игроком +camera.CameraType = Enum.CameraType.Scriptable + +-- Облёт через 3 точки за 6 секунд (3 этапа по 2 сек) +local points = { + Vector3.new(0, 20, -30), + Vector3.new(0, 15, 0), + Vector3.new(0, 10, 30), +} + +for _, point in ipairs(points) do + local goal = { CFrame = CFrame.new(point, Vector3.new(0, 5, 0)) } + local tween = TweenService:Create(camera, TweenInfo.new(2), goal) + tween:Play() + tween.Completed:Wait() +end + +-- Вернуть камеру игроку +camera.CameraType = Enum.CameraType.Custom +print("Поехали!")`} + } + /> ), }, @@ -2780,22 +2931,37 @@ game.onCutsceneDone(() => { body: ( <>

- Отдел game.fx создаёт красивые эффекты-линии: - Beam — светящаяся линия между двумя точками + Beam — светящаяся линия между двумя точками (лазер, мост света), Trail — шлейф за движущимся объектом (след за ракетой).

- {`// Лазер между двумя башнями + {`// Лазер между двумя башнями const t1 = game.scene.findOne('Башня1'); const t2 = game.scene.findOne('Башня2'); const laser = game.fx.beam({ - from: t1, - to: t2, - color: '#ff3344', - width: 0.3 -});`} + from: t1, to: t2, + color: '#ff3344', width: 0.3 +});`}
} + lua={{`-- В Roblox Beam — это инстанс на Attachment'е +local t1 = workspace:WaitForChild("Башня1") +local t2 = workspace:WaitForChild("Башня2") + +-- Attachment'ы — точки на Part'ах +local a0 = Instance.new("Attachment", t1) +local a1 = Instance.new("Attachment", t2) + +local beam = Instance.new("Beam") +beam.Attachment0 = a0 +beam.Attachment1 = a1 +beam.Color = ColorSequence.new(Color3.fromRGB(255, 51, 68)) +beam.Width0 = 0.3 +beam.Width1 = 0.3 +beam.LightEmission = 1 +beam.Parent = t1`}} + /> ), }, @@ -2804,40 +2970,62 @@ const laser = game.fx.beam({ title: 'G6. Мультиплеер: игроки, комната, команды', body: ( <> -

- В Рублоксе можно сделать игру на несколько игроков - в одной комнате. Главные отделы: -

-
    -
  • - game.players — список игроков: - all(), count(), - me() (это я); -
  • -
  • - game.room — общее состояние комнаты, - которое видят все игроки; -
  • -
  • - game.teams — команды. -
  • -
+

В Рублоксе можно сделать игру на несколько игроков.

- {`// Общий счёт команды — виден всем игрокам в комнате + +

В JS — отделы game.players, game.room, game.teams:

+ {`// Общий счёт команды — виден всем игрокам game.room.set('totalScore', 0); -// когда счёт меняется — обновляем надпись у всех +// когда счёт меняется — обновляем надпись game.room.onChange('totalScore', (val) => { game.ui.set('score', 'Счёт команды: ' + val); }); -// сколько игроков сейчас в игре -game.log('Игроков в комнате:', game.players.count()); +game.log('Игроков:', game.players.count()); -// когда новый игрок зашёл game.onPlayerJoin((p) => { game.ui.showText(p.name + ' присоединился!', 2); });`} + } + lua={<> +

В Lua — стандартный Roblox-стиль:

+ {`local Players = game:GetService("Players") +local Teams = game:GetService("Teams") +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +-- Общий счёт команды через NumberValue в ReplicatedStorage +-- (виден всем игрокам через .Changed) +local totalScore = Instance.new("NumberValue") +totalScore.Name = "TotalScore" +totalScore.Value = 0 +totalScore.Parent = ReplicatedStorage + +totalScore.Changed:Connect(function(newValue) + print("Счёт команды:", newValue) +end) + +print("Игроков:", #Players:GetPlayers()) + +-- Когда новый игрок зашёл +Players.PlayerAdded:Connect(function(player) + print(player.Name .. " присоединился!") +end)`} +

Команды (Teams):

+ {`-- Команды создают в Teams сервисе или скриптом +local redTeam = Instance.new("Team") +redTeam.Name = "Red" +redTeam.TeamColor = BrickColor.new("Bright red") +redTeam.AutoAssignable = true +redTeam.Parent = game:GetService("Teams") + +-- Назначить игрока в команду +Players.PlayerAdded:Connect(function(player) + player.Team = redTeam +end)`} + } + /> ), }, @@ -2847,29 +3035,66 @@ game.onPlayerJoin((p) => { body: ( <>

- Лидерборд — таблица очков игроков справа-сверху (как - в Roblox). Объяви стат и меняй значение: + Лидерборд — таблица очков справа-сверху (как в Roblox).

- {`game.leaderstats.define('Монеты', { initial: 0, icon: 'coin' }); + + {`game.leaderstats.define('Монеты', { initial: 0, icon: 'coin' }); game.leaderstats.define('Уровень', { initial: 1 }); -game.leaderstats.me.add('Монеты', 5); // +5 текущему игроку -game.leaderstats.me.set('Уровень', 2); // задать значение +game.leaderstats.me.add('Монеты', 5); +game.leaderstats.me.set('Уровень', 2); const c = game.leaderstats.me.get('Монеты');`} -

- Достижения — всплывающие ачивки с редкостью и звуком: -

- {`game.achievements.define([ - { id: 'first_coin', name: 'Первая монетка', description: 'Собери монету', icon: 'coin', rarity: 'common' }, +

Достижения:

+ {`game.achievements.define([ + { id: 'first_coin', name: 'Первая монетка', icon: 'coin', rarity: 'common' }, { id: 'rich', name: 'Богач', description: '100 монет', icon: 'trophy', rarity: 'legendary' } ]); game.achievements.unlock('first_coin'); -// или авто-разблокировка по статy: game.achievements.bindToStat('rich', 'Монеты', 100);`} + } + lua={<> +

В Lua — стандартный Roblox-паттерн: создаём папку + leaderstats в Player с IntValue внутри:

+ {`local Players = game:GetService("Players") + +Players.PlayerAdded:Connect(function(player) + -- Папка leaderstats — Roblox автоматически показывает её в HUD + local stats = Instance.new("Folder") + stats.Name = "leaderstats" + stats.Parent = player + + -- Стат "Монеты" + local coins = Instance.new("IntValue") + coins.Name = "Монеты" + coins.Value = 0 + coins.Parent = stats + + -- Стат "Уровень" + local level = Instance.new("IntValue") + level.Name = "Уровень" + level.Value = 1 + level.Parent = stats +end) + +-- Добавить монеты текущему игроку +local function addCoins(player, amount) + local stats = player:FindFirstChild("leaderstats") + if stats then + stats.Монеты.Value = stats.Монеты.Value + amount + end +end`} +

+ Папка с именем leaderstats на игроке — + магическое имя в Roblox. Любые IntValue/NumberValue/StringValue + внутри неё автоматически попадают в HUD справа сверху. +

+ } + /> Лидерборд и достижения сохраняются в БД и подтягиваются при - следующем входе игрока. + следующем входе игрока (DataStoreService в Roblox). ), @@ -2880,16 +3105,61 @@ game.achievements.bindToStat('rich', 'Монеты', 100);`}
body: ( <>

- Всплывающие цифры урона над врагом — как в RPG. Самый - простой способ — авто-режим (цифры над всеми мобами при уроне): + Всплывающие цифры урона над врагом — как в RPG.

- {`game.fx.autoMobFloaters(true);`} -

Ручной вызов в нужный момент:

- {`game.fx.damageFloater(enemy.position, 25); // обычный урон -game.fx.damageFloater(enemy.position, 100, { isCrit: true }); // крит — крупно, жёлтый -game.fx.damageFloater('player', 30, { isHeal: true }); // лечение, зелёный -game.fx.damageFloater(pos, 0, { isMiss: true }); // промах MISS`} + +

В JS это готовая команда:

+ {`game.fx.autoMobFloaters(true); // авто для всех мобов + +// или вручную +game.fx.damageFloater(enemy.position, 25); +game.fx.damageFloater(enemy.position, 100, { isCrit: true }); +game.fx.damageFloater('player', 30, { isHeal: true }); +game.fx.damageFloater(pos, 0, { isMiss: true });`} + } + lua={<> +

В Lua делаем сами через BillboardGui + TweenService:

+ {`local TweenService = game:GetService("TweenService") + +local function showDamage(position, amount, isCrit) + -- Невидимый Part-якорь в нужной точке + local anchor = Instance.new("Part") + anchor.Anchored = true + anchor.CanCollide = false + anchor.Transparency = 1 + anchor.Size = Vector3.new(0.1, 0.1, 0.1) + anchor.Position = position + Vector3.new(0, 2, 0) + anchor.Parent = workspace + + -- BillboardGui над якорем + local bg = Instance.new("BillboardGui", anchor) + bg.Size = UDim2.new(3, 0, 1, 0) + bg.AlwaysOnTop = true + + local label = Instance.new("TextLabel", bg) + label.Size = UDim2.new(1, 0, 1, 0) + label.BackgroundTransparency = 1 + label.Text = "-" .. amount + label.TextColor3 = isCrit + and Color3.fromRGB(255, 200, 0) -- жёлтый крит + or Color3.fromRGB(255, 80, 80) -- красный обычный + label.TextScaled = true + label.Font = Enum.Font.GothamBold + + -- Анимируем вверх + исчезание + local goal = { Position = anchor.Position + Vector3.new(0, 4, 0) } + TweenService:Create(anchor, TweenInfo.new(1), goal):Play() + + task.delay(1, function() anchor:Destroy() end) +end + +-- Пример использования: +showDamage(workspace.Враг.Position, 25, false) +showDamage(workspace.Враг.Position, 100, true) -- крит`} + } + /> ), }, @@ -2898,30 +3168,68 @@ game.fx.damageFloater(pos, 0, { isMiss: true }); // промах MISS`} -

- Полноценный инвентарь (сетка + хотбар, стаки, редкости). - Сначала опиши предметы, потом выдавай: -

- {`game.items.define([ - { id: 'berry', name: 'Ягоды', emoji: '🍓', rarity: 'common', maxStack: 16 }, - { id: 'potion', name: 'Зелье', emoji: '🧪', rarity: 'rare', maxStack: 8, onUseEffect: 'heal:50' }, - { id: 'sword', name: 'Меч', emoji: '⚔️', rarity: 'legendary', maxStack: 1 }, + +

В JS — готовый отдел game.items и game.inventory:

+ {`game.items.define([ + { id: 'berry', name: 'Ягоды', emoji: '🍓', rarity: 'common', maxStack: 16 }, + { id: 'potion', name: 'Зелье', emoji: '🧪', rarity: 'rare', maxStack: 8, onUseEffect: 'heal:50' }, + { id: 'sword', name: 'Меч', emoji: '⚔️', rarity: 'legendary', maxStack: 1 }, ]); game.inventory.give('sword', 1); -game.inventory.give('berry', 5); // стак`} -

Сбор предмета с земли (скрипт на предмете):

- - {`game.self.onInteract(() => { +game.inventory.give('berry', 5);`} +

Сбор предмета с земли:

+ {`game.self.onInteract(() => { game.inventory.give('berry', 2); game.self.delete(); -}, { text: 'Собрать', key: 'e', distance: 3 });`} - - Редкости: common (серый), uncommon (зелёный), rare (голубой), - epic (фиолетовый), legendary (золотой). Окно инвентаря — - клавиша I, drag-drop, ПКМ-меню. - +}, { text: 'Собрать', distance: 3 });`}
+ + Редкости: common (серый), uncommon (зелёный), rare (голубой), + epic (фиолетовый), legendary (золотой). Окно инвентаря — + клавиша I, drag-drop, ПКМ-меню. + + } + lua={<> +

+ В Roblox инвентарь — это Backpack игрока с Tool'ами, + плюс свои IntValue'ы для подсчёта стаков. + Готового «инвентаря с редкостями» нет — собирается из частей: +

+ {`-- Пример: ягоды как IntValue в leaderstats +local Players = game:GetService("Players") + +Players.PlayerAdded:Connect(function(player) + local stats = Instance.new("Folder") + stats.Name = "leaderstats" + stats.Parent = player + + local berries = Instance.new("IntValue", stats) + berries.Name = "Ягоды" + berries.Value = 0 +end) + +-- Сбор ягод (скрипт на собираемом Part) +local part = script.Parent +local prompt = Instance.new("ProximityPrompt") +prompt.ActionText = "Собрать" +prompt.Parent = part + +prompt.Triggered:Connect(function(player) + local berries = player.leaderstats and player.leaderstats:FindFirstChild("Ягоды") + if berries then + berries.Value = berries.Value + 2 + part:Destroy() + end +end)`} +

+ Для полноценной системы с редкостями, иконками и UI окном — + надо собирать ScreenGui вручную (по статье C). Это много кода — + проще использовать JS-вариант с готовым game.items. +

+ } + /> ), }, @@ -2930,18 +3238,52 @@ game.inventory.give('berry', 5); // стак`}
title: 'G10. Небо, облака, туман, время суток', body: ( <> -

Кастомное небо одной строкой — пресеты:

- {`game.scene.setSkybox({ preset: 'sunset' }); -// пресеты: clear-summer-day | lowpoly-roblox | cloudy | sunset | starry-night | space`} -

Облака, туман и плавный переход:

- {`game.scene.setClouds({ enabled: true, cover: 0.5, speed: 0.02 }); + +

Пресеты неба одной командой:

+ {`game.scene.setSkybox({ preset: 'sunset' }); +// пресеты: clear-summer-day | lowpoly-roblox | cloudy | sunset | starry-night | space + +game.scene.setClouds({ enabled: true, cover: 0.5, speed: 0.02 }); game.scene.setFog({ color: '#dddddd', density: 0.006 }); -game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3); // плавно за 3 сек`} -

Простое управление цветом неба и временем суток:

- {`game.environment.setSkyColor('#0a1024'); // тёмное небо -game.environment.setTimeOfDay(0); // ночь (0..24) -game.environment.setTimeOfDay(12); // полдень`} +game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3); + +game.environment.setSkyColor('#0a1024'); +game.environment.setTimeOfDay(0); // ночь +game.environment.setTimeOfDay(12); // полдень`}
+ } + lua={<> +

В Roblox небо — это инстансы Sky и Atmosphere + в Lighting:

+ {`local Lighting = game:GetService("Lighting") + +-- Sky-инстанс с собственными текстурами +local sky = Instance.new("Sky") +sky.SkyboxBk = "rbxassetid://..." -- задняя грань +sky.SkyboxFt = "rbxassetid://..." -- передняя +sky.SkyboxLf = "rbxassetid://..." -- левая +sky.SkyboxRt = "rbxassetid://..." -- правая +sky.SkyboxUp = "rbxassetid://..." -- верх +sky.SkyboxDn = "rbxassetid://..." -- низ +sky.Parent = Lighting + +-- Туман +Lighting.FogColor = Color3.fromRGB(221, 221, 221) +Lighting.FogStart = 50 +Lighting.FogEnd = 500 + +-- Atmosphere (мгла, плотность) +local atmosphere = Instance.new("Atmosphere") +atmosphere.Density = 0.3 +atmosphere.Color = Color3.fromRGB(199, 199, 199) +atmosphere.Parent = Lighting + +-- Время суток (часы и минуты от полуночи) +Lighting:SetMinutesAfterMidnight(12 * 60) -- полдень +Lighting:SetMinutesAfterMidnight(0) -- полночь`} + } + /> ), }, @@ -2950,32 +3292,91 @@ game.environment.setTimeOfDay(12); // полдень`} title: 'G11. Диалоги, меню, экран загрузки', body: ( <> -

Диалог NPC построчно:

- {`game.modal.dialog('Староста', [ + +

Диалог NPC:

+ {`game.modal.dialog('Староста', [ 'Привет, путник!', 'Собери 10 монет и возвращайся.', ], () => game.ui.showText('Квест начат!', 2));`} -

Окно Да/Нет и лутбокс:

- {`game.modal.confirmation('Выход', 'Точно выйти?', () => game.player.respawn(), null); +

Окно Да/Нет и лутбокс:

+ {`game.modal.confirmation('Выход', 'Точно выйти?', + () => game.player.respawn(), null); game.modal.lootbox([ { name: 'Меч', color: '#f0ad4e', rarity: 'legendary' }, { name: 'Щит', color: '#5bc0de', rarity: 'rare' }, ], (item) => game.ui.showText('Выпал: ' + item.name, 3));`} -

Экран загрузки при переходе между уровнями:

- {`game.loading.show({ +

Экран загрузки:

+ {`game.loading.show({ style: 'ken-burns', placeName: 'Глава 2 — Шахта', - studioName: 'Моя студия', duration: 2 }); game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`} - - Стартовый экран загрузки игры настраивается без кода — - см. раздел вики «Экран загрузки» (карточка в разборе игр) и - вкладку «Стартовый экран» в настройках проекта. - + } + lua={<> +

+ В Roblox/Lua нет готовых модалок — всё собирается через + ScreenGui с Frame'ами. Это много кода (~30-100 строк + на диалог), но полностью кастомизируется. +

+ {`-- Простейший диалог: ScreenGui + Frame + TextLabel + Button +local Players = game:GetService("Players") +local player = Players.LocalPlayer +local gui = player:WaitForChild("PlayerGui") + +local function showDialog(speaker, lines, onDone) + local screen = Instance.new("ScreenGui", gui) + local frame = Instance.new("Frame", screen) + frame.Size = UDim2.new(0.6, 0, 0.25, 0) + frame.Position = UDim2.new(0.2, 0, 0.65, 0) + frame.BackgroundColor3 = Color3.new(0, 0, 0) + frame.BackgroundTransparency = 0.4 + + local nameLabel = Instance.new("TextLabel", frame) + nameLabel.Size = UDim2.new(1, 0, 0.2, 0) + nameLabel.Text = speaker + nameLabel.TextColor3 = Color3.fromRGB(255, 220, 100) + nameLabel.BackgroundTransparency = 1 + + local textLabel = Instance.new("TextLabel", frame) + textLabel.Size = UDim2.new(1, -20, 0.6, 0) + textLabel.Position = UDim2.new(0, 10, 0.2, 0) + textLabel.TextColor3 = Color3.new(1, 1, 1) + textLabel.BackgroundTransparency = 1 + textLabel.TextWrapped = true + + local idx = 1 + local function showLine() + textLabel.Text = lines[idx] + end + + local btn = Instance.new("TextButton", frame) + btn.Size = UDim2.new(0.3, 0, 0.15, 0) + btn.Position = UDim2.new(0.65, 0, 0.82, 0) + btn.Text = "Дальше" + + btn.MouseButton1Click:Connect(function() + idx = idx + 1 + if idx > #lines then + screen:Destroy() + if onDone then onDone() end + else + showLine() + end + end) + + showLine() +end + +showDialog("Староста", { + "Привет, путник!", + "Собери 10 монет и возвращайся.", +}, function() print("Квест начат!") end)`} + } + /> ), }, @@ -2984,21 +3385,49 @@ game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2 title: 'G12. Машины и главное меню', body: ( <> -

- Машина, на которой можно ездить (вход hold-F, WASD руль): -

- {`game.scene.spawn('vehicle:car', { x: 0, y: 1, z: 0, name: 'Тачка' }); + +

Машина, на которой можно ездить (вход hold-F, WASD руль):

+ {`game.scene.spawn('vehicle:car', { x: 0, y: 1, z: 0, name: 'Тачка' }); game.onVehicleEnter(() => game.ui.showText('За рулём! WASD — ехать', 2)); game.onVehicleExit(() => game.ui.showText('Вышел', 1));`} -

Главное меню игры с живой камерой и кнопкой ИГРАТЬ:

- {`game.mainMenu.show({ +

Главное меню:

+ {`game.mainMenu.show({ title: 'МОЯ ИГРА', camera: 'orbit', playButtonText: 'ИГРАТЬ', patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] }, onPlay: () => game.ui.showText('Поехали!', 2) });`} + } + lua={<> +

+ В Roblox машина — это сложная Model с VehicleSeat + внутри. Когда игрок садится в VehicleSeat — у него + появляются .Throttle и .Steer + свойства от WASD автоматически: +

+ {`-- VehicleSeat внутри Model +local seat = workspace:WaitForChild("Тачка"):WaitForChild("VehicleSeat") + +-- Слушаем игрока в кресле +seat:GetPropertyChangedSignal("Occupant"):Connect(function() + if seat.Occupant then + print("За рулём! WASD — ехать") + else + print("Вышел") + end +end) + +-- Throttle (W/S) и Steer (A/D) — автоматически в seat.Throttle и seat.Steer +-- Применяй их к скорости/повороту в RunService.Heartbeat`} +

+ Главное меню — собирается через ScreenGui (см. C2-C3), + или используется готовый StarterGui от Roblox. +

+ } + /> ), },