import React from 'react'; import { Code, ScriptKind, Step, Note, Try, Shot } from './docsData'; /** * docsLessons.jsx — тексты уроков для 50 мини-игр (раздел K вики). * * LESSONS — объект, ключ = id игры из docsGames.js, значение — * { intro, mechanics, body } где body — JSX подробной инструкции. * Хелперы оформления (Code, ScriptKind, Step, Note, Try, Shot) общие * с docsData.jsx. * * Уроки наполняются по одному. Если урока для игры ещё нет — карточка * в каталоге показывает «Урок скоро». */ export const LESSONS = { // ════════════════════════════════════════════════════ // ИГРА 1 — «Собери монетки» // ════════════════════════════════════════════════════ 'collect-coins': { body: ( <>

Что получится

Небольшая площадка, по которой раскиданы золотые монетки. Игрок ходит и собирает их — за каждую монетку счёт растёт на единицу. Когда собраны все 8 монеток — победа, летит конфетти.

Чему научишься

Шаг 1. Площадка

Сначала построим пол, по которому будет ходить игрок.

На вкладке Главная выбери инструмент Блок, в палитре слева — блок травы. Кликай по сцене и собери квадратную площадку примерно 14×14 блоков. Это игровое поле. По краю площадки поставь стенку из камня в один блок высотой — чтобы игрок не убежал за край и не упал. Не обязательно делать ровно 14×14 — главное, чтобы было где разгуляться. Бортик можно сделать и повыше.

Шаг 2. Монетки

Монетка — это примитив-сфера жёлтого цвета. Поставим 8 штук.

Выбери инструмент Примитив, в палитре — Сфера. Поставь сферу на площадку. Выдели её и в инспекторе справа задай: размер 0.6 по всем осям (монетка небольшая), цвет жёлтый #ffd700, материал «Неон» — чтобы монетка светилась. Подними монетку чуть над полом — высота Y около 1.2, чтобы игрок «врезался» в неё телом. В инспекторе выключи «Столкновение» — тогда игрок проходит сквозь монетку, а не упирается в неё. Касание мы поймаем скриптом. Дай монетке понятное имя — например «Монетка_1». Потом выдели её и нажми Ctrl+D, чтобы дублировать. Расставь 8 монеток по площадке.

Шаг 3. Главный скрипт

Главный скрипт считает монетки и проверяет победу.

{`// === ИГРА «СОБЕРИ МОНЕТКИ» — главный скрипт === // Этот скрипт глобальный: считает собранные монетки и проверяет победу. let score = 0; // сколько монеток собрано const TOTAL = 8; // всего монеток на уровне game.ui.score = score; // показать счёт 0 в углу game.ui.showText('Собери все монетки!', 2); // подсказка на старте // Монетки сообщают сюда о сборе через game.broadcast('coin'). // Каждый скрипт работает в своей «песочнице» — переменные одного // скрипта не видны другому. Поэтому скрипты общаются сообщениями: // один шлёт game.broadcast('имя'), другой ловит game.onMessage('имя'). game.onMessage('coin', () => { score = score + 1; // +1 к счёту game.ui.score = score; // обновить число на экране game.sound.play('coin'); // звон монетки if (score >= TOTAL) { // собраны все — победа! game.ui.showText('Победа! Все монетки твои!', 4); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Разберём построчно:

  • let score = 0 — переменная-счётчик, в начале монеток собрано 0;
  • game.ui.score = score — выводит счёт в угол экрана;
  • game.onMessage('coin', ...) — подписка на сообщение «coin». Каждый раз, когда монетка пришлёт это сообщение, выполнится наша функция — прибавит счёт;
  • if (score {'>'}= TOTAL) — когда счёт дошёл до 8, показываем победу и запускаем конфетти.

Шаг 4. Скрипт монетки

Теперь повесим скрипт на каждую монетку. Он ловит касание и сообщает главному скрипту: меня собрали.

{`// === Скрипт монетки === // game.self — это сама монетка, на которой висит скрипт. game.self.onTouch(() => { // игрок коснулся монетки — сообщаем главному скрипту game.broadcast('coin'); game.self.delete(); // монетка исчезает со сцены });`}

Что происходит: onTouch срабатывает, когда игрок дотронулся до монетки. Внутри мы шлём game.broadcast('coin') — главный скрипт ловит это сообщение и прибавляет очко, а game.self.delete() убирает монетку.

Скрипт монетки одинаковый для всех 8 монеток. Чтобы не писать его 8 раз: создай скрипт на первой монетке, а когда дублируешь монетку (Ctrl+D) — скрипт копируется вместе с ней.

Шаг 5. Проверка

Нажми Запустить. Походи по площадке и собери монетки:

  • при касании монетка исчезает и звенит;
  • счётчик в углу растёт: 1, 2, 3...;
  • когда собрал все 8 — надпись «Победа!» и конфетти.

Если что-то не работает — открой Консоль внизу справа, там будут ошибки скриптов.

добавь монеткам вращение — в главном скрипте через game.onTick поворачивай все монетки, чтобы они крутились и были заметнее. Поставь больше монеток. Сделай одну монетку красной и «секретной» — за неё давай сразу +5 очков. ), }, // ════════════════════════════════════════════════════ // ИГРА 2 — «Прыгай по платформам» // ════════════════════════════════════════════════════ 'platform-jump': { body: ( <>

Что получится

Паркур: в воздухе висит дорожка из платформ с разрывами. Игрок прыгает с платформы на платформу и должен добраться до зелёной финишной площадки. Если упал вниз — игра возвращает на старт.

Чему научишься

  • Примитивы-платформы — как из растянутых кубов строить дорожку для паркура;
  • Точка спавна — откуда игрок начинает игру;
  • Падение и респаун — как вернуть игрока на старт, если он свалился (onTick + game.player.respawn);
  • Финиш — как сделать победную зону, на которую нужно встать.

Шаг 1. Старт и платформы

Поставь небольшую стартовую площадку из блоков травы (4×4) — отсюда игрок начнёт. Возьми инструмент Примитив Куб. Поставь куб перед стартом. В инспекторе задай размер: ширина 2, высота 0.5 (платформа плоская), глубина 2. Цвет — коричневый. Подними платформу в воздух — высота Y около 1.5. Убедись, что «Столкновение» включено (на платформу надо вставать) и «Закреплён» включён (платформа не падает). Дублируй платформу (Ctrl+D) и расставь 6-7 штук дорожкой вперёд — каждую следующую чуть выше и со сдвигом, чтобы между ними был прыжок. Не делай разрывы слишком большими — игрок должен допрыгнуть. Платформа — это куб с маленькой высотой (0.5). Так делают все паркур-площадки: берёшь куб и сплющиваешь его по Y.

Шаг 2. Финиш

Поставь в конце дорожки ещё один куб, побольше (4×0.5×4) — это финишная площадка. Покрась его в зелёный, материал «Неон» — чтобы финиш светился и был заметен. Дай имя «Финиш».

Шаг 3. Главный скрипт

Главный скрипт следит за падением и обрабатывает победу.

{`// === ИГРА «ПРЫГАЙ ПО ПЛАТФОРМАМ» — главный скрипт === let won = false; // победа уже была? game.ui.showText('Допрыгай до зелёной площадки!', 3); // Каждый кадр проверяем высоту игрока. game.onTick(() => { if (won) return; const p = game.player.position; // упал ниже всех платформ — вернуть на старт if (p && p.y < -3) { game.player.respawn(); game.ui.showText('Упал! Пробуй снова.', 1.5); game.sound.play('lose'); } }); // Финиш сообщает сюда о победе через broadcast/onMessage: // скрипты живут в РАЗНЫХ песочницах, общая переменная между ними // не видна — связь только сообщениями. game.onMessage('finish', () => { if (won) return; won = true; game.ui.showText('Победа! Ты дошёл до финиша!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Что тут важно:

  • game.onTick(...) — функция внутри выполняется каждый кадр. Так мы постоянно следим за игроком;
  • if (p.y {'<'} -3) — если игрок опустился ниже -3 по высоте, значит свалился с платформ;
  • game.player.respawn() — возвращает игрока на точку спавна;
  • won — флажок, чтобы победа сработала только один раз.

Шаг 4. Скрипт финиша

{`// === Скрипт финиша === // Висит на невидимой зоне над зелёной площадкой. // Игрок встал на площадку — его тело внутри зоны — победа. game.self.onTouch(() => { game.broadcast('finish'); // сообщаем главному скрипту о победе });`}

Когда игрок касается финиша, скрипт шлёт game.broadcast('finish'). Главный скрипт ловит это сообщение через game.onMessage('finish', ...) — там показывается «Победа» и летит конфетти. Скрипты в разных «песочницах» общаются только сообщениями.

Шаг 5. Проверка

  • прыгай Space с платформы на платформу;
  • упал вниз — игра вернёт на старт со звуком;
  • встал на зелёную площадку — «Победа» и конфетти.
добавь движущуюся платформу — пусть одна платформа ездит туда-сюда через game.tween с yoyo: true. Сделай платформы поменьше — прыгать будет сложнее. Поставь на пути монетки из урока 1. ), }, // ════════════════════════════════════════════════════ // ИГРА 3 — «Не упади» // ════════════════════════════════════════════════════ 'dont-fall': { body: ( <>

Что получится

Дорожка из жёлтых плиток. Как только игрок встаёт на плитку, она через секунду исчезает. Стоять нельзя — нужно всё время бежать вперёд, к зелёному финишу. Зазевался — плитка пропала, и ты падаешь вниз.

Чему научишься

  • Таймер game.after — как выполнить что-то не сразу, а через несколько секунд;
  • Исчезновение объекта — как убрать плитку со сцены с задержкой;
  • Флажок-защёлка — как сделать, чтобы плитка запускалась на исчезновение только один раз.

Шаг 1. Дорожка из плиток

Поставь твёрдую стартовую площадку из блоков камня. Сделай плитку — примитив-куб размером 2×0.4×2, жёлтого цвета. «Столкновение» и «Закреплён» — включены. Дублируй плитку и выложи дорожку вперёд — 12-14 плиток в ряд, с небольшими промежутками. Можно зигзагом влево-вправо, чтобы было интереснее. В конце поставь зелёную неоновую финишную площадку.

Шаг 2. Главный скрипт

Следит за падением и победой — как в уроке 2.

{`// === ИГРА «НЕ УПАДИ» — главный скрипт === let won = false; game.ui.showText('Беги вперёд! Плитки исчезают!', 3); game.onTick(() => { if (won) return; // упал вниз — на старт const p = game.player.position; if (p && p.y < -3) { game.player.respawn(); game.ui.showText('Упал! Снова.', 1.5); game.sound.play('lose'); } }); // Финиш сообщает о победе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('finish', () => { if (won) return; won = true; game.ui.showText('Победа! Ты добежал!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Шаг 3. Скрипт исчезающей плитки

Самое главное — повесить на каждую плитку скрипт, который убирает её через секунду после касания.

{`// === Скрипт исчезающей плитки === let triggered = false; // плитка уже запущена на исчезновение? game.self.onTouch(() => { if (triggered) return; // если уже запущена — выходим triggered = true; game.sound.play('click'); // через 1.2 секунды плитка пропадает game.after(1.2, () => { game.self.delete(); }); });`}

Разберём:

  • triggered — флажок-защёлка. Игрок может коснуться плитки несколько раз, но запустить таймер нужно только один раз — флажок это гарантирует;
  • game.after(1.2, () => {'{...}'}) — «через 1.2 секунды выполни это». Внутри — game.self.delete(), плитка исчезает;
  • можно поменять 1.2 на другое число — меньше значит сложнее, плитки пропадают быстрее.
Звук game.sound.play('click') при касании — подсказка игроку: «плитка пошла исчезать, беги!».

Шаг 4. Проверка

  • встаёшь на плитку — щелчок, через секунду она пропадает;
  • стоишь на месте — проваливаешься, респаун на старте;
  • добежал до зелёного финиша — «Победа».
сделай разные плитки с разной задержкой — одни исчезают за 0.6с (быстрые), другие за 2с. Добавь на дорожку монетки из урока 1 — собирай их на бегу. ), }, // ════════════════════════════════════════════════════ // ИГРА 4 — «Кнопка-открывашка» // ════════════════════════════════════════════════════ 'button-door': { body: ( <>

Что получится

Комната перегорожена стеной с дверью. Игрок подходит к красной кнопке, нажимает клавишу E — и дверь плавно уезжает вверх, открывая проход к финишу.

Чему научишься

  • Взаимодействие по E (ProximityPrompt) — как сделать объект, с которым игрок «общается» клавишей E (game.self.onInteract);
  • Поиск объекта по имениgame.scene.findOne;
  • Твин — как плавно подвинуть объект (game.tween).

Шаг 1. Комната, стена и дверь

Сделай пол комнаты из блоков камня (примерно 16×24). Поперёк комнаты построй стену из двух кубов-примитивов, оставив между ними проём шириной 3. В проём поставь дверь — куб 3×5×0.8, коричневый. Дай ему имя «Дверь» — по имени его найдёт скрипт.

Шаг 2. Кнопка и финиш

Перед дверью поставь кнопку — примитив-цилиндр, низкий и широкий, красный неоновый. Имя — «Кнопка». За дверью поставь зелёную финишную площадку.

Шаг 3. Главный скрипт

{`// === ИГРА «КНОПКА-ОТКРЫВАШКА» — главный скрипт === game.ui.showText('Подойди к красной кнопке и нажми E', 4); // Финиш сообщает о победе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('win', () => { game.ui.showText('Победа! Дверь открыта, ты прошёл!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Шаг 4. Скрипт кнопки — главное

Скрипт кнопки делает всю работу: ловит нажатие E и открывает дверь.

{`// === Скрипт кнопки === let opened = false; game.self.onInteract(() => { if (opened) return; // дверь уже открыта opened = true; game.sound.play('click'); // находим дверь по имени const door = game.scene.findOne('Дверь'); // плавно поднимаем её вверх на высоту 8 game.tween(door, { y: 8 }, { duration: 1.2, easing: 'ease' }); game.ui.showText('Дверь открывается!', 2); }, { text: 'Открыть дверь', // подсказка над кнопкой distance: 4 // на сколько метров подойти });`}

Разберём:

  • game.self.onInteract(fn, опции) — это и есть взаимодействие по E. Когда игрок подходит ближе чем на distance метров, над кнопкой появляется подсказка «Открыть дверь». Нажатие E запускает функцию;
  • game.scene.findOne('Дверь') — находит дверь по имени, которое мы задали в инспекторе;
  • game.tween(door, {'{ y: 8 }'}, ...) — плавно меняет высоту двери с текущей до 8, за 1.2 секунды. Дверь уезжает вверх — проход открыт.
Почему твин, а не просто переставить дверь? Твин двигает плавно — дверь красиво «уезжает». Если бы мы просто задали новую высоту, дверь дёрнулась бы рывком.

Шаг 5. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}

Запусти игру:

  • подойди к кнопке — появится подсказка «Открыть дверь»;
  • нажми E — дверь плавно уедет вверх;
  • пройди в открытый проём, встань на финиш — «Победа».
сделай две кнопки и две двери — каждая кнопка открывает свою дверь. Или наоборот: чтобы дверь открылась, нужно нажать две кнопки. Добавь звук закрытия и сделай, чтобы дверь сама закрывалась через 5 секунд. ), }, // ════════════════════════════════════════════════════ // ИГРА 5 — «Лабиринт» // ════════════════════════════════════════════════════ 'maze': { body: ( <>

Что получится

Лабиринт из стен. Игрок появляется в одном углу и должен найти путь к зелёному выходу в другом конце. Стены высокие — через них не перепрыгнуть, нужно искать проход.

Чему научишься

  • Постройка из блоков — как строить стены и коридоры;
  • Планирование уровня — как нарисовать лабиринт так, чтобы из него был выход;
  • Триггер-финиш — как сделать невидимую зону выхода.

Шаг 1. Нарисуй лабиринт на бумаге

Прежде чем строить — нарисуй лабиринт на листе в клетку. Клетка = проход или стена. Отметь, где старт, где выход. Обязательно проверь карандашом, что от старта до выхода есть путь — иначе игру не пройти.

Это самый важный шаг. Строить в редакторе по готовой схеме легко. Строить «на глазок» — запутаешься и забудешь оставить проход.

Шаг 2. Пол

Инструментом Блок выложи квадратный пол из камня — по размеру твоего лабиринта.

Шаг 3. Стены

По своей схеме поставь стены. Стена — это столбик из блоков высотой 3 (чтобы игрок не перепрыгнул). Сначала построй внешнюю границу — рамку по краю пола. Потом — внутренние перегородки-коридоры по схеме. Не забудь оставить проходы! Время от времени запускай игру и проверяй, что лабиринт реально проходится.

Шаг 4. Старт и финиш

Поставь точку спавна (вкладка Игра → Ставить спавн) в начале лабиринта. В конце лабиринта положи на пол зелёную неоновую плитку — финиш. Размер чуть меньше клетки, «Столкновение» выключи, имя — «Финиш».

Шаг 5. Скрипты

Скрипты совсем простые — лабиринт держится на постройке.

{`// === ИГРА «ЛАБИРИНТ» — главный скрипт === game.ui.showText('Найди выход из лабиринта!', 3); // Финиш сообщает о победе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('win', () => { game.ui.showText('Победа! Ты нашёл выход!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`} {`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}

Шаг 6. Проверка

  • пройди лабиринт от старта до зелёного выхода;
  • дошёл до выхода — «Победа» и конфетти;
  • убедись, что нигде нет «дыр» в стенах и тупиков без смысла.
спрячь в тупиках лабиринта монетки из урока 1 — пусть игрок собирает их по пути. Сделай лабиринт побольше. Добавь второй этаж — лестницу из блоков наверх. ), }, // ════════════════════════════════════════════════════ // ИГРА 6 — «Цветные плитки» // ════════════════════════════════════════════════════ 'color-tiles': { body: ( <>

Что получится

На полу — сетка серых плиток. Когда игрок наступает на плитку, она становится ярко-зелёной. Цель — обойти и раскрасить все плитки.

Чему научишься

  • Смена цвета объектаgame.scene.setColor;
  • game.self.ref — как скрипт указывает «адрес» своего объекта;
  • Счётчик с целью — раскрашено столько-то из всех.

Шаг 1. Сетка плиток

Сделай пол из блоков травы. Плитка — примитив-куб 1.8×0.3×1.8, серого цвета. «Столкновение» включено (на плитку наступают). Дублируй плитку и разложи сеткой 6×6 — ровными рядами с небольшими промежутками. Дай каждой понятное имя.

Шаг 2. Главный скрипт

{`// === ИГРА «ЦВЕТНЫЕ ПЛИТКИ» — главный скрипт === let painted = 0; // сколько плиток раскрашено const TOTAL = 36; // всего плиток (6×6) game.ui.score = 0; game.ui.showText('Наступи на все плитки!', 3); // Плитки сообщают о покраске через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('paint', () => { painted = painted + 1; game.ui.score = painted; game.sound.play('pickup'); if (painted >= TOTAL) { game.ui.showText('Победа! Все плитки раскрашены!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`} Замени число 36 на столько плиток, сколько реально поставил. Если сетка 5×5 — будет 25.

Шаг 3. Скрипт плитки

{`// === Скрипт цветной плитки === let painted = false; // плитка уже раскрашена? game.self.onTouch(() => { if (painted) return; painted = true; // меняем цвет плитки на ярко-зелёный game.scene.setColor(game.self.ref, '#33dd55'); game.broadcast('paint'); // сообщаем главному скрипту о покраске });`}

Главное здесь:

  • game.scene.setColor(ref, цвет) — меняет цвет объекта прямо во время игры;
  • game.self.ref — «адрес» этой плитки. Любая команда game.scene.* работает по ref, и через game.self.ref скрипт передаёт адрес своего объекта;
  • game.broadcast('paint') — шлёт сообщение «paint». Главный скрипт ловит его через game.onMessage('paint', ...) и прибавляет счёт. Скрипты живут в разных «песочницах» — переменные одного не видны другому, общаются только сообщениями;
  • флажок painted — чтобы плитка засчиталась только один раз.

Шаг 4. Проверка

  • наступаешь на серую плитку — она зеленеет, счёт растёт;
  • раскрасил все — «Победа».
сделай так, чтобы плитки красились в случайный цвет — через game.random. Или сделай «вредные» красные плитки: наступил — счёт обнулился, начинай заново. ), }, // ════════════════════════════════════════════════════ // ИГРА 7 — «Поймай падающее» // ════════════════════════════════════════════════════ 'catch-falling': { body: ( <>

Что получится

С неба каждые полторы секунды падает жёлтый куб в случайном месте. Игрок бегает по площадке и ловит кубы — касаешься куба, он засчитывается и исчезает. Поймай 15 кубов.

Чему научишься

  • Спавн объектов из скрипта — создавать кубы прямо во время игры (game.scene.spawn);
  • Таймер game.every — делать что-то снова и снова с интервалом;
  • Случайные числаgame.random;
  • Отслеживание объектов — как помнить, какие объекты «наши».

Шаг 1. Площадка

Сделай площадку 16×16 из травы с каменным бортиком — чтобы было где ловить и некуда падать.

Кубы создаются скриптом, заранее их ставить не нужно — вся игра в одном скрипте.

Шаг 2. Главный скрипт

{`// === ИГРА «ПОЙМАЙ ПАДАЮЩЕЕ» — главный скрипт === let score = 0; const GOAL = 15; // сколько кубов нужно поймать let won = false; // Пойманные кубы — чтобы не засчитать один куб дважды. const caught = {}; game.ui.score = 0; game.ui.showText('Лови падающие кубы! Нужно 15', 3); // Каждые 1.5 секунды роняем с неба новый куб. game.every(1.5, () => { if (won) return; const x = game.random(-6, 6); // случайная точка над площадкой const z = game.random(-6, 6); const cube = game.scene.spawn('primitive:cube', { x: x, y: 14, z: z, sx: 0.8, sy: 0.8, sz: 0.8, color: '#ffcc33', anchored: false, // anchored:false — куб ПАДАЕТ (физика) }); // если за 6 секунд не поймали — куб исчезнет сам game.scene.deleteAfter(cube, 6); }); // Игрок коснулся падающего куба. onPlayerTouch шлёт e.target — // ref ('primitive:N') только для заспавненных объектов (кубов), // пол и стены сюда НЕ приходят. game.onPlayerTouch((e) => { if (won) return; const ref = e && e.target; if (!ref || caught[ref]) return; // нет ref или куб уже пойман caught[ref] = true; score = score + 1; game.ui.score = score; game.sound.play('coin'); game.scene.delete(ref); // пойманный куб исчезает if (score >= GOAL) { won = true; game.ui.showText('Победа! Ты поймал 15 кубов!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Разберём по частям:

  • game.every(1.5, fn) — каждые 1.5 секунды выполняет функцию. В ней мы создаём куб;
  • game.scene.spawn('primitive:cube', опции) — создаёт куб прямо во время игры. Высота y: 14 — куб появляется высоко, а anchored: false — он падает по физике;
  • game.random(-6, 6) — случайное число от -6 до 6, так куб падает в случайном месте;
  • caught[ref] = true — помечаем уже пойманный куб, чтобы одно касание не засчиталось дважды;
  • deleteAfter(cube, 6) — если куб не поймали за 6 секунд, он сам исчезнет (уже упал на землю).
Зачем нужен caught? Касание куба может прийти несколько раз подряд. Пометка caught[ref] гарантирует: каждый куб засчитываем ровно один раз.

Шаг 3. Проверка

  • кубы сыплются с неба каждые 1.5 секунды;
  • добежал и коснулся куба — звон, +1 очко;
  • не успел — куб упал и пропал;
  • поймал 15 — «Победа».
ускорь падение кубов — спавни их чаще (поменяй 1.5 на 0.8). Сделай редкие красные кубы, которые дают +5 очков. Добавь таймер: успей поймать 15 кубов за минуту. ), }, // ════════════════════════════════════════════════════ // ИГРА 8 — «Беги к финишу» // ════════════════════════════════════════════════════ 'run-to-finish': { body: ( <>

Что получится

Длинная беговая трасса. Как только начинается игра — включается секундомер. Игрок бежит к зелёному финишу. Добежал — секундомер останавливается и показывает результат. Можно соревноваться: кто быстрее.

Чему научишься

  • Секундомерgame.ui.timer;
  • dt в onTick — как измерять прошедшее время;
  • Округление — показать время красиво.

Шаг 1. Трасса

Выложи из блоков длинную дорожку — например, 6 блоков в ширину и 60 в длину. По бокам поставь невысокие бортики, чтобы игрок не сбежал с трассы. В конце трассы положи зелёную неоновую финишную плитку, имя «Финиш». Точку спавна поставь в самом начале трассы.

Шаг 2. Главный скрипт

{`// === ИГРА «БЕГИ К ФИНИШУ» — главный скрипт === let finished = false; let time = 0; // прошло секунд game.ui.timer = 0; game.ui.showText('Беги к зелёному финишу — на время!', 3); // Каждый кадр прибавляем к таймеру прошедшее время. game.onTick((dt) => { if (finished) return; time = time + dt; // dt — секунды с прошлого кадра game.ui.timer = time; // показываем секундомер }); // Финиш сообщает о завершении забега через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('finish', () => { if (finished) return; finished = true; // округляем время до десятых const t = Math.round(time * 10) / 10; game.ui.showText('Финиш! Твоё время: ' + t + ' сек', 6); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Главное здесь — измерение времени:

  • game.onTick((dt) => {'{...}'}) — функция получает dt: сколько секунд прошло с прошлого кадра (обычно ~0.016 — это 1/60 секунды);
  • time = time + dt — складывая все dt, мы получаем, сколько всего прошло времени;
  • game.ui.timer = time — выводит секундомер на экран в формате ММ:СС;
  • Math.round(time * 10) / 10 — округляет до одной цифры после запятой (12.3 вместо 12.34567).

Шаг 3. Скрипт финиша

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('finish'); // сообщаем главному скрипту о финише });`}

Шаг 4. Проверка

  • с началом игры в углу побежал секундомер;
  • беги к финишу Shift для ускорения;
  • встал на зелёную плитку — секундомер замер, показалось твоё время.
поставь на трассе препятствия — кубы-стены, через которые надо перепрыгивать. Сделай несколько «дорожек» и засекай время на каждой. Покажи лучшее время через game.save — рекорд. ), }, // ════════════════════════════════════════════════════ // ИГРА 9 — «Светофор» // ════════════════════════════════════════════════════ 'traffic-light': { body: ( <>

Что получится

В конце дорожки — большой шар-светофор. Он по очереди становится зелёным и красным. На зелёный можно бежать к финишу, на красный нужно замереть. Двинулся на красный — игра возвращает на старт.

Чему научишься

  • Фазы по таймеру — переключать состояние игры через game.after;
  • Проверка движения — как узнать, что игрок сдвинулся (сравнить позиции);
  • game.scene.setColor — менять цвет светофора.

Шаг 1. Дорожка и светофор

Сделай длинную дорожку из блоков (8 в ширину, ~50 в длину). В конце дорожки, повыше, поставь большую сферу — это светофор. Материал «Неон», цвет красный. Имя — «Светофор». Перед светофором — зелёная финишная плитка. Точка спавна — в начале дорожки.

Шаг 2. Главный скрипт

Это самый сложный скрипт пока — разберём внимательно.

{`// === ИГРА «СВЕТОФОР» — главный скрипт === let phase = 'green'; // 'green' (беги) или 'red' (замри) let won = false; let prev = null; // прошлая позиция игрока game.ui.showText('Зелёный — беги! Красный — замри!', 3); // findOne нельзя сразу в начале скрипта — снимок сцены приходит // чуть позже. Светофор находим и запускаем цикл через game.after. let light = null; // Переключаем свет: зелёный 3с, красный 2.5с, по кругу. function green() { if (won || !light) return; phase = 'green'; game.scene.setColor(light, '#22dd55'); game.ui.showText('ЗЕЛЁНЫЙ — беги!', 1.2); game.after(3, red); } function red() { if (won || !light) return; phase = 'red'; game.scene.setColor(light, '#e23b3b'); game.ui.showText('КРАСНЫЙ — замри!', 1.2); game.after(2.5, green); } game.after(0.2, () => { light = game.scene.findOne('Светофор'); green(); // начинаем с зелёного }); // Каждый кадр: если красный и игрок шевелится — на старт. game.onTick((dt) => { if (won) return; const p = game.player.position; if (!p) return; if (prev && phase === 'red') { // на сколько игрок сдвинулся с прошлого кадра const moved = Math.hypot(p.x - prev.x, p.z - prev.z); if (moved / dt > 0.8) { // двигался — пойман game.player.respawn(); game.ui.showText('Двинулся на красный! На старт.', 2); game.sound.play('lose'); } } prev = { x: p.x, z: p.z }; // запомнить позицию }); // Финиш сообщает о победе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты дошёл до финиша!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Как работают фазы:

  • green() ставит зелёный цвет и через game.after(3, red) запланирует переключение на красный через 3 секунды;
  • red() — наоборот: красный, и через 2.5с обратно green. Так свет мигает по кругу;
  • две функции вызывают друг друга через таймер — это и есть «вечный» светофор.

Как ловится движение:

  • каждый кадр запоминаем позицию игрока в prev;
  • Math.hypot(dx, dz) — на сколько игрок сдвинулся за кадр;
  • moved / dt — это его скорость. Если на красный скорость больше 0.8 — игрок шевелится, возвращаем на старт.

Шаг 3. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}
  • светофор мигает зелёный/красный;
  • на зелёный беги, на красный замри;
  • двинулся на красный — вернёт на старт;
  • добежал до финиша на зелёный — «Победа».
делай красную фазу всё короче с каждым кругом — игра будет напряжённее. Добавь второй светофор посередине. Сделай несколько игроков-соперников. ), }, // ════════════════════════════════════════════════════ // ИГРА 10 — «Прыжок-пружина» // ════════════════════════════════════════════════════ 'spring-jump': { body: ( <>

Что получится

Башня из площадок-этажей на разной высоте. Между ними — оранжевые батуты. Игрок встаёт на батут, тот подбрасывает его высоко вверх — на следующий этаж. Допрыгай до верха.

Чему научишься

  • game.player.boostJump — мощный подброс игрока вверх;
  • Многоуровневая сцена — строить вверх, не только вширь;
  • Один скрипт на много объектов — все батуты работают одинаково.

Шаг 1. Башня

Поставь стартовую площадку из блоков на земле. Сделай 2-3 площадки-этажа из кубов-примитивов на высотах 7, 14, 21 — друг над другом со сдвигом. «Закреплён» и «Столкновение» включены. На вершине — зелёная финишная плитка.

Шаг 2. Батуты

Батут — примитив-цилиндр, низкий и широкий (2×0.4×2), оранжевый неоновый. Поставь по батуту на старте и на каждом этаже (кроме верхнего) — там, откуда нужно прыгать выше.

Шаг 3. Главный скрипт

{`// === ИГРА «ПРЫЖОК-ПРУЖИНА» — главный скрипт === let won = false; game.ui.showText('Прыгай по батутам всё выше!', 3); // упал вниз — на старт game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -3) { game.player.respawn(); game.sound.play('lose'); } }); // Финиш сообщает о победе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты допрыгал до верха!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Шаг 4. Скрипт батута

Повесь этот скрипт на каждый батут — он одинаковый.

{`// === Скрипт батута === // Игрок встал на батут — мощный подброс вверх. game.self.onTouch(() => { game.player.boostJump(3.2); // 3.2 = в 3 раза выше обычного прыжка game.sound.play('jump'); });`}

game.player.boostJump(сила) — мгновенно подбрасывает игрока. 1 — как обычный прыжок, 3.2 — в три с лишним раза выше. Подбери число так, чтобы игрок допрыгивал от батута до следующего этажа.

Если игрок не долетает до этажа — увеличь силу (boostJump(4)) или поставь этажи ближе. Если перелетает — наоборот, уменьши.

Шаг 5. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}
  • встал на батут — подлетел вверх;
  • попал на этаж — иди к следующему батуту;
  • добрался до зелёного верха — «Победа».
сделай батуты с разной силой — где-то слабый, где-то очень мощный. Добавь движущиеся этажи (твин из урока 2). Расставь в воздухе монетки, которые ловишь во время полёта. ), }, // ════════════════════════════════════════════════════ // ИГРА 11 — «Эхо-комната» // ════════════════════════════════════════════════════ 'echo-room': { body: ( <>

Что получится

Комната с шестью разноцветными плитками. Когда игрок наступает на плитку — она звучит и вспыхивает искрами. Каждая плитка — свой звук. Пройди все шесть, потом встань на зелёный финиш в центре.

Чему научишься

  • Звук на событиеgame.sound.play;
  • game.self.position — узнать, где находится объект скрипта;
  • Частицы-вспышка — эффект при касании.
Звук в играх обязателен — игра без звука кажется «мёртвой». Этот урок как раз про то, как оживить игру звуком.

Шаг 1. Комната

Сделай пол комнаты (14×14) и стены из блоков высотой 3 — замкнутое пространство.

Шаг 2. Звуковые плитки

Плитка — примитив-цилиндр, низкий и широкий, неоновый. Сделай 6 штук разных цветов и расставь по комнате. В центре комнаты — зелёная финишная плитка.

Шаг 3. Главный скрипт

{`// === ИГРА «ЭХО-КОМНАТА» — главный скрипт === let stepped = 0; // на сколько плиток наступили const TOTAL = 6; game.ui.score = 0; game.ui.showText('Наступи на все цветные плитки!', 3); // Плитки и финиш сообщают сюда через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. // игрок впервые наступил на звуковую плитку game.onMessage('step', () => { stepped = stepped + 1; game.ui.score = stepped; if (stepped >= TOTAL) { game.ui.showText('Все плитки звучали! Иди на финиш.', 3); } }); // игрок встал на финиш game.onMessage('finish', () => { if (stepped < TOTAL) { game.ui.showText('Сначала пройди все ' + TOTAL + ' плиток!', 2); return; } game.ui.showText('Победа! Эхо-комната пройдена!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Шаг 4. Скрипт звуковой плитки

Повесь на каждую плитку. Поменяй звук — у каждой плитки свой: 'coin', 'jump', 'pickup', 'click', 'hit'.

{`// === Скрипт звуковой плитки === let used = false; // на эту плитку уже наступали? game.self.onTouch(() => { // звук играет КАЖДЫЙ раз — это эхо-комната game.sound.play('coin'); // у каждой плитки свой звук! // вспышка частиц над плиткой game.scene.spawnParticles('sparks', game.self.position, { duration: 0.6, color: '#e23b3b' }); // засчитываем плитку только в первый раз if (!used) { used = true; game.broadcast('step'); // сообщаем главному скрипту о новой плитке } });`}

Разберём:

  • game.sound.play('coin') — проигрывает звук. Доступные звуки: coin, jump, pickup, click, hit, win, lose;
  • game.self.position — координаты самой плитки, над ней появятся искры;
  • game.broadcast('step') — шлёт сообщение главному скрипту, тот ловит его через game.onMessage('step', ...). Скрипты в разных «песочницах» общаются только сообщениями;
  • звук играет при каждом касании (эхо!), а в счёт плитка идёт только первый раз — за это отвечает флажок used.

Шаг 5. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('finish'); // сообщаем главному скрипту о финише });`}
  • наступаешь на плитку — звук и искры;
  • прошёл все 6 — появится подсказка идти на финиш;
  • встал на финиш — «Победа».
сделай мелодию: пусть игрок наступает на плитки в нужном порядке (это будет похоже на музыкальную игру). Добавь больше плиток. Меняй цвет плитки после нажатия. ), }, // ════════════════════════════════════════════════════ // ИГРА 12 — «Дверь по коду» // ════════════════════════════════════════════════════ 'code-door': { body: ( <>

Что получится

Перед запертой дверью — четыре кнопки с цифрами. Чтобы дверь открылась, нужно нажать кнопки (клавишей E) в правильном порядке — знать секретный код. Ошибся — код сбрасывается, начинай заново.

Чему научишься

  • Хранить последовательность — массив нажатых кнопок;
  • Проверять порядок — совпадает ли ввод с кодом;
  • Сбрасывать прогресс — при ошибке начать заново.

Шаг 1. Комната, дверь, кнопки

Сделай комнату со стеной и дверью — как в уроке 4. Дверь назови «Дверь». Перед дверью поставь 4 кнопки-цилиндра в ряд. Назови их «Кнопка_1», «Кнопка_2», «Кнопка_3», «Кнопка_4». За дверью — зелёный финиш.

Шаг 2. Главный скрипт

Здесь самое интересное — проверка кода. Разберём подробно.

{`// === ИГРА «ДВЕРЬ ПО КОДУ» — главный скрипт === // СЕКРЕТНЫЙ КОД — порядок кнопок. Поменяй на свой! const CODE = [3, 1, 4, 2]; let entered = []; // что игрок уже нажал let opened = false; game.ui.showText('Нажми кнопки в правильном порядке (E)', 4); // Кнопки и финиш сообщают сюда через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. // Номер нажатой кнопки приходит в data: { num }. // игрок нажал кнопку с номером d.num game.onMessage('press', (d) => { if (opened) return; game.sound.play('click'); entered.push(d.num); // проверяем — совпадает ли начало с кодом const i = entered.length - 1; if (entered[i] !== CODE[i]) { // ошибка — сброс entered = []; game.ui.showText('Неверно! Код сброшен.', 1.5); game.sound.play('lose'); return; } // весь код введён верно if (entered.length === CODE.length) { opened = true; game.ui.showText('Код верный! Дверь открывается.', 3); game.sound.play('win'); const door = game.scene.findOne('Дверь'); game.tween(door, { y: 9 }, { duration: 1.2, easing: 'ease' }); } else { game.ui.showText('Верно! Дальше...', 1); } }); // игрок встал на финиш game.onMessage('win', () => { game.ui.showText('Победа! Ты разгадал код!', 5); game.sound.play('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 = [3, 1, 4, 2] — секретный код, массив номеров кнопок по порядку;
  • entered — список того, что игрок уже нажал;
  • game.onMessage('press', (d) => ...) — ловит сообщение от кнопки. Номер кнопки приходит в d.num. Кнопки шлют его через game.broadcast('press', {'{ num: ... }'}) — скрипты в разных «песочницах» общаются только сообщениями;
  • entered.push(d.num) — добавляем новое нажатие в конец списка;
  • entered[i] !== CODE[i] — проверяем: совпало ли последнее нажатие с нужной цифрой кода. Не совпало — entered = [] очищает список, начинай заново;
  • когда длина entered равна длине CODE и всё совпало — код разгадан, дверь уезжает твином.
Хитрость: мы проверяем каждое нажатие сразу. Если игрок ошибся на втором шаге — не нужно ждать четвёртого, код сбросится тут же.

Шаг 3. Скрипты кнопок

На каждую кнопку — свой скрипт. Отличается только цифра в press(...) и в подсказке.

{`// === Скрипт кнопки-цифры 1 === game.self.onInteract(() => { // сообщаем главному скрипту номер нажатой кнопки game.broadcast('press', { num: 1 }); // ← номер кнопки }, { text: 'Нажать кнопку 1', distance: 3 });`}

Для «Кнопки_2» поставь {'{ num: 2 }'} и текст «Нажать кнопку 2», и так далее.

Шаг 4. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}
  • подходи к кнопкам, жми E;
  • нажал по коду 3-1-4-2 — дверь открылась;
  • ошибся — «Код сброшен», начинай сначала;
  • прошёл в дверь, встал на финиш — «Победа».
сделай код длиннее — 6 кнопок. Подскажи код игроку через надписи-billboard над кнопками. Сделай так, чтобы после трёх ошибок включалась «сигнализация» — звук. ), }, // ════════════════════════════════════════════════════ // ИГРА 13 — «Торговец» // ════════════════════════════════════════════════════ 'trader': { body: ( <>

Что получится

В лавке за прилавком стоит NPC-торговец. Игрок подходит, нажимает E — торговец отвечает репликой и дарит ключ. С ключом можно открыть дверь и дойти до финиша.

Чему научишься

  • NPC — создавать персонажей-жителей (game.scene.spawnNpc);
  • Реплики NPCnpc.say;
  • Инвентарь — выдавать и проверять предметы (game.inventory).

Шаг 1. Лавка

Сделай пол лавки из деревянных блоков. Поставь прилавок — длинный куб-примитив. Имя «Прилавок». Сделай стену с дверью (имя «Дверь») и зелёный финиш за ней — как в уроке 4. Самого торговца ставить не нужно — он появится из скрипта командой spawnNpc.

Шаг 2. Главный скрипт

{`// === ИГРА «ТОРГОВЕЦ» — главный скрипт === game.ui.showText('Поговори с торговцем — нажми E у прилавка', 4); // создаём NPC-торговца за прилавком const trader = game.scene.spawnNpc('character-a', { x: 0, y: 1, z: 5, // y=1 — на верху блоков пола (иначе утоплен) name: 'Торговец Боб', hp: 100, speed: 0, // торговец стоит на месте }); let hasKey = false; // Прилавок, дверь и финиш сообщают сюда через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. // игрок заговорил с торговцем game.onMessage('talk', () => { if (hasKey) { trader.say('Иди к двери, ключ у тебя!', 3); return; } hasKey = true; trader.say('Привет! Вот тебе ключ от двери. Удачи!', 4); game.inventory.add({ name: 'Ключ' }); game.ui.showText('Ты получил Ключ!', 2); game.sound.play('pickup'); }); // игрок пытается открыть дверь game.onMessage('openDoor', () => { if (!game.inventory.has('Ключ')) { game.ui.showText('Дверь заперта. Нужен ключ от торговца.', 2); return; } game.ui.showText('Ключ подошёл! Дверь открыта.', 3); game.sound.play('win'); const door = game.scene.findOne('Дверь'); game.tween(door, { y: 9 }, { duration: 1.2, easing: 'ease' }); }); // игрок встал на финиш game.onMessage('win', () => { game.ui.showText('Победа! Ты прошёл лавку торговца!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • game.scene.spawnNpc('character-a', опции) — создаёт NPC. speed: 0 — торговец не ходит, стоит за прилавком;
  • trader.say('текст', 4) — над головой NPC на 4 секунды появляется реплика;
  • game.inventory.add({'{ name: '}'Ключ'{' }'}) — кладёт предмет в инвентарь игрока;
  • game.inventory.has('Ключ') — проверяет, есть ли предмет. Дверь открывается только с ключом.

Шаг 3. Скрипт прилавка

{`// === Скрипт прилавка === game.self.onInteract(() => { game.broadcast('talk'); // сообщаем главному скрипту: говорим с торговцем }, { text: 'Поговорить с торговцем', distance: 4 });`}

Шаг 4. Скрипт двери

{`// === Скрипт двери === game.self.onInteract(() => { game.broadcast('openDoor'); // сообщаем главному скрипту: открыть дверь }, { text: 'Открыть дверь', distance: 4 });`}

Шаг 5. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}
  • подойди к прилавку, нажми E — торговец заговорит и даст ключ;
  • подойди к двери, нажми E — с ключом она откроется;
  • встань на финиш — «Победа».
сделай, чтобы торговец продавал ключ за монетки (собери монетки из урока 1, потом купи). Добавь второго NPC с другим предметом. Пусть торговец ходит за прилавком через npc.moveTo. ), }, // ════════════════════════════════════════════════════ // ИГРА 14 — «Собери по тегам» // ════════════════════════════════════════════════════ 'collect-by-tag': { body: ( <>

Что получится

На площадке вперемешку стоят жёлтые звёзды и синие кубы. Собирать нужно только звёзды — кубы это обманки. Игра помечает все звёзды специальной меткой-тегом, а потом по тегу легко считает, сколько звёзд ещё осталось.

Чему научишься

  • Теги — это «ярлычки», которые можно навесить на объекты, чтобы потом находить именно их (game.scene.tag);
  • Снять тегgame.scene.untag;
  • Найти всё по тегуgame.scene.getTagged возвращает список всех объектов с этим тегом;
  • Считать остаток — узнавать, сколько целей ещё не собрано.
Тег — это как наклейка. Можешь наклеить на всё, что нужно собрать, ярлычок «звезда», а потом одной командой спросить: «дай мне все объекты с ярлычком звезда». Очень удобно, когда объектов много.

Шаг 1. Площадка

Инструментом Блок выложи квадратную травяную площадку примерно 16×16.

Шаг 2. Звёзды и кубы-обманки

Звезда — примитив-конус жёлтого цвета #ffd700, материал «Неон». Поставь 7 штук и дай им имена «Звезда_1», «Звезда_2» ... «Звезда_7». «Столкновение» у звёзд выключи — игрок проходит сквозь звезду, а касание поймает скрипт. Куб-обманка — примитив-куб синего цвета #3b82f6. Поставь 5 штук вперемешку со звёздами. Имена для них не важны — собирать их не надо. Очень важно, чтобы звёзды назывались именно «Звезда_1» ... «Звезда_7» — главный скрипт ищет их по этим именам, чтобы навесить тег.

Шаг 3. Главный скрипт

Главный скрипт сначала «наклеивает» тег на все звёзды, а потом считает, сколько звёзд осталось.

{`// === ИГРА «СОБЕРИ ПО ТЕГАМ» — главный скрипт === game.ui.showText('Собери все ЖЁЛТЫЕ звёзды!', 3); game.ui.score = 0; // помечаем все звёзды тегом 'звезда'. // findOne нельзя сразу в начале — снимок сцены приходит чуть позже, // поэтому простановку тегов делаем через game.after. game.after(0.2, () => { for (let i = 1; i <= 7; i++) { const star = game.scene.findOne('Звезда_' + i); if (star) game.scene.tag(star, 'звезда'); } }); // Звёзды сообщают о сборе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('collected', () => { // getTagged вернёт все ещё не собранные звёзды const left = game.scene.getTagged('звезда').length; game.ui.score = 7 - left; game.sound.play('coin'); if (left === 0) { game.ui.showText('Победа! Все звёзды собраны!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Разберём построчно:

  • цикл for внутри game.after(0.2, ...) проходит по звёздам с 1 по 7, находит каждую по имени через findOne (небольшая задержка нужна, чтобы снимок сцены успел появиться);
  • game.scene.tag(star, 'звезда') — навешивает на звезду тег-ярлычок «звезда»;
  • game.scene.getTagged('звезда') — возвращает список всех объектов с тегом «звезда». А .length — это сколько их в списке;
  • 7 - left — если осталось 5 звёзд, значит собрано 2 (всего 7 минус 5). Это и показываем в счёте;
  • if (left === 0) — звёзд с тегом не осталось, все собраны — победа.

Шаг 4. Скрипт звезды

Этот скрипт повесь на каждую из 7 звёзд.

{`// === Скрипт звезды === game.self.onTouch(() => { // снимаем тег и удаляем звезду game.scene.untag(game.self.ref, 'звезда'); game.self.delete(); game.broadcast('collected'); // сообщаем главному скрипту о сборе });`}

Что происходит при касании:

  • game.scene.untag(game.self.ref, 'звезда') — снимает тег «звезда» с этой звезды. Теперь getTagged её больше не вернёт;
  • game.self.delete() — звезда исчезает со сцены;
  • game.broadcast('collected') — шлёт сообщение главному скрипту. Тот ловит его через game.onMessage('collected', ...) и пересчитывает остаток. Скрипты в разных «песочницах» общаются только сообщениями.
У синих кубов скрипта нет — поэтому коснуться их можно, но ничего не произойдёт. Тег есть только у звёзд, кубы — просто украшение-обманка.

Шаг 5. Проверка

  • собирай жёлтые звёзды — счёт растёт, звенит монетка;
  • синие кубы можно трогать — ничего не происходит;
  • собрал все 7 звёзд — «Победа» и конфетти.
добавь второй тег — например, «бонус» на пару особых красных звёзд, которые дают больше очков. Сделай больше кубов-обманок. Покрась звёзды в разные цвета, но всё равно помечай тегом «звезда». ), }, // ════════════════════════════════════════════════════ // ИГРА 15 — «Тир» // ════════════════════════════════════════════════════ 'shooting-range': { body: ( <>

Что получится

Настоящий тир: на постаментах стоят красные мишени-шары. Нужно кликать по ним мышкой — за каждое попадание мишень взрывается искрами и даёт очко. Выбей все 8 мишеней.

Чему научишься

  • Клик по 3D-объекту — как сделать так, чтобы объект реагировал на щелчок мышью (game.self.onClick);
  • Счётчик попаданий — считать, сколько мишеней выбито;
  • Эффект-взрыв — частицы при попадании.
onTouch из прошлых уроков срабатывал, когда игрок касается объекта телом. А onClick — когда игрок щёлкает по объекту мышкой, даже издалека. Для тира это как раз то, что нужно.

Шаг 1. Площадка тира

Выложи прямоугольную площадку из каменных блоков — это зал тира.

Шаг 2. Постаменты и мишени

Постамент — примитив-куб, узкий и высокий (размер 1×2×1), серого цвета. Поставь 8 постаментов дальше от точки старта, чтобы было куда целиться. Мишень — примитив-сфера ярко-красного цвета #ff3030, материал «Неон». Поставь по сфере на каждый постамент, повыше. У мишеней «Столкновение» выключи. Дай им имена «Мишень_2», «Мишень_4» и т.д.

Шаг 3. Главный скрипт

{`// === ИГРА «ТИР» — главный скрипт === let score = 0; const TOTAL = 8; game.ui.score = 0; game.ui.showText('Кликай по красным мишеням!', 3); // Мишени сообщают о попадании через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('hit', () => { score = score + 1; game.ui.score = score; game.sound.play('hit'); if (score >= TOTAL) { game.ui.showText('Победа! Все мишени выбиты!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Здесь всё знакомо:

  • score — счётчик попаданий;
  • TOTAL = 8 — всего мишеней;
  • game.onMessage('hit', ...) — ловит сообщение «hit» от мишеней. Каждая мишень шлёт его через game.broadcast('hit'), когда по ней попали. Внутри +1 к счёту и проверка: выбиты ли все 8.

Шаг 4. Скрипт мишени

Главное в этом уроке — повесь скрипт на каждую мишень-сферу.

{`// === Скрипт мишени === // Клик по 3D-объекту = выстрел в него. game.self.onClick(() => { // взрыв искр на месте мишени game.scene.spawnParticles('explosion', game.self.position, { count: 1, color: '#ff6633' }); game.self.delete(); // мишень сбита game.broadcast('hit'); // сообщаем главному скрипту о попадании });`}

Разберём:

  • game.self.onClick(() => {'{...}'}) — срабатывает, когда игрок щёлкнул мышкой по этой мишени;
  • game.scene.spawnParticles('explosion', ...) — на месте мишени вспыхивает взрыв оранжевых искр;
  • game.self.position — координаты самой мишени, там и будет взрыв;
  • game.self.delete() — мишень исчезает, а game.broadcast('hit') шлёт сообщение главному скрипту — тот добавляет очко. Скрипты в разных «песочницах» общаются только сообщениями.

Шаг 5. Проверка

  • наведи прицел на красную мишень и щёлкни мышкой;
  • мишень взрывается искрами, счёт растёт;
  • выбил все 8 мишеней — «Победа» и конфетти.
сделай несколько мишеней маленькими — по ним сложнее попасть. Добавь «вредную» чёрную мишень: попал по ней — минус очко. Сделай, чтобы мишени двигались твином из урока 4 — попасть будет труднее. ), }, // ════════════════════════════════════════════════════ // ИГРА 16 — «Лава-пол» // ════════════════════════════════════════════════════ 'lava-floor': { body: ( <>

Что получится

Перед игроком — большое озеро раскалённой лавы. Идти по лаве нельзя — она жжёт и отнимает здоровье. Нужно перепрыгивать с одного каменного островка на другой, пока не доберёшься до зелёного финиша на той стороне.

Чему научишься

  • Урон игроку — как отнять здоровье (game.player.damage);
  • Здоровье (HP) — у игрока есть полоска жизни, она кончилась — игрок воскресает на старте;
  • Невидимая зона-триггер — объект, который не виден, но ловит касание игрока.

Шаг 1. Озеро лавы и старт

Инструментом Блок выбери блок лавы и выложи большое прямоугольное озеро. В начале озера поставь поверх лавы небольшую стартовую площадку из каменных блоков — отсюда игрок начинает, тут безопасно.

Шаг 2. Островки и финиш

Островок — примитив-куб, плоский и широкий (размер примерно 2.4×0.6×2.4), серого цвета. Подними его чуть над лавой. «Столкновение» и «Закреплён» включены. Дублируй островок и расставь дорожкой через всё озеро — так, чтобы с каждого можно было допрыгнуть до следующего. В конце поставь зелёную неоновую финишную площадку, имя «Финиш».

Шаг 3. Зона лавы

Чтобы лава наносила урон, над озером нужен один большой невидимый куб-зона. Он ловит касание игрока.

Поставь куб-примитив и растяни его так, чтобы он накрыл всё озеро лавы. Тонкий по высоте. В инспекторе у этого куба выключи «Видимый» и выключи «Столкновение». Дай имя «ЛаваЗона». Почему отдельная невидимая зона, а не сами блоки лавы? На блоки скрипт повесить нельзя, а на примитив — можно. Поэтому делаем один большой невидимый примитив-ловушку поверх лавы.

Шаг 4. Главный скрипт

{`// === ИГРА «ЛАВА-ПОЛ» — главный скрипт === let won = false; game.ui.showText('Прыгай по островкам! Лава жжёт!', 3); // если HP кончилось — игрок воскреснет на старте сам. // следим за падением в лаву ниже уровня. game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -2) { game.player.respawn(); } }); // Финиш сообщает о победе через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты перебрался через лаву!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}
  • game.onTick следит, не провалился ли игрок совсем низко — тогда возвращает его на старт;
  • game.onMessage('win', ...) — победа. Финиш шлёт сюда сообщение «win» через game.broadcast('win').

Шаг 5. Скрипт лавы — главное

{`// === Скрипт лавы === // Игрок коснулся лавы — урон. У damage есть защита (i-frames), // так что урон не каждый кадр, а раз в ~0.5 секунды. game.self.onTouch(() => { game.player.damage(20); game.sound.play('hit'); });`}

Разберём:

  • game.player.damage(20) — отнимает у игрока 20 единиц здоровья. Когда HP дойдёт до нуля, игрок сам воскреснет на старте;
  • урон срабатывает не каждый кадр, а примерно раз в полсекунды — это встроенная «защита от частого урона» (по-английски i-frames). Иначе на лаве здоровье улетало бы мгновенно;
  • звук 'hit' — сигнал «ой, жжётся!».

Шаг 6. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); // сообщаем главному скрипту о победе });`}
  • прыгай с островка на островок;
  • попал в лаву — здоровье тает, слышен звук урона;
  • здоровье кончилось — воскресаешь на старте;
  • добрался до зелёного финиша — «Победа».
сделай урон лавы посильнее (damage(35)) — будет сложнее. Добавь по пути «аптечку» — зелёную сферу, касание которой лечит через game.player.heal. Сделай острова поменьше — прыгать точнее. ), }, // ════════════════════════════════════════════════════ // ИГРА 17 — «Ключ и сундук» // ════════════════════════════════════════════════════ 'key-chest': { body: ( <>

Что получится

На поляне среди зелёных кустов спрятан золотой ключ. Игрок ищет ключ, подбирает его, а потом подходит к сундуку и открывает его клавишей E. Без ключа сундук не откроется.

Чему научишься

  • Инвентарь — у игрока есть «рюкзак», куда можно класть предметы (game.inventory.add);
  • Проверка предмета — есть ли нужная вещь в инвентаре (game.inventory.has);
  • Взаимодействие по E — открыть сундук (game.self.onInteract).

Шаг 1. Поляна и кусты

Выложи большую квадратную травяную площадку — это поляна. Куст — примитив-куб зелёного цвета #2f7d32. Поставь 5-6 кустов вразброс по поляне — среди них и спрячется ключ.

Шаг 2. Ключ и сундук

Ключ — примитив-тор (колечко) жёлтого цвета #ffd700, материал «Неон». Положи его где-нибудь в уголке, рядом с кустом. «Столкновение» у ключа выключи. Имя — «Ключ». Сундук — примитив-куб коричневого цвета. Поставь его в центре поляны. Имя — «Сундук».

Шаг 3. Главный скрипт

{`// === ИГРА «КЛЮЧ И СУНДУК» — главный скрипт === game.ui.showText('Найди ключ и открой сундук!', 3); // Ключ и сундук сообщают сюда через broadcast/onMessage — // скрипты в разных песочницах, связь только сообщениями. // игрок подобрал ключ game.onMessage('takeKey', () => { game.inventory.add({ name: 'Ключ' }); game.ui.showText('Ты нашёл Ключ!', 2); game.sound.play('pickup'); }); // игрок пытается открыть сундук game.onMessage('openChest', () => { if (!game.inventory.has('Ключ')) { game.ui.showText('Сундук заперт. Сначала найди ключ.', 2); return; } game.ui.showText('Победа! Сундук открыт — там сокровище!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Здесь два обработчика сообщений:

  • game.onMessage('takeKey', ...) — игрок нашёл ключ. game.inventory.add({'{ name: '}'Ключ'{' }'}) кладёт предмет «Ключ» в рюкзак игрока;
  • game.onMessage('openChest', ...) — попытка открыть сундук. Сначала game.inventory.has('Ключ') проверяет, есть ли ключ в рюкзаке. Если нет — сообщение «Сундук заперт» и return прерывает функцию;
  • если ключ есть — победа, летит конфетти;
  • сообщения «takeKey» и «openChest» шлют скрипты ключа и сундука через game.broadcast(...) — скрипты в разных «песочницах» общаются только сообщениями.
return — это «выйти из функции прямо сейчас». Если ключа нет, дальше код просто не выполняется — сундук остаётся закрытым.

Шаг 4. Скрипт ключа

{`// === Скрипт ключа === game.self.onTouch(() => { game.broadcast('takeKey'); // сообщаем главному скрипту: ключ найден game.self.delete(); // ключ подобран });`}

Игрок коснулся ключа — скрипт шлёт сообщение game.broadcast('takeKey') (главный скрипт положит ключ в инвентарь), а game.self.delete() убирает ключ с поляны: он же теперь в рюкзаке.

Шаг 5. Скрипт сундука

{`// === Скрипт сундука === game.self.onInteract(() => { game.broadcast('openChest'); // сообщаем главному скрипту: открыть сундук }, { text: 'Открыть сундук', distance: 4 });`}

Когда игрок подходит ближе чем на 4 метра, над сундуком появляется подсказка «Открыть сундук». Нажатие E шлёт сообщение «openChest» — а главный скрипт уже проверит, есть ли ключ.

Шаг 6. Проверка

  • подойди к сундуку без ключа, нажми E — «Сундук заперт»;
  • найди ключ-колечко, коснись его — «Ты нашёл Ключ»;
  • вернись к сундуку, нажми E — «Победа», конфетти.
спрячь два ключа, и пусть сундук открывается только когда оба в инвентаре. Сделай ключ совсем маленьким — искать труднее. Добавь второй сундук с другой наградой. ), }, // ════════════════════════════════════════════════════ // ИГРА 18 — «Качели» // ════════════════════════════════════════════════════ 'swing': { body: ( <>

Что получится

Между двумя площадками висят большие качели. Они сами раскачиваются туда-сюда. Игрок запрыгивает на качели с возвышенности и, прокатившись на них, перебирается на финишную площадку.

Чему научишься

  • Констрейнт-петля (шарнир) — как закрепить объект на оси, чтобы он мог поворачиваться вокруг неё (game.constraints.hinge);
  • Управлять углом петлиhinge.setAngle;
  • Повторяющийся таймерgame.every для раскачивания.
Констрейнт — это «связь» или «крепление». Петля-шарнир (по-английски hinge) — это как петля у двери: объект не падает и не уезжает, а только поворачивается вокруг оси. Качели как раз качаются вокруг своей оси.

Шаг 1. Площадки

Сделай стартовую возвышенность — небольшую башенку из каменных блоков высотой 2-3 блока. С неё игрок запрыгнет на качели.

Шаг 2. Качели и финиш

Качели — примитив-куб, широкий и плоский (размер примерно 4×0.5×3), коричневого цвета. Подними его в воздух на уровень возвышенности. Имя — «Качели». По другую сторону от качелей поставь зелёную неоновую финишную площадку. Имя — «Финиш».

Шаг 3. Главный скрипт

Главный скрипт делает качели «настоящими»: вешает их на петлю и раскачивает.

{`// === ИГРА «КАЧЕЛИ» — главный скрипт === let won = false; game.ui.showText('Запрыгни на качели и прокатись!', 3); // делаем качели на петле и раскачиваем их. // ВАЖНО: game.scene.findOne нельзя звать сразу в начале скрипта — // снимок сцены приходит чуть позже. Ждём 0.2с через game.after. let hinge = null; let dir = -35; game.after(0.2, () => { const swing = game.scene.findOne('Качели'); hinge = game.constraints.hinge(swing, { pivotX: 0, pivotZ: 0, // ось — посередине качелей angle: 35 }); // раскачиваем туда-сюда каждые 1.4 секунды game.every(1.4, () => { if (won || !hinge) return; hinge.setAngle(dir); dir = -dir; }); }); game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -3) game.player.respawn(); }); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты перебрался на качелях!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём по частям:

  • game.after(0.2, ...) — ждём 0.2 секунды перед поиском качелей. Снимок сцены приходит чуть позже старта, поэтому findOne сразу позвать нельзя — качелей ещё «не видно»;
  • game.scene.findOne('Качели') — находим качели по имени;
  • game.constraints.hinge(swing, опции) — вешаем качели на петлю-шарнир. pivotX и pivotZ — где ось вращения (0, 0 — точно посередине качелей);
  • hinge.setAngle(dir) — поворачивает качели на заданный угол. -35 — наклон в одну сторону, 35 — в другую;
  • dir = -dir — меняет знак: было -35, стало 35, потом снова -35. Так качели качаются туда-сюда;
  • game.every(1.4, ...) — каждые 1.4 секунды меняем угол — вот и раскачивание.

Шаг 4. Скрипт финиша и проверка

Финиш ловит касание игрока и шлёт сообщение 'win' — главный скрипт его поймает и покажет победу.

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • качели сами качаются туда-сюда;
  • запрыгни на них с возвышенности, поймай момент;
  • перебрался на зелёную площадку — «Победа».
сделай качели быстрее — поменяй 1.4 на 0.9 секунды. Увеличь угол наклона до 50 — размах станет больше. Поставь подряд двое качелей — перепрыгивай с одних на другие. ), }, // ════════════════════════════════════════════════════ // ИГРА 19 — «Лифт» // ════════════════════════════════════════════════════ 'elevator': { body: ( <>

Что получится

В комнате два этажа — нижний и верхний. Между ними ездит синяя платформа-лифт: сама поднимается наверх и опускается вниз, без остановки. Игрок встаёт на лифт, дожидается верха и сходит на финиш.

Чему научишься

  • Твин с повтором — как заставить движение повторяться много раз (repeat);
  • Yoyo — как сделать, чтобы объект ехал туда, а потом сам возвращался обратно;
  • Постоянное движение — лифт ездит «вечно».
Слово «yoyo» — от игрушки йо-йо, которая то опускается, то поднимается. В твине yoyo: true означает: доехал до конца — поезжай обратно. А repeat — сколько раз так повторить.

Шаг 1. Два этажа

Выложи из каменных блоков нижний этаж — пол комнаты. Сбоку, высоко над полом (примерно на высоте 12 блоков), выложи верхний этаж — площадку, до которой нужно добраться.

Шаг 2. Лифт и финиш

Лифт — примитив-куб, плоский и широкий (размер примерно 3.5×0.5×3.5), синего цвета #3357ff, материал «Неон». Поставь его на нижнем этаже. «Столкновение» и «Закреплён» включены. Имя — «Лифт». На верхнем этаже поставь зелёную неоновую финишную площадку. Имя — «Финиш».

Шаг 3. Главный скрипт

{`// === ИГРА «ЛИФТ» — главный скрипт === let won = false; game.ui.showText('Встань на синий лифт — он повезёт наверх', 3); // лифт вечно ездит: вниз (y=1) ↔ верх (y=12.3). // findOne нельзя сразу в начале — снимок сцены приходит чуть позже. game.after(0.2, () => { const lift = game.scene.findOne('Лифт'); game.tween(lift, { y: 12.3 }, { duration: 3.5, yoyo: true, // обратно вниз repeat: 999, // почти бесконечно easing: 'ease' }); }); game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -3) game.player.respawn(); }); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты поднялся на лифте!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём твин лифта:

  • game.after(0.2, ...) — ждём 0.2 секунды: снимок сцены приходит чуть позже старта, поэтому findOne сразу позвать нельзя;
  • game.scene.findOne('Лифт') — находим лифт по имени;
  • game.tween(lift, {'{ y: 12.3 }'}, опции) — плавно поднимает лифт на высоту 12.3;
  • duration: 3.5 — подъём занимает 3.5 секунды;
  • yoyo: true — доехав до верха, лифт сам поедет обратно вниз;
  • repeat: 999 — повторить этот путь 999 раз, то есть практически бесконечно — лифт ездит всю игру;
  • easing: 'ease' — лифт мягко разгоняется и тормозит, а не дёргается.

Шаг 4. Скрипт финиша и проверка

Финиш ловит касание игрока и шлёт сообщение 'win'. Главный скрипт ловит его через game.onMessage('win', ...) — так два скрипта из разных «песочниц» общаются.

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • лифт ездит вверх-вниз сам по себе;
  • встань на него внизу и дождись верха;
  • сойди на верхний этаж, встань на финиш — «Победа».
сделай лифт медленнее (duration: 5) или быстрее. Добавь второй лифт, который ездит в другую сторону. Поставь на верхнем этаже монетки из урока 1. ), }, // ════════════════════════════════════════════════════ // ИГРА 20 — «Имена над врагами» // ════════════════════════════════════════════════════ 'enemy-names': { body: ( <>

Что получится

На арене стоят три врага: Гоблин, Скелет и Орк. Над головой каждого висит метка с его именем и здоровьем. Игрок подходит к врагу, кликает по нему — наносит урон, и метка показывает, сколько HP осталось. Победи всех троих.

Чему научишься

  • Метки-billboard — надписи, которые висят над объектом и всегда повёрнуты к игроку (game.scene.setLabel);
  • Убрать меткуgame.scene.clearLabel;
  • NPC-враги — создавать врагов и наносить им урон;
  • Обновление метки — менять текст метки, когда здоровье врага меняется.
Billboard-метка — это надпись в 3D-мире, которая всегда «смотрит лицом» на игрока, как бы он ни ходил вокруг. Так в играх показывают имена и полоски HP над персонажами.

Шаг 1. Арена

Выложи большую квадратную площадку из каменных блоков — это арена.

Врагов ставить руками не нужно — они появятся из скрипта командой spawnNpc, как торговец в уроке 13.

Шаг 2. Главный скрипт

Вся игра в одном скрипте. Он создаёт врагов, вешает над ними метки и обрабатывает удары.

{`// === ИГРА «ИМЕНА НАД ВРАГАМИ» — главный скрипт === game.ui.showText('Победи всех врагов! Кликай по ним', 3); // данные врагов: имя и позиция const enemyData = [ { name: 'Гоблин', x: -5, z: 3, hp: 60 }, { name: 'Скелет', x: 4, z: 5, hp: 80 }, { name: 'Орк', x: 0, z: 8, hp: 100 }, ]; let alive = enemyData.length; enemyData.forEach((d) => { // создаём NPC-врага const npc = game.scene.spawnNpc('character-b', { x: d.x, y: 1, z: d.z, // y=1 — на верху блоков пола name: d.name, hp: d.hp, speed: 0, }); // вешаем над врагом метку-billboard с именем и HP let hp = d.hp; function updateLabel() { game.scene.setLabel(npc.ref, d.name + ' HP: ' + hp, { color: '#ff5555', height: 3 }); } updateLabel(); // когда враг гибнет — убираем метку npc.onDeath(() => { game.scene.clearLabel(npc.ref); alive = alive - 1; game.sound.play('hit'); if (alive <= 0) { game.ui.showText('Победа! Все враги повержены!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } }); // урон врагу по клику игрока рядом — упрощённо бьём раз в клик // (в этой игре кликаем по сцене возле врага) game.onClick(() => { if (hp <= 0) return; // бьём только если игрок близко к этому врагу const p = game.player.position; if (!p || !npc.position) return; const dist = Math.hypot(p.x - npc.position.x, p.z - npc.position.z); if (dist < 4) { hp = hp - 30; if (hp < 0) hp = 0; npc.damage(30); updateLabel(); game.scene.spawnParticles('sparks', npc.position, { duration: 0.4 }); } }); });`}

Разберём по частям. Сначала про создание врагов:

  • enemyData — список врагов: у каждого имя, координаты и запас здоровья;
  • enemyData.forEach((d) => {'{...}'}) — перебираем список и для каждого делаем одно и то же;
  • game.scene.spawnNpc('character-b', ...) — создаёт NPC-врага. speed: 0 — враг стоит на месте.

Теперь про метку над врагом:

  • game.scene.setLabel(npc.ref, текст, опции) — вешает над врагом надпись. Текст склеен из имени и HP: получится «Гоблин HP: 60»;
  • color — цвет надписи, height — на какой высоте над врагом она висит;
  • updateLabel() — функция, которая заново пишет метку. Зовём её каждый раз, когда HP изменилось.

Про удар и гибель:

  • game.onClick — игрок щёлкнул мышью. Math.hypot(...) считает расстояние до врага: бьём, только если ближе 4 метров;
  • npc.damage(30) — наносим врагу 30 урона, updateLabel() обновляет цифру HP в метке;
  • npc.onDeath(...) — когда враг погиб, game.scene.clearLabel(npc.ref) убирает его метку, а счётчик alive уменьшается;
  • когда alive дошёл до нуля — все враги повержены, победа.

Шаг 3. Проверка

  • над каждым врагом видна метка с именем и HP;
  • подойди к врагу ближе и кликай — HP в метке падает;
  • HP дошло до нуля — враг гибнет, метка пропадает;
  • победил всех троих — «Победа» и конфетти.
добавь четвёртого врага в список enemyData — просто допиши строчку. Сделай врагов посильнее (больше HP). Поменяй цвет метки в зависимости от здоровья: зелёная, пока HP высокое, красная — когда мало. ), }, // ════════════════════════════════════════════════════ // ИГРА 21 — «Преследователь» // ════════════════════════════════════════════════════ 'chaser': { body: ( <>

Что получится

По полю за тобой гонится злой NPC-охотник. Он бегает чуть быстрее обычного и не отстаёт. Нужно петлять между каменными кубами-укрытиями и добежать до зелёного укрытия-финиша. Поймал — игра возвращает на старт.

Чему научишься

  • NPC-персонаж — как создать живого героя скриптом (game.scene.spawnNpc);
  • Преследование — как заставить NPC гнаться за игроком (enemy.follow('player'));
  • Расстояние между точками — как узнать, далеко ли враг (Math.hypot).

Шаг 1. Поле и укрытия

Инструментом Блок выложи большое травяное поле — широкое и длинное, чтобы было где убегать. Поставь несколько больших серых кубов-примитивов (3×3×3) — это укрытия, за которыми ты будешь петлять, пока враг их обегает. «Столкновение» и «Закреплён» включены. В дальнем конце поля положи зелёную неоновую площадку — финиш. Имя — «Финиш». Точку спавна поставь в начале.

Шаг 2. Главный скрипт

Главный скрипт создаёт врага, запускает погоню и следит, не догнал ли он игрока.

{`// === ИГРА «ПРЕСЛЕДОВАТЕЛЬ» — главный скрипт === let won = false; game.ui.showText('Убегай от врага! Добеги до укрытия!', 3); // создаём NPC-преследователя const enemy = game.scene.spawnNpc('character-b', { x: 0, y: 1, z: -3, // y=1 — на верху блоков пола (иначе утоплен) name: 'Охотник', hp: 100, speed: 4, }); // враг постоянно гонится за игроком enemy.follow('player'); // каждый кадр проверяем — не догнал ли враг. // enemy.position наполняется не сразу после spawnNpc (NPC появляется // через кадр) — пока позиции нет, пропускаем кадр. game.onTick(() => { if (won) return; const p = game.player.position; const e = enemy.position; if (!p || !e) return; const dist = Math.hypot(p.x - e.x, p.z - e.z); if (dist < 1.6) { // враг поймал — на старт game.player.respawn(); game.ui.showText('Пойман! Беги снова!', 2); game.sound.play('lose'); } }); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { if (won) return; won = true; enemy.stop(); game.ui.showText('Победа! Ты убежал от врага!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём построчно:

  • game.scene.spawnNpc('character-b', опции) — создаёт NPC прямо во время игры. В опциях — где появится, имя, здоровье и speed — скорость бега;
  • enemy.follow('player') — командует NPC «гонись за игроком». Дальше враг сам бежит за тобой каждый кадр;
  • Math.hypot(p.x - e.x, p.z - e.z) — расстояние между игроком и врагом. Если оно меньше 1.6 — враг вплотную, значит поймал;
  • enemy.stop() в победе — враг замирает, погоня закончена.
Скорость врага speed: 4 чуть больше скорости обычной ходьбы. Если убегать слишком легко — увеличь её, слишком сложно — уменьши.

Шаг 3. Скрипт финиша

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}

Когда игрок касается финиша, скрипт шлёт сообщение game.broadcast('win'). Главный скрипт ловит его через game.onMessage('win', ...) — останавливает врага и показывает «Победа». Так два скрипта из разных «песочниц» общаются сообщениями.

Шаг 4. Проверка

  • с началом игры за тобой бежит охотник;
  • петляй между серыми кубами, чтобы враг отстал;
  • поймал — респаун на старте;
  • добежал до зелёного укрытия — «Победа».
добавь второго врага — просто создай ещё один NPC и тоже дай ему follow('player'). Сделай укрытий побольше. Спрячь по полю монетки из урока 1 — собирай их на бегу. ), }, // ════════════════════════════════════════════════════ // ИГРА 22 — «Зона опасности» // ════════════════════════════════════════════════════ 'danger-zone': { body: ( <>

Что получится

Длинный коридор, а посередине — большая красная зона. Пока игрок внутри неё, у него тает здоровье. Перед зоной лежит зелёная аптечка — подбери её, чтобы пополнить HP, и пробеги опасный участок до финиша.

Чему научишься

  • Вход и выход из зоны — события onTouch и onUntouch;
  • Урон игрокуgame.player.damage;
  • Лечениеgame.player.heal;
  • Повторяющийся таймерgame.every.

Шаг 1. Коридор, зона и аптечка

Выложи длинный пол-коридор из камня. Посередине поставь большой красный куб-примитив, материал «Неон». «Столкновение» выключи — игрок проходит сквозь него, а вход поймает скрипт. Имя — «ЗонаОпасности». Перед зоной сбоку положи маленькую зелёную сферу — аптечку. «Столкновение» выключено, имя — «Аптечка». В конце коридора — зелёная неоновая площадка-финиш, имя «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «ЗОНА ОПАСНОСТИ» — главный скрипт === let inZone = false; // игрок сейчас в красной зоне? let won = false; game.ui.showText('Пробеги через красную зону к финишу!', 3); // пока игрок в зоне — каждые 0.6с снимаем HP game.every(0.6, () => { if (won) return; if (inZone) { game.player.damage(12); game.sound.play('hit'); } }); // зона и финиш шлют сюда сообщения через broadcast — скрипты // в разных песочницах, общие переменные между ними не видны. game.onMessage('zone-enter', () => { inZone = true; game.ui.showText('Опасно! Беги быстрее!', 1.5); }); game.onMessage('zone-leave', () => { inZone = false; }); game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты прошёл зону опасности!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • inZone — флажок «игрок внутри красной зоны». Скрипт зоны включает и выключает его сообщениями;
  • game.every(0.6, fn) — каждые 0.6 секунды выполняет функцию. Внутри: если игрок в зоне — game.player.damage(12) снимает 12 HP;
  • game.onMessage('zone-enter', ...) и game.onMessage('zone-leave', ...) — ловят сообщения от зоны: «игрок вошёл» и «вышел». Зона шлёт их через game.broadcast, потому что её скрипт работает в отдельной «песочнице» и не видит переменную inZone напрямую;
  • game.onMessage('win', ...) — ловит сообщение от финиша.

Шаг 3. Скрипт зоны опасности

{`// === Скрипт зоны опасности === // onTouch — игрок вошёл, onUntouch — вышел. game.self.onTouch(() => { game.broadcast('zone-enter'); }); game.self.onUntouch(() => { game.broadcast('zone-leave'); });`}

Здесь важна пара событий: onTouch срабатывает, когда игрок входит в зону, а onUntouch — когда выходит. Так зона точно знает, внутри игрок или нет.

Шаг 4. Скрипт аптечки

{`// === Скрипт аптечки === game.self.onTouch(() => { game.player.heal(60); game.ui.showText('+60 HP', 1.5); game.sound.play('pickup'); game.self.delete(); });`}

game.player.heal(60) — добавляет игроку 60 единиц здоровья. Аптечку взяли один раз — game.self.delete() убирает её.

Шаг 5. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • подбери зелёную аптечку — +60 HP;
  • в красной зоне здоровье тает каждые 0.6 секунды;
  • выбежал из зоны — урон прекратился;
  • добежал до финиша живым — «Победа».
сделай зону опаснее — снимай больше HP или чаще. Поставь несколько аптечек по пути. Добавь вторую зону другого цвета, которая, наоборот, лечит, пока ты в ней стоишь. ), }, // ════════════════════════════════════════════════════ // ИГРА 23 — «Переключатели» // ════════════════════════════════════════════════════ 'switches': { body: ( <>

Что получится

Перед стеной с дверью стоят три красных рычага. Дёргать их (клавишей E) нужно в правильном порядке. Угадал последовательность — дверь уезжает вверх. Ошибся — все рычаги сбрасываются, начинай заново.

Чему научишься

  • Проверка последовательности — как сравнить, что нажал игрок, с правильным порядком;
  • Взаимодействие по Egame.self.onInteract;
  • Массив-список — как копить нажатия и сбрасывать их при ошибке.

Шаг 1. Стена, дверь и рычаги

Сделай пол комнаты из камня. Поперёк комнаты построй стену из двух кубов с проёмом в центре. В проём поставь куб-дверь, имя — «Дверь». Перед дверью поставь три рычага — высокие красные неоновые цилиндры. Имена — «Рычаг_1», «Рычаг_2», «Рычаг_3». За дверью — зелёная неоновая площадка-финиш.

Шаг 2. Главный скрипт

Главный скрипт хранит правильный порядок и проверяет каждое нажатие.

{`// === ИГРА «ПЕРЕКЛЮЧАТЕЛИ» — главный скрипт === // правильный порядок рычагов const ORDER = [2, 3, 1]; let pressed = []; let opened = false; game.ui.showText('Дёрни рычаги в нужном порядке (E)', 4); // рычаги дёргаются через broadcast('lever', { num }) — скрипты // в разных песочницах, состояние pressed[] живёт только здесь. game.onMessage('lever', (d) => { const n = d.num; if (opened) return; game.sound.play('click'); pressed.push(n); const i = pressed.length - 1; if (pressed[i] !== ORDER[i]) { pressed = []; game.ui.showText('Неверно! Рычаги сброшены.', 1.5); game.sound.play('lose'); return; } if (pressed.length === ORDER.length) { opened = true; game.ui.showText('Верно! Дверь открыта.', 3); game.sound.play('win'); const door = game.scene.findOne('Дверь'); game.tween(door, { y: 9 }, { duration: 1.2, easing: 'ease' }); } else { game.ui.showText('Так держать!', 1); } }); game.onMessage('win', () => { game.ui.showText('Победа! Ты разгадал порядок!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Самое интересное — проверка порядка:

  • ORDER = [2, 3, 1] — секретный порядок: сначала второй рычаг, потом третий, потом первый. Поменяй числа на свой порядок;
  • pressed.push(n) — добавляет нажатый рычаг в список того, что игрок уже дёрнул;
  • if (pressed[i] !== ORDER[i]) — сравниваем последнее нажатие с тем, что должно быть. Не совпало — pressed = [] очищает список, начинай заново;
  • if (pressed.length === ORDER.length) — все три рычага дёрнуты правильно — дверь уезжает вверх через game.tween.
Идея «копим список и сверяем по шагам» — та же, что в игре «Дверь по коду» (урок 12). Так проверяют любые комбинации: коды, пароли, последовательности.

Шаг 3. Скрипт рычага

На каждый рычаг свой скрипт — отличается только номер. Вот скрипт первого рычага:

{`// === Скрипт рычага 1 === game.self.onInteract(() => { game.broadcast('lever', { num: 1 }); }, { text: 'Дёрнуть рычаг 1', distance: 3 });`}

Рычаг шлёт сообщение game.broadcast('lever', {'{ num: 1 }'}): имя сообщения — 'lever', а второй кусок — «посылка» с номером рычага. Главный скрипт ловит её через game.onMessage('lever', ...) и узнаёт номер из d.num.

На «Рычаг_2» — такой же скрипт, но с num: 2 и текстом «Дёрнуть рычаг 2», на «Рычаг_3» — с num: 3. Номер сообщает главному скрипту, какой именно рычаг дёрнули.

Шаг 4. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • подойди к рычагу — появится подсказка «Дёрнуть рычаг»;
  • дёргай E в верном порядке — дверь откроется;
  • ошибся — «Неверно», начинай заново;
  • прошёл в проём, встал на финиш — «Победа».
добавь четвёртый рычаг и удлини ORDER. Сделай рычаги одного цвета, чтобы порядок было сложнее угадать. Покрась рычаг в зелёный, когда его дёрнули правильно — через game.scene.setColor. ), }, // ════════════════════════════════════════════════════ // ИГРА 24 — «Падающий мост» // ════════════════════════════════════════════════════ 'falling-bridge': { body: ( <>

Что получится

Над пропастью протянут мост из деревянных досок. Стоит игроку наступить на доску — через секунду она рушится. Значит, бежать нужно без остановки. Замешкался — проваливаешься в пропасть.

Чему научишься

  • Таймер game.after — сделать что-то через секунду, а не сразу;
  • Удаление объекта — как убрать доску со сцены;
  • Флажок-защёлка — чтобы доска запускалась на падение только один раз.

Шаг 1. Старт, пропасть и мост

Поставь твёрдую стартовую площадку из камня. Дальше — пустота: пропасть, через которую нет пола. Над пропастью выложи мост из досок — кубов-примитивов 2.4×0.35×2 коричневого цвета, в один ряд. «Столкновение» и «Закреплён» включены. Имена — «Доска_1», «Доска_2» и так далее. На другом краю пропасти — твёрдая площадка с зелёным неоновым финишем, имя «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «ПАДАЮЩИЙ МОСТ» — главный скрипт === let won = false; game.ui.showText('Беги по мосту — доски рушатся!', 3); game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -3) { game.player.respawn(); game.ui.showText('Упал в пропасть! Снова.', 1.5); game.sound.play('lose'); } }); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Ты перебежал мост!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Главный скрипт game.onTick каждый кадр следит за высотой игрока: упал ниже -3 — провалился в пропасть, возвращаем на старт. А game.onMessage('win', ...) ждёт сообщение от финиша — финиш работает в своей «песочнице», поэтому о победе он сообщает через game.broadcast.

Шаг 3. Скрипт доски

Этот скрипт вешается на каждую доску моста.

{`// === Скрипт доски моста === let cracking = false; game.self.onTouch(() => { if (cracking) return; cracking = true; game.sound.play('click'); game.after(1, () => { game.self.delete(); }); });`}

Разберём:

  • cracking — флажок-защёлка. Игрок может наступить на доску несколько раз, но запустить таймер падения нужно только один раз;
  • game.after(1, fn) — «через 1 секунду выполни это». Внутри — game.self.delete(), доска исчезает;
  • звук 'click' при касании — сигнал игроку: «доска затрещала, беги!».
Скрипт одинаковый для всех досок. Создай его на первой доске, а когда дублируешь доску (Ctrl+D) — скрипт скопируется вместе с ней.

Шаг 4. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • встал на доску — щелчок, через секунду она рушится;
  • стоишь на месте — проваливаешься, респаун на старте;
  • перебежал мост, встал на финиш — «Победа».
сделай доски «быстрее» — пусть рушатся не через 1, а через 0.6 секунды. Сделай мост подлиннее или с поворотом. Поставь на нём монетки из урока 1 — собирай на бегу. ), }, // ════════════════════════════════════════════════════ // ИГРА 25 — «Камера-облёт» // ════════════════════════════════════════════════════ 'flyby-camera': { body: ( <>

Что получится

Когда игра начинается, камера сама красиво облетает уровень по нескольким точкам — показывает игроку, что его ждёт. Как только облёт закончился, управление возвращается, и игрок идёт к финишу.

Чему научишься

  • Кат-сцена камеры — как сделать кино-облёт уровня (game.camera.cutscene);
  • Событие конца облёта game.onCutsceneDone;
  • Массив точек — как задать путь, по которому полетит камера.

Шаг 1. Уровень с декорациями

Выложи широкое травяное поле — дорогу от старта к финишу. Вдоль дороги расставь несколько фиолетовых неоновых столбов-цилиндров — чтобы во время облёта было на что посмотреть. В конце дороги — зелёная неоновая площадка-финиш, имя «Финиш». Точку спавна поставь в начале.

Шаг 2. Главный скрипт

Главный скрипт запускает облёт камеры и ждёт, когда он закончится.

{`// === ИГРА «КАМЕРА-ОБЛЁТ» — главный скрипт === let won = false; // при старте — облёт уровня камерой по точкам game.camera.cutscene([ { x: 0, y: 18, z: -10 }, { x: 12, y: 12, z: 8 }, { x: -12, y: 12, z: 18 }, { x: 0, y: 10, z: 28 }, ], { segDuration: 1.8 }); // когда облёт закончился — отдаём камеру игроку game.onCutsceneDone(() => { game.ui.showText('Вперёд, к зелёному финишу!', 3); }); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Уровень пройден!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • game.camera.cutscene([точки], опции) — запускает кино-облёт. Камера летит по списку точек одна за другой;
  • каждая точка — это {'{ x, y, z }'}, место, откуда камера смотрит на уровень;
  • segDuration: 1.8 — сколько секунд камера летит от одной точки до следующей;
  • game.onCutsceneDone(fn) — функция внутри выполнится, когда облёт закончится. Тут мы показываем подсказку «Вперёд, к финишу!».
Облёт нужен, чтобы игрок сразу увидел весь уровень — где он, куда бежать, что его ждёт. Так делают во многих больших играх перед началом миссии.

Шаг 3. Скрипт финиша и проверка

Финиш ловит касание игрока и шлёт сообщение 'win'. Главный скрипт ловит его через game.onMessage('win', ...) — так скрипты из разных «песочниц» общаются между собой.

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • с началом игры камера облетает уровень;
  • облёт закончился — управление вернулось, появилась подсказка;
  • дойди до зелёного финиша — «Победа».
добавь камере больше точек — облёт станет длиннее и интереснее. Замедли облёт, увеличив segDuration. Поставь по пути монетки или препятствия, которые камера покажет во время облёта. ), }, // ════════════════════════════════════════════════════ // ИГРА 26 — «Магнит монет» // ════════════════════════════════════════════════════ 'coin-magnet': { body: ( <>

Что получится

По полю разбросаны золотые монетки. Стоит игроку подойти к монетке поближе — она сама подлетает к нему, будто притянутая магнитом. Собери все 8 монеток.

Чему научишься

  • Проверка расстояния каждый кадр — как монетка «видит», что игрок рядом (game.onTick + Math.hypot);
  • Твин к позиции игрока — как заставить объект лететь к игроку (game.tween);
  • Флажки состояния — чтобы монетка летела один раз и засчиталась один раз.

Шаг 1. Поле и монетки

Выложи большое травяное поле. Поставь 8 монеток — жёлтые сферы-примитивы 0.6×0.6×0.6, материал «Неон». «Столкновение» выключи — касание поймает скрипт. Имена — «Монетка_1» … «Монетка_8». Расставь монетки по углам и краям поля — чтобы за каждой нужно было подойти.

Шаг 2. Главный скрипт

{`// === ИГРА «МАГНИТ МОНЕТ» — главный скрипт === let score = 0; const TOTAL = 8; game.ui.score = 0; game.ui.showText('Подходи к монеткам — они притянутся!', 3); // монетки сообщают о сборе через broadcast('coin') — скрипты // в разных песочницах, счётчик score живёт только здесь. game.onMessage('coin', () => { score = score + 1; game.ui.score = score; game.sound.play('coin'); if (score >= TOTAL) { game.ui.showText('Победа! Все монетки собраны!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Главный скрипт простой — он только считает собранные монетки и проверяет победу. Каждая монетка работает в своей «песочнице», поэтому о сборе она сообщает через game.broadcast('coin'), а главный скрипт ловит сообщение через game.onMessage('coin', ...). Вся «магнитная магия» — в скрипте самой монетки.

Шаг 3. Скрипт магнитной монетки

Этот скрипт вешается на каждую монетку.

{`// === Скрипт магнитной монетки === let flying = false; // монетка уже летит к игроку? let taken = false; game.onTick(() => { if (taken) return; const c = game.self.position; const p = game.player.position; // позиции могут быть не готовы первые кадры — ждём if (!c || !p) return; const dist = Math.hypot(p.x - c.x, p.z - c.z); // игрок коснулся монетки — собрана if (dist < 1.2) { taken = true; game.self.delete(); game.broadcast('coin'); // сообщить главному скрипту return; } // подошёл близко — монетка летит к игроку if (!flying && dist < 6) { flying = true; game.tween(game.self.ref, { x: p.x, y: p.y + 1, z: p.z }, { duration: 0.5, easing: 'ease' }); } });`}

Разберём по частям:

  • первые кадры game.self.position и game.player.position могут быть ещё не готовы — проверка if (!c || !p) return просто пропускает такой кадр;
  • каждый кадр считаем dist — расстояние от монетки до игрока через Math.hypot;
  • если dist {'<'} 1.2 — игрок дотянулся до монетки. Ставим taken, удаляем монетку и засчитываем её;
  • если dist {'<'} 6 и монетка ещё не летит — включаем flying и запускаем game.tween к позиции игрока. Монетка плавно подлетает за 0.5 секунды;
  • флажки flying и taken нужны, чтобы твин запустился один раз и монетка засчиталась один раз.
Твин летит к той точке, где игрок был в момент запуска. Если игрок отбежит — монетка прилетит «в пустоту», но dist {'<'} 6 запустит её снова.

Шаг 4. Проверка

  • подходишь к монетке ближе 6 метров — она летит к тебе;
  • монетка коснулась — звон, +1 очко;
  • собрал все 8 — «Победа».
увеличь «радиус магнита» — поменяй 6 на большее число, монетки будут лететь издалека. Сделай редкую красную монетку, которая убегает от игрока, а не притягивается. ), }, // ════════════════════════════════════════════════════ // ИГРА 27 — «Двойной прыжок» // ════════════════════════════════════════════════════ 'double-jump': { body: ( <>

Что получится

Паркур, где платформы стоят так далеко друг от друга, что обычным прыжком до них не добраться. Скрипт включает игроку двойной прыжок — можно прыгнуть ещё раз прямо в воздухе и допрыгнуть до следующей платформы.

Чему научишься

  • Двойной прыжок — особая способность игрока (game.player.setDoubleJump);
  • Дизайн паркура — как ставить платформы под двойной прыжок;
  • Падение и респаун — возврат на старт при падении.

Шаг 1. Старт и платформы

Поставь небольшую стартовую площадку из травы. Расставь 5 платформ — кубов-примитивов 2.5×0.5×2.5 коричневого цвета. Делай между ними большие разрывы — такие, чтобы обычным прыжком не долететь. «Столкновение» и «Закреплён» включены. Подними каждую следующую платформу чуть выше — дорожка идёт вверх. В конце — зелёная неоновая площадка-финиш, имя «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «ДВОЙНОЙ ПРЫЖОК» — главный скрипт === let won = false; // включаем игроку двойной прыжок — теперь можно прыгнуть // ещё раз прямо в воздухе game.player.setDoubleJump(true); game.ui.showText('Жми Space ДВАЖДЫ — двойной прыжок!', 4); game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -3) { game.player.respawn(); game.sound.play('lose'); } }); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { if (won) return; won = true; game.ui.showText('Победа! Двойной прыжок освоен!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Главное здесь:

  • game.player.setDoubleJump(true) — даёт игроку способность прыгать ещё раз в воздухе. Достаточно одной этой строчки;
  • теперь, нажав Space второй раз во время полёта, игрок подпрыгивает ещё выше;
  • game.onTick следит за падением — упал ниже -3, возвращаем на старт.
Без двойного прыжка этот паркур пройти нельзя — платформы специально стоят слишком далеко. Способность меняет правила игры.

Шаг 3. Скрипт финиша и проверка

Финиш ловит касание игрока и шлёт сообщение 'win'. Главный скрипт ловит его через game.onMessage('win', ...) — так два скрипта из разных «песочниц» общаются.

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • прыгай Space, в воздухе жми ещё раз — второй прыжок;
  • так допрыгивай с платформы на платформу;
  • упал вниз — респаун на старте;
  • встал на зелёный финиш — «Победа».
раздвинь платформы ещё дальше — паркур станет сложнее. Добавь движущуюся платформу через game.tween с yoyo: true. Поставь монетки между платформами — собирай их в прыжке. ), }, // ════════════════════════════════════════════════════ // ИГРА 28 — «Призрачные стены» // ════════════════════════════════════════════════════ 'ghost-walls': { body: ( <>

Что получится

Коридор перегорожен четырьмя фиолетовыми стенами. Каждую можно сделать призрачной: кликнул по стене — она становится полупрозрачной и проходимой. Пройди сквозь все стены к финишу.

Чему научишься

  • Клик по объекту — событие game.self.onClick;
  • Проходимость — как убрать у объекта столкновение во время игры (game.physics.passThrough);
  • Прозрачностьgame.scene.setOpacity.

Шаг 1. Коридор и стены

Выложи длинный пол-коридор из камня. Поперёк коридора поставь 4 широкие фиолетовые стены — кубы-примитивы, материал «Неон». «Столкновение» включено — пока в них упираются. Имена — «Стена_1» … «Стена_4». В конце коридора — зелёная неоновая площадка-финиш, имя «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «ПРИЗРАЧНЫЕ СТЕНЫ» — главный скрипт === game.ui.showText('Кликай по фиолетовым стенам — пройди сквозь!', 4); // финиш сообщает о победе через broadcast — скрипты в разных // песочницах, общая переменная между ними не видна. game.onMessage('win', () => { game.ui.showText('Победа! Ты прошёл сквозь все стены!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Главный скрипт только обрабатывает победу — он ловит сообщение 'win' от финиша через game.onMessage. Вся «магия» — в скриптах стен.

Шаг 3. Скрипт призрачной стены

Этот скрипт вешается на каждую стену.

{`// === Скрипт призрачной стены === let ghost = false; game.self.onClick(() => { if (ghost) return; ghost = true; // стена становится проходимой и полупрозрачной game.physics.passThrough(game.self.ref, true); game.scene.setOpacity(game.self.ref, 0.25); game.sound.play('click'); game.ui.showText('Стена стала призрачной!', 1.5); });`}

Разберём:

  • game.self.onClick(fn) — функция внутри срабатывает, когда игрок кликнул по этой стене мышкой;
  • game.physics.passThrough(ref, true) — убирает у стены столкновение: теперь сквозь неё можно пройти;
  • game.scene.setOpacity(ref, 0.25) — делает стену почти прозрачной (0.25 — четверть непрозрачности), чтобы было видно: она призрачная;
  • ghost — флажок, чтобы клики после первого ничего не делали.
Сначала меняем физику (passThrough), потом внешний вид (setOpacity) — игрок и видит, и чувствует, что стена «расколдована».

Шаг 4. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • кликни по фиолетовой стене — она побледнеет;
  • пройди сквозь призрачную стену;
  • так пройди все 4 стены;
  • встань на зелёный финиш — «Победа».
сделай среди стен «обманки» — стены, которые при клике не исчезают, а наоборот мигают красным. Добавь стенам задержку: после клика стена становится призрачной только через секунду (game.after). ), }, // ════════════════════════════════════════════════════ // ИГРА 29 — «Магазин» // ════════════════════════════════════════════════════ 'shop': { body: ( <>

Что получится

Игрок собирает монетки, разбросанные по комнате. Потом подходит к прилавку и покупает ключ за 5 монет. С ключом в инвентаре он открывает запертую дверь и доходит до финиша.

Чему научишься

  • Валюта-экономика — копить монеты и тратить их;
  • Инвентарь — класть предмет игроку (game.inventory.add) и проверять, есть ли он (game.inventory.has);
  • Покупка — проверить, хватает ли денег.

Шаг 1. Комната, прилавок, дверь

Сделай пол комнаты из досок дерева. Поставь 7 монеток — жёлтые сферы-примитивы, «Столкновение» выключено. Имена — «Монетка_1» … «Монетка_7». Поставь прилавок — длинный куб, имя «Прилавок». Дальше — стена из двух кубов с проёмом, в проёме куб-дверь (имя «Дверь»). За дверью — зелёный неоновый финиш.

Шаг 2. Главный скрипт

Главный скрипт — «мозг» магазина: считает монеты, продаёт ключ, открывает дверь.

{`// === ИГРА «МАГАЗИН» — главный скрипт === let coins = 0; const PRICE = 5; // ключ стоит 5 монет let bought = false; game.ui.score = 0; game.ui.showText('Собери монетки и купи ключ у продавца!', 4); // монетки/прилавок/дверь/финиш шлют сюда сообщения через broadcast — // скрипты в разных песочницах, состояние coins/bought живёт только здесь. game.onMessage('coin', () => { coins = coins + 1; game.ui.score = coins; game.sound.play('coin'); }); game.onMessage('buy', () => { if (bought) { game.ui.showText('Ключ уже куплен, иди к двери!', 2); return; } if (coins < PRICE) { game.ui.showText('Мало монет! Нужно ' + PRICE + ', есть ' + coins, 2); game.sound.play('lose'); return; } bought = true; coins = coins - PRICE; game.ui.score = coins; game.inventory.add({ name: 'Ключ' }); game.ui.showText('Куплен Ключ! Открой дверь.', 3); game.sound.play('win'); }); game.onMessage('open-door', () => { if (!game.inventory.has('Ключ')) { game.ui.showText('Дверь заперта. Купи ключ в магазине.', 2); return; } const door = game.scene.findOne('Дверь'); game.tween(door, { y: 9 }, { duration: 1.2, easing: 'ease' }); game.ui.showText('Дверь открыта!', 2); }); game.onMessage('win', () => { game.ui.showText('Победа! Ты прошёл магазин!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Каждый объект магазина — монетка, прилавок, дверь, финиш — работает в своей «песочнице» и не видит переменные главного скрипта. Поэтому объекты шлют сообщения через game.broadcast('имя'), а главный скрипт ловит их через game.onMessage('имя', ...). Разберём четыре обработчика:

  • onMessage('coin', ...) — монетка собрана: coins растёт, число на экране тоже;
  • onMessage('buy', ...) — покупка ключа. Сначала проверяем coins {'<'} PRICE — хватает ли денег. Хватает — отнимаем 5 монет и game.inventory.add кладёт ключ в инвентарь;
  • onMessage('open-door', ...) game.inventory.has('Ключ') проверяет, есть ли ключ. Есть — дверь уезжает вверх;
  • onMessage('win', ...) — победа на финише.
Инвентарь — это «карман» игрока. Положил предмет через add, проверил через has. Так делают ключи, оружие, зелья — что угодно.

Шаг 3. Скрипт монетки

{`// === Скрипт монетки === game.self.onTouch(() => { game.broadcast('coin'); game.self.delete(); });`}

Шаг 4. Скрипт прилавка и двери

{`// === Скрипт прилавка === game.self.onInteract(() => { game.broadcast('buy'); }, { text: 'Купить ключ (5 монет)', distance: 4 });`} {`// === Скрипт двери === game.self.onInteract(() => { game.broadcast('open-door'); }, { text: 'Открыть дверь', distance: 4 });`}

И прилавок, и дверь — это объекты с взаимодействием по E. Подошёл к прилавку и нажал E — объект шлёт сообщение 'buy', подошёл к двери и нажал E — летит сообщение 'open-door'. Главный скрипт ловит эти сообщения и решает, что делать.

Шаг 5. Скрипт финиша и проверка

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('win'); });`}
  • собери монетки — счётчик растёт;
  • у прилавка нажми E — если монет 5 и больше, купишь ключ;
  • у двери нажми E — с ключом она откроется;
  • дойди до финиша — «Победа».
добавь второй товар — например, «Зелье скорости» за 3 монеты. Сделай монеток побольше или подними цену ключа. Поставь два прилавка с разными ценами. ), }, // ════════════════════════════════════════════════════ // ИГРА 30 — «Квест с заданиями» // ════════════════════════════════════════════════════ 'quest-tasks': { body: ( <>

Что получится

На поле стоит NPC-старейшина. Он даёт цепочку из трёх заданий: собери монетку, дойди до синего флага, вернись к нему. Каждое задание открывает следующее. Выполнил всё — победа.

Чему научишься

  • NPC-диалоги — как NPC «говорит» (npc.say);
  • Этапы квеста — как хранить, на каком шаге квеста сейчас игрок (переменная-состояние);
  • Цепочка заданий — как одно задание открывает следующее.

Шаг 1. Поле, тумба, монетка, флаг

Выложи большое травяное поле. Поставь тумбу-квестодателя — куб, имя «Квестодатель». Рядом с ней скрипт создаст NPC. В одном углу поля поставь жёлтую сферу-монетку (имя «КвестМонетка»), в другом — синий конус-флаг (имя «КвестФлаг»). У обоих «Столкновение» выключено.

Шаг 2. Главный скрипт

Главный скрипт хранит этап квеста и ведёт игрока по заданиям.

{`// === ИГРА «КВЕСТ С ЗАДАНИЯМИ» — главный скрипт === // этап квеста: 0=не начат, 1=собрать монетку, 2=дойти до флага, // 3=вернуться к NPC, 4=готово let stage = 0; game.ui.showText('Подойди к квестодателю (E)', 3); // создаём NPC рядом с тумбой const npc = game.scene.spawnNpc('character-a', { x: 1.5, y: 1, z: 2, name: 'Старейшина', hp: 100, speed: 0, // y=1 — на полу }); // квестодатель/монетка/флаг шлют сюда сообщения через broadcast — // скрипты в разных песочницах, этап квеста stage живёт только здесь. game.onMessage('talk', () => { if (stage === 0) { stage = 1; npc.say('Задание 1: найди жёлтую монетку!', 4); game.ui.showText('Квест: собери монетку', 3); } else if (stage === 3) { stage = 4; npc.say('Молодец! Квест выполнен!', 4); game.ui.showText('Победа! Квест пройден!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } else if (stage === 4) { npc.say('Спасибо, герой!', 3); } else { npc.say('Сначала выполни задание!', 3); } }); game.onMessage('coin-done', () => { if (stage !== 1) return; stage = 2; game.sound.play('coin'); npc.say('Отлично! Теперь дойди до синего флага.', 4); game.ui.showText('Квест: дойди до флага', 3); }); game.onMessage('flag-done', () => { if (stage !== 2) return; stage = 3; game.sound.play('pickup'); game.ui.showText('Квест: вернись к квестодателю', 3); });`}

Главное здесь — переменная stage:

  • stage хранит, на каком шаге квеста игрок: 0 — не начат, 1 — собирает монетку, 2 — идёт к флагу, 3 — возвращается, 4 — готово;
  • квестодатель, монетка и флаг — отдельные скрипты, каждый в своей «песочнице». Они не видят переменную stage, поэтому шлют сообщения через game.broadcast('имя'), а главный скрипт ловит их через game.onMessage('имя', ...);
  • onMessage('talk', ...) — разговор с NPC. Что он скажет, зависит от stage: на старте даёт первое задание, в конце поздравляет;
  • onMessage('coin-done', ...) и onMessage('flag-done', ...) — ловят сообщения от монетки и флага. Они проверяют stage и переводят квест на следующий этап;
  • npc.say('текст', 4) — NPC показывает над собой облачко с репликой на 4 секунды.
Проверки вроде if (stage !== 1) return важны: они не дают засчитать задание раньше времени. Монетку нельзя «сдать», пока NPC не дал задание её собрать.

Шаг 3. Скрипт квестодателя

{`// === Скрипт квестодателя === game.self.onInteract(() => { game.broadcast('talk'); }, { text: 'Поговорить', distance: 4 });`}

Шаг 4. Скрипты монетки и флага

{`// === Скрипт квест-монетки === game.self.onTouch(() => { game.broadcast('coin-done'); game.self.delete(); });`} {`// === Скрипт квест-флага === game.self.onTouch(() => { game.broadcast('flag-done'); });`}

Шаг 5. Проверка

  • подойди к квестодателю, нажми E — он даст первое задание;
  • собери монетку — NPC даст второе задание;
  • дойди до флага — NPC попросит вернуться;
  • вернись к квестодателю и поговори — «Победа».
добавь четвёртый этап квеста — например, «принеси два предмета». Сделай NPC награду: после квеста дай игроку предмет через game.inventory.add. Поставь второго NPC со своим квестом. ), }, // ════════════════════════════════════════════════════ // ИГРА 31 — «Защита базы» // ════════════════════════════════════════════════════ 'base-defense': { body: ( <>

Что получится

Из дальнего конца поля к твоей базе идут волны NPC-врагов. Кликай по ним, чтобы уничтожать. Уничтожь 12 врагов — победа. Пропустишь 5 врагов до базы — поражение.

Чему научишься

  • Волны врагов — спавнить NPC снова и снова (game.every + spawnNpc);
  • Движение NPC к точкеenemy.moveTo;
  • Клик по врагу — уничтожение (enemy.remove);
  • Два счётчика — победа и поражение одновременно.

Шаг 1. Поле и база

Выложи длинное поле из камня — дорогу, по которой пойдут враги. У ближнего края поставь большой синий неоновый куб — базу, имя «База». Точку спавна поставь рядом с базой.

Враги создаются скриптом — заранее их ставить не нужно.

Шаг 2. Главный скрипт

Это большой скрипт — разберём его по частям.

{`// === ИГРА «ЗАЩИТА БАЗЫ» — главный скрипт === let killed = 0; // сколько врагов уничтожено let leaked = 0; // сколько врагов дошло до базы const GOAL = 12; // победа — уничтожить 12 врагов const MAX_LEAK = 5; // проигрыш — 5 врагов прорвались let over = false; game.ui.score = 0; game.ui.showText('Защити базу! Кликай по врагам', 3); // каждые 2 секунды появляется новый враг далеко от базы let total = 0; game.every(2, () => { if (over || total >= GOAL + MAX_LEAK) return; total = total + 1; const x = game.random(-8, 8); const enemy = game.scene.spawnNpc('character-b', { x: x, y: 1, z: 38, name: 'Враг', hp: 30, speed: 2.5, // y=1 — на полу }); // враг идёт к базе (точка 0,0,0) enemy.moveTo(0, 2); let dead = false; // клик по врагу — уничтожить game.onClick(() => { if (dead || over) return; const p = game.player.position; const e = enemy.position; if (p && e && Math.hypot(p.x - e.x, p.z - e.z) < 5) { dead = true; enemy.remove(); game.scene.spawnParticles('explosion', e, { count: 1 }); game.sound.play('hit'); killed = killed + 1; game.ui.score = killed; if (killed >= GOAL && !over) { over = true; game.ui.showText('Победа! База защищена!', 5); game.sound.play('win'); } } }); // если враг дошёл до базы — пропуск const watch = game.every(0.4, () => { if (dead || over) { game.cancel(watch); return; } // enemy.position готов не сразу после spawnNpc — ждём const ep = enemy.position; if (ep && ep.z < 4) { dead = true; game.cancel(watch); enemy.remove(); leaked = leaked + 1; game.sound.play('lose'); game.ui.showText('Враг прорвался! (' + leaked + '/' + MAX_LEAK + ')', 2); if (leaked >= MAX_LEAK && !over) { over = true; game.ui.showText('База разрушена! Поражение.', 5); } } }); });`}

Как появляются волны врагов:

  • game.every(2, ...) — каждые 2 секунды создаётся новый враг в случайной точке у дальнего края (z: 38);
  • enemy.moveTo(0, 2) — командует врагу идти к точке базы (координата 0) со скоростью 2.

Что происходит с каждым врагом:

  • game.onClick(...) — клик мышкой. Если игрок близко к врагу (меньше 5 метров) — enemy.remove() убирает врага, летит взрыв, killed растёт;
  • game.every(0.4, ...) в переменной watch — следит за врагом. Если он дошёл до базы (z {'<'} 4) — счётчик leaked растёт;
  • game.cancel(watch) — останавливает этот таймер-следилку, когда враг уже мёртв или ушёл;
  • две проверки: killed {'>'}= GOAL — победа, leaked {'>'}= MAX_LEAK — поражение.
У каждого врага свой game.onClick и свой таймер watch. Флажок dead — личный для каждого врага: он не даёт убить или засчитать одного врага дважды.

Шаг 3. Проверка

  • каждые 2 секунды появляется новый враг;
  • подбеги и кликни по врагу — взрыв, +1 к счёту;
  • враг дошёл до базы — счётчик прорывов растёт;
  • 12 уничтожено — «Победа», 5 прорвались — «Поражение».
ускорь волны — спавни врагов чаще (поменяй 2 на 1). Сделай врагов быстрее или живучее. Добавь «жирного» врага раз в 5 волн — большого и медленного. ), }, // ════════════════════════════════════════════════════ // ИГРА 32 — «Гонка с кругами» // ════════════════════════════════════════════════════ 'lap-race': { body: ( <>

Что получится

Кольцевая трасса с четырьмя чекпоинтами по углам. Нужно проехать 2 круга, пересекая чекпоинты строго по порядку. Сверху идёт секундомер — старайся уложиться побыстрее.

Чему научишься

  • Чекпоинты по порядку — как проверить, что игрок проезжает точки в нужной последовательности;
  • Счётчик кругов — как считать пройденные круги;
  • Секундомерgame.ui.timer + dt.

Шаг 1. Кольцевая трасса

Выложи квадратное кольцо из камня — большой квадрат с дыркой в центре. По нему игрок будет наматывать круги. По четырём сторонам кольца поставь 4 чекпоинта — высокие тонкие кубы-примитивы, материал «Неон». Первый сделай зелёным (старт/финиш), остальные жёлтыми. «Столкновение» выключено. Имена — «Чекпоинт_1» … «Чекпоинт_4». Точку спавна поставь у первого (зелёного) чекпоинта.

Шаг 2. Главный скрипт

{`// === ИГРА «ГОНКА С КРУГАМИ» — главный скрипт === const LAPS = 2; // сколько кругов проехать const CP_COUNT = 4; // чекпоинтов на круге let nextCp = 0; // какой чекпоинт ждём (0..3) let lap = 0; // текущий круг let time = 0; let won = false; game.ui.timer = 0; game.ui.showText('Проедь 2 круга через чекпоинты!', 3); game.onTick((dt) => { if (won) return; time = time + dt; game.ui.timer = time; }); // чекпоинты шлют сюда broadcast('checkpoint', { num }) — скрипты // в разных песочницах, прогресс гонки nextCp/lap живёт только здесь. game.onMessage('checkpoint', (d) => { // игрок пересёк чекпоинт с номером n (1..4) const n = d.num; if (won) return; // ждём именно следующий по порядку чекпоинт if (n - 1 !== nextCp) return; game.sound.play('click'); nextCp = nextCp + 1; if (nextCp >= CP_COUNT) { nextCp = 0; lap = lap + 1; if (lap >= LAPS) { won = true; const t = Math.round(time * 10) / 10; game.ui.showText('Финиш! Круги пройдены за ' + t + ' сек', 6); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } else { game.ui.showText('Круг ' + lap + ' из ' + LAPS + '!', 2); } } });`}

Главное здесь — порядок чекпоинтов:

  • game.onMessage('checkpoint', (d) => {'{...}'}) — ловит сообщение от чекпоинта. Каждый чекпоинт — отдельный скрипт в своей «песочнице», поэтому номер приходит в «посылке» d.num, а не через общую переменную;
  • nextCp — номер чекпоинта, который мы ждём следующим (начинаем с 0);
  • if (n - 1 !== nextCp) return — если игрок пересёк не тот чекпоинт (например, проехал второй раньше первого) — пропускаем, он не засчитан;
  • пересёк правильный — nextCp сдвигается дальше. Прошёл все 4 — lap (круг) растёт, а nextCp сбрасывается в 0;
  • if (lap {'>'}= LAPS) — проехал 2 круга, секундомер останавливается, показывается время.
Проверка порядка не даёт «срезать» — нельзя проехать половину трассы и засчитать круг. Только все чекпоинты по очереди дадут круг.

Шаг 3. Скрипт чекпоинта

На каждый чекпоинт — свой скрипт, отличается только номер. Вот скрипт первого чекпоинта:

{`// === Скрипт чекпоинта 1 === game.self.onTouch(() => { game.broadcast('checkpoint', { num: 1 }); });`}

Чекпоинт шлёт сообщение game.broadcast('checkpoint', {'{ num: 1 }'}): имя сообщения — 'checkpoint', а «посылка» с номером говорит главному скрипту, какую точку пересёк игрок. На «Чекпоинт_2» — такой же скрипт с num: 2, на «Чекпоинт_3» — с num: 3, на «Чекпоинт_4» — с num: 4.

Шаг 4. Проверка

  • с началом игры пошёл секундомер;
  • проезжай чекпоинты по кругу по порядку;
  • после 4 чекпоинтов засчитывается круг;
  • проехал 2 круга — «Финиш» и твоё время.
увеличь число кругов — поменяй LAPS на 3. Поставь на трассе препятствия. Покрась пройденный чекпоинт в зелёный через game.scene.setColor, чтобы было видно, где ты уже был. ), }, // ════════════════════════════════════════════════════ // ИГРА 33 — «Платформер с боссом» // ════════════════════════════════════════════════════ 'boss-platformer': { body: ( <>

Что получится

Сначала паркур — допрыгай по платформам до высокой арены. Там тебя ждёт NPC-босс с большим запасом здоровья. Кликай по нему, пока полоска HP не обнулится. Над боссом висит метка с его здоровьем.

Чему научишься

  • Паркур + бой — как соединить две механики в одной игре;
  • Спавн босса один раз — создать NPC, когда игрок дошёл до арены;
  • Метка над NPC — показать HP босса (game.scene.setLabel);
  • Событие смерти NPCboss.onDeath.

Шаг 1. Старт, паркур, арена

Поставь стартовую площадку из травы внизу. Расставь 4 платформы — кубы-примитивы, ступеньками вверх к арене. «Столкновение» и «Закреплён» включены. Наверху выложи большую арену из камня — площадку, где будет бой с боссом.

Босса заранее ставить не нужно — его создаёт скрипт, когда игрок добирается до арены.

Шаг 2. Главный скрипт

{`// === ИГРА «ПЛАТФОРМЕР С БОССОМ» — главный скрипт === let won = false; let bossSpawned = false; let bossHp = 120; game.ui.showText('Пройди паркур до арены босса!', 3); // упал — на старт game.onTick(() => { if (won) return; const p = game.player.position; if (!p) return; // позиция игрока может быть не готова первые кадры if (p.y < -3) { game.player.respawn(); game.sound.play('lose'); } // дошёл до арены — спавним босса один раз if (!bossSpawned && p.z > 24 && p.y > 5) { bossSpawned = true; const boss = game.scene.spawnNpc('character-b', { x: 0, y: 7, z: 32, name: 'БОСС', hp: bossHp, speed: 2, // y=7 — на полу арены (блоки y=6) }); boss.follow('player'); game.scene.setLabel(boss.ref, 'БОСС HP: ' + bossHp, { color: '#ff3333', height: 3.5 }); game.ui.showText('БОСС! Кликай по нему!', 3); boss.onDeath(() => { won = true; game.scene.clearLabel(boss.ref); game.ui.showText('Победа! Босс повержен!', 5); game.sound.play('win'); const p2 = game.player.position; game.scene.spawnParticles('confetti', { x: p2.x, y: p2.y + 3, z: p2.z }, { duration: 3, count: 3 }); }); // удар по боссу кликом, если игрок близко game.onClick(() => { if (won) return; const pp = game.player.position; const bp = boss.position; if (pp && bp && Math.hypot(pp.x - bp.x, pp.z - bp.z) < 5) { bossHp = bossHp - 20; if (bossHp < 0) bossHp = 0; boss.damage(20); game.scene.setLabel(boss.ref, 'БОСС HP: ' + bossHp, { color: '#ff3333', height: 3.5 }); game.scene.spawnParticles('sparks', bp, { duration: 0.4 }); game.sound.play('hit'); } }); } });`}

Разберём по частям. Сначала — паркур:

  • game.onTick следит за падением: упал ниже -3 — респаун;
  • if (!bossSpawned && p.z {'>'} 24 && p.y {'>'} 5) — как только игрок зашёл на арену (далеко по z и высоко по y) — создаём босса. Флажок bossSpawned не даёт создать его второй раз.

Потом — бой:

  • boss.follow('player') — босс гонится за игроком;
  • game.scene.setLabel(boss.ref, текст, опции) — вешает над боссом метку-табличку с его HP. Это billboard — она всегда повёрнута к камере;
  • game.onClick — клик по боссу, если игрок близко: bossHp уменьшается на 20, boss.damage(20) ранит NPC, метка обновляется;
  • boss.onDeath(fn) — когда у босса HP дошло до нуля, эта функция выполнится: победа, метку убираем через clearLabel.
Босс hp: 120, удар снимает 20 — значит нужно 6 точных кликов. Меняй эти числа, чтобы сделать бой легче или сложнее.

Шаг 3. Проверка

  • пройди паркур по платформам до арены;
  • на арене появится босс с меткой HP;
  • подбегай и кликай — HP босса падает;
  • HP дошло до нуля — «Победа».
сделай босса сильнее — больше HP или быстрее. Добавь второго мини-босса. Сделай, чтобы босс тоже наносил игроку урон при касании — паркур обратно за аптечкой. ), }, // ════════════════════════════════════════════════════ // ИГРА 34 — «Сбор урожая» // ════════════════════════════════════════════════════ 'harvest': { body: ( <>

Что получится

На грядках растут шесть растений. Каждое медленно растёт (анимация-твин) и за 5 секунд становится спелым — жёлтым. Только спелое растение можно собрать клавишей E. Собрал слишком рано — не считается. Собери весь урожай.

Чему научишься

  • Твин роста — как плавно увеличить объект (game.tween по размеру);
  • Событие onDone — сделать что-то, когда твин закончился;
  • Состояние «спелое» — флажок, который меняет правила взаимодействия.

Шаг 1. Грядки и растения

Выложи поле из блоков земли — это грядки. Поставь 6 растений — конусы-примитивы, изначально маленькие (например, размер 0.4×0.5×0.4), зелёного цвета. «Столкновение» включено. Имена — «Растение_1» … «Растение_6».

Шаг 2. Главный скрипт

{`// === ИГРА «СБОР УРОЖАЯ» — главный скрипт === let harvested = 0; const GOAL = 6; game.ui.score = 0; game.ui.showText('Дождись, пока растения вырастут, и собери!', 4); // растения сообщают о сборе через broadcast('harvested') — скрипты // в разных песочницах, счётчик harvested живёт только здесь. game.onMessage('harvested', () => { harvested = harvested + 1; game.ui.score = harvested; game.sound.play('coin'); if (harvested >= GOAL) { game.ui.showText('Победа! Весь урожай собран!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Главный скрипт считает собранные растения и проверяет победу. Каждое растение работает в своей «песочнице», поэтому о сборе оно сообщает через game.broadcast('harvested'), а главный скрипт ловит сообщение через game.onMessage('harvested', ...). Вся «грядочная магия» — в скрипте растения.

Шаг 3. Скрипт растения

Этот скрипт вешается на каждое растение.

{`// === Скрипт растения === let ripe = false; // растение выросло (спелое)? let picked = false; // растение медленно растёт за 5 секунд. Вместе с sy растёт и y — // чтобы низ конуса оставался на земле (растение «растёт из земли»). game.tween(game.self.ref, { sx: 1.3, sy: 2.6, sz: 1.3, y: 2.3 }, { duration: 5, onDone: () => { ripe = true; game.scene.setColor(game.self.ref, '#ffcc33'); // спелое — жёлтое } }); // собрать растение (E) game.self.onInteract(() => { if (picked) return; if (!ripe) { game.ui.showText('Ещё не выросло! Подожди.', 1.5); return; } picked = true; game.self.delete(); game.broadcast('harvested'); }, { text: 'Собрать', distance: 3 });`}

Разберём:

  • game.tween(ref, {'{ sx, sy, sz, y }'}, опции) — твин по размеру. Растение плавно увеличивается с маленького до большого за 5 секунд. Заодно растёт и y — чтобы низ конуса оставался на земле, а не уходил под неё;
  • onDone: () => {'{...}'} — функция, которая выполнится, когда твин закончится. Внутри: ставим ripe = true (растение спелое) и красим его в жёлтый;
  • game.self.onInteract — сбор по E. Если !ripe — растение ещё не поспело, показываем подсказку и выходим;
  • спелое — picked = true, растение исчезает и засчитывается.
Флажок ripe меняет правила: до того как он стал true, собрать растение нельзя. Это и есть «состояние» объекта.

Шаг 4. Проверка

  • с началом игры растения начинают расти;
  • попробуй собрать раннее — «Ещё не выросло!»;
  • через 5 секунд растение пожелтело — спелое;
  • собери все 6 спелых — «Победа».
сделай растения с разным временем роста — одни за 3 секунды, другие за 8. Добавь «перезрелое» состояние: если не собрать вовремя, растение чернеет и пропадает. Поставь грядок побольше. ), }, // ════════════════════════════════════════════════════ // ИГРА 35 — «Прятки от NPC» // ════════════════════════════════════════════════════ 'hide-from-npc': { body: ( <>

Что получится

По полю ходит NPC-искатель и постоянно идёт к игроку. Среди поля расставлены стены-укрытия. Нужно петлять за ними и не дать себя поймать целых 40 секунд. Поймал — игрок возвращается на старт, но таймер не сбрасывается.

Чему научишься

  • NPC-преследователь spawnNpc + follow('player');
  • Таймер выживания — продержаться нужное количество секунд (game.ui.timer + dt);
  • Проверка поимкиMath.hypot между игроком и NPC.

Шаг 1. Поле и укрытия

Выложи большое травяное поле — простор, чтобы было где убегать. Расставь по полю 7 стен-укрытий — серые кубы-примитивы 4×4×1.5. «Столкновение» и «Закреплён» включены. Расположи их так, чтобы вокруг каждого можно было обежать. Точку спавна поставь в углу поля.

NPC-искатель создаётся скриптом — ставить его заранее не нужно.

Шаг 2. Главный скрипт

Вся игра — в одном скрипте. Разберём его.

{`// === ИГРА «ПРЯТКИ ОТ NPC» — главный скрипт === let time = 0; const SURVIVE = 40; // продержись 40 секунд let won = false; game.ui.timer = 0; game.ui.showText('Прячься за стенами 40 секунд!', 4); // NPC-искатель ходит за игроком const seeker = game.scene.spawnNpc('character-b', { x: 0, y: 1, z: 10, name: 'Искатель', hp: 100, speed: 3, // y=1 — на полу }); seeker.follow('player'); game.onTick((dt) => { if (won) return; time = time + dt; game.ui.timer = time; // искатель поймал — на старт, время продолжается. // seeker.position появляется через кадр после spawnNpc — ждём. const p = game.player.position; const e = seeker.position; if (p && e && Math.hypot(p.x - e.x, p.z - e.z) < 1.7) { game.player.respawn(); game.ui.showText('Найден! Прячься снова!', 1.5); game.sound.play('lose'); } // продержался — победа if (time >= SURVIVE) { won = true; seeker.stop(); game.ui.showText('Победа! Ты прятался 40 секунд!', 5); game.sound.play('win'); game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Разберём:

  • game.scene.spawnNpc(...) создаёт искателя, seeker.follow('player') — он гонится за игроком;
  • time = time + dt каждый кадр копит прошедшее время, game.ui.timer выводит секундомер;
  • Math.hypot(...) {'<'} 1.7 — искатель вплотную. Игрок возвращается на старт, но time не обнуляется — таймер идёт дальше;
  • if (time {'>'}= SURVIVE) — продержался 40 секунд: seeker.stop() останавливает искателя, показывается «Победа».
Хитрость игры: при поимке таймер не сбрасывается. Игрока просто отбрасывает на старт. Поэтому даже если раз поймали — до победы всё равно недалеко.

Шаг 3. Проверка

  • с началом игры за тобой ходит искатель;
  • петляй вокруг серых стен, чтобы он не догнал;
  • поймал — респаун на старте, таймер идёт дальше;
  • продержался 40 секунд — «Победа».
добавь второго искателя — игра станет напряжённее. Сделай искателя быстрее или удлини время выживания. Поставь больше укрытий — или, наоборот, убери часть, чтобы прятаться было сложнее. ), }, // ════════════════════════════════════════════════════ // ИГРА 36 — «Головоломка с ящиками» // ════════════════════════════════════════════════════ 'box-puzzle': { body: ( <>

Что получится

На поле стоят 3 коричневых ящика и 3 зелёные плиты-цели. Игрок подходит к ящику и нажимает E — ящик плавно перепрыгивает на следующую клетку. Задача — расставить все ящики по их плитам. Получилось — победа.

Чему научишься

  • Твин-перемещение — как плавно сдвигать объект из точки в точку (game.tween);
  • Список позиций — как хранить ряд клеток в массиве и ходить по нему по кругу;
  • Проверка состояния — как понять, что все три ящика стоят на своих местах;
  • Несколько объектов с одной логикой — каждый ящик знает свой номер.

Шаг 1. Поле, плиты и ящики

Построй ровную площадку из камня примерно 18×18 блоков. Поставь 3 примитива-цилиндра в ряд впереди: размер 2.4×0.2×2.4, цвет зелёный #22dd55, материал «Неон». Это плиты-цели. «Столкновение» — выключи, чтобы ящик мог встать прямо на плиту. Поставь 3 примитива-куба размером 1.8 — это ящики. Цвет коричневый #b5651d. Каждый ящик — напротив своей плиты, но в дальнем ряду. Дай имена «Ящик_1», «Ящик_2», «Ящик_3».

Шаг 2. Главный скрипт

Главный скрипт хранит, какие ящики уже на плитах, и ловит момент победы.

{`// === ИГРА «ГОЛОВОЛОМКА С ЯЩИКАМИ» — главный скрипт === // для каждого ящика — на какой плите он сейчас (true/false) const onPlate = [false, false, false]; let won = false; game.ui.showText('Поставь все 3 ящика на зелёные плиты!', 4); // Ящики сообщают сюда через game.broadcast('box', { i, on }). // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('box', (d) => { // ящик с номером d.i (0..2) встал/сошёл с плиты onPlate[d.i] = d.on; if (d.on) game.sound.play('click'); // все три ящика на плитах? if (!won && onPlate[0] && onPlate[1] && onPlate[2]) { won = true; game.ui.showText('Победа! Все ящики на местах!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } });`}

Разберём:

  • onPlate — массив из трёх «галочек»: стоит ли ящик №0, №1, №2 на плите;
  • game.onMessage('box', ...) — главный скрипт ждёт сообщение «box» от ящиков. В d.i — номер ящика, в d.on — стоит ли он на плите;
  • if (onPlate[0] {'&&'} onPlate[1] {'&&'} onPlate[2]) — победа, только когда все три «галочки» стоят разом.

Шаг 3. Скрипт ящика

Скрипт висит на ящике. Каждый ящик ходит по своему ряду из 5 клеток. Тут показан ящик №1 (для второго и третьего поменяй число i в сообщении на 1 и 2).

{`// === Скрипт ящика 1 === // ряд позиций по Z, по которым прыгает ящик const ROW = [-6, -3, 0, 3, 6]; let cell = 0; // на какой клетке ряда стоит ящик const PLATE_Z = 6; // плита в конце ряда game.self.onInteract(() => { // перепрыгиваем на следующую клетку (по кругу) cell = (cell + 1) % ROW.length; const z = ROW[cell]; game.tween(game.self.ref, { z: z }, { duration: 0.4, easing: 'ease' }); // сообщаем главному скрипту — стоит ли ящик на плите game.broadcast('box', { i: 0, on: z === PLATE_Z }); }, { text: 'Двинуть ящик', distance: 3 });`}

Что происходит:

  • ROW — пять клеток ряда (значения Z);
  • cell = (cell + 1) % ROW.length — переходим к следующей клетке; % заворачивает с конца ряда обратно к началу;
  • game.tween(...) — плавно сдвигает ящик на новую клетку за 0.4 секунды;
  • z === PLATE_Z — проверка «ящик на плите?»: результат (true/false) уходит в поле on сообщения;
  • game.broadcast('box', ...) — шлёт сообщение «box» главному скрипту, а тот ловит его через game.onMessage('box', ...).
У каждого ящика свой номер i: первый шлёт {'{'} i: 0 {'}'}, второй — {'{'} i: 1 {'}'}, третий — {'{'} i: 2 {'}'}. Не перепутай числа!

Шаг 4. Проверка

  • подойди к ящику, нажми E — ящик плавно прыгает вперёд;
  • доведи каждый ящик до его зелёной плиты;
  • все три на местах — «Победа» и конфетти.
добавь четвёртый ящик и плиту. Сделай ряды длиннее — головоломка станет дольше. Поставь ящикам разные цвета и плиты в тон — пусть каждый ящик ищет «свой» цвет. ), }, // ════════════════════════════════════════════════════ // ИГРА 37 — «Полоса препятствий» // ════════════════════════════════════════════════════ 'obstacle-course': { body: ( <>

Что получится

Длинная трасса с препятствиями: красные шипы наносят урон, в полу — ямы, над одной ямой ездит синяя платформа. Посередине стоит жёлтый чекпоинт — пройдёшь его, и после падения игра вернёт тебя сюда, а не на старт. В конце — зелёный финиш.

Чему научишься

  • Урон от препятствия — как отнять у игрока здоровье (game.player.damage);
  • Движущаяся платформа — твин «туда-обратно» с yoyo: true;
  • Чекпоинт — как перенести точку возрождения (game.player.setSpawn);
  • Падение в яму — возврат игрока через onTick.

Шаг 1. Трасса с ямами

Построй длинную дорожку из камня — 8 блоков в ширину, около 50 в длину. Вырежи в полу две ямы — просто не клади блоки в этих местах. Игрок, который провалится, упадёт вниз. Расставь по трассе примитивы-конусы красного цвета — это шипы. Размер 1.2×1.6×1.2, материал «Неон», «Столкновение» выключи (касание ловит скрипт). Над первой ямой поставь синий куб «ДвижПлатформа» (3×0.5×3), над второй — узкий коричневый «Мостик». Посередине трассы — жёлтый конус «Чекпоинт». В конце — зелёный куб «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «ПОЛОСА ПРЕПЯТСТВИЙ» — главный скрипт === let won = false; game.ui.showText('Пройди полосу: шипы, ямы, платформа!', 4); // движущаяся платформа ездит над ямой туда-сюда. // findOne нельзя сразу в начале — снимок сцены приходит чуть позже. game.after(0.2, () => { const mover = game.scene.findOne('ДвижПлатформа'); game.tween(mover, { x: 3 }, { duration: 2, yoyo: true, repeat: 999, easing: 'ease' }); }); game.onTick(() => { if (won) return; const p = game.player.position; if (p && p.y < -3) { game.player.respawn(); // упал в яму — на чекпоинт/старт game.sound.play('lose'); } }); // Чекпоинт и финиш сообщают сюда через game.broadcast. // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('checkpoint', () => { // ставим точку возрождения на чекпоинт game.player.setSpawn({ x: -0.5, y: 1, z: 24 }); game.ui.showText('Чекпоинт сохранён!', 2); game.sound.play('pickup'); }); game.onMessage('finish', () => { if (won) return; won = true; game.ui.showText('Победа! Полоса пройдена!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • game.after(0.2, ...) — ждём чуть-чуть, потому что findOne в самом начале ещё не видит объекты сцены;
  • game.tween(mover, ...) с yoyo: true и repeat: 999 — платформа бесконечно ездит туда и обратно;
  • onTick ловит падение в яму (p.y {'<'} -3) и возвращает игрока;
  • game.onMessage('checkpoint', ...) ловит сообщение от чекпоинта и зовёт game.player.setSpawn(...) — теперь респаун будет в середине трассы;
  • game.onMessage('finish', ...) — победа с конфетти, флажок won защищает от повтора.

Шаг 3. Скрипты шипа, чекпоинта и финиша

{`// === Скрипт шипа === game.self.onTouch(() => { game.player.damage(25); game.sound.play('hit'); });`} {`// === Скрипт чекпоинта === game.self.onTouch(() => { game.broadcast('checkpoint'); });`} {`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('finish'); });`}

Шип при касании отнимает 25 здоровья. Чекпоинт сохраняет точку возрождения. Финиш зовёт победу.

setSpawn меняет место, куда вернёт respawn. До чекпоинта это старт, после — середина трассы. Так игроку не приходится бежать заново.

Шаг 4. Проверка

  • наступил на шип — здоровье уменьшилось;
  • упал в яму — вернуло на старт (или на чекпоинт);
  • прошёл жёлтый конус — респаун теперь посередине;
  • дошёл до зелёного финиша — «Победа».
добавь второй чекпоинт ближе к финишу. Сделай шипы, которые двигаются (твин). Добавь монетки на сложных участках — награда за риск. ), }, // ════════════════════════════════════════════════════ // ИГРА 38 — «Музыкальная игра» // ════════════════════════════════════════════════════ 'music-game': { body: ( <>

Что получится

На поле 4 цветные плитки-ноты. Игра сначала проигрывает мелодию из 5 нот — слушай внимательно. Потом нужно повторить её, нажимая плитки E в том же порядке. Ошибся — мелодия начинается сначала.

Чему научишься

  • Последовательность — как хранить мелодию в массиве чисел;
  • Отложенные события — как проиграть ноты одну за другой через game.after;
  • Сравнение с эталоном — как проверить, что игрок нажимает в правильном порядке;
  • Разные звуки — у каждой плитки свой game.sound.

Шаг 1. Поле и 4 ноты

Построй небольшую площадку из блоков (примерно 16×13). Поставь 4 примитива-куба в ряд: размер 2.4×0.4×2.4, материал «Неон». Цвета — красный, жёлтый, зелёный, синий. Имена «Нота_1»…«Нота_4». «Столкновение» включено.

Шаг 2. Главный скрипт

Главный скрипт хранит мелодию, проигрывает её и проверяет, верно ли игрок повторяет.

{`// === ИГРА «МУЗЫКАЛЬНАЯ ИГРА» — главный скрипт === const SOUNDS = ['coin', 'jump', 'click', 'hit']; // плитки 1..4 // загаданная последовательность из 5 нот const SEQ = [1, 3, 2, 4, 1]; let playerStep = 0; // на каком шаге игрок let won = false; let canPress = false; game.ui.showText('Слушай мелодию, потом повтори!', 3); // проигрываем мелодию: нота за нотой каждые 0.8 сек SEQ.forEach((note, i) => { game.after(1 + i * 0.8, () => { game.sound.play(SOUNDS[note - 1]); game.ui.showText('Нота ' + (i + 1) + ' из ' + SEQ.length, 0.7); }); }); // после мелодии разрешаем игроку повторять game.after(1 + SEQ.length * 0.8 + 0.5, () => { canPress = true; game.ui.showText('Теперь повтори мелодию!', 3); }); // Плитки сообщают сюда нажатую ноту через game.broadcast('press', { n }). // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('press', (d) => { if (won || !canPress) return; // правильная нота? if (d.n === SEQ[playerStep]) { playerStep = playerStep + 1; if (playerStep >= SEQ.length) { won = true; game.ui.showText('Победа! Мелодия повторена верно!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } } else { // ошибка — сброс playerStep = 0; game.ui.showText('Ошибка! Слушай и пробуй снова.', 2); game.sound.play('lose'); } });`}

Разберём:

  • SEQ = [1, 3, 2, 4, 1] — загаданная мелодия: номера нот по порядку;
  • SEQ.forEach((note, i) ={'>'} ...) — для каждой ноты ставим таймер: нота i прозвучит через 1 + i * 0.8 секунд;
  • canPress — флажок: нажимать можно только после того, как мелодия доиграла;
  • game.onMessage('press', ...) ловит сообщение от плитки; d.n — номер нажатой ноты;
  • if (d.n === SEQ[playerStep]) — нажатая нота совпала с нужной? Да — шаг вперёд; нет — сброс playerStep в ноль.

Шаг 3. Скрипт ноты-плитки

Скрипт висит на каждой плитке. Тут показана нота №1 — для остальных поменяй звук и число в press.

{`// === Скрипт ноты-плитки 1 === game.self.onInteract(() => { game.sound.play('coin'); game.scene.spawnParticles('sparks', game.self.position, { duration: 0.4, color: '#e23b3b' }); game.broadcast('press', { n: 1 }); }, { text: 'Сыграть ноту', distance: 3 });`}

При нажатии E плитка играет свой звук, вспыхивает искрами и шлёт game.broadcast('press', {'{'} n: 1 {'}'}) — сообщение со своим номером. У ноты №2 будет {'{'} n: 2 {'}'}, у №3 — {'{'} n: 3 {'}'} и т.д.

Цвет искр в spawnParticles и звук должны совпадать с цветом плитки — так игроку понятнее, что он нажал.

Шаг 4. Проверка

  • в начале игра сама проигрывает 5 нот по очереди;
  • дождись надписи «Теперь повтори мелодию»;
  • нажимай плитки в том же порядке;
  • повторил всё верно — «Победа», ошибся — сброс.
удлини мелодию SEQ до 7-8 нот. Сделай 5-ю плитку. Добавь подсветку: пусть плитка на миг меняет цвет, когда игра её проигрывает. ), }, // ════════════════════════════════════════════════════ // ИГРА 39 — «Башня — стройка» // ════════════════════════════════════════════════════ 'tower-build': { body: ( <>

Что получится

Над землёй висят 8 полупрозрачных «призрачных» блоков один над другим. Игрок подходит к призраку и нажимает E — блок становится настоящим: плотным и коричневым. Строить нужно строго снизу вверх. Построил всю башню — победа.

Чему научишься

  • Призрак-блок — стеклянный примитив, который нельзя задеть;
  • Превращение объекта — как сделать призрак твёрдым и непрозрачным (passThrough, setOpacity, setCollide);
  • Порядок действий — проверка «следующий по очереди»;
  • Счётчик стройки — сколько блоков уже поставлено.

Шаг 1. Поле и места-призраки

Построй площадку из травы (16×16 блоков). Поставь примитив-куб размером 2×2×2 на полу. Цвет светло-синий #88aaff, материал «Стекло» — куб станет полупрозрачным. «Столкновение» выключи: сквозь призрак можно пройти. Дублируй куб 7 раз и поставь их стопкой друг над другом — каждый следующий на 2 единицы выше. Имена «Место_1»…«Место_8», снизу вверх.

Шаг 2. Главный скрипт

{`// === ИГРА «БАШНЯ — СТРОЙКА» — главный скрипт === const STEPS = 8; let placed = 0; // сколько блоков поставлено game.ui.score = 0; game.ui.showText('Подходи к местам и ставь блоки (E) снизу вверх', 4); // Места-призраки сообщают сюда через game.broadcast('place', { n }). // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('place', (d) => { // блок поставлен в место номер d.n (1..STEPS); // ставить можно только следующее по порядку место if (d.n !== placed + 1) { game.ui.showText('Сначала поставь блок ниже!', 1.5); return; } placed = placed + 1; game.ui.score = placed; game.sound.play('click'); if (placed >= STEPS) { game.ui.showText('Победа! Башня построена!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } else { game.ui.showText('Блок ' + placed + ' из ' + STEPS, 1.5); } });`}

Разберём:

  • placed — сколько блоков уже стоит;
  • game.onMessage('place', ...) ловит сообщение от места-призрака; d.n — его номер;
  • if (d.n !== placed + 1) — если игрок жмёт не на следующий по очереди блок, стройка не засчитывается;
  • game.ui.score = placed — счётчик показывает прогресс башни;
  • когда placed дошёл до 8 — победа.

Шаг 3. Скрипт места-призрака

Скрипт висит на каждом призраке. Тут показано место №1 — у остальных поменяй число в place.

{`// === Скрипт места под блок 1 === let built = false; game.self.onInteract(() => { if (built) return; // ставим настоящий блок — делаем призрак твёрдым и непрозрачным game.physics.passThrough(game.self.ref, false); game.scene.setOpacity(game.self.ref, 1); game.scene.setColor(game.self.ref, '#b5651d'); game.scene.setCollide(game.self.ref, true); built = true; game.broadcast('place', { n: 1 }); }, { text: 'Поставить блок', distance: 4 });`}

Что происходит при нажатии:

  • passThrough(ref, false) — сквозь блок больше нельзя пройти;
  • setOpacity(ref, 1) — блок становится полностью непрозрачным;
  • setColor(ref, '#b5651d') — перекрашиваем в коричневый «деревянный»;
  • setCollide(ref, true) — теперь на блок можно встать.
У каждого места свой номер n в сообщении: место_1 шлёт {'{'} n: 1 {'}'}, место_2 — {'{'} n: 2 {'}'} и так до 8.

Шаг 4. Проверка

  • подойди к нижнему призраку, нажми E — он стал коричневым;
  • встань на новый блок и ставь следующий выше;
  • если жмёшь не по порядку — подсказка «сначала ниже»;
  • построил все 8 — «Победа».
сделай башню выше — 12-15 блоков. Добавь блоки разного цвета. Поставь монетку на самом верху как награду за постройку. ), }, // ════════════════════════════════════════════════════ // ИГРА 40 — «Выживание от волн» // ════════════════════════════════════════════════════ 'wave-survival': { body: ( <>

Что получится

Открытая арена. На игрока накатывают 3 волны врагов-NPC, одна за другой. С каждой волной врагов всё больше. Кликай по врагам, чтобы их уничтожать. Отбил все 3 волны — победа.

Чему научишься

  • NPC-враги — как создавать персонажей-врагов (spawnNpc) и заставлять их преследовать;
  • Волны — как запускать врагов группами;
  • Рекурсивный вызов — как функция волны запускает саму себя для следующей волны;
  • Клик по цели — проверка расстояния при game.onClick.

Шаг 1. Арена

Построй большую ровную площадку из камня (примерно 24×24 блока). Стены не нужны — врагов лучше видно на открытом поле. Поставь точку спавна игрока в центр арены. Враги создаются прямо в скрипте функцией spawnNpc — заранее ставить их не нужно.

Шаг 2. Главный скрипт

Вся игра — в одном глобальном скрипте: он запускает волны и считает врагов.

{`// === ИГРА «ВЫЖИВАНИЕ ОТ ВОЛН» — главный скрипт === const WAVES = 3; // всего волн let wave = 0; let won = false; game.ui.showText('Отбей 3 волны врагов! Кликай по ним', 3); // запускаем очередную волну function startWave() { if (won) return; wave = wave + 1; game.ui.showText('Волна ' + wave + ' из ' + WAVES + '!', 3); game.sound.play('hit'); const count = wave + 2; // врагов всё больше: 3, 4, 5 let aliveInWave = count; for (let i = 0; i < count; i++) { // враг появляется по краю поля const angle = (i / count) * 6.28; const ex = Math.cos(angle) * 10; const ez = Math.sin(angle) * 10; const enemy = game.scene.spawnNpc('character-b', { x: ex, y: 1, z: ez, name: 'Враг', hp: 40, speed: 2, // y=1 — на полу }); enemy.follow('player'); let dead = false; game.onClick(() => { if (dead || won) return; const p = game.player.position; const e = enemy.position; if (p && e && Math.hypot(p.x - e.x, p.z - e.z) < 5) { dead = true; enemy.remove(); game.scene.spawnParticles('explosion', e, { count: 1 }); game.sound.play('hit'); aliveInWave = aliveInWave - 1; // вся волна перебита — следующая if (aliveInWave <= 0) { if (wave >= WAVES) { won = true; game.ui.showText('Победа! Все волны отбиты!', 5); game.sound.play('win'); const pp = game.player.position; if (pp) game.scene.spawnParticles('confetti', { x: pp.x, y: pp.y + 3, z: pp.z }, { duration: 3, count: 3 }); } else { game.after(2, startWave); } } } }); } } game.after(2, startWave); // первая волна через 2 секунды`}

Разберём:

  • startWave() — функция одной волны. Она создаёт wave + 2 врагов: 3, потом 4, потом 5;
  • Math.cos / Math.sin — расставляют врагов по кругу вокруг арены;
  • enemy.follow('player') — враг идёт к игроку;
  • game.onClick(...) — при клике проверяем Math.hypot(...) {'<'} 5: враг рядом — он уничтожен;
  • if (aliveInWave {'<='} 0) ... game.after(2, startWave) — вся волна перебита, через 2 секунды функция запускает сама себя для следующей волны. Это и есть рекурсия.

Шаг 3. Проверка

  • через 2 секунды появляется первая волна из 3 врагов;
  • кликай по врагам рядом — они взрываются;
  • перебил всю волну — через 2 секунды идёт следующая;
  • отбил 3 волны — «Победа» и конфетти.
Рекурсия — это когда функция вызывает саму себя. Здесь startWave запускает startWave снова, пока не кончатся волны. Главное — есть условие остановки (wave {'>='} WAVES), иначе волны шли бы бесконечно. сделай 5 волн вместо 3. Дай врагам больше здоровья (hp) или скорости (speed) — станет сложнее. Добавь между волнами «передышку» подлиннее. ), }, // ════════════════════════════════════════════════════ // ИГРА 41 — «Платформер-приключение» // ════════════════════════════════════════════════════ 'adventure-platformer': { body: ( <>

Что получится

Большой уровень-приключение: длинная цепочка платформ-паркура, на пути — золотые монетки, посередине жёлтый чекпоинт, а на самом верху — финиш-сокровище. Эта игра собирает вместе почти всё, что ты прошёл в простых уроках.

Чему научишься

  • Собирать игру из кусочков — как соединить паркур, монетки, чекпоинт и финиш в один уровень;
  • Большой главный скрипт — один скрипт ловит сообщения от всех объектов игры;
  • Несколько целей сразу — добраться до финиша И собрать монетки.

Шаг 1. Большой уровень

Поставь стартовую площадку из травы внизу. Выстрой цепочку платформ-кубов (2.5×0.5×2.5) вверх и вперёд — около 9 штук, каждая чуть выше предыдущей. Посередине подъёма сделай площадку из камня — это зона чекпоинта. Сверху — финишную площадку. Расставь золотые сферы-монетки над некоторыми платформами. Поставь жёлтый конус «Чекпоинт» и золотой куб «Сокровище».

Шаг 2. Главный скрипт

{`// === ИГРА «ПЛАТФОРМЕР-ПРИКЛЮЧЕНИЕ» — главный скрипт === let coins = 0; let won = false; game.ui.score = 0; game.ui.showText('Доберись до сокровища! Собирай монетки', 4); game.onTick(() => { if (won) return; // позиция игрока может быть ещё не готова первые кадры — // проверяем p перед обращением к p.y. const p = game.player.position; if (p && p.y < -3) { game.player.respawn(); game.sound.play('lose'); } }); // Монетки, чекпоинт и сокровище сообщают сюда через game.broadcast. // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('coin', () => { coins = coins + 1; game.ui.score = coins; game.sound.play('coin'); }); game.onMessage('checkpoint', () => { game.player.setSpawn({ x: -0.5, y: 7, z: 28 }); game.ui.showText('Чекпоинт! Дальше — отсюда.', 2); game.sound.play('pickup'); }); game.onMessage('treasure', () => { if (won) return; won = true; game.ui.showText('Победа! Сокровище и ' + coins + ' монет!', 6); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • game.onMessage('coin', ...) — пришла монетка, +1 к счёту;
  • game.onMessage('checkpoint', ...) — перенести точку возрождения наверх;
  • game.onMessage('treasure', ...) — победа: в надписи показываем, сколько монеток успел собрать;
  • onTick — следит за падением, как в уроках 2 и 37.
Главный скрипт большой, но устроен просто: он ждёт три разных сообщения. Каждый объект уровня шлёт нужное ему через game.broadcast.

Шаг 3. Скрипты монетки, чекпоинта и сокровища

{`// === Скрипт монетки === game.self.onTouch(() => { game.broadcast('coin'); game.self.delete(); });`} {`// === Скрипт чекпоинта === game.self.onTouch(() => { game.broadcast('checkpoint'); });`} {`// === Скрипт сокровища === game.self.onTouch(() => { game.broadcast('treasure'); });`}

Монетка при касании засчитывается и исчезает. Чекпоинт сохраняет место. Сокровище зовёт победу.

Шаг 4. Проверка

  • прыгай по платформам вверх, собирай монетки;
  • упал — респаун (на старт или на чекпоинт);
  • прошёл чекпоинт — дальше начинаешь отсюда;
  • добрался до сокровища — «Победа» с числом монеток.
добавь шипы из урока 37 на сложные платформы. Сделай врага-преследователя из урока 21. Добавь второй уровень выше первого — длинное приключение. ), }, // ════════════════════════════════════════════════════ // ИГРА 42 — «RPG-деревня» // ════════════════════════════════════════════════════ 'rpg-village': { body: ( <>

Что получится

Маленькая деревня с двумя жителями: старостой и кузнецом. Поговори со старостой — он даст квест: найти потерянный амулет за домом. Подними амулет и отнеси его кузнецу — получишь награду. Это настоящая RPG с этапами квеста.

Чему научишься

  • NPC-жители — как создавать персонажей и заставлять их говорить (spawnNpc, say);
  • Этапы квеста — переменная stage, которая помнит, на каком шаге игрок;
  • Инвентарь — как класть и проверять предмет (game.inventory);
  • Диалоги по условию — NPC говорит разное в зависимости от этапа.

Шаг 1. Деревня

Построй большую травяную площадку (примерно 32×32 блока). Сложи два домика из блоков — один деревянный, другой из красного кирпича. Поставь две тумбы-кубы рядом с местами NPC: «Староста» у входа, «Кузнец» у кирпичного дома. Это «кнопки» для разговора. За дальним домом спрячь примитив-тор (бублик) фиолетового цвета — это «Амулет».

Шаг 2. Главный скрипт

{`// === ИГРА «RPG-ДЕРЕВНЯ» — главный скрипт === // этап: 0=начало, 1=ищем амулет, 2=несём кузнецу, 3=готово let stage = 0; game.ui.showText('Деревня. Поговори со старостой (E)', 4); const elder = game.scene.spawnNpc('character-a', { x: 1.6, y: 1, z: 2, name: 'Староста', hp: 100, speed: 0, // y=1 — на полу }); const smith = game.scene.spawnNpc('character-b', { x: 12.6, y: 1, z: 7, name: 'Кузнец', hp: 100, speed: 0, // y=1 — на полу }); // Староста, амулет и кузнец сообщают сюда через game.broadcast. // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('elderTalk', () => { if (stage === 0) { stage = 1; elder.say('Найди потерянный амулет за домом!', 4); game.ui.showText('Квест: найди фиолетовый амулет', 3); } else if (stage === 1) { elder.say('Амулет всё ещё не у тебя...', 3); } else { elder.say('Спасибо за помощь деревне!', 3); } }); game.onMessage('takeAmulet', () => { if (stage !== 1) return; stage = 2; game.inventory.add({ name: 'Амулет' }); game.sound.play('pickup'); game.ui.showText('Амулет найден! Отнеси кузнецу.', 3); }); game.onMessage('smithTalk', () => { if (stage === 2 && game.inventory.has('Амулет')) { stage = 3; game.inventory.remove('Амулет'); smith.say('Отличный амулет! Вот награда, герой!', 4); game.ui.showText('Победа! Квест RPG-деревни выполнен!', 6); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } else if (stage === 3) { smith.say('Доброго пути!', 3); } else { smith.say('Принеси мне амулет — поговори со старостой.', 4); } });`}

Разберём:

  • stage — переменная-этап: 0 начало, 1 ищем амулет, 2 несём кузнецу, 3 квест выполнен;
  • spawnNpc(...) со speed: 0 — житель стоит на месте;
  • elder.say(...) — над NPC появляется реплика;
  • game.inventory.add / has / remove — кладём амулет в сумку, проверяем и забираем;
  • game.onMessage('elderTalk', ...) и game.onMessage('smithTalk', ...) говорят разное в зависимости от stage — это и есть «живой» квест.

Шаг 3. Скрипты NPC и амулета

{`// === Скрипт старосты === game.self.onInteract(() => { game.broadcast('elderTalk'); }, { text: 'Поговорить со старостой', distance: 4 });`} {`// === Скрипт кузнеца === game.self.onInteract(() => { game.broadcast('smithTalk'); }, { text: 'Поговорить с кузнецом', distance: 4 });`} {`// === Скрипт амулета === game.self.onTouch(() => { game.broadcast('takeAmulet'); game.self.delete(); });`} Тумба-куб — это «кнопка разговора». Сам NPC создаётся скриптом и стоит рядом с тумбой. Игрок жмёт E на тумбе — и NPC отвечает.

Шаг 4. Проверка

  • поговори со старостой — он выдаст квест;
  • найди фиолетовый амулет за дальним домом, подними его;
  • отнеси амулет кузнецу, поговори с ним;
  • кузнец благодарит — «Победа».
добавь третьего NPC с ещё одним предметом — цепочка квестов станет длиннее. Сделай так, чтобы за квест игрок получал монетки в счёт. Добавь жителям реплики «просто так». ), }, // ════════════════════════════════════════════════════ // ИГРА 43 — «Гонка с препятствиями» // ════════════════════════════════════════════════════ 'obstacle-race': { body: ( <>

Что получится

Гоночная трасса на время. По дороге — синие плитки-бусты: наступил, и игрок ускоряется. И красные шипы-ловушки: задел — урон и замедление. Доберись до финиша как можно быстрее, секундомер покажет твоё время.

Чему научишься

  • Скорость игрока — как ускорять и замедлять (game.player.setSpeed);
  • Временный эффект — буст действует 3 секунды, потом скорость возвращается;
  • Секундомер — счёт времени через onTick и game.ui.timer;
  • Два типа препятствий — полезное и вредное.

Шаг 1. Трасса

Построй длинную дорожку из камня — 8 блоков в ширину, около 70 в длину, с бортиками по бокам. Расставь синие кубы-бусты (4×0.3×2, материал «Неон», «Столкновение» выключено) поперёк дороги — 3 штуки. Расставь красные конусы-шипы (1.2×1.6×1.2) — 5 штук, в шахматном порядке слева-справа. В конце поставь зелёный куб «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «ГОНКА С ПРЕПЯТСТВИЯМИ» — главный скрипт === let time = 0; let won = false; game.ui.timer = 0; game.ui.showText('Гонка! Синее ускоряет, шипы мешают', 4); game.onTick((dt) => { if (won) return; time = time + dt; game.ui.timer = time; }); // Бусты, шипы и финиш сообщают сюда через game.broadcast. // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('boost', () => { // ускоряем игрока на 3 секунды game.player.setSpeed(1.8); game.sound.play('pickup'); game.ui.showText('УСКОРЕНИЕ!', 1); game.after(3, () => game.player.setSpeed(1)); }); game.onMessage('spike', () => { game.player.damage(15); game.player.setSpeed(0.5); // шип замедляет game.sound.play('hit'); game.after(1.5, () => game.player.setSpeed(1)); }); game.onMessage('finish', () => { if (won) return; won = true; const t = Math.round(time * 10) / 10; game.ui.showText('Финиш! Время: ' + t + ' сек', 6); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • onTick((dt) ={'>'} ...)dt это секунды с прошлого кадра; складываем их в time — получается секундомер;
  • game.onMessage('boost', ...) setSpeed(1.8) делает игрока в 1.8 раза быстрее, а game.after(3, ...) через 3 секунды возвращает обычную скорость setSpeed(1);
  • game.onMessage('spike', ...) — отнимает здоровье и наоборот замедляет до setSpeed(0.5) на 1.5 секунды;
  • Math.round(time * 10) / 10 — округляем время до десятых долей секунды.

Шаг 3. Скрипты буста, шипа и финиша

{`// === Скрипт буста === game.self.onTouch(() => { game.broadcast('boost'); });`} {`// === Скрипт шипа-ловушки === game.self.onTouch(() => { game.broadcast('spike'); });`} {`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('finish'); });`} setSpeed — множитель скорости. 1 — обычная, 1.8 — быстро, 0.5 — медленно. После эффекта всегда возвращай setSpeed(1), иначе игрок навсегда останется быстрым или медленным.

Шаг 4. Проверка

  • с началом игры идёт секундомер;
  • наехал на синюю плитку — ускорение на 3 секунды;
  • задел красный шип — урон и замедление;
  • домчал до финиша — игра покажет твоё время.
добавь несколько кругов, как в гонке из урока 32. Сделай бусты-«стрелки», которые ещё и подбрасывают. Поставь рекорд времени и попробуй его побить. ), }, // ════════════════════════════════════════════════════ // ИГРА 44 — «Tower Defense» // ════════════════════════════════════════════════════ 'tower-defense': { body: ( <>

Что получится

Враги идут по каменной дороге к твоей синей базе. По бокам дороги — площадки: подойди и нажми E, чтобы построить башню. Башни сами стреляют по врагам рядом. Уничтожь 14 врагов, не пропустив 8 — победа.

Чему научишься

  • Враги по маршруту — NPC идут от старта к базе (moveTo);
  • Постройка башен — нажатие E создаёт объект (game.scene.spawn);
  • Авто-атака — башни сами бьют врагов рядом через game.every;
  • Счёт побед и прорывов — два условия конца игры.

Шаг 1. Дорога, площадки и база

Построй большую травяную площадку. По центру выложи каменную дорогу на 1 блок выше — по ней пойдут враги. По бокам дороги поставь 4 куба-площадки (3×2×3, серые) — имена «Площадка_1»…«Площадка_4». Это места под башни. В конце дороги поставь большой синий куб «База».

Шаг 2. Главный скрипт

Главный скрипт делает всё: спавнит врагов, заставляет башни стрелять и проверяет, кто победил.

{`// === ИГРА «TOWER DEFENSE» — главный скрипт === let leaked = 0; // врагов прошло до базы const MAX_LEAK = 8; let killed = 0; const GOAL = 14; // победа — уничтожить 14 врагов let over = false; // список башен: {x, z} const towers = []; // список живых врагов: {npc, alive} const enemies = []; game.ui.score = 0; game.ui.showText('Ставь башни (E)! Не пропусти врагов', 4); // Площадки сообщают сюда о постройке башни через // game.broadcast('addTower', { x, z }). // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('addTower', (d) => { towers.push({ x: d.x, z: d.z }); game.sound.play('click'); game.ui.showText('Башня построена!', 1.5); }); // спавн врагов let total = 0; game.every(2.2, () => { if (over || total >= GOAL + MAX_LEAK) return; total = total + 1; const npc = game.scene.spawnNpc('character-b', { x: -0.5, y: 1, z: -3, name: 'Враг', hp: 50, speed: 2, }); npc.moveTo(-0.5, 42); const rec = { npc: npc, alive: true }; enemies.push(rec); npc.onDeath(() => { rec.alive = false; killed = killed + 1; game.ui.score = killed; if (killed >= GOAL && !over) { over = true; game.ui.showText('Победа! База защищена!', 5); game.sound.play('win'); } }); }); // башни стреляют: каждые 0.8с бьём врага рядом с любой башней game.every(0.8, () => { if (over) return; for (const t of towers) { for (const e of enemies) { if (!e.alive) continue; const p = e.npc.position; // позиция NPC появляется через кадр после spawn — пропускаем if (!p) continue; if (Math.hypot(p.x - t.x, p.z - t.z) < 7) { e.npc.damage(25); game.scene.spawnParticles('sparks', p, { duration: 0.3 }); break; // одна башня — один выстрел за тик } } } }); // проверка прорыва к базе game.every(0.5, () => { if (over) return; for (const e of enemies) { if (e.alive && e.npc.position && e.npc.position.z > 40) { e.alive = false; e.npc.remove(); leaked = leaked + 1; game.sound.play('lose'); game.ui.showText('Враг прорвался! (' + leaked + '/' + MAX_LEAK + ')', 2); if (leaked >= MAX_LEAK && !over) { over = true; game.ui.showText('База разрушена! Поражение.', 5); } } } });`}

Разберём:

  • towers и enemies — два списка: где стоят башни и какие враги живы;
  • game.onMessage('addTower', ...) ловит сообщение от площадки и кладёт координаты башни в список towers;
  • первый game.every(2.2, ...) — каждые 2.2 секунды выпускает врага и зовёт npc.moveTo(...) — враг идёт к базе;
  • второй game.every(0.8, ...) — каждая башня ищет врага ближе 7 единиц и бьёт его;
  • третий game.every(0.5, ...) — проверяет, не дошёл ли враг до базы (position.z {'>'} 40);
  • победа — killed {'>='} GOAL, поражение — leaked {'>='} MAX_LEAK.

Шаг 3. Скрипт площадки под башню

{`// === Скрипт площадки под башню === let built = false; game.self.onInteract(() => { if (built) return; built = true; const pos = game.self.position; // ставим башню — жёлтый цилиндр поверх площадки game.scene.spawn('primitive:cylinder', { x: pos.x, y: pos.y + 2.5, z: pos.z, sx: 1.5, sy: 3, sz: 1.5, color: '#ffcc33', }); game.broadcast('addTower', { x: pos.x, z: pos.z }); }, { text: 'Построить башню', distance: 4 });`}

При нажатии E скрипт создаёт жёлтый цилиндр-башню над площадкой и шлёт сообщение game.broadcast('addTower', ...) с координатами башни — главный скрипт ловит его, кладёт башню в список, и она начинает стрелять.

Сама «стрельба» — это не пули, а проверка расстояния в game.every. Так делают почти все простые Tower Defense: башня просто бьёт ближайшего врага по таймеру.

Шаг 3.1. Башня — своя модель из редактора моделей

Жёлтый цилиндр-башня — это просто заглушка. Можно поставить вместо неё свою воксельную модель, которую ты сделал в редакторе моделей проекта.

  1. В редакторе проекта открой панель «Мои модели» и создай башню в редакторе воксельных моделей.
  2. Узнай её id — он показан рядом с названием модели в панели «Мои модели» (например 3).
  3. В скрипте площадки замени блок game.scene.spawn('primitive:cylinder', ...) на такой:
{`// ставим башню — своя воксельная модель (id = 3) game.scene.spawn('user:3', { x: pos.x, y: pos.y + 2, z: pos.z, rotationY: 0, });`}

У пользовательских моделей нет sx/sy/sz и color — размер и цвет задаются в самом редакторе модели. Из параметров только позиция и rotationY (поворот по вертикальной оси). Высоту y подбери под высоту своей модели, чтобы она ровно встала на площадку.

Шаг 4. Проверка

  • враги идут по дороге к базе;
  • подойди к площадке, нажми E — встаёт башня;
  • башня сама стреляет искрами по врагам рядом;
  • уничтожил 14 — победа, пропустил 8 — поражение.
добавь больше площадок под башни. Сделай врагам больше здоровья на поздних волнах. Покрась башни разными цветами — «обычная» и «сильная». ), }, // ════════════════════════════════════════════════════ // ИГРА 45 — «Стрелялка-арена» // ════════════════════════════════════════════════════ 'arena-shooter': { body: ( <>

Что получится

Закрытая арена. Со всех сторон к игроку сбегаются враги-NPC. Кликай по врагам — они гибнут. Но и враги бьют тебя, если подошли вплотную: у игрока есть здоровье. Перебей 15 врагов, не потеряв всё HP.

Чему научишься

  • «Стрельба» кликом — клик уничтожает врага рядом по проверке расстояния;
  • Урон игроку — враг бьёт по таймеру (game.every + damage);
  • Отслеживание здоровья — конец игры при onHpChange;
  • Отмена таймераgame.cancel, когда враг умер.

Шаг 1. Арена со стенами

Построй квадратную площадку из камня (примерно 26×26 блоков). По всему краю поставь стены в 3 блока высотой — чтобы враги и игрок не убегали с арены. Поставь точку спавна игрока в центр. Врагов снова создаёт скрипт — расставлять их заранее не нужно. Поэтому у этой игры только скриншот результата.

Шаг 2. Главный скрипт

{`// === ИГРА «СТРЕЛЯЛКА-АРЕНА» — главный скрипт === let score = 0; const GOAL = 15; let over = false; game.ui.score = 0; game.ui.showText('Перебей 15 врагов! Кликай по ним', 3); // проигрыш, когда HP игрока кончилось game.onHpChange((e) => { if (!over && e.hp <= 0) { over = true; game.ui.showText('Поражение! Тебя одолели враги.', 5); } }); // спавним нового врага каждые 1.8 сек game.every(1.8, () => { if (over || score >= GOAL) return; const angle = game.random(0, 6.28); const ex = Math.cos(angle) * 11; const ez = Math.sin(angle) * 11; const enemy = game.scene.spawnNpc('character-b', { x: ex, y: 1, z: ez, name: 'Враг', hp: 30, speed: 2.2, // y=1 — на полу }); enemy.follow('player'); let dead = false; // враг бьёт игрока, если подошёл вплотную const dmgTimer = game.every(0.7, () => { if (dead || over) { game.cancel(dmgTimer); return; } const p = game.player.position; const e = enemy.position; if (p && e && Math.hypot(p.x - e.x, p.z - e.z) < 1.8) { game.player.damage(10); game.sound.play('hit'); } }); // клик по врагу — убить game.onClick(() => { if (dead || over) return; const p = game.player.position; const e = enemy.position; if (p && e && Math.hypot(p.x - e.x, p.z - e.z) < 6) { dead = true; game.cancel(dmgTimer); enemy.remove(); game.scene.spawnParticles('explosion', e, { count: 1 }); game.sound.play('hit'); score = score + 1; game.ui.score = score; if (score >= GOAL && !over) { over = true; game.ui.showText('Победа! Арена зачищена!', 5); game.sound.play('win'); game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } } }); });`}

Разберём:

  • game.onHpChange((e) ={'>'} ...) — срабатывает, когда здоровье игрока меняется. Если e.hp {'<='} 0 — поражение;
  • game.every(1.8, ...) — каждые 1.8 секунды появляется новый враг по краю арены и follow('player') — идёт к игроку;
  • dmgTimer — отдельный таймер: пока враг жив, каждые 0.7 секунды проверяет, рядом ли он, и бьёт;
  • game.cancel(dmgTimer) — когда враг умер, его таймер урона выключается, чтобы не работал зря;
  • клик ближе 6 единиц к врагу — он уничтожен, +1 к счёту.
Каждый враг — это маленький «механизм»: свой флажок dead, свой таймер dmgTimer, свой обработчик клика. Когда враг гибнет, его механизм аккуратно выключается через game.cancel.

Шаг 3. Проверка

  • враги сбегаются к тебе со всех сторон;
  • кликай по врагу рядом — он взрывается, +1 к счёту;
  • враг вплотную — отнимает здоровье;
  • перебил 15 — «Победа», потерял всё HP — «Поражение».
добавь «аптечки» — сферы, которые лечат при касании. Сделай врагов сильнее (больше hp и speed). Увеличь цель до 25 врагов. ), }, // ════════════════════════════════════════════════════ // ИГРА 46 — «Кликер» // ════════════════════════════════════════════════════ 'clicker': { body: ( <>

Что получится

В центре площадки — большой жёлтый куб. Кликай по нему, и копятся очки. По бокам — две кнопки-улучшения: красная добавляет силу клика, синяя включает авто-доход (очки капают сами). Накопи 200 очков — победа.

Чему научишься

  • Клик по объектуgame.self.onClick;
  • Экономика улучшений — тратим очки, чтобы зарабатывать больше;
  • Авто-доход — очки сами растут через game.every;
  • Несколько мест проверки победы — общая функция checkWin.

Шаг 1. Куб и кнопки

Построй небольшую площадку из блоков. В центр поставь большой жёлтый куб «Кликер» (3×3×3, материал «Неон»). Слева поставь красный куб «УлучшениеСила» (2×2×2), справа — синий куб «УлучшениеАвто».

Шаг 2. Главный скрипт

{`// === ИГРА «КЛИКЕР» — главный скрипт === let points = 0; // очки let perClick = 1; // очков за клик let autoIncome = 0; // очков в секунду автоматически const GOAL = 200; let won = false; game.ui.score = 0; game.ui.showText('Кликай по жёлтому кубу! Цель: 200 очков', 4); // авто-доход: каждую секунду прибавляем autoIncome game.every(1, () => { if (won) return; if (autoIncome > 0) { points = points + autoIncome; game.ui.score = points; checkWin(); } }); function checkWin() { if (!won && points >= GOAL) { won = true; game.ui.showText('Победа! Накоплено 200 очков!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } } // Куб-кликер и кнопки апгрейда сообщают сюда через game.broadcast. // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('click', () => { if (won) return; points = points + perClick; game.ui.score = points; game.sound.play('click'); checkWin(); }); game.onMessage('buyPower', () => { if (points < 20) { game.ui.showText('Нужно 20 очков для улучшения!', 1.5); return; } points = points - 20; perClick = perClick + 2; game.ui.score = points; game.sound.play('pickup'); game.ui.showText('Сила клика: +' + perClick + ' за клик', 2); }); game.onMessage('buyAuto', () => { if (points < 40) { game.ui.showText('Нужно 40 очков для авто-дохода!', 1.5); return; } points = points - 40; autoIncome = autoIncome + 3; game.ui.score = points; game.sound.play('pickup'); game.ui.showText('Авто-доход: +' + autoIncome + ' в секунду', 2); });`}

Разберём:

  • points — очки, perClick — сколько даёт один клик, autoIncome — сколько капает само в секунду;
  • game.every(1, ...) — каждую секунду прибавляет autoIncome к очкам;
  • checkWin() — общая функция: проверка победы вызывается и из клика, и из авто-дохода;
  • game.onMessage('click', ...) ловит клик по кубу — +perClick очков;
  • game.onMessage('buyPower', ...) — за 20 очков добавляет +2 к силе клика;
  • game.onMessage('buyAuto', ...) — за 40 очков добавляет +3 к авто-доходу.

Шаг 3. Скрипты куба и кнопок

{`// === Скрипт куба-кликера === game.self.onClick(() => { game.broadcast('click'); // куб слегка вспыхивает game.scene.spawnParticles('sparks', game.self.position, { duration: 0.3 }); });`} {`// === Скрипт улучшения «сила клика» (20 очков) === game.self.onInteract(() => { game.broadcast('buyPower'); }, { text: 'Купить +силу клика (20)', distance: 3 });`} {`// === Скрипт улучшения «авто-доход» (40 очков) === game.self.onInteract(() => { game.broadcast('buyAuto'); }, { text: 'Купить авто-доход (40)', distance: 3 });`} Главная идея кликера: сначала кликаешь руками, потом покупаешь улучшения — и игра «играет сама». Это экономика: тратишь очки, чтобы зарабатывать быстрее.

Шаг 4. Проверка

  • кликай по жёлтому кубу — очки растут;
  • накопил 20 — купи силу клика, клики станут «жирнее»;
  • накопил 40 — купи авто-доход, очки капают сами;
  • дошёл до 200 — «Победа».
добавь третье улучшение — например, ещё дороже и мощнее. Сделай цель больше (1000 очков). Покажи perClick и autoIncome отдельной надписью через game.ui.set. ), }, // ════════════════════════════════════════════════════ // ИГРА 47 — «Квест-побег» // ════════════════════════════════════════════════════ 'escape-quest': { body: ( <>

Что получится

Игрок заперт в комнате. Чтобы выбраться, нужно найти и нажать 3 красные кнопки. Две на виду, а третья спрятана за ящиком — её надо обойти. Нажал все три — дверь поднимается, и можно выйти к финишу.

Чему научишься

  • Закрытая комната — стены с проёмом под дверь;
  • Состояние головоломки — счётчик нажатых кнопок;
  • Открытие двери — твин по достижении условия;
  • Спрятанный объект — кнопка за ящиком, которую надо найти.

Шаг 1. Комната, кнопки, дверь

Построй пол и стены закрытой комнаты (примерно 20×20). В одной стене оставь проём шириной 3 блока — под дверь. Закрой проём примитивом-кубом «Дверь» (1×4×3, коричневый). Поставь 3 цилиндра-кнопки красного цвета по углам комнаты. Одну спрячь за большим кубом-ящиком. За дверью снаружи поставь зелёный куб «Финиш».

Шаг 2. Главный скрипт

{`// === ИГРА «КВЕСТ-ПОБЕГ» — главный скрипт === let pressed = 0; // сколько кнопок нажато const TOTAL = 3; let escaped = false; game.ui.showText('Найди и нажми 3 кнопки, чтобы выйти!', 4); // Кнопки и финиш сообщают сюда через game.broadcast. // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('pressButton', () => { pressed = pressed + 1; game.sound.play('click'); game.ui.showText('Кнопка ' + pressed + ' из ' + TOTAL, 1.5); if (pressed >= TOTAL) { // все кнопки — открываем дверь const door = game.scene.findOne('Дверь'); game.tween(door, { y: 9 }, { duration: 1.2, easing: 'ease' }); game.ui.showText('Все кнопки нажаты! Дверь открыта!', 3); game.sound.play('win'); } }); game.onMessage('escape', () => { if (escaped) return; escaped = true; game.ui.showText('Победа! Ты сбежал из комнаты!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); });`}

Разберём:

  • pressed — счётчик нажатых кнопок, TOTAL — сколько их всего;
  • game.onMessage('pressButton', ...) ловит сообщение, которое кнопка шлёт при нажатии;
  • if (pressed {'>='} TOTAL) — когда нажаты все три, находим дверь по имени и поднимаем её твином вверх;
  • game.onMessage('escape', ...) — победа, когда игрок прошёл через открытую дверь к финишу.

Шаг 3. Скрипты кнопки и финиша

{`// === Скрипт кнопки 1 === let used = false; game.self.onInteract(() => { if (used) return; used = true; game.scene.setColor(game.self.ref, '#22dd55'); // нажата — зелёная game.broadcast('pressButton'); }, { text: 'Нажать кнопку', distance: 3 });`} {`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('escape'); });`}

Кнопка при нажатии становится зелёной (видно, что нажата), шлёт game.broadcast('pressButton') и больше не срабатывает благодаря флажку used.

Скрипт у всех трёх кнопок одинаковый — каждая просто шлёт сообщение 'pressButton'. Главный скрипт сам считает, сколько кнопок нажато.

Шаг 4. Проверка

  • нажми E на видимых кнопках — они становятся зелёными;
  • найди третью кнопку за ящиком;
  • после третьей дверь поднимается вверх;
  • выйди наружу на зелёный финиш — «Победа».
спрячь все кнопки похитрее — например, в разных комнатах. Добавь четвёртую кнопку. Сделай «ложную» кнопку, которая сбрасывает счётчик. ), }, // ════════════════════════════════════════════════════ // ИГРА 48 — «Мультиплеер: Салки» // ════════════════════════════════════════════════════ 'mp-tag': { body: ( <>

Что получится

Догонялки для нескольких игроков. Один игрок — водящий, он догоняет остальных, а они убегают и прячутся за укрытиями. Это мультиплеерная игра: чтобы играть с друзьями, её нужно опубликовать с галочкой «Мультиплеер».

Важно про мультиплеер. Чтобы играть с друзьями, опубликуй игру и поставь галочку «Мультиплеер» — тогда несколько игроков смогут зайти в одну комнату. Если открыть игру в одиночку, она работает как демо: ты увидишь правила, но догонять будет некого.

Чему научишься

  • Игроки комнатыgame.players: список всех, кто играет;
  • Общее состояниеgame.room: данные, которые видят все игроки;
  • Вход и выход — события onPlayerJoin / onPlayerLeave;
  • Реакция на изменение game.room.onChange.

Шаг 1. Поле с укрытиями

Построй большую травяную площадку (примерно 28×28 блоков). Расставь по полю несколько кубов-укрытий (3.5×4×1.5, серые) — за ними убегающие смогут прятаться.

Шаг 2. Главный скрипт

{`// === ИГРА «МУЛЬТИПЛЕЕР: САЛКИ» — главный скрипт === // // Это МУЛЬТИПЛЕЕРНАЯ игра. Чтобы играть с друзьями, опубликуй её // с галочкой «Мультиплеер» — тогда в комнату смогут зайти несколько // игроков. В одиночку игра показывает только правила. game.ui.showText('Салки! Опубликуй игру для игры с друзьями', 4); // общий счётчик игроков комнаты — виден всем function refresh() { const n = game.players.count(); game.ui.set('info', 'Игроков в комнате: ' + n, { x: 50, y: 8 }); // водящий выбирается так: первый зашедший игрок const all = game.players.all(); if (all.length > 0) { game.room.set('tagger', all[0].sessionId); } } // при входе/выходе игрока — обновляем game.onPlayerJoin((p) => { game.ui.showText(p.name + ' присоединился к салкам!', 2); refresh(); }); game.onPlayerLeave(() => refresh()); refresh(); // если ты водящий — догоняй других; если убегаешь — прячься. game.room.onChange('tagger', (taggerId) => { const me = game.players.me(); if (me && me.sessionId === taggerId) { game.ui.showText('Ты ВОДЯЩИЙ! Догоняй и осаль других!', 3); } else { game.ui.showText('Убегай от водящего!', 3); } });`}

Разберём:

  • game.players.count() — сколько игроков сейчас в комнате;
  • game.players.all() — список всех игроков; me() — это «я»;
  • game.room.set('tagger', ...) — записываем в общее состояние комнаты, кто водящий. Это видят все игроки;
  • game.onPlayerJoin / onPlayerLeave — события входа и выхода игрока;
  • game.room.onChange('tagger', ...) — срабатывает у всех, когда водящий поменялся: каждый узнаёт, он водящий или убегает.

Шаг 3. Проверка

  • в одиночку игра покажет правила (демо-режим);
  • опубликуй игру с галочкой «Мультиплеер»;
  • пусть друзья зайдут по ссылке в твою комнату;
  • первый зашедший — водящий, остальные убегают.
Разница простой и мультиплеерной игры: обычные данные (let score) есть только у тебя, а game.room — общие для всей комнаты. Поэтому «кто водящий» хранят именно в game.room. добавь надпись с именем водящего через game.ui.set. Сделай так, чтобы при касании водящего с убегающим они менялись ролями. Добавь таймер раунда. ), }, // ════════════════════════════════════════════════════ // ИГРА 49 — «Мультиплеер: Гонка» // ════════════════════════════════════════════════════ 'mp-race': { body: ( <>

Что получится

Гонка нескольких игроков по одной трассе. Кто первым добежит до финиша — его имя записывается в общий счёт комнаты, и все игроки видят победителя. Это снова мультиплеерная игра.

Важно про мультиплеер. Чтобы соревноваться с друзьями, опубликуй игру с галочкой «Мультиплеер» — тогда несколько игроков попадут в одну комнату и побегут вместе. В одиночку игра открывается как демо.

Чему научишься

  • Общий счёт комнатыgame.room хранит имя победителя для всех;
  • Узнать себяgame.players.me() и имя игрока;
  • Один победитель — как не дать записать второго;
  • Обновление у всехgame.room.onChange.

Шаг 1. Трасса

Построй длинную прямую дорожку из камня (около 70 блоков) с бортиками по бокам. В конце поставь широкий зелёный куб «Финиш». Точку спавна поставь в начале трассы.

Шаг 2. Главный скрипт

{`// === ИГРА «МУЛЬТИПЛЕЕР: ГОНКА» — главный скрипт === // // Мультиплеерная гонка. Чтобы соревноваться с друзьями — опубликуй // игру с галочкой «Мультиплеер». game.ui.showText('Гонка! Беги к финишу первым', 3); // показываем, сколько игроков и кто уже финишировал function refresh() { const n = game.players.count(); const winner = game.room.get('winner'); let txt = 'Игроков: ' + n; if (winner) txt = txt + ' | Победил: ' + winner; game.ui.set('info', txt, { x: 50, y: 8 }); } refresh(); game.onPlayerJoin(() => refresh()); game.onPlayerLeave(() => refresh()); // когда кто-то финишировал — обновляем у всех game.room.onChange('winner', () => refresh()); // Финиш сообщает сюда через game.broadcast('finish'). // Скрипты живут в РАЗНЫХ песочницах — общая переменная между ними не // видна, поэтому связь только через сообщения broadcast/onMessage. game.onMessage('finish', () => { // если победитель ещё не определён — записываем себя if (!game.room.get('winner')) { const me = game.players.me(); const myName = me ? me.name : 'Игрок'; game.room.set('winner', myName); game.ui.showText('Ты пришёл первым! Победа!', 5); game.sound.play('win'); const p = game.player.position; game.scene.spawnParticles('confetti', { x: p.x, y: p.y + 3, z: p.z }, { duration: 3, count: 3 }); } else { game.ui.showText('Финиш! Но кто-то был быстрее.', 4); } });`}

Разберём:

  • game.room.get('winner') — читаем общую переменную комнаты: записан ли уже победитель;
  • refresh() — рисует надпись со счётом: число игроков и имя победителя;
  • game.onMessage('finish', ...) — если победителя ещё нет, игрок записывает своё имя через game.room.set('winner', myName);
  • if (!game.room.get('winner')) — защита: только первый добежавший станет победителем, остальные увидят «кто-то был быстрее»;
  • game.room.onChange('winner', ...) — как только победитель записан, надпись обновляется у всех игроков сразу.

Шаг 3. Скрипт финиша

{`// === Скрипт финиша === game.self.onTouch(() => { game.broadcast('finish'); });`}

Когда любой игрок касается финиша, скрипт шлёт сообщение game.broadcast('finish') — а главный скрипт ловит его и уже решает, первый этот игрок или нет.

Шаг 4. Проверка

  • в одиночку — демо: добежишь и станешь победителем сам;
  • опубликуй с галочкой «Мультиплеер»;
  • пусть друзья зайдут в комнату и побегут с тобой;
  • первый на финише — его имя в надписи у всех.
Главная идея: game.room — это «общая доска» комнаты. Один игрок пишет на неё имя победителя, и все остальные тут же это видят. добавь секундомер и записывай в комнату ещё и время победителя. Сделай несколько кругов. Добавь места для 2-3 места, не только для первого. ), }, // ════════════════════════════════════════════════════ // ИГРА 50 — «Своя игра» // ════════════════════════════════════════════════════ 'make-your-own': { body: ( <>

Что это за урок

Это последний урок — и он особенный. Здесь нет готовой игры. Тебе даётся пустая песочница: ровная зелёная площадка и один скрипт-приветствие. Твоя задача — придумать и собрать свою собственную игру с нуля. Всё, что нужно, ты уже знаешь из уроков 1-49.

Чему научишься

  • Придумывать игру — выбирать жанр и идею;
  • Планировать сцену — что и где построить;
  • Ставить цель — что игрок должен сделать, чтобы выиграть;
  • Соединять механики — брать кусочки из разных уроков и складывать в свою игру.

Шаг 1. Выбери жанр

Сначала реши, какая у тебя игра. Жанр — это «вид» игры. Вот какие ты уже умеешь делать:

  • Паркур — прыжки по платформам (уроки 2, 10, 41);
  • Гонка на время — добежать быстрее (уроки 8, 32, 43);
  • Сбор предметов — собрать всё на уровне (уроки 1, 6, 34);
  • Стрелялка — отбиваться от врагов (уроки 15, 40, 45);
  • Квест — головоломки и задания (уроки 12, 30, 42, 47);
  • Tower Defense — защита базы (уроки 31, 44);
  • Мультиплеер — игра с друзьями (уроки 48, 49).
Не бойся смешивать! Лучшие игры — это сочетание: паркур с монетками и врагом, квест с гонкой. Возьми один главный жанр и добавь к нему пару механик из других.

Шаг 2. Спланируй сцену

Прежде чем строить — представь свою игру. Ответь себе на вопросы:

  • Где начинает игрок? — поставь точку спавна;
  • Где он будет ходить? — построй пол, платформы, дорогу из блоков;
  • Что мешает? — шипы, ямы, враги, стены;
  • Что помогает? — монетки, бусты, чекпоинты;
  • Где конец игры? — финиш, сокровище или нужный счёт.

Можно даже нарисовать план на бумаге — это помогает не запутаться.

Шаг 3. Поставь цель

У каждой игры должна быть понятная цель — то, ради чего игрок играет. Без цели игра скучная. Цель может быть такой:

  • добраться до финиша;
  • собрать все предметы;
  • набрать нужное число очков;
  • продержаться какое-то время;
  • выполнить квест.

И обязательно покажи игроку, когда он победил — надписью game.ui.showText('Победа!', 5), звуком game.sound.play('win') и конфетти.

Шаг 4. Напиши скрипты

Сцена сама по себе не «живая» — её оживляют скрипты. Начинай с главного скрипта: в нём заводи переменные (счёт, флажок победы) и лови сообщения через game.onMessage('имя', fn). На объекты вешай небольшие скрипты — они шлют сообщения главному через game.broadcast('имя'). Так главный скрипт узнаёт, что монетку собрали или кнопку нажали. Ты делал так в каждом уроке.

Каждый скрипт работает в своей «песочнице» — переменные одного скрипта не видны другому. Поэтому скрипты общаются сообщениями: один шлёт game.broadcast('имя'), другой ловит game.onMessage('имя', fn). Можно передать данные: game.broadcast('имя', {'{'} ... {'}'}).

Базовый набор инструментов, который ты знаешь:

  • game.self.onTouch — реакция на касание;
  • game.self.onInteract — реакция на E;
  • game.self.onClick — реакция на клик;
  • game.broadcast и game.onMessage — связь между скриптами;
  • game.onTick — каждый кадр;
  • game.after и game.every — таймеры;
  • game.tween — плавное движение;
  • game.scene.spawnNpc — враги и NPC;
  • game.ui.score и game.ui.showText — счёт и подсказки.

Шаг 5. Проверяй и улучшай

Не строй сразу всё. Делай по чуть-чуть и почаще нажимай Запустить: построил пол — проверь, добавил врага — проверь. Так легче найти ошибку. Если что-то не работает — открой Консоль, там видны ошибки скриптов.

Не существует «неправильной» игры. Если в неё весело играть — она удалась. Начни с простого: маленький уровень, одна цель. А потом добавляй новое: врага, монетки, второй этаж. Большие игры всегда начинались с маленьких.

Поздравляем!

Ты прошёл все 50 уроков и теперь умеешь строить сцены, писать скрипты, оживлять врагов, делать квесты, гонки и даже мультиплеер. Это настоящие навыки создателя игр. Впереди — только твоя фантазия. Придумывай, строй, показывай друзьям. Удачи, и пусть твои игры будут лучшими!

), }, // ════════════════════════════════════════════════════ // РАЗБОР ИГР · Двор с табличкой (оригинал id=1991) // ════════════════════════════════════════════════════ 'guide-dvor': { body: ( <>

Что получится

Маленький уютный двор: зелёный газон, деревянный забор, деревья и большая 3D-табличка в центре. По табличке можно нажать мышкой прямо в игре — и что-то произойдёт. А ещё двор учит главному: как крутить камеру вокруг героя, как в настоящем Roblox.

Чему научишься

  • Камера и мышь — зажми правую кнопку мыши и веди, чтобы осмотреться вокруг героя;
  • Зум — колесо мыши приближает и отдаляет. Совсем близко — игра сама переходит в вид «от первого лица»;
  • Shift-Lock на клавише L — герой всегда смотрит туда же, куда камера;
  • Клик по 3D-табличке — как сделать кнопку прямо в игровом мире (game.billboard.onClick).

Шаг 1. Двор

Инструментом Блок построй площадку из травы примерно 10×10. Это газон. По краю поставь забор из блоков-брёвен в 2 блока высотой, оставив спереди проход. Из палитры моделей добавь пару деревьев — для красоты. На вкладке Игра поставь точку спавна в центре.

Шаг 2. Табличка

Табличка — это особый примитив «3D-табличка» (биллборд). У неё есть кнопка, на которую можно нажимать.

Выбери Примитив → категория Геймплей3D-табличка. Поставь её в центр двора. Выдели табличку. В инспекторе справа нажми «Редактировать табличку» — откроется окно, где можно задать текст, иконку, цвет и кнопку. Запомни номер таблички — кликни по ней в Иерархии, он выглядит как primitive:41 (число у тебя может быть другое).

Шаг 3. Скрипт клика

Теперь сделаем, чтобы по нажатию на табличку менялся цвет неба.

{`// === ДВОР С ТАБЛИЧКОЙ — главный скрипт === // Подписываемся на клик по кнопке таблички. // 'primitive:41' — номер твоей таблички, 'buy' — её кнопка. game.billboard.onClick('primitive:41', 'buy', () => { game.environment.setSkyColor('#88c0ff'); // небо стало голубым game.log('По табличке нажали!'); });`}

game.billboard.onClick(номер, кнопка, функция) — «когда нажмут на эту кнопку, выполни функцию». Внутри мы меняем цвет неба командой game.environment.setSkyColor.

Шаг 4. Проверка

Нажми Играть. Походи по двору на WASD. Зажми правую кнопку мыши и веди — камера крутится вокруг героя. Покрути колесо — приближается и отдаляется. Наведи курсор на кнопку таблички и кликни — небо поменяет цвет. Камера и мышь — это фундамент почти любой игры. Разберёшься здесь — дальше будет намного легче. поставь рядом ещё две таблички с разными цветами неба и сделай переключатель «утро — день — ночь» из трёх кнопок. ), }, // ════════════════════════════════════════════════════ // РАЗБОР ИГР · Витрина GUI (оригинал id=1995) // ════════════════════════════════════════════════════ 'guide-vitrina': { body: ( <>

Что получится

Витрина магазина, как в играх-кликерах. 3D-мира почти нет — весь экран это интерфейс: счётчик монет и яркие кнопки. И все кнопки живые: пульсируют, крутятся, увеличиваются при наведении и «вдавливаются» при нажатии.

Чему научишься

  • GUI-кнопки с градиентом, обводкой текста и скруглением;
  • Анимация-пресет — кнопка пульсирует сама по себе (свойство «Анимация: pulse»);
  • Твин — плавное изменение: кнопка плавно крутится с 0° до 360° (game.gui.tween);
  • Связь кнопок и счётчика через сообщения (game.broadcast и game.onMessage).

Шаг 1. Счётчик и кнопки

Поставь маленький пол и точку спавна (мир мы почти не видим). На вкладке Интерфейс добавь надпись-счётчик слева сверху — это монеты. Добавь кнопку. В инспекторе задай ей градиент фона, обводку текста и скругление углов. Размер задаётся в процентах: ширина 18 — это 18% экрана. Поля кнопок задаются в процентах от экрана, а не в пикселях. Так интерфейс одинаково выглядит на любом мониторе. Если поставить ширину 220 — кнопка растянется на весь экран!

Шаг 2. Живые анимации

Выдели кнопку и в инспекторе выбери свойство «Анимация: pulse» — в игре она начнёт пульсировать сама. Там же есть hover (что делать при наведении мышкой, например увеличиться) и active (при нажатии — сжаться).

Шаг 3. Скрипт кнопки «X2»

Повесим на кнопку «X2 денег» скрипт: при клике она эффектно крутится и на 5 секунд удваивает награду.

{`// === Скрипт кнопки X2 === game.self.onClick(() => { // сначала вернём поворот в 0, потом плавно крутанём на 360° game.gui.update(game.self, { rotation: 0 }); game.gui.tween(game.self, { rotation: 360 }, { duration: 0.5 }); // включаем множитель x2 на 5 секунд game.broadcast('multiplier_set', 2); game.after(5, () => game.broadcast('multiplier_set', 1)); });`}

game.gui.tween(объект, что-меняем, как-долго) — это и есть плавная анимация. Без неё кнопка прыгнула бы резко, а с твином крутится гладко, как в дорогих играх.

Шаг 4. Главный скрипт-счётчик

{`// === Витрина GUI — главный скрипт === let coins = 0; // монеты let multiplier = 1; // множитель награды game.hud.setHotbarVisible(false); // в этой игре инвентарь не нужен // Кнопки шлют 'coin_add' — добавляем монеты с учётом множителя. game.onMessage('coin_add', (amount) => { coins = coins + amount * multiplier; game.gui.update('hud_coins', { text: 'Монеты: ' + coins }); }); // Кнопка X2 шлёт 'multiplier_set' — меняем множитель. game.onMessage('multiplier_set', (m) => { multiplier = m; });`}

Шаг 5. Проверка

Нажми Играть. Жми кнопки — счётчик монет растёт. Нажми «X2» — кнопка крутится, и 5 секунд монеты идут вдвое быстрее. сделай кнопку, которая при наведении мышкой меняет цвет (свойство hover), а при клике плавно уезжает за край экрана через твин по координате X. ), }, // ════════════════════════════════════════════════════ // РАЗБОР ИГР · Тайна старого сундука (оригинал id=2037) // ════════════════════════════════════════════════════ 'guide-sunduk': { body: ( <>

Что получится

Маленькое приключение. Лесная поляна с каменными руинами, светящийся сундук в центре и страж рядом. Игра показывает модальные сцены — это когда мир затемняется, всё замирает, и на экране появляется что-то важное: диалог, выбор приза или большая надпись.

Чему научишься

  • Диалог по фразам с кнопкой ▶ (game.modal.dialog);
  • Кат-сцена — затемнить мир, оставить «прожектор» на объекте, подлететь камерой (game.modal.open);
  • Вопрос Да/Нет (game.modal.confirmation);
  • Лутбокс — выбор приза из карточек (game.modal.lootbox);
  • как находить объект по имени и следить за расстоянием до него.

Шаг 1. Поляна, сундук и страж

Построй каменную площадку (greystone) с обломками стен и колоннами вокруг — это руины. В центр поставь сундук (можно собрать из примитива-куба) и в инспекторе дай ему имя «chest». Слева поставь стража (из примитивов: цилиндр-тело, сфера-голова, конус-шлем) с именем «guard».

Шаг 2. Диалог со стражем

В обычном Roblox такую сцену собирают из 5-6 кусков вручную. У нас — одна команда. Главный скрипт следит за расстоянием до стража и запускает диалог.

{`// === ТАЙНА СУНДУКА — главный скрипт === game.hud.setHotbarVisible(false); let phase = 'start'; // этап квеста game.onTick((dt) => { if (game.modal.isOpen()) return; // во время модала ничего не триггерим const p = game.player.position; // расстояние до стража (он в точке -6, 4) if (phase === 'start') { const d = Math.hypot(p.x - (-6), p.z - 4); if (d < 4) { phase = 'talked'; game.modal.dialog('Страж Руин', [ 'Стой, путник. Это место хранит тайну веков...', 'В центре руин дремлет Старый сундук.', 'Он заперт магией. Подойди — и он сам откроется.', ], () => game.log('Иди к сундуку!')); } } });`} Имя объекта (например «chest») ищи не сразу при старте, а внутри game.onTick — сцена «появляется» не мгновенно, и в первую секунду объект ещё не найден.

Шаг 3. Кат-сцена сундука

Подошёл к сундуку — затемняем мир, но сундук оставляем ярким (вокруг него прожектор), камера сама подлетает.

{`// (продолжение onTick) — подошёл к сундуку if (phase === 'talked') { const d = Math.hypot(p.x - 0, p.z - (-7)); if (d < 5) { phase = 'open'; const chest = game.scene.findOne('chest'); game.modal.open({ darken: 0.72, // затемнить мир на 72% spotlights: [chest], // сундук остаётся ярким (прожектор) cameraOverride: { target: chest, distance: 9, duration: 0.7 }, blockInput: true, // заблокировать управление content: { elements: [ { kind: 'text', x: 50, y: 20, text: 'Старый сундук', textSize: 50, textColor: '#ffd700', animationPreset: 'glow' }, ]}, }); } }`}

Шаг 4. Выбор приза и победа

Готовые сцены, которые вызываются одной строкой:

  • game.modal.confirmation('Открыть?', 'текст', наДа, наНет) — вопрос Да/Нет;
  • game.modal.lootbox([призы], выбор) — карточки призов;
  • game.modal.bossIntro(имя, hp, [враги]) — заставка перед боссом.
{`// Лутбокс — четыре приза, игрок выбирает один game.modal.lootbox([ { name: 'Меч зари', icon: '⚔', color: '#c0392b' }, { name: 'Щит луны', icon: '🛡', color: '#2c5fb3' }, { name: 'Кошель злата', icon: '💰', color: '#b8860b' }, { name: 'Перо феникса', icon: '🔥', color: '#8e44ad' }, ], (item) => { game.log('Игрок выбрал: ' + item.name); });`}

Шаг 5. Проверка

Нажми Играть и подойди к стражу — пойдёт диалог. Иди к сундуку — мир затемнится, камера подлетит к нему. Ответь «Да», выбери приз — увидишь финальную надпись. Кнопка диалога на последней фразе сама превращается из ▶ в галочку ✓ — это значит «закрыть диалог». добавь второго стража с другим квестом и сделай так, чтобы сундук открывался только после разговора с обоими. ), }, // ════════════════════════════════════════════════════ // РАЗБОР ИГР · Парк животных (оригинал id=2046) // ════════════════════════════════════════════════════ 'guide-zoo': { body: ( <>

Что получится

Самая весёлая игра! Ты начинаешь её в виде... пончика. По парку стоят таблички: нажми на кнопку таблички — и твой герой превращается в бургер, болид, пришельца, рыбу или человечка. А по клавише B открывается магазин скинов.

Чему научишься

  • Кастомный скин — герой может быть любой 3D-моделью, не только человечком (game.player.setSkin);
  • Смена скина в игре — без перезапуска, прямо на ходу;
  • Магазин скинов — встроенный, открывается клавишей B;
  • снова 3D-таблички — те же, что в игре «Двор», но теперь их кнопка меняет скин.

Шаг 1. Парк и стартовый скин

Построй полянку с деревьями и поставь точку спавна. Нажми кнопку «Скины» в шапке редактора. Выбери стартовый скин (например пончик), включи галочку «Магазин скинов» и задай стартовые рублики (например 200). Поставь несколько 3D-табличек (как в игре «Двор») — по одной на каждый скин. Запомни их номера (primitive:10 и т.д.).

Шаг 2. Скрипт превращений

Главный скрипт подписывается на клик по каждой табличке и меняет скин одной командой.

{`// === ПАРК ЖИВОТНЫХ — главный скрипт === game.player.setSkinCoins(200); // 200 рубликов на магазин // Каждая табличка при клике надевает свой скин. game.billboard.onClick('primitive:10', 'buy', () => game.player.setSkin('burger')); game.billboard.onClick('primitive:11', 'buy', () => game.player.setSkin('car-race')); game.billboard.onClick('primitive:12', 'buy', () => game.player.setSkin('alien')); game.billboard.onClick('primitive:13', 'buy', () => game.player.setSkin('fish')); game.billboard.onClick('primitive:14', 'buy', () => game.player.setSkin('character-a')); // Узнать, когда скин сменился: game.player.onSkinChange((newSkin) => { game.log('Теперь я: ' + newSkin); });`}

game.player.setSkin('burger') — одна строчка меняет всё тело героя на новую модель. Имена скинов (burger, car-race, alien…) видны в окне «Скины».

Шаг 3. Магазин скинов

Магазин уже встроен — мы включили его галочкой в окне «Скины». В игре он открывается клавишей B. Командами скинами тоже можно управлять:

{`game.player.unlockSkin('alien'); // открыть скин игроку game.player.openSkinShop(); // открыть магазин из кода game.player.setSkinCoins(500); // задать баланс рубликов`}

Шаг 4. Проверка

Нажми Играть — ты появишься пончиком. Походи — пончик смешно покачивается на ходу. Нажми на кнопку таблички — превратишься в другого героя. Нажми B — откроется магазин скинов. Скины бывают двух видов: человечки (умеют махать, прыгать, танцевать) и модели (пончик, машинка — они просто покачиваются). Свою модель .glb тоже можно загрузить в окне «Скины». сделай скин платным: дай игроку 100 рубликов, поставь скину цену 50 в магазине и проверь — хватит ли денег его купить. ), }, }; /** Есть ли готовый текст урока для игры с таким id. */ export function hasLesson(id) { return !!LESSONS[id]; }