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 41e0f7b6a4 - Show all commits

View File

@ -1,5 +1,24 @@
import React from 'react';
import { Code, ScriptKind, Step, Note, Try, Shot } from './docsData';
import { LangTabs } from './docsLang';
import { LUA_OVERRIDES } from './docsGamesBuildersLua';
/**
* Хелпер: оборачивает JS-код в LangTabs с Lua-параллелью из LUA_OVERRIDES.
* <CodeBoth game="collect-coins" script="g1_main">{`// JS код...`}</CodeBoth>
* если открыт JS показывает JS код
* если открыт Lua показывает Lua-код из LUA_OVERRIDES[game][script]
*/
function CodeBoth({ game, script, children }) {
const luaCode = LUA_OVERRIDES[game]?.[script];
const luaResolved = typeof luaCode === 'function' ? luaCode({ id: script }) : luaCode;
return (
<LangTabs
js={<Code>{children}</Code>}
lua={luaResolved ? <Code lang="lua">{luaResolved}</Code> : null}
/>
);
}
/**
* docsLessons.jsx тексты уроков для 50 мини-игр (раздел K вики).
@ -104,7 +123,7 @@ export const LESSONS = {
Главный скрипт считает монетки и проверяет победу.
</p>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «СОБЕРИ МОНЕТКИ» — главный скрипт ===
<CodeBoth game="collect-coins" script="g1_main">{`// === ИГРА «СОБЕРИ МОНЕТКИ» — главный скрипт ===
// Этот скрипт глобальный: считает собранные монетки и проверяет победу.
let score = 0; // сколько монеток собрано
@ -131,7 +150,7 @@ game.onMessage('coin', () => {
{ x: p.x, y: p.y + 3, z: p.z },
{ duration: 3, count: 3 });
}
});`}</Code>
});`}</CodeBoth>
<p>
Разберём построчно:
</p>
@ -154,14 +173,14 @@ game.onMessage('coin', () => {
касание и сообщает главному скрипту: меня собрали.
</p>
<ScriptKind kind="object" on="каждую монетку" />
<Code>{`// === Скрипт монетки ===
<CodeBoth game="collect-coins" script="g1_coin_1">{`// === Скрипт монетки ===
// game.self это сама монетка, на которой висит скрипт.
game.self.onTouch(() => {
// игрок коснулся монетки сообщаем главному скрипту
game.broadcast('coin');
game.self.delete(); // монетка исчезает со сцены
});`}</Code>
});`}</CodeBoth>
<p>
Что происходит: <code>onTouch</code> срабатывает, когда
игрок дотронулся до монетки. Внутри мы шлём
@ -274,7 +293,7 @@ game.self.onTouch(() => {
Главный скрипт следит за падением и обрабатывает победу.
</p>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «ПРЫГАЙ ПО ПЛАТФОРМАМ» — главный скрипт ===
<CodeBoth game="platform-jump" script="g2_main">{`// === ИГРА «ПРЫГАЙ ПО ПЛАТФОРМАМ» — главный скрипт ===
let won = false; // победа уже была?
@ -304,7 +323,7 @@ game.onMessage('finish', () => {
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z },
{ duration: 3, count: 3 });
});`}</Code>
});`}</CodeBoth>
<p>Что тут важно:</p>
<ul>
<li><code>game.onTick(...)</code> функция внутри
@ -320,13 +339,13 @@ game.onMessage('finish', () => {
<h3 className="lessonH">Шаг 4. Скрипт финиша</h3>
<ScriptKind kind="object" on="зелёную финишную площадку" />
<Code>{`// === Скрипт финиша ===
<CodeBoth game="platform-jump" script="g2_finish">{`// === Скрипт финиша ===
// Висит на невидимой зоне над зелёной площадкой.
// Игрок встал на площадку его тело внутри зоны победа.
game.self.onTouch(() => {
game.broadcast('finish'); // сообщаем главному скрипту о победе
});`}</Code>
});`}</CodeBoth>
<p>
Когда игрок касается финиша, скрипт шлёт
<code> game.broadcast('finish')</code>. Главный скрипт ловит
@ -401,7 +420,7 @@ game.self.onTouch(() => {
<h3 className="lessonH">Шаг 2. Главный скрипт</h3>
<p>Следит за падением и победой как в уроке 2.</p>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «НЕ УПАДИ» — главный скрипт ===
<CodeBoth game="dont-fall" script="g3_main">{`// === ИГРА «НЕ УПАДИ» — главный скрипт ===
let won = false;
@ -428,7 +447,7 @@ game.onMessage('finish', () => {
const p = game.player.position;
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 });
});`}</Code>
});`}</CodeBoth>
<h3 className="lessonH">Шаг 3. Скрипт исчезающей плитки</h3>
<p>
@ -436,7 +455,7 @@ game.onMessage('finish', () => {
который убирает её через секунду после касания.
</p>
<ScriptKind kind="object" on="каждую плитку" />
<Code>{`// === Скрипт исчезающей плитки ===
<CodeBoth game="dont-fall" script="g3_tile_1">{`// === Скрипт исчезающей плитки ===
let triggered = false; // плитка уже запущена на исчезновение?
@ -448,7 +467,7 @@ game.self.onTouch(() => {
game.after(1.2, () => {
game.self.delete();
});
});`}</Code>
});`}</CodeBoth>
<p>Разберём:</p>
<ul>
<li><code>triggered</code> флажок-защёлка. Игрок может
@ -532,7 +551,7 @@ game.self.onTouch(() => {
<h3 className="lessonH">Шаг 3. Главный скрипт</h3>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «КНОПКА-ОТКРЫВАШКА» — главный скрипт ===
<CodeBoth game="button-door" script="g4_main">{`// === ИГРА «КНОПКА-ОТКРЫВАШКА» — главный скрипт ===
game.ui.showText('Подойди к красной кнопке и нажми E', 4);
@ -544,7 +563,7 @@ game.onMessage('win', () => {
const p = game.player.position;
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 });
});`}</Code>
});`}</CodeBoth>
<h3 className="lessonH">Шаг 4. Скрипт кнопки главное</h3>
<p>
@ -552,7 +571,7 @@ game.onMessage('win', () => {
открывает дверь.
</p>
<ScriptKind kind="object" on="красную кнопку" />
<Code>{`// === Скрипт кнопки ===
<CodeBoth game="button-door" script="g4_button">{`// === Скрипт кнопки ===
let opened = false;
@ -570,7 +589,7 @@ game.self.onInteract(() => {
}, {
text: 'Открыть дверь', // подсказка над кнопкой
distance: 4 // на сколько метров подойти
});`}</Code>
});`}</CodeBoth>
<p>Разберём:</p>
<ul>
<li><code>game.self.onInteract(fn, опции)</code> это
@ -592,10 +611,10 @@ game.self.onInteract(() => {
<h3 className="lessonH">Шаг 5. Скрипт финиша и проверка</h3>
<ScriptKind kind="object" on="зелёный финиш" />
<Code>{`// === Скрипт финиша ===
<CodeBoth game="button-door" script="g4_finish">{`// === Скрипт финиша ===
game.self.onTouch(() => {
game.broadcast('win'); // сообщаем главному скрипту о победе
});`}</Code>
});`}</CodeBoth>
<p>Запусти игру:</p>
<ul>
<li>подойди к кнопке появится подсказка «Открыть дверь»;</li>
@ -688,7 +707,7 @@ game.self.onTouch(() => {
<h3 className="lessonH">Шаг 5. Скрипты</h3>
<p>Скрипты совсем простые лабиринт держится на постройке.</p>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «ЛАБИРИНТ» — главный скрипт ===
<CodeBoth game="maze" script="g5_main">{`// === ИГРА «ЛАБИРИНТ» — главный скрипт ===
game.ui.showText('Найди выход из лабиринта!', 3);
@ -700,12 +719,12 @@ game.onMessage('win', () => {
const p = game.player.position;
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 });
});`}</Code>
});`}</CodeBoth>
<ScriptKind kind="object" on="зелёный финиш" />
<Code>{`// === Скрипт финиша ===
<CodeBoth game="maze" script="g5_finish">{`// === Скрипт финиша ===
game.self.onTouch(() => {
game.broadcast('win'); // сообщаем главному скрипту о победе
});`}</Code>
});`}</CodeBoth>
<h3 className="lessonH">Шаг 6. Проверка</h3>
<ul>
@ -764,7 +783,7 @@ game.self.onTouch(() => {
<h3 className="lessonH">Шаг 2. Главный скрипт</h3>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «ЦВЕТНЫЕ ПЛИТКИ» — главный скрипт ===
<CodeBoth game="color-tiles" script="g6_main">{`// === ИГРА «ЦВЕТНЫЕ ПЛИТКИ» — главный скрипт ===
let painted = 0; // сколько плиток раскрашено
const TOTAL = 36; // всего плиток (6×6)
@ -785,7 +804,7 @@ game.onMessage('paint', () => {
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 });
}
});`}</Code>
});`}</CodeBoth>
<Note>
Замени число <code>36</code> на столько плиток, сколько
реально поставил. Если сетка 5×5 будет 25.
@ -793,7 +812,7 @@ game.onMessage('paint', () => {
<h3 className="lessonH">Шаг 3. Скрипт плитки</h3>
<ScriptKind kind="object" on="каждую плитку" />
<Code>{`// === Скрипт цветной плитки ===
<CodeBoth game="color-tiles" script="g6_tile_1">{`// === Скрипт цветной плитки ===
let painted = false; // плитка уже раскрашена?
@ -803,7 +822,7 @@ game.self.onTouch(() => {
// меняем цвет плитки на ярко-зелёный
game.scene.setColor(game.self.ref, '#33dd55');
game.broadcast('paint'); // сообщаем главному скрипту о покраске
});`}</Code>
});`}</CodeBoth>
<p>Главное здесь:</p>
<ul>
<li><code>game.scene.setColor(ref, цвет)</code> меняет
@ -874,7 +893,7 @@ game.self.onTouch(() => {
<h3 className="lessonH">Шаг 2. Главный скрипт</h3>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «ПОЙМАЙ ПАДАЮЩЕЕ» — главный скрипт ===
<CodeBoth game="catch-falling" script="g7_main">{`// === ИГРА «ПОЙМАЙ ПАДАЮЩЕЕ» — главный скрипт ===
let score = 0;
const GOAL = 15; // сколько кубов нужно поймать
@ -922,7 +941,7 @@ game.onPlayerTouch((e) => {
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 });
}
});`}</Code>
});`}</CodeBoth>
<p>Разберём по частям:</p>
<ul>
<li><code>game.every(1.5, fn)</code> каждые 1.5 секунды
@ -1005,7 +1024,7 @@ game.onPlayerTouch((e) => {
<h3 className="lessonH">Шаг 2. Главный скрипт</h3>
<ScriptKind kind="global" />
<Code>{`// === ИГРА «БЕГИ К ФИНИШУ» — главный скрипт ===
<CodeBoth game="run-to-finish" script="g8_main">{`// === ИГРА «БЕГИ К ФИНИШУ» — главный скрипт ===
let finished = false;
let time = 0; // прошло секунд
@ -1032,7 +1051,7 @@ game.onMessage('finish', () => {
const p = game.player.position;
game.scene.spawnParticles('confetti',
{ x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 });
});`}</Code>
});`}</CodeBoth>
<p>Главное здесь измерение времени:</p>
<ul>
<li><code>game.onTick((dt) =&gt; {'{...}'})</code>
@ -1048,10 +1067,10 @@ game.onMessage('finish', () => {
<h3 className="lessonH">Шаг 3. Скрипт финиша</h3>
<ScriptKind kind="object" on="зелёный финиш" />
<Code>{`// === Скрипт финиша ===
<CodeBoth game="run-to-finish" script="g8_finish">{`// === Скрипт финиша ===
game.self.onTouch(() => {
game.broadcast('finish'); // сообщаем главному скрипту о финише
});`}</Code>
});`}</CodeBoth>
<h3 className="lessonH">Шаг 4. Проверка</h3>
<ul>