feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39

Merged
min merged 215 commits from feat/lua-50-games-bundle into main 2026-06-09 21:59:25 +00:00
Showing only changes of commit 22881f5176 - Show all commits

View File

@ -1248,9 +1248,11 @@ end)`}</Code>}
нужен скрипт. нужен скрипт.
</p> </p>
<p> <p>
Скрипты пишут на языке <b>JavaScript</b> одном из самых Скрипты пишут на одном из двух языков: <b>JavaScript</b>
популярных языков в мире. Не пугайся: начнём с простого, или <b>Lua</b>. Оба работают одинаково. Подробнее про
а редактор подсказывает команды по ходу набора. выбор языка статья <b>D0</b> выше. В этом уроке покажем
пример на обоих языках переключай вкладки, чтобы видеть
нужный.
</p> </p>
<p><b>Как создать первый скрипт:</b></p> <p><b>Как создать первый скрипт:</b></p>
<Step n="1"> <Step n="1">
@ -1260,9 +1262,13 @@ end)`}</Code>}
<Shot src="d1-scripts-tree.png" <Shot src="d1-scripts-tree.png"
caption="Категория «Скрипты» в иерархии объектов" /> caption="Категория «Скрипты» в иерархии объектов" />
<Step n="2"> <Step n="2">
Откроется окно кода. Напиши в нём одну строку: Откроется окно кода. Вверху выбери язык (кнопки JS/Lua)
и напиши одну строку:
</Step> </Step>
<Code>{`game.log('Привет! Игра запустилась.');`}</Code> <LangTabs
js={<Code>{`game.log('Привет! Игра запустилась.');`}</Code>}
lua={<Code>{`print("Привет! Игра запустилась.")`}</Code>}
/>
<Shot src="d5-script-editor.png" wide <Shot src="d5-script-editor.png" wide
caption="Окно редактора кода скрипта" /> caption="Окно редактора кода скрипта" />
<Step n="3"> <Step n="3">
@ -1270,15 +1276,23 @@ end)`}</Code>}
открой <b>Консоль</b> там появится твоё сообщение. открой <b>Консоль</b> там появится твоё сообщение.
</Step> </Step>
<p> <p>
Это твой первый работающий скрипт. Команда Это твой первый работающий скрипт.
<code> game.log(...)</code> печатает сообщение в консоль.
</p> </p>
<Note> <LangTabs
Каждая команда заканчивается точкой с запятой js={<Note>
<code> ;</code> как точка в конце предложения. Текст Команда <code>game.log(...)</code> печатает в консоль.
пишут в кавычках: <code>'привет'</code>. Забыл кавычки Каждая команда в JavaScript заканчивается точкой с запятой
или точку с запятой будет ошибка. <code> ;</code> как точка в предложении. Текст пишут
</Note> в кавычках: <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> <p>
Скрипт на объекте относится к конкретному кубу, модели Скрипт на объекте относится к конкретному кубу, модели
или кнопке. Внутри такого скрипта работает волшебное слово или кнопке. Внутри такого скрипта работает волшебное слово
<code> game.self</code> это и есть тот объект, на котором (своё для каждого языка):
висит скрипт. Через него ловят клик по объекту или касание
игроком.
</p> </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> <p>
<b>Как привязать скрипт к объекту:</b> выдели объект <b>Как привязать скрипт к объекту:</b> выдели объект
на сцене, потом создай скрипт он автоматически привяжется на сцене, потом создай скрипт он автоматически привяжется
к выделенному объекту. Или укажи носителя в настройках к выделенному объекту. Или укажи носителя в настройках
скрипта. скрипта.
</p> </p>
<Note> <LangTabs
js={<Note>
Простое правило: если в коде урока есть Простое правило: если в коде урока есть
<code> game.self</code> это скрипт <b>на объекте</b>. <code> game.self</code> это скрипт <b>на объекте</b>.
Если <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> </p>
<Code>{`// Создаём переменную и кладём в неё число <LangTabs
js={<Code>{`// JS: создаём переменную через let
let score = 0; let score = 0;
// Меняем значение // Меняем значение
score = score + 10; // теперь в score лежит 10 score = score + 10; // теперь в score лежит 10
score = score + 5; // теперь 15 score = score + 5; // теперь 15
game.log('Очков:', score); // напечатает: Очков: 15`}</Code> game.log('Очков:', score); // напечатает: Очков: 15`}</Code>}
<p> 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> это слово «создать переменную». Пишут <code>let</code> это слово «создать переменную». Пишут
его только <b>один раз</b>, когда коробочку заводят. Дальше его только <b>один раз</b>, когда коробочку заводят. Дальше
меняют значение уже без <code>let</code>. меняют значение уже без <code>let</code>.
</p> </p>}
lua={<p>
<code>local</code> это слово «создать переменную внутри
скрипта». Пишут его только <b>один раз</b>. Если опустить
<code> local</code> переменная станет глобальной (доступна
всем скриптам), это редко нужно.
</p>}
/>
<p>В переменную можно класть не только числа:</p> <p>В переменную можно класть не только числа:</p>
<Code>{`let name = 'Герой'; // текст — в кавычках <LangTabs
js={<Code>{`let name = 'Герой'; // текст — в кавычках
let isWin = false; // да/нет true или false let isWin = false; // да/нет true или false
let coinCount = 0; // число без кавычек`}</Code> let coinCount = 0; // число без кавычек`}</Code>}
<Note> lua={<Code>{`local name = "Герой" -- текст — в кавычках
local isWin = false -- да/нет true или false
local coinCount = 0 -- число без кавычек`}</Code>}
/>
<LangTabs
js={<Note>
Если значение <b>никогда</b> не меняется вместо Если значение <b>никогда</b> не меняется вместо
<code> let</code> можно писать <code>const</code> <code> let</code> можно писать <code>const</code>
(«постоянная»). Например, найденную один раз дверь: («постоянная»). Например, найденную один раз дверь:
<code> const door = game.scene.findOne('Дверь');</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> <p>
В каждом скрипте есть одно главное волшебное слово В каждом скрипте есть одно главное волшебное слово
<code> game</code>. Через него ты управляешь всей игрой. <code> game</code>. Через него ты управляешь всей игрой.
У <code>game</code> много «отделов»: Но «отделы» у JS и Lua разные:
</p> </p>
<LangTabs
js={<>
<table className="docTable"> <table className="docTable">
<tbody> <tbody>
<tr><td><code>game.player</code></td><td>управление игроком</td></tr> <tr><td><code>game.player</code></td><td>управление игроком</td></tr>
@ -1385,40 +1456,87 @@ let coinCount = 0; // число — без кавычек`}</Code>
«у <b>игры</b>, у <b>игрока</b>, выполни <b>телепорт</b> «у <b>игры</b>, у <b>игрока</b>, выполни <b>телепорт</b>
в точку 0, 5, 0». в точку 0, 5, 0».
</p> </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> <p>
Полный список всех команд каждого отдела в Справочнике Знак <code>:</code> (двоеточие) в Lua это вызов
(раздел H). Не нужно его заучивать: при наборе кода метода объекта. <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> </p>
</> </>
), ),
}, },
{ {
id: 'log-console', id: 'log-console',
title: 'D5. game.log, консоль, отладка', title: 'D5. log/print, консоль, отладка',
body: ( body: (
<> <>
<p> <p>
<b>Консоль</b> окошко в правом нижнем углу редактора. <b>Консоль</b> окошко в правом нижнем углу редактора.
Туда выводятся все сообщения и ошибки скриптов. Туда выводятся все сообщения и ошибки скриптов.
</p> </p>
<p> <LangTabs
js={<p>
Команда <code>game.log(...)</code> печатает в консоль Команда <code>game.log(...)</code> печатает в консоль
что угодно. Это главный инструмент <b>отладки</b> что угодно. Это главный инструмент <b>отладки</b>
проверки, что код работает правильно: проверки, что код работает правильно:
</p> </p>}
lua={<p>
Команда <code>print(...)</code> печатает в консоль
что угодно. Это главный инструмент <b>отладки</b>
проверки, что код работает правильно:
</p>}
/>
<ScriptKind kind="global" /> <ScriptKind kind="global" />
<Code>{`let score = 0; <LangTabs
js={<Code>{`let score = 0;
score = score + 10; score = score + 10;
game.log('Очки сейчас:', score); // Очки сейчас: 10 game.log('Очки сейчас:', score); // Очки сейчас: 10
let pos = game.player.position; let pos = game.player.position;
game.log('Игрок стоит в точке:', pos);`}</Code> game.log('Игрок стоит в точке:', pos);`}</Code>}
<p> 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> по коду и посмотри, какие значения <code> game.log</code> по коду и посмотри, какие значения
печатаются. Так ты увидишь, где именно что-то пошло не так. печатаются. Так ты увидишь, где именно что-то пошло не так.
</p> </p>}
lua={<p>
Если игра ведёт себя странно расставь
<code> print</code> по коду и посмотри, какие значения
печатаются. Так ты увидишь, где именно что-то пошло не так.
</p>}
/>
<Note> <Note>
Если в скрипте опечатка текст ошибки появится Если в скрипте опечатка текст ошибки появится
в Консоли <b>красным</b>, и там же будет написан номер в Консоли <b>красным</b>, и там же будет написан номер
@ -1429,39 +1547,77 @@ game.log('Игрок стоит в точке:', pos);`}</Code>
}, },
{ {
id: 'events', id: 'events',
title: 'D6. События: onTick, onKey, onClick, onTouch', title: 'D6. События: тик, клавиши, клик, касание',
body: ( body: (
<> <>
<p> <p>
<b>Событие</b> это «что-то случилось». Скрипт может <b>Событие</b> это «что-то случилось». Скрипт может
ждать событие и реагировать на него. Самые важные: ждать событие и реагировать на него. Самые важные:
</p> </p>
<table className="docTable"> <LangTabs
js={<table className="docTable">
<tbody> <tbody>
<tr><td><code>game.onTick(fn)</code></td><td>каждый кадр (60 раз в секунду)</td></tr> <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.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.onClick(fn)</code></td><td>игрок кликнул по объекту</td></tr>
<tr><td><code>game.self.onTouch(fn)</code></td><td>игрок коснулся объекта</td></tr> <tr><td><code>game.self.onTouch(fn)</code></td><td>игрок коснулся объекта</td></tr>
</tbody> </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> <p>Пример куб, который исчезает по клику:</p>
<ScriptKind kind="object" on="куб" /> <ScriptKind kind="object" on="куб" />
<Code>{`game.self.onClick(() => { <LangTabs
js={<Code>{`game.self.onClick(() => {
game.self.delete(); // удалить сам себя game.self.delete(); // удалить сам себя
game.log('Куб удалён!'); game.log('Куб удалён!');
});`}</Code> });`}</Code>}
<p> 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>Что такое <code>{`() => { ... }`}</code>?</b> Это
«функция» набор команд, упакованных вместе. Команды «функция» набор команд, упакованных вместе. Команды
внутри фигурных скобок выполнятся <b>не сразу</b>, а только внутри фигурных скобок выполнятся <b>не сразу</b>, а только
когда случится событие. То есть «когда кликнули тогда когда случится событие. То есть «когда кликнули тогда
удалить и напечатать». удалить и напечатать».
</p> </p>}
<Note> 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 раз <code>onTick</code> выполняется ОЧЕНЬ часто 60 раз
в секунду. Не делай внутри него тяжёлых вещей. Подробнее в секунду. Не делай внутри него тяжёлых вещей. Подробнее
об этой ошибке раздел J4. об этой ошибке раздел J4.
</Note> </Note>}
lua={<Note>
<code>Heartbeat</code> выполняется ОЧЕНЬ часто 60 раз
в секунду. Не делай внутри тяжёлых вычислений. Подробнее
об этой ошибке раздел J4.
</Note>}
/>
</> </>
), ),
}, },
@ -1472,25 +1628,35 @@ game.log('Игрок стоит в точке:', pos);`}</Code>
<> <>
<p> <p>
<b>Условие</b> это развилка: «<b>если</b> что-то верно <b>Условие</b> это развилка: «<b>если</b> что-то верно
сделай одно, <b>иначе</b> другое». В JavaScript это сделай одно, <b>иначе</b> другое». В обоих языках это
слова <code>if</code> («если») и <code>else</code> слова <code>if</code> («если») и <code>else</code>
(«иначе»). («иначе»), но запись чуть отличается.
</p> </p>
<ScriptKind kind="global" /> <ScriptKind kind="global" />
<Code>{`let coins = 7; <LangTabs
js={<Code>{`let coins = 7;
if (coins >= 10) { if (coins >= 10) {
game.ui.showText('Хватает на покупку!', 2); game.ui.showText('Хватает на покупку!', 2);
} else { } else {
game.ui.showText('Нужно больше монет', 2); game.ui.showText('Нужно больше монет', 2);
}`}</Code> }`}</Code>}
lua={<Code>{`local coins = 7
if coins >= 10 then
print("Хватает на покупку!")
else
print("Нужно больше монет")
end`}</Code>}
/>
<p> <p>
Тут проверяется: <code>coins {'>'}= 10</code> «монет Тут проверяется: <code>coins {'>'}= 10</code> «монет
10 или больше?». Сейчас монет 7, значит условие неверно, 10 или больше?». Сейчас монет 7, значит условие неверно,
и сработает ветка <code>else</code>. и сработает ветка <code>else</code>.
</p> </p>
<p><b>Знаки сравнения:</b></p> <p><b>Знаки сравнения:</b></p>
<table className="docTable"> <LangTabs
js={<table className="docTable">
<tbody> <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>
@ -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>
<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> </tbody>
</table> </table>}
<Note> lua={<table className="docTable">
Для проверки «равно» пишут <b>три</b> знака равенства <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> <code> ===</code>, а не один. Один знак <code>=</code>
это «положить значение в переменную», совсем другое это «положить значение в переменную», совсем другое
действие. действие. И не равно это <code>!==</code>.
</Note> </Note>}
lua={<Note>
В Lua для проверки «равно» пишут <b>два</b> знака равенства
<code> ==</code>. А «не равно» это <code>~=</code>
(тильда + равно). Запомни этот значок он встречается
только в Lua.
</Note>}
/>
</> </>
), ),
}, },
{ {
id: 'timers', id: 'timers',
title: 'D8. Таймеры: after, every, cancel', title: 'D8. Таймеры: задержка, повтор, отмена',
body: ( body: (
<> <>
<p> <p>
Таймеры запускают команды <b>не сразу, а потом</b>: Таймеры запускают команды <b>не сразу, а потом</b>:
</p> </p>
<ul> <LangTabs
js={<ul>
<li> <li>
<code>game.after(сек, fn)</code> выполнить <code>game.after(сек, fn)</code> выполнить
<b> один раз</b> через несколько секунд; <b> один раз</b> через несколько секунд;
@ -1529,9 +1715,26 @@ if (coins >= 10) {
<li> <li>
<code>game.cancel(id)</code> остановить таймер. <code>game.cancel(id)</code> остановить таймер.
</li> </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" /> <ScriptKind kind="global" />
<Code>{`// Через 3 секунды показать текст <LangTabs
js={<Code>{`// Через 3 секунды показать текст
game.after(3, () => { game.after(3, () => {
game.ui.showText('Игра началась!', 2); game.ui.showText('Игра началась!', 2);
}); });
@ -1546,12 +1749,42 @@ const ticker = game.every(1, () => {
game.after(10, () => { game.after(10, () => {
game.cancel(ticker); game.cancel(ticker);
game.ui.showText('Время вышло!', 2); game.ui.showText('Время вышло!', 2);
});`}</Code> });`}</Code>}
<p> 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> читается так: Запись <code>(game.ui.score || 0)</code> читается так:
«возьми счёт, а если его ещё нет возьми 0». Это защита «возьми счёт, а если его ещё нет возьми 0». Это защита
от ошибки в самом начале, когда счётчик ещё пустой. от ошибки в самом начале, когда счётчик ещё пустой.
</p> </p>}
lua={<p>
В Lua переменная <code>running</code> флаг работы цикла.
Когда нужно остановить таймер, ставим <code>running = false</code>,
и цикл сам завершится после <code>task.wait(1)</code>.
Это проще, чем хранить номер таймера.
</p>}
/>
</> </>
), ),
}, },