feat(wiki): LangTabs во все 12 статей раздела G (Большие системы)

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
This commit is contained in:
min 2026-06-09 02:50:38 +03:00
parent e3bff777b2
commit f52eb81e69

View File

@ -2616,10 +2616,12 @@ end`}</Code>}
<>
<p>
<b>NPC</b> (неигровой персонаж) это житель твоей игры:
торговец, враг, проводник. Создаётся командой
<code> game.scene.spawnNpc(модель, опции)</code>.
торговец, враг, проводник.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS используем <code>game.scene.spawnNpc(модель, опции)</code>:</p>
<Code>{`// Создаём NPC по имени Боб
const bob = game.scene.spawnNpc('character-a', {
x: 5, y: 0, z: 0,
@ -2628,11 +2630,8 @@ const bob = game.scene.spawnNpc('character-a', {
speed: 3
});
// Боб говорит реплику над головой (3 секунды)
bob.say('Привет, путник!', 3);
// Боб идёт в точку (x = 10, z = 0)
bob.moveTo(10, 0);`}</Code>
bob.say('Привет, путник!', 3); // реплика над головой
bob.moveTo(10, 0); // идёт в точку`}</Code>
<p><b>Что умеет NPC:</b></p>
<table className="docTable">
<tbody>
@ -2641,8 +2640,8 @@ bob.moveTo(10, 0);`}</Code>
<tr><td><code>stop()</code></td><td>остановиться</td></tr>
<tr><td><code>say(текст, сек)</code></td><td>реплика над головой</td></tr>
<tr><td><code>damage(n)</code></td><td>нанести урон NPC</td></tr>
<tr><td><code>remove()</code></td><td>убрать со сцены</td></tr>
<tr><td><code>onDeath(fn)</code></td><td>что сделать при гибели</td></tr>
<tr><td><code>remove()</code></td><td>убрать</td></tr>
<tr><td><code>onDeath(fn)</code></td><td>при гибели</td></tr>
</tbody>
</table>
<p><b>Пример враг гонится за игроком:</b></p>
@ -2650,13 +2649,65 @@ bob.moveTo(10, 0);`}</Code>
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 });
});`}</Code>
</>}
lua={<>
<p>
В Lua NPC это обычный Model с Humanoid внутри.
Движение делается через <code>humanoid:MoveTo(point)</code>.
Реплики через ChatService или BillboardGui.
</p>
<Code>{`-- 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))`}</Code>
<p><b>Враг гонится за игроком:</b></p>
<Code>{`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)`}</Code>
</>}
/>
</>
),
},
@ -2666,15 +2717,16 @@ enemy.onDeath(() => {
body: (
<>
<p>
<b>Инвентарь</b> это сумка предметов внизу экрана.
<b> Инструмент</b> предмет, который игрок берёт в руку:
меч, фонарик, лопата.
<b>Инвентарь</b> сумка предметов. <b>Инструмент</b>
предмет, который игрок берёт в руку: меч, фонарик.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<Code>{`// Выдать игроку меч прямо в руку
game.player.giveTool('sword', {
name: 'Стальной меч',
equip: true // сразу взять в руку
equip: true
});
// Ловим, когда игрок применил инструмент (ЛКМ)
@ -2682,23 +2734,65 @@ game.player.onToolUse((e) => {
game.log('Игрок применил:', e.tool);
});`}</Code>
<p>
Команды отдела <code>game.inventory</code>:
<code> add(item)</code> добавить предмет,
<code> remove(имя)</code> убрать,
<code> has(имя)</code> есть ли предмет,
<code> list()</code> список всех предметов.
Команды <code>game.inventory</code>: <code>add</code>,
<code> remove</code>, <code>has</code>, <code>list</code>.
</p>
</>}
lua={<>
<Code>{`-- В 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)`}</Code>
<p>
Инвентарь игрока = его <code>Backpack</code> (Roblox-сервис).
Чтобы посмотреть что есть: <code>player.Backpack:GetChildren()</code>.
</p>
</>}
/>
<p><b>Пример игра «ключ и сундук»:</b></p>
<ScriptKind kind="object" on="сундук" />
<Code>{`game.self.onInteract(() => {
// проверяем, есть ли у игрока ключ
<LangTabs
js={<Code>{`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 });`}</Code>
}, { text: 'Открыть', distance: 4 });`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
@ -2707,16 +2801,16 @@ game.player.onToolUse((e) => {
title: 'G3. Звук: свои звуки и 3D-позиционный звук',
body: (
<>
<p>
Звук оживляет игру. Команда
<code> game.sound.play(id, опции)</code>.
</p>
<p>Звук оживляет игру.</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS команда <code>game.sound.play(id, опции)</code>:</p>
<Code>{`// Готовые звуки-пресеты
game.sound.play('coin'); // звон монетки
game.sound.play('win'); // победа
game.sound.play('jump'); // прыжок
game.sound.play('hit'); // удар
game.sound.play('coin');
game.sound.play('win');
game.sound.play('jump');
game.sound.play('hit');
// Свой загруженный звук, потише
game.sound.play('sound_1', { volume: 0.7 });`}</Code>
@ -2726,19 +2820,40 @@ game.sound.play('sound_1', { volume: 0.7 });`}</Code>
<code> hit</code>, <code>coin</code>.
</p>
<p>
<b>3D-звук</b> если указать опцию <code>at</code>,
звук пойдёт из точки в мире: чем дальше игрок, тем тише.
<b>3D-звук</b> опция <code>at</code> привязывает
звук к точке в мире, тише с расстоянием.
</p>
<Code>{`// Звук костра — слышен только когда подходишь близко
game.sound.play('sound_2', {
<Code>{`game.sound.play('sound_2', {
at: { x: 0, y: 1, z: 0 },
loop: true // звук повторяется по кругу
loop: true
});`}</Code>
</>}
lua={<>
<p>В Lua используется <code>Sound</code>-инстанс:</p>
<Code>{`-- Простой звук — играет везде одинаково
local sound = Instance.new("Sound")
sound.SoundId = "rbxassetid://9120386436" -- свой ID
sound.Volume = 0.7
sound.Parent = workspace
sound:Play()`}</Code>
<p>
<b>3D-звук</b> родителем ставим Part в мире.
Sound автоматически становится позиционным.
</p>
<Code>{`-- Звук костра — слышен близко
local campfire = workspace.Костёр
local sound = Instance.new("Sound")
sound.SoundId = "rbxassetid://..."
sound.RollOffMaxDistance = 30 -- метры до полной тишины
sound.Looped = true
sound.Parent = campfire -- родитель = Part 3D-звук
sound:Play()`}</Code>
</>}
/>
<Note>
Звук в играх обязателен игра без звука кажется
«мёртвой». Но не запускай длинную музыку в самом начале:
это скучно и тормозит старт. Звуки вешай на события:
прыжок, попадание, победа.
Звук обязателен игра без звука кажется «мёртвой».
Но не запускай длинную музыку в начале это тормозит старт.
Звуки вешай на события: прыжок, попадание, победа.
</Note>
</>
),
@ -2748,29 +2863,65 @@ game.sound.play('sound_2', {
title: 'G4. Камера: FOV, привязка, катсцены',
body: (
<>
<p>Отдел <code>game.camera</code> управляет видом игрока:</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS отдел <code>game.camera</code>:</p>
<table className="docTable">
<tbody>
<tr><td><code>setFov(градусы)</code></td><td>угол обзора больше «шире» видно</td></tr>
<tr><td><code>shake(сила, сек)</code></td><td>тряска камеры (взрыв, удар)</td></tr>
<tr><td><code>focusOn(ref)</code></td><td>навести камеру на объект</td></tr>
<tr><td><code>cutscene(точки, опции)</code></td><td>пролёт камеры по точкам</td></tr>
<tr><td><code>reset()</code></td><td>вернуть камеру игроку</td></tr>
<tr><td><code>setFov(градусы)</code></td><td>угол обзора</td></tr>
<tr><td><code>shake(сила, сек)</code></td><td>тряска камеры</td></tr>
<tr><td><code>focusOn(ref)</code></td><td>навести на объект</td></tr>
<tr><td><code>cutscene(точки, опции)</code></td><td>пролёт камеры</td></tr>
<tr><td><code>reset()</code></td><td>вернуть игроку</td></tr>
</tbody>
</table>
<p><b>Пример облёт уровня при старте игры:</b></p>
<ScriptKind kind="global" />
<Code>{`// камера плавно пролетает через три точки
<Code>{`// Облёт уровня при старте
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);
});`}</Code>
</>}
lua={<>
<p>В Lua стандартный Roblox Camera через Workspace.CurrentCamera:</p>
<table className="docTable">
<tbody>
<tr><td><code>camera.FieldOfView = 90</code></td><td>угол обзора</td></tr>
<tr><td><code>camera.CameraType = Enum.CameraType.Scriptable</code></td><td>отключить авто-следование</td></tr>
<tr><td><code>camera.CFrame = CFrame.new(pos, look)</code></td><td>поставить камеру</td></tr>
<tr><td><code>TweenService:Create(camera, ...)</code></td><td>плавный пролёт</td></tr>
</tbody>
</table>
<Code>{`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("Поехали!")`}</Code>
</>}
/>
</>
),
},
@ -2780,22 +2931,37 @@ game.onCutsceneDone(() => {
body: (
<>
<p>
Отдел <code>game.fx</code> создаёт красивые эффекты-линии:
<b>Beam</b> светящаяся линия между двумя точками
(лазер, мост света), <b>Trail</b> шлейф за движущимся
объектом (след за ракетой).
</p>
<ScriptKind kind="global" />
<Code>{`// Лазер между двумя башнями
<LangTabs
js={<Code>{`// Лазер между двумя башнями
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
});`}</Code>
from: t1, to: t2,
color: '#ff3344', width: 0.3
});`}</Code>}
lua={<Code>{`-- В 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`}</Code>}
/>
</>
),
},
@ -2804,40 +2970,62 @@ const laser = game.fx.beam({
title: 'G6. Мультиплеер: игроки, комната, команды',
body: (
<>
<p>
В Рублоксе можно сделать игру на несколько игроков
в одной комнате. Главные отделы:
</p>
<ul>
<li>
<code>game.players</code> список игроков:
<code> all()</code>, <code>count()</code>,
<code> me()</code> (это я);
</li>
<li>
<code>game.room</code> общее состояние комнаты,
которое видят все игроки;
</li>
<li>
<code>game.teams</code> команды.
</li>
</ul>
<p>В Рублоксе можно сделать игру на несколько игроков.</p>
<ScriptKind kind="global" />
<Code>{`// Общий счёт команды — виден всем игрокам в комнате
<LangTabs
js={<>
<p>В JS отделы <code>game.players</code>, <code>game.room</code>, <code>game.teams</code>:</p>
<Code>{`// Общий счёт команды — виден всем игрокам
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);
});`}</Code>
</>}
lua={<>
<p>В Lua стандартный Roblox-стиль:</p>
<Code>{`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)`}</Code>
<p><b>Команды (Teams):</b></p>
<Code>{`-- Команды создают в 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)`}</Code>
</>}
/>
</>
),
},
@ -2847,29 +3035,66 @@ game.onPlayerJoin((p) => {
body: (
<>
<p>
<b>Лидерборд</b> таблица очков игроков справа-сверху (как
в Roblox). Объяви стат и меняй значение:
<b>Лидерборд</b> таблица очков справа-сверху (как в Roblox).
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<Code>{`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('Монеты');`}</Code>
<p>
<b>Достижения</b> всплывающие ачивки с редкостью и звуком:
</p>
<p><b>Достижения</b>:</p>
<Code>{`game.achievements.define([
{ id: 'first_coin', name: 'Первая монетка', description: 'Собери монету', icon: 'coin', rarity: 'common' },
{ 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);`}</Code>
</>}
lua={<>
<p>В Lua стандартный Roblox-паттерн: создаём папку
<code> leaderstats</code> в Player с IntValue внутри:</p>
<Code>{`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`}</Code>
<p>
Папка с именем <code>leaderstats</code> на игроке
магическое имя в Roblox. Любые IntValue/NumberValue/StringValue
внутри неё автоматически попадают в HUD справа сверху.
</p>
</>}
/>
<Note>
Лидерборд и достижения сохраняются в БД и подтягиваются при
следующем входе игрока.
следующем входе игрока (DataStoreService в Roblox).
</Note>
</>
),
@ -2880,16 +3105,61 @@ game.achievements.bindToStat('rich', 'Монеты', 100);`}</Code>
body: (
<>
<p>
Всплывающие <b>цифры урона</b> над врагом как в RPG. Самый
простой способ авто-режим (цифры над всеми мобами при уроне):
Всплывающие <b>цифры урона</b> над врагом как в RPG.
</p>
<ScriptKind kind="global" />
<Code>{`game.fx.autoMobFloaters(true);`}</Code>
<p>Ручной вызов в нужный момент:</p>
<Code>{`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`}</Code>
<LangTabs
js={<>
<p>В JS это готовая команда:</p>
<Code>{`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 });`}</Code>
</>}
lua={<>
<p>В Lua делаем сами через BillboardGui + TweenService:</p>
<Code>{`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) -- крит`}</Code>
</>}
/>
</>
),
},
@ -2898,11 +3168,10 @@ game.fx.damageFloater(pos, 0, { isMiss: true }); // промах MISS`}</
title: 'G9. Предметы и инвентарь с редкостями',
body: (
<>
<p>
Полноценный инвентарь (сетка + хотбар, стаки, редкости).
Сначала опиши предметы, потом выдавай:
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS готовый отдел <code>game.items</code> и <code>game.inventory</code>:</p>
<Code>{`game.items.define([
{ id: 'berry', name: 'Ягоды', emoji: '🍓', rarity: 'common', maxStack: 16 },
{ id: 'potion', name: 'Зелье', emoji: '🧪', rarity: 'rare', maxStack: 8, onUseEffect: 'heal:50' },
@ -2910,18 +3179,57 @@ game.fx.damageFloater(pos, 0, { isMiss: true }); // промах MISS`}</
]);
game.inventory.give('sword', 1);
game.inventory.give('berry', 5); // стак`}</Code>
<p>Сбор предмета с земли (скрипт на предмете):</p>
<ScriptKind kind="object" on="собираемый предмет" />
game.inventory.give('berry', 5);`}</Code>
<p>Сбор предмета с земли:</p>
<Code>{`game.self.onInteract(() => {
game.inventory.give('berry', 2);
game.self.delete();
}, { text: 'Собрать', key: 'e', distance: 3 });`}</Code>
}, { text: 'Собрать', distance: 3 });`}</Code>
<Note>
Редкости: common (серый), uncommon (зелёный), rare (голубой),
epic (фиолетовый), legendary (золотой). Окно инвентаря
клавиша <kbd className="kbd">I</kbd>, drag-drop, ПКМ-меню.
</Note>
</>}
lua={<>
<p>
В Roblox инвентарь это <code>Backpack</code> игрока с Tool'ами,
плюс свои <code>IntValue</code>'ы для подсчёта стаков.
Готового «инвентаря с редкостями» нет собирается из частей:
</p>
<Code>{`-- Пример: ягоды как 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)`}</Code>
<p>
Для полноценной системы с редкостями, иконками и UI окном
надо собирать ScreenGui вручную (по статье C). Это много кода
проще использовать JS-вариант с готовым <code>game.items</code>.
</p>
</>}
/>
</>
),
},
@ -2930,18 +3238,52 @@ game.inventory.give('berry', 5); // стак`}</Code>
title: 'G10. Небо, облака, туман, время суток',
body: (
<>
<p>Кастомное небо одной строкой пресеты:</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>Пресеты неба одной командой:</p>
<Code>{`game.scene.setSkybox({ preset: 'sunset' });
// пресеты: clear-summer-day | lowpoly-roblox | cloudy | sunset | starry-night | space`}</Code>
<p>Облака, туман и плавный переход:</p>
<Code>{`game.scene.setClouds({ enabled: true, cover: 0.5, speed: 0.02 });
// пресеты: 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 сек`}</Code>
<p>Простое управление цветом неба и временем суток:</p>
<Code>{`game.environment.setSkyColor('#0a1024'); // тёмное небо
game.environment.setTimeOfDay(0); // ночь (0..24)
game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3);
game.environment.setSkyColor('#0a1024');
game.environment.setTimeOfDay(0); // ночь
game.environment.setTimeOfDay(12); // полдень`}</Code>
</>}
lua={<>
<p>В Roblox небо это инстансы <code>Sky</code> и <code>Atmosphere</code>
в Lighting:</p>
<Code>{`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) -- полночь`}</Code>
</>}
/>
</>
),
},
@ -2950,32 +3292,91 @@ game.environment.setTimeOfDay(12); // полдень`}</Code>
title: 'G11. Диалоги, меню, экран загрузки',
body: (
<>
<p><b>Диалог NPC</b> построчно:</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p><b>Диалог NPC</b>:</p>
<Code>{`game.modal.dialog('Староста', [
'Привет, путник!',
'Собери 10 монет и возвращайся.',
], () => game.ui.showText('Квест начат!', 2));`}</Code>
<p><b>Окно Да/Нет</b> и <b>лутбокс</b>:</p>
<Code>{`game.modal.confirmation('Выход', 'Точно выйти?', () => game.player.respawn(), null);
<Code>{`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));`}</Code>
<p><b>Экран загрузки</b> при переходе между уровнями:</p>
<p><b>Экран загрузки</b>:</p>
<Code>{`game.loading.show({
style: 'ken-burns',
placeName: 'Глава 2 — Шахта',
studioName: 'Моя студия',
duration: 2
});
game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`}</Code>
<Note>
Стартовый экран загрузки игры настраивается без кода
см. раздел вики «Экран загрузки» (карточка в разборе игр) и
вкладку «Стартовый экран» в настройках проекта.
</Note>
</>}
lua={<>
<p>
В Roblox/Lua нет готовых модалок всё собирается через
<b> ScreenGui</b> с Frame'ами. Это много кода (~30-100 строк
на диалог), но полностью кастомизируется.
</p>
<Code>{`-- Простейший диалог: 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)`}</Code>
</>}
/>
</>
),
},
@ -2984,14 +3385,14 @@ game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2
title: 'G12. Машины и главное меню',
body: (
<>
<p>
<b>Машина</b>, на которой можно ездить (вход hold-F, WASD руль):
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p><b>Машина</b>, на которой можно ездить (вход hold-F, WASD руль):</p>
<Code>{`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));`}</Code>
<p><b>Главное меню</b> игры с живой камерой и кнопкой ИГРАТЬ:</p>
<p><b>Главное меню</b>:</p>
<Code>{`game.mainMenu.show({
title: 'МОЯ ИГРА',
camera: 'orbit',
@ -2999,6 +3400,34 @@ game.onVehicleExit(() => game.ui.showText('Вышел', 1));`}</Code>
patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] },
onPlay: () => game.ui.showText('Поехали!', 2)
});`}</Code>
</>}
lua={<>
<p>
В Roblox машина это сложная Model с VehicleSeat
внутри. Когда игрок садится в VehicleSeat у него
появляются <code>.Throttle</code> и <code>.Steer</code>
свойства от WASD автоматически:
</p>
<Code>{`-- 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`}</Code>
<p>
Главное меню собирается через ScreenGui (см. C2-C3),
или используется готовый StarterGui от Roblox.
</p>
</>}
/>
</>
),
},