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:
parent
e3bff777b2
commit
f52eb81e69
@ -2616,11 +2616,13 @@ end`}</Code>}
|
|||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<b>NPC</b> (неигровой персонаж) — это житель твоей игры:
|
<b>NPC</b> (неигровой персонаж) — это житель твоей игры:
|
||||||
торговец, враг, проводник. Создаётся командой
|
торговец, враг, проводник.
|
||||||
<code> game.scene.spawnNpc(модель, опции)</code>.
|
|
||||||
</p>
|
</p>
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`// Создаём NPC по имени Боб
|
<LangTabs
|
||||||
|
js={<>
|
||||||
|
<p>В JS используем <code>game.scene.spawnNpc(модель, опции)</code>:</p>
|
||||||
|
<Code>{`// Создаём NPC по имени Боб
|
||||||
const bob = game.scene.spawnNpc('character-a', {
|
const bob = game.scene.spawnNpc('character-a', {
|
||||||
x: 5, y: 0, z: 0,
|
x: 5, y: 0, z: 0,
|
||||||
name: 'Боб',
|
name: 'Боб',
|
||||||
@ -2628,35 +2630,84 @@ const bob = game.scene.spawnNpc('character-a', {
|
|||||||
speed: 3
|
speed: 3
|
||||||
});
|
});
|
||||||
|
|
||||||
// Боб говорит реплику над головой (3 секунды)
|
bob.say('Привет, путник!', 3); // реплика над головой
|
||||||
bob.say('Привет, путник!', 3);
|
bob.moveTo(10, 0); // идёт в точку`}</Code>
|
||||||
|
<p><b>Что умеет NPC:</b></p>
|
||||||
// Боб идёт в точку (x = 10, z = 0)
|
<table className="docTable">
|
||||||
bob.moveTo(10, 0);`}</Code>
|
<tbody>
|
||||||
<p><b>Что умеет NPC:</b></p>
|
<tr><td><code>moveTo(x, z)</code></td><td>идти в точку</td></tr>
|
||||||
<table className="docTable">
|
<tr><td><code>follow('player')</code></td><td>гнаться за игроком</td></tr>
|
||||||
<tbody>
|
<tr><td><code>stop()</code></td><td>остановиться</td></tr>
|
||||||
<tr><td><code>moveTo(x, z)</code></td><td>идти в точку</td></tr>
|
<tr><td><code>say(текст, сек)</code></td><td>реплика над головой</td></tr>
|
||||||
<tr><td><code>follow('player')</code></td><td>гнаться за игроком</td></tr>
|
<tr><td><code>damage(n)</code></td><td>нанести урон NPC</td></tr>
|
||||||
<tr><td><code>stop()</code></td><td>остановиться</td></tr>
|
<tr><td><code>remove()</code></td><td>убрать</td></tr>
|
||||||
<tr><td><code>say(текст, сек)</code></td><td>реплика над головой</td></tr>
|
<tr><td><code>onDeath(fn)</code></td><td>при гибели</td></tr>
|
||||||
<tr><td><code>damage(n)</code></td><td>нанести урон NPC</td></tr>
|
</tbody>
|
||||||
<tr><td><code>remove()</code></td><td>убрать со сцены</td></tr>
|
</table>
|
||||||
<tr><td><code>onDeath(fn)</code></td><td>что сделать при гибели</td></tr>
|
<p><b>Пример — враг гонится за игроком:</b></p>
|
||||||
</tbody>
|
<Code>{`const enemy = game.scene.spawnNpc('character-b', {
|
||||||
</table>
|
|
||||||
<p><b>Пример — враг гонится за игроком:</b></p>
|
|
||||||
<Code>{`const enemy = game.scene.spawnNpc('character-b', {
|
|
||||||
x: 0, y: 0, z: 20, name: 'Враг', hp: 50, speed: 2
|
x: 0, y: 0, z: 20, name: 'Враг', hp: 50, speed: 2
|
||||||
});
|
});
|
||||||
|
|
||||||
enemy.follow('player'); // началась погоня
|
enemy.follow('player');
|
||||||
|
|
||||||
enemy.onDeath(() => {
|
enemy.onDeath(() => {
|
||||||
game.ui.showText('Враг побеждён!', 2);
|
game.ui.showText('Враг побеждён!', 2);
|
||||||
game.scene.spawnParticles('explosion',
|
|
||||||
enemy.position, { duration: 1 });
|
|
||||||
});`}</Code>
|
});`}</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,39 +2717,82 @@ enemy.onDeath(() => {
|
|||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<b>Инвентарь</b> — это сумка предметов внизу экрана.
|
<b>Инвентарь</b> — сумка предметов. <b>Инструмент</b> —
|
||||||
<b> Инструмент</b> — предмет, который игрок берёт в руку:
|
предмет, который игрок берёт в руку: меч, фонарик.
|
||||||
меч, фонарик, лопата.
|
|
||||||
</p>
|
</p>
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`// Выдать игроку меч прямо в руку
|
<LangTabs
|
||||||
|
js={<>
|
||||||
|
<Code>{`// Выдать игроку меч прямо в руку
|
||||||
game.player.giveTool('sword', {
|
game.player.giveTool('sword', {
|
||||||
name: 'Стальной меч',
|
name: 'Стальной меч',
|
||||||
equip: true // сразу взять в руку
|
equip: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ловим, когда игрок применил инструмент (ЛКМ)
|
// Ловим, когда игрок применил инструмент (ЛКМ)
|
||||||
game.player.onToolUse((e) => {
|
game.player.onToolUse((e) => {
|
||||||
game.log('Игрок применил:', e.tool);
|
game.log('Игрок применил:', e.tool);
|
||||||
});`}</Code>
|
});`}</Code>
|
||||||
<p>
|
<p>
|
||||||
Команды отдела <code>game.inventory</code>:
|
Команды <code>game.inventory</code>: <code>add</code>,
|
||||||
<code> add(item)</code> — добавить предмет,
|
<code> remove</code>, <code>has</code>, <code>list</code>.
|
||||||
<code> remove(имя)</code> — убрать,
|
</p>
|
||||||
<code> has(имя)</code> — есть ли предмет,
|
</>}
|
||||||
<code> list()</code> — список всех предметов.
|
lua={<>
|
||||||
</p>
|
<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>
|
<p><b>Пример — игра «ключ и сундук»:</b></p>
|
||||||
<ScriptKind kind="object" on="сундук" />
|
<ScriptKind kind="object" on="сундук" />
|
||||||
<Code>{`game.self.onInteract(() => {
|
<LangTabs
|
||||||
// проверяем, есть ли у игрока ключ
|
js={<Code>{`game.self.onInteract(() => {
|
||||||
if (game.inventory.has('Ключ')) {
|
if (game.inventory.has('Ключ')) {
|
||||||
game.ui.showText('Сундук открыт!', 2);
|
game.ui.showText('Сундук открыт!', 2);
|
||||||
game.inventory.remove('Ключ'); // ключ потрачен
|
game.inventory.remove('Ключ');
|
||||||
} else {
|
} else {
|
||||||
game.ui.showText('Нужен ключ', 1.5);
|
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,38 +2801,59 @@ game.player.onToolUse((e) => {
|
|||||||
title: 'G3. Звук: свои звуки и 3D-позиционный звук',
|
title: 'G3. Звук: свои звуки и 3D-позиционный звук',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>Звук оживляет игру.</p>
|
||||||
Звук оживляет игру. Команда
|
|
||||||
<code> game.sound.play(id, опции)</code>.
|
|
||||||
</p>
|
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`// Готовые звуки-пресеты
|
<LangTabs
|
||||||
game.sound.play('coin'); // звон монетки
|
js={<>
|
||||||
game.sound.play('win'); // победа
|
<p>В JS — команда <code>game.sound.play(id, опции)</code>:</p>
|
||||||
game.sound.play('jump'); // прыжок
|
<Code>{`// Готовые звуки-пресеты
|
||||||
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>
|
game.sound.play('sound_1', { volume: 0.7 });`}</Code>
|
||||||
<p>
|
<p>
|
||||||
Пресеты: <code>jump</code>, <code>pickup</code>,
|
Пресеты: <code>jump</code>, <code>pickup</code>,
|
||||||
<code> win</code>, <code>lose</code>, <code>click</code>,
|
<code> win</code>, <code>lose</code>, <code>click</code>,
|
||||||
<code> hit</code>, <code>coin</code>.
|
<code> hit</code>, <code>coin</code>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>3D-звук</b> — если указать опцию <code>at</code>,
|
<b>3D-звук</b> — опция <code>at</code> привязывает
|
||||||
звук пойдёт из точки в мире: чем дальше игрок, тем тише.
|
звук к точке в мире, тише с расстоянием.
|
||||||
</p>
|
</p>
|
||||||
<Code>{`// Звук костра — слышен только когда подходишь близко
|
<Code>{`game.sound.play('sound_2', {
|
||||||
game.sound.play('sound_2', {
|
|
||||||
at: { x: 0, y: 1, z: 0 },
|
at: { x: 0, y: 1, z: 0 },
|
||||||
loop: true // звук повторяется по кругу
|
loop: true
|
||||||
});`}</Code>
|
});`}</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>
|
||||||
Звук в играх обязателен — игра без звука кажется
|
Звук обязателен — игра без звука кажется «мёртвой».
|
||||||
«мёртвой». Но не запускай длинную музыку в самом начале:
|
Но не запускай длинную музыку в начале — это тормозит старт.
|
||||||
это скучно и тормозит старт. Звуки вешай на события:
|
Звуки вешай на события: прыжок, попадание, победа.
|
||||||
прыжок, попадание, победа.
|
|
||||||
</Note>
|
</Note>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@ -2748,29 +2863,65 @@ game.sound.play('sound_2', {
|
|||||||
title: 'G4. Камера: FOV, привязка, катсцены',
|
title: 'G4. Камера: FOV, привязка, катсцены',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>Отдел <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>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p><b>Пример — облёт уровня при старте игры:</b></p>
|
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`// камера плавно пролетает через три точки
|
<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>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<Code>{`// Облёт уровня при старте
|
||||||
game.camera.cutscene([
|
game.camera.cutscene([
|
||||||
{ x: 0, y: 20, z: -30 },
|
{ x: 0, y: 20, z: -30 },
|
||||||
{ x: 0, y: 15, z: 0 },
|
{ x: 0, y: 15, z: 0 },
|
||||||
{ x: 0, y: 10, z: 30 }
|
{ x: 0, y: 10, z: 30 }
|
||||||
], { segDuration: 2 }); // 2 секунды на отрезок
|
], { segDuration: 2 });
|
||||||
|
|
||||||
// когда облёт закончится — отдать камеру игроку
|
|
||||||
game.onCutsceneDone(() => {
|
game.onCutsceneDone(() => {
|
||||||
game.ui.showText('Поехали!', 2);
|
game.ui.showText('Поехали!', 2);
|
||||||
});`}</Code>
|
});`}</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: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
Отдел <code>game.fx</code> создаёт красивые эффекты-линии:
|
<b>Beam</b> — светящаяся линия между двумя точками
|
||||||
<b> Beam</b> — светящаяся линия между двумя точками
|
|
||||||
(лазер, мост света), <b>Trail</b> — шлейф за движущимся
|
(лазер, мост света), <b>Trail</b> — шлейф за движущимся
|
||||||
объектом (след за ракетой).
|
объектом (след за ракетой).
|
||||||
</p>
|
</p>
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`// Лазер между двумя башнями
|
<LangTabs
|
||||||
|
js={<Code>{`// Лазер между двумя башнями
|
||||||
const t1 = game.scene.findOne('Башня1');
|
const t1 = game.scene.findOne('Башня1');
|
||||||
const t2 = game.scene.findOne('Башня2');
|
const t2 = game.scene.findOne('Башня2');
|
||||||
|
|
||||||
const laser = game.fx.beam({
|
const laser = game.fx.beam({
|
||||||
from: t1,
|
from: t1, to: t2,
|
||||||
to: t2,
|
color: '#ff3344', width: 0.3
|
||||||
color: '#ff3344',
|
});`}</Code>}
|
||||||
width: 0.3
|
lua={<Code>{`-- В Roblox Beam — это инстанс на Attachment'е
|
||||||
});`}</Code>
|
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. Мультиплеер: игроки, комната, команды',
|
title: 'G6. Мультиплеер: игроки, комната, команды',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>В Рублоксе можно сделать игру на несколько игроков.</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>
|
|
||||||
<ScriptKind kind="global" />
|
<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.set('totalScore', 0);
|
||||||
|
|
||||||
// когда счёт меняется — обновляем надпись у всех
|
// когда счёт меняется — обновляем надпись
|
||||||
game.room.onChange('totalScore', (val) => {
|
game.room.onChange('totalScore', (val) => {
|
||||||
game.ui.set('score', 'Счёт команды: ' + val);
|
game.ui.set('score', 'Счёт команды: ' + val);
|
||||||
});
|
});
|
||||||
|
|
||||||
// сколько игроков сейчас в игре
|
game.log('Игроков:', game.players.count());
|
||||||
game.log('Игроков в комнате:', game.players.count());
|
|
||||||
|
|
||||||
// когда новый игрок зашёл
|
|
||||||
game.onPlayerJoin((p) => {
|
game.onPlayerJoin((p) => {
|
||||||
game.ui.showText(p.name + ' присоединился!', 2);
|
game.ui.showText(p.name + ' присоединился!', 2);
|
||||||
});`}</Code>
|
});`}</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: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<b>Лидерборд</b> — таблица очков игроков справа-сверху (как
|
<b>Лидерборд</b> — таблица очков справа-сверху (как в Roblox).
|
||||||
в Roblox). Объяви стат и меняй значение:
|
|
||||||
</p>
|
</p>
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`game.leaderstats.define('Монеты', { initial: 0, icon: 'coin' });
|
<LangTabs
|
||||||
|
js={<>
|
||||||
|
<Code>{`game.leaderstats.define('Монеты', { initial: 0, icon: 'coin' });
|
||||||
game.leaderstats.define('Уровень', { initial: 1 });
|
game.leaderstats.define('Уровень', { initial: 1 });
|
||||||
|
|
||||||
game.leaderstats.me.add('Монеты', 5); // +5 текущему игроку
|
game.leaderstats.me.add('Монеты', 5);
|
||||||
game.leaderstats.me.set('Уровень', 2); // задать значение
|
game.leaderstats.me.set('Уровень', 2);
|
||||||
const c = game.leaderstats.me.get('Монеты');`}</Code>
|
const c = game.leaderstats.me.get('Монеты');`}</Code>
|
||||||
<p>
|
<p><b>Достижения</b>:</p>
|
||||||
<b>Достижения</b> — всплывающие ачивки с редкостью и звуком:
|
<Code>{`game.achievements.define([
|
||||||
</p>
|
{ id: 'first_coin', name: 'Первая монетка', icon: 'coin', rarity: 'common' },
|
||||||
<Code>{`game.achievements.define([
|
|
||||||
{ id: 'first_coin', name: 'Первая монетка', description: 'Собери монету', icon: 'coin', rarity: 'common' },
|
|
||||||
{ id: 'rich', name: 'Богач', description: '100 монет', icon: 'trophy', rarity: 'legendary' }
|
{ id: 'rich', name: 'Богач', description: '100 монет', icon: 'trophy', rarity: 'legendary' }
|
||||||
]);
|
]);
|
||||||
game.achievements.unlock('first_coin');
|
game.achievements.unlock('first_coin');
|
||||||
// или авто-разблокировка по статy:
|
|
||||||
game.achievements.bindToStat('rich', 'Монеты', 100);`}</Code>
|
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>
|
<Note>
|
||||||
Лидерборд и достижения сохраняются в БД и подтягиваются при
|
Лидерборд и достижения сохраняются в БД и подтягиваются при
|
||||||
следующем входе игрока.
|
следующем входе игрока (DataStoreService в Roblox).
|
||||||
</Note>
|
</Note>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@ -2880,16 +3105,61 @@ game.achievements.bindToStat('rich', 'Монеты', 100);`}</Code>
|
|||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
Всплывающие <b>цифры урона</b> над врагом — как в RPG. Самый
|
Всплывающие <b>цифры урона</b> над врагом — как в RPG.
|
||||||
простой способ — авто-режим (цифры над всеми мобами при уроне):
|
|
||||||
</p>
|
</p>
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`game.fx.autoMobFloaters(true);`}</Code>
|
<LangTabs
|
||||||
<p>Ручной вызов в нужный момент:</p>
|
js={<>
|
||||||
<Code>{`game.fx.damageFloater(enemy.position, 25); // обычный урон
|
<p>В JS это готовая команда:</p>
|
||||||
game.fx.damageFloater(enemy.position, 100, { isCrit: true }); // крит — крупно, жёлтый
|
<Code>{`game.fx.autoMobFloaters(true); // авто для всех мобов
|
||||||
game.fx.damageFloater('player', 30, { isHeal: true }); // лечение, зелёный
|
|
||||||
game.fx.damageFloater(pos, 0, { isMiss: true }); // промах MISS`}</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 });`}</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,30 +3168,68 @@ game.fx.damageFloater(pos, 0, { isMiss: true }); // промах MISS`}</
|
|||||||
title: 'G9. Предметы и инвентарь с редкостями',
|
title: 'G9. Предметы и инвентарь с редкостями',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
|
||||||
Полноценный инвентарь (сетка + хотбар, стаки, редкости).
|
|
||||||
Сначала опиши предметы, потом выдавай:
|
|
||||||
</p>
|
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`game.items.define([
|
<LangTabs
|
||||||
{ id: 'berry', name: 'Ягоды', emoji: '🍓', rarity: 'common', maxStack: 16 },
|
js={<>
|
||||||
{ id: 'potion', name: 'Зелье', emoji: '🧪', rarity: 'rare', maxStack: 8, onUseEffect: 'heal:50' },
|
<p>В JS — готовый отдел <code>game.items</code> и <code>game.inventory</code>:</p>
|
||||||
{ id: 'sword', name: 'Меч', emoji: '⚔️', rarity: 'legendary', maxStack: 1 },
|
<Code>{`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('sword', 1);
|
||||||
game.inventory.give('berry', 5); // стак`}</Code>
|
game.inventory.give('berry', 5);`}</Code>
|
||||||
<p>Сбор предмета с земли (скрипт на предмете):</p>
|
<p>Сбор предмета с земли:</p>
|
||||||
<ScriptKind kind="object" on="собираемый предмет" />
|
<Code>{`game.self.onInteract(() => {
|
||||||
<Code>{`game.self.onInteract(() => {
|
|
||||||
game.inventory.give('berry', 2);
|
game.inventory.give('berry', 2);
|
||||||
game.self.delete();
|
game.self.delete();
|
||||||
}, { text: 'Собрать', key: 'e', distance: 3 });`}</Code>
|
}, { text: 'Собрать', distance: 3 });`}</Code>
|
||||||
<Note>
|
<Note>
|
||||||
Редкости: common (серый), uncommon (зелёный), rare (голубой),
|
Редкости: common (серый), uncommon (зелёный), rare (голубой),
|
||||||
epic (фиолетовый), legendary (золотой). Окно инвентаря —
|
epic (фиолетовый), legendary (золотой). Окно инвентаря —
|
||||||
клавиша <kbd className="kbd">I</kbd>, drag-drop, ПКМ-меню.
|
клавиша <kbd className="kbd">I</kbd>, drag-drop, ПКМ-меню.
|
||||||
</Note>
|
</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. Небо, облака, туман, время суток',
|
title: 'G10. Небо, облака, туман, время суток',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>Кастомное небо одной строкой — пресеты:</p>
|
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`game.scene.setSkybox({ preset: 'sunset' });
|
<LangTabs
|
||||||
// пресеты: clear-summer-day | lowpoly-roblox | cloudy | sunset | starry-night | space`}</Code>
|
js={<>
|
||||||
<p>Облака, туман и плавный переход:</p>
|
<p>Пресеты неба одной командой:</p>
|
||||||
<Code>{`game.scene.setClouds({ enabled: true, cover: 0.5, speed: 0.02 });
|
<Code>{`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.setFog({ color: '#dddddd', density: 0.006 });
|
||||||
game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3); // плавно за 3 сек`}</Code>
|
game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3);
|
||||||
<p>Простое управление цветом неба и временем суток:</p>
|
|
||||||
<Code>{`game.environment.setSkyColor('#0a1024'); // тёмное небо
|
game.environment.setSkyColor('#0a1024');
|
||||||
game.environment.setTimeOfDay(0); // ночь (0..24)
|
game.environment.setTimeOfDay(0); // ночь
|
||||||
game.environment.setTimeOfDay(12); // полдень`}</Code>
|
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. Диалоги, меню, экран загрузки',
|
title: 'G11. Диалоги, меню, экран загрузки',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p><b>Диалог NPC</b> построчно:</p>
|
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`game.modal.dialog('Староста', [
|
<LangTabs
|
||||||
|
js={<>
|
||||||
|
<p><b>Диалог NPC</b>:</p>
|
||||||
|
<Code>{`game.modal.dialog('Староста', [
|
||||||
'Привет, путник!',
|
'Привет, путник!',
|
||||||
'Собери 10 монет и возвращайся.',
|
'Собери 10 монет и возвращайся.',
|
||||||
], () => game.ui.showText('Квест начат!', 2));`}</Code>
|
], () => game.ui.showText('Квест начат!', 2));`}</Code>
|
||||||
<p><b>Окно Да/Нет</b> и <b>лутбокс</b>:</p>
|
<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([
|
game.modal.lootbox([
|
||||||
{ name: 'Меч', color: '#f0ad4e', rarity: 'legendary' },
|
{ name: 'Меч', color: '#f0ad4e', rarity: 'legendary' },
|
||||||
{ name: 'Щит', color: '#5bc0de', rarity: 'rare' },
|
{ name: 'Щит', color: '#5bc0de', rarity: 'rare' },
|
||||||
], (item) => game.ui.showText('Выпал: ' + item.name, 3));`}</Code>
|
], (item) => game.ui.showText('Выпал: ' + item.name, 3));`}</Code>
|
||||||
<p><b>Экран загрузки</b> при переходе между уровнями:</p>
|
<p><b>Экран загрузки</b>:</p>
|
||||||
<Code>{`game.loading.show({
|
<Code>{`game.loading.show({
|
||||||
style: 'ken-burns',
|
style: 'ken-burns',
|
||||||
placeName: 'Глава 2 — Шахта',
|
placeName: 'Глава 2 — Шахта',
|
||||||
studioName: 'Моя студия',
|
|
||||||
duration: 2
|
duration: 2
|
||||||
});
|
});
|
||||||
game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`}</Code>
|
game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`}</Code>
|
||||||
<Note>
|
</>}
|
||||||
Стартовый экран загрузки игры настраивается без кода —
|
lua={<>
|
||||||
см. раздел вики «Экран загрузки» (карточка в разборе игр) и
|
<p>
|
||||||
вкладку «Стартовый экран» в настройках проекта.
|
В Roblox/Lua нет готовых модалок — всё собирается через
|
||||||
</Note>
|
<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,21 +3385,49 @@ game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2
|
|||||||
title: 'G12. Машины и главное меню',
|
title: 'G12. Машины и главное меню',
|
||||||
body: (
|
body: (
|
||||||
<>
|
<>
|
||||||
<p>
|
|
||||||
<b>Машина</b>, на которой можно ездить (вход hold-F, WASD руль):
|
|
||||||
</p>
|
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`game.scene.spawn('vehicle:car', { x: 0, y: 1, z: 0, name: 'Тачка' });
|
<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.onVehicleEnter(() => game.ui.showText('За рулём! WASD — ехать', 2));
|
||||||
game.onVehicleExit(() => game.ui.showText('Вышел', 1));`}</Code>
|
game.onVehicleExit(() => game.ui.showText('Вышел', 1));`}</Code>
|
||||||
<p><b>Главное меню</b> игры с живой камерой и кнопкой ИГРАТЬ:</p>
|
<p><b>Главное меню</b>:</p>
|
||||||
<Code>{`game.mainMenu.show({
|
<Code>{`game.mainMenu.show({
|
||||||
title: 'МОЯ ИГРА',
|
title: 'МОЯ ИГРА',
|
||||||
camera: 'orbit',
|
camera: 'orbit',
|
||||||
playButtonText: 'ИГРАТЬ',
|
playButtonText: 'ИГРАТЬ',
|
||||||
patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] },
|
patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] },
|
||||||
onPlay: () => game.ui.showText('Поехали!', 2)
|
onPlay: () => game.ui.showText('Поехали!', 2)
|
||||||
});`}</Code>
|
});`}</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>
|
||||||
|
</>}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user