feat(wiki): LangTabs во ВСЕ статьи D1-D8 "Скрипты — основы"

Переключатель JS/Lua теперь реально влияет на содержимое в каждой
из 8 статей раздела. Для каждой темы дан рабочий код на обоих языках:

- D1 Что такое скрипт: game.log vs print
- D2 Глобальный/на объекте: game.self vs script.Parent
- D3 Переменные: let vs local
- D4 game vs game:GetService/workspace
- D5 game.log vs print
- D6 События: game.onTick/onClick vs RunService.Heartbeat / ClickDetector
- D7 if/else: === vs ==, !== vs ~=, then/end
- D8 Таймеры: game.after/every/cancel vs task.delay/wait/spawn

Также пояснительные плашки <Note> подобраны под язык —
указания специфичные для синтаксиса каждого языка.
This commit is contained in:
min 2026-06-09 02:37:00 +03:00
parent d019da0ab6
commit 22881f5176

View File

@ -1248,9 +1248,11 @@ end)`}</Code>}
нужен скрипт.
</p>
<p>
Скрипты пишут на языке <b>JavaScript</b> одном из самых
популярных языков в мире. Не пугайся: начнём с простого,
а редактор подсказывает команды по ходу набора.
Скрипты пишут на одном из двух языков: <b>JavaScript</b>
или <b>Lua</b>. Оба работают одинаково. Подробнее про
выбор языка статья <b>D0</b> выше. В этом уроке покажем
пример на обоих языках переключай вкладки, чтобы видеть
нужный.
</p>
<p><b>Как создать первый скрипт:</b></p>
<Step n="1">
@ -1260,9 +1262,13 @@ end)`}</Code>}
<Shot src="d1-scripts-tree.png"
caption="Категория «Скрипты» в иерархии объектов" />
<Step n="2">
Откроется окно кода. Напиши в нём одну строку:
Откроется окно кода. Вверху выбери язык (кнопки JS/Lua)
и напиши одну строку:
</Step>
<Code>{`game.log('Привет! Игра запустилась.');`}</Code>
<LangTabs
js={<Code>{`game.log('Привет! Игра запустилась.');`}</Code>}
lua={<Code>{`print("Привет! Игра запустилась.")`}</Code>}
/>
<Shot src="d5-script-editor.png" wide
caption="Окно редактора кода скрипта" />
<Step n="3">
@ -1270,15 +1276,23 @@ end)`}</Code>}
открой <b>Консоль</b> там появится твоё сообщение.
</Step>
<p>
Это твой первый работающий скрипт. Команда
<code> game.log(...)</code> печатает сообщение в консоль.
Это твой первый работающий скрипт.
</p>
<Note>
Каждая команда заканчивается точкой с запятой
<code> ;</code> как точка в конце предложения. Текст
пишут в кавычках: <code>'привет'</code>. Забыл кавычки
или точку с запятой будет ошибка.
</Note>
<LangTabs
js={<Note>
Команда <code>game.log(...)</code> печатает в консоль.
Каждая команда в JavaScript заканчивается точкой с запятой
<code> ;</code> как точка в предложении. Текст пишут
в кавычках: <code>'привет'</code>.
</Note>}
lua={<Note>
Команда <code>print(...)</code> печатает в консоль.
В Lua точку с запятой ставить <b>не нужно</b>.
Текст пишут в кавычках: <code>"привет"</code> или
<code> 'привет'</code>. Lua использует <code>..</code>
(две точки) для склейки текста: <code>"А=" .. 5</code>.
</Note>}
/>
</>
),
},
@ -1303,22 +1317,45 @@ end)`}</Code>}
<p>
Скрипт на объекте относится к конкретному кубу, модели
или кнопке. Внутри такого скрипта работает волшебное слово
<code> game.self</code> это и есть тот объект, на котором
висит скрипт. Через него ловят клик по объекту или касание
игроком.
(своё для каждого языка):
</p>
<LangTabs
js={<>
<Code>{`// JS: game.self — это и есть тот объект, на котором висит скрипт
game.self.onClick(() => {
game.log('Кликнули по мне!');
});`}</Code>
</>}
lua={<>
<Code>{`-- Lua: script.Parent — это и есть тот объект, на котором висит скрипт
local part = script.Parent
part.Touched:Connect(function(hit)
print("Касание объекта " .. part.Name)
end)`}</Code>
</>}
/>
<p>
<b>Как привязать скрипт к объекту:</b> выдели объект
на сцене, потом создай скрипт он автоматически привяжется
к выделенному объекту. Или укажи носителя в настройках
скрипта.
</p>
<Note>
<LangTabs
js={<Note>
Простое правило: если в коде урока есть
<code> game.self</code> это скрипт <b>на объекте</b>.
Если <code>game.self</code> нет скрипт <b>глобальный</b>.
Плашка в начале каждого урока всегда подскажет.
</Note>
</Note>}
lua={<Note>
Простое правило: если в коде есть
<code> script.Parent</code> это скрипт <b>на объекте</b>.
Если только <code>game</code> глобальный.
В Lua глобальные скрипты обычно работают со списком
игроков: <code>game:GetService("Players")</code>.
</Note>}
/>
</>
),
},
@ -1332,29 +1369,61 @@ end)`}</Code>}
скрипт хранит значение. Например, количество очков,
имя игрока, выбранный уровень.
</p>
<Code>{`// Создаём переменную и кладём в неё число
<LangTabs
js={<Code>{`// JS: создаём переменную через let
let score = 0;
// Меняем значение
score = score + 10; // теперь в score лежит 10
score = score + 5; // теперь 15
game.log('Очков:', score); // напечатает: Очков: 15`}</Code>
<p>
game.log('Очков:', score); // напечатает: Очков: 15`}</Code>}
lua={<Code>{`-- Lua: создаём переменную через local
local score = 0
-- Меняем значение
score = score + 10 -- теперь в score лежит 10
score = score + 5 -- теперь 15
print("Очков:", score) -- напечатает: Очков: 15`}</Code>}
/>
<LangTabs
js={<p>
<code>let</code> это слово «создать переменную». Пишут
его только <b>один раз</b>, когда коробочку заводят. Дальше
меняют значение уже без <code>let</code>.
</p>
</p>}
lua={<p>
<code>local</code> это слово «создать переменную внутри
скрипта». Пишут его только <b>один раз</b>. Если опустить
<code> local</code> переменная станет глобальной (доступна
всем скриптам), это редко нужно.
</p>}
/>
<p>В переменную можно класть не только числа:</p>
<Code>{`let name = 'Герой'; // текст — в кавычках
<LangTabs
js={<Code>{`let name = 'Герой'; // текст — в кавычках
let isWin = false; // да/нет true или false
let coinCount = 0; // число без кавычек`}</Code>
<Note>
let coinCount = 0; // число без кавычек`}</Code>}
lua={<Code>{`local name = "Герой" -- текст — в кавычках
local isWin = false -- да/нет true или false
local coinCount = 0 -- число без кавычек`}</Code>}
/>
<LangTabs
js={<Note>
Если значение <b>никогда</b> не меняется вместо
<code> let</code> можно писать <code>const</code>
(«постоянная»). Например, найденную один раз дверь:
<code> const door = game.scene.findOne('Дверь');</code>
</Note>
</Note>}
lua={<Note>
В Lua нет отдельного слова для «не-меняющейся» переменной
всё через <code>local</code>. Если хочешь явно показать
что значение не меняется, пиши имя ЗАГЛАВНЫМИ:
<code> local DOOR = workspace.Дверь</code>.
Это договорённость, а не правило Lua.
</Note>}
/>
</>
),
},
@ -1366,8 +1435,10 @@ let coinCount = 0; // число — без кавычек`}</Code>
<p>
В каждом скрипте есть одно главное волшебное слово
<code> game</code>. Через него ты управляешь всей игрой.
У <code>game</code> много «отделов»:
Но «отделы» у JS и Lua разные:
</p>
<LangTabs
js={<>
<table className="docTable">
<tbody>
<tr><td><code>game.player</code></td><td>управление игроком</td></tr>
@ -1385,40 +1456,87 @@ let coinCount = 0; // число — без кавычек`}</Code>
«у <b>игры</b>, у <b>игрока</b>, выполни <b>телепорт</b>
в точку 0, 5, 0».
</p>
</>}
lua={<>
<table className="docTable">
<tbody>
<tr><td><code>workspace</code></td><td>3D-объекты сцены</td></tr>
<tr><td><code>game:GetService("Players")</code></td><td>список игроков</td></tr>
<tr><td><code>game.Workspace</code></td><td>то же что workspace</td></tr>
<tr><td><code>script.Parent</code></td><td>объект-носитель скрипта</td></tr>
<tr><td><code>game:GetService("RunService")</code></td><td>каждый кадр (Heartbeat)</td></tr>
<tr><td><code>game:GetService("UserInputService")</code></td><td>клавиши и мышь</td></tr>
<tr><td><code>game:GetService("TweenService")</code></td><td>плавные движения</td></tr>
</tbody>
</table>
<p>
Полный список всех команд каждого отдела в Справочнике
(раздел H). Не нужно его заучивать: при наборе кода
редактор сам показывает подсказки.
Знак <code>:</code> (двоеточие) в Lua это вызов
метода объекта. <code>game:GetService("Players")</code>
читается так: «у <b>game</b> вызови <b>GetService</b>
и дай ему текст <b>Players</b>».
</p>
<p>
Точка <code>.</code> это доступ к полю объекта.
<code> workspace.Floor.BrickColor</code> у workspace
взять Floor, у него взять BrickColor.
</p>
</>}
/>
<p>
Полный список всех команд в Справочнике (раздел H). Не нужно
его заучивать: при наборе кода редактор сам показывает подсказки.
</p>
</>
),
},
{
id: 'log-console',
title: 'D5. game.log, консоль, отладка',
title: 'D5. log/print, консоль, отладка',
body: (
<>
<p>
<b>Консоль</b> окошко в правом нижнем углу редактора.
Туда выводятся все сообщения и ошибки скриптов.
</p>
<p>
<LangTabs
js={<p>
Команда <code>game.log(...)</code> печатает в консоль
что угодно. Это главный инструмент <b>отладки</b>
проверки, что код работает правильно:
</p>
</p>}
lua={<p>
Команда <code>print(...)</code> печатает в консоль
что угодно. Это главный инструмент <b>отладки</b>
проверки, что код работает правильно:
</p>}
/>
<ScriptKind kind="global" />
<Code>{`let score = 0;
<LangTabs
js={<Code>{`let score = 0;
score = score + 10;
game.log('Очки сейчас:', score); // Очки сейчас: 10
let pos = game.player.position;
game.log('Игрок стоит в точке:', pos);`}</Code>
<p>
game.log('Игрок стоит в точке:', pos);`}</Code>}
lua={<Code>{`local score = 0
score = score + 10
print("Очки сейчас:", score) -- Очки сейчас: 10
local pos = game.Players.LocalPlayer.Character.HumanoidRootPart.Position
print("Игрок стоит в точке:", pos)`}</Code>}
/>
<LangTabs
js={<p>
Если игра ведёт себя странно расставь
<code> game.log</code> по коду и посмотри, какие значения
печатаются. Так ты увидишь, где именно что-то пошло не так.
</p>
</p>}
lua={<p>
Если игра ведёт себя странно расставь
<code> print</code> по коду и посмотри, какие значения
печатаются. Так ты увидишь, где именно что-то пошло не так.
</p>}
/>
<Note>
Если в скрипте опечатка текст ошибки появится
в Консоли <b>красным</b>, и там же будет написан номер
@ -1429,39 +1547,77 @@ game.log('Игрок стоит в точке:', pos);`}</Code>
},
{
id: 'events',
title: 'D6. События: onTick, onKey, onClick, onTouch',
title: 'D6. События: тик, клавиши, клик, касание',
body: (
<>
<p>
<b>Событие</b> это «что-то случилось». Скрипт может
ждать событие и реагировать на него. Самые важные:
</p>
<table className="docTable">
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.onTick(fn)</code></td><td>каждый кадр (60 раз в секунду)</td></tr>
<tr><td><code>game.onKey('space', fn)</code></td><td>игрок нажал клавишу</td></tr>
<tr><td><code>game.self.onClick(fn)</code></td><td>игрок кликнул по объекту</td></tr>
<tr><td><code>game.self.onTouch(fn)</code></td><td>игрок коснулся объекта</td></tr>
</tbody>
</table>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>RunService.Heartbeat:Connect(fn)</code></td><td>каждый кадр</td></tr>
<tr><td><code>UserInputService.InputBegan:Connect(fn)</code></td><td>любая клавиша</td></tr>
<tr><td><code>part.ClickDetector.MouseClick:Connect(fn)</code></td><td>клик по объекту</td></tr>
<tr><td><code>part.Touched:Connect(fn)</code></td><td>касание объекта</td></tr>
</tbody>
</table>}
/>
<p>Пример куб, который исчезает по клику:</p>
<ScriptKind kind="object" on="куб" />
<Code>{`game.self.onClick(() => {
<LangTabs
js={<Code>{`game.self.onClick(() => {
game.self.delete(); // удалить сам себя
game.log('Куб удалён!');
});`}</Code>
<p>
});`}</Code>}
lua={<Code>{`local part = script.Parent
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function(player)
part:Destroy() -- удалить сам себя
print("Куб удалён!")
end)`}</Code>}
/>
<LangTabs
js={<p>
<b>Что такое <code>{`() => { ... }`}</code>?</b> Это
«функция» набор команд, упакованных вместе. Команды
внутри фигурных скобок выполнятся <b>не сразу</b>, а только
когда случится событие. То есть «когда кликнули тогда
удалить и напечатать».
</p>
<Note>
</p>}
lua={<p>
<b>Что такое <code>function() ... end</code>?</b> Это
«функция» набор команд, упакованных вместе. Команды
между <code>function()</code> и <code>end</code> выполнятся
<b> не сразу</b>, а только когда случится событие. То есть
«когда кликнули тогда удалить и напечатать».
Метод <code>:Connect</code> «подключает» функцию
к событию.
</p>}
/>
<LangTabs
js={<Note>
<code>onTick</code> выполняется ОЧЕНЬ часто 60 раз
в секунду. Не делай внутри него тяжёлых вещей. Подробнее
об этой ошибке раздел J4.
</Note>
</Note>}
lua={<Note>
<code>Heartbeat</code> выполняется ОЧЕНЬ часто 60 раз
в секунду. Не делай внутри тяжёлых вычислений. Подробнее
об этой ошибке раздел J4.
</Note>}
/>
</>
),
},
@ -1472,25 +1628,35 @@ game.log('Игрок стоит в точке:', pos);`}</Code>
<>
<p>
<b>Условие</b> это развилка: «<b>если</b> что-то верно
сделай одно, <b>иначе</b> другое». В JavaScript это
сделай одно, <b>иначе</b> другое». В обоих языках это
слова <code>if</code> («если») и <code>else</code>
(«иначе»).
(«иначе»), но запись чуть отличается.
</p>
<ScriptKind kind="global" />
<Code>{`let coins = 7;
<LangTabs
js={<Code>{`let coins = 7;
if (coins >= 10) {
game.ui.showText('Хватает на покупку!', 2);
} else {
game.ui.showText('Нужно больше монет', 2);
}`}</Code>
}`}</Code>}
lua={<Code>{`local coins = 7
if coins >= 10 then
print("Хватает на покупку!")
else
print("Нужно больше монет")
end`}</Code>}
/>
<p>
Тут проверяется: <code>coins {'>'}= 10</code> «монет
10 или больше?». Сейчас монет 7, значит условие неверно,
и сработает ветка <code>else</code>.
</p>
<p><b>Знаки сравнения:</b></p>
<table className="docTable">
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>a === b</code></td><td>a равно b</td></tr>
<tr><td><code>a !== b</code></td><td>a не равно b</td></tr>
@ -1499,25 +1665,45 @@ if (coins >= 10) {
<tr><td><code>a {'>'}= b</code></td><td>a больше или равно b</td></tr>
<tr><td><code>a {'<'}= b</code></td><td>a меньше или равно b</td></tr>
</tbody>
</table>
<Note>
Для проверки «равно» пишут <b>три</b> знака равенства
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>a == b</code></td><td>a равно b</td></tr>
<tr><td><code>a ~= b</code></td><td>a не равно b</td></tr>
<tr><td><code>a {'>'} b</code></td><td>a больше b</td></tr>
<tr><td><code>a {'<'} b</code></td><td>a меньше b</td></tr>
<tr><td><code>a {'>'}= b</code></td><td>a больше или равно b</td></tr>
<tr><td><code>a {'<'}= b</code></td><td>a меньше или равно b</td></tr>
</tbody>
</table>}
/>
<LangTabs
js={<Note>
В JS для проверки «равно» пишут <b>три</b> знака равенства
<code> ===</code>, а не один. Один знак <code>=</code>
это «положить значение в переменную», совсем другое
действие.
</Note>
действие. И не равно это <code>!==</code>.
</Note>}
lua={<Note>
В Lua для проверки «равно» пишут <b>два</b> знака равенства
<code> ==</code>. А «не равно» это <code>~=</code>
(тильда + равно). Запомни этот значок он встречается
только в Lua.
</Note>}
/>
</>
),
},
{
id: 'timers',
title: 'D8. Таймеры: after, every, cancel',
title: 'D8. Таймеры: задержка, повтор, отмена',
body: (
<>
<p>
Таймеры запускают команды <b>не сразу, а потом</b>:
</p>
<ul>
<LangTabs
js={<ul>
<li>
<code>game.after(сек, fn)</code> выполнить
<b> один раз</b> через несколько секунд;
@ -1529,9 +1715,26 @@ if (coins >= 10) {
<li>
<code>game.cancel(id)</code> остановить таймер.
</li>
</ul>
</ul>}
lua={<ul>
<li>
<code>task.delay(сек, fn)</code> выполнить
<b> один раз</b> через несколько секунд;
</li>
<li>
<code>task.wait(сек)</code> приостановить скрипт
на N секунд (внутри цикла или функции);
</li>
<li>
Для повторяющихся таймеров обычный цикл
<code> while true do task.wait(1); ... end</code>
в отдельной корутине через <code>task.spawn</code>.
</li>
</ul>}
/>
<ScriptKind kind="global" />
<Code>{`// Через 3 секунды показать текст
<LangTabs
js={<Code>{`// Через 3 секунды показать текст
game.after(3, () => {
game.ui.showText('Игра началась!', 2);
});
@ -1546,12 +1749,42 @@ const ticker = game.every(1, () => {
game.after(10, () => {
game.cancel(ticker);
game.ui.showText('Время вышло!', 2);
});`}</Code>
<p>
});`}</Code>}
lua={<Code>{`-- Через 3 секунды показать текст
task.delay(3, function()
print("Игра началась!")
end)
-- Каждую секунду прибавлять очко.
-- Запускаем в отдельной корутине чтобы не блокировать скрипт.
local score = 0
local running = true
task.spawn(function()
while running do
task.wait(1)
score = score + 1
end
end)
-- Через 10 секунд остановить начисление очков
task.delay(10, function()
running = false
print("Время вышло! Набрано очков:", score)
end)`}</Code>}
/>
<LangTabs
js={<p>
Запись <code>(game.ui.score || 0)</code> читается так:
«возьми счёт, а если его ещё нет возьми 0». Это защита
от ошибки в самом начале, когда счётчик ещё пустой.
</p>
</p>}
lua={<p>
В Lua переменная <code>running</code> флаг работы цикла.
Когда нужно остановить таймер, ставим <code>running = false</code>,
и цикл сам завершится после <code>task.wait(1)</code>.
Это проще, чем хранить номер таймера.
</p>}
/>
</>
),
},