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`}
title: 'G9. Предметы и инвентарь с редкостями',
body: (
<>
-
- Полноценный инвентарь (сетка + хотбар, стаки, редкости).
- Сначала опиши предметы, потом выдавай:
-
- {`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.
+
+ >}
+ />
>
),
},