import React from 'react'; import DocIcon from './docsIcons'; import { LangTabs } from './docsLang'; /** * docsData.jsx — контент вики редактора Рублокс (разделы A-J). * * Структура: DOCS = массив глав. Каждая глава — * { id, icon, title, summary, sections: [{ id, title, body }] } * - icon — имя SVG-иконки из docsIcons.jsx (НЕ эмодзи). * - summary — короткое описание для карточки на главной вики. * * Хелперы для оформления: * - — код-блок (тёмный, моноширинный) * - — плашка «куда писать скрипт» * - — шаг инструкции * - — жёлтая плашка-подсказка * - — зелёная плашка «попробуй сам» * * Контент написан для детей 5 класса+. Каждый пример — рабочий код, * который можно скопировать в свою игру. Эмодзи в UI не используются — * только SVG-иконки (см. docsIcons.jsx). */ // ── Код-блок ────────────────────────────────────────────────────── export const Code = ({ children }) => (
{children}
); // ── Плашка «куда писать скрипт» ─────────────────────────────────── // kind="global" — глобальный скрипт (создаётся в категории «Скрипты») // kind="object" — скрипт привязан к объекту (передай on="название объекта") export const ScriptKind = ({ kind, on }) => { if (kind === 'object') { return (
Куда писать: этот скрипт нужно повесить на объект {on ? <> — на {on} : null}. Выдели объект на сцене и создай скрипт прямо на нём. Тогда внутри скрипта работает слово game.self — это и есть твой объект.
); } return (
Куда писать: это глобальный скрипт. Создай его в иерархии в категории Скрипты (кнопка «+»). Он не привязан ни к какому объекту и запускается один раз при старте игры.
); }; // ── Шаг инструкции ──────────────────────────────────────────────── export const Step = ({ n, children }) => (
{n}
{children}
); // ── Жёлтая плашка-подсказка ─────────────────────────────────────── export const Note = ({ children }) => (
{children}
); // ── Зелёная плашка «попробуй сам» ───────────────────────────────── export const Try = ({ children }) => (
Попробуй сам: {children}
); // ── Скриншот интерфейса с подписью ──────────────────────────────── // Версия вики-ассетов (cache-buster). Бампать при добавлении/замене картинок // в public/wiki/ — иначе у юзеров, открывавших статью ДО заливки файла, // браузер кэширует битый SPA-fallback (HTML вместо PNG) и Ctrl+Shift+R не // помогает (инцидент 2026-06-03 с guide-taxisim). Новый ?v=N = новый URL. export const WIKI_ASSET_V = 4; /** URL вики-картинки с версией для обхода кэша. */ export const wikiUrl = (name) => `/wiki/${name}?v=${WIKI_ASSET_V}`; // src — имя файла из public/wiki/, caption — подпись под картинкой. // wide — для широких скринов (обзор, лента): растянуть на всю ширину. export const Shot = ({ src, caption, wide }) => (
{caption {caption &&
{caption}
}
); // ══════════════════════════════════════════════════════════════════ // DOCS — разделы вики A-J // ══════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════ // AI_CONTEXT — полный справочник API скриптов Рублокса одним // текстом для вставки в нейросеть. Держать в синхроне с // ScriptSandboxWorker.js при добавлении новых game-API. // ════════════════════════════════════════════════════════════ const AI_CONTEXT = `Ты — помощник по написанию скриптов для онлайн-конструктора 3D-игр «Рублокс» (аналог Roblox, движок Babylon.js). Скрипты пишутся на JavaScript. Всё доступно через глобальный объект game. Ниже — полный API. Пиши ТОЛЬКО рабочий код, используя ТОЛЬКО эти методы. Не выдумывай команды. Координаты в метрах, повороты в РАДИАНАХ (90°=Math.PI/2). === ДВА ВИДА СКРИПТОВ === 1) Глобальный — в категории «Скрипты», запускается 1 раз при старте. Управляет всей сценой. 2) На объекте — висит на примитиве/модели/блоке. В нём доступен game.self — сам объект-носитель. Всегда указывай пользователю, КУДА писать скрипт. === ИГРОК game.player === .position -> {x,y,z}; .yaw, .pitch (рад); .forward -> {x,y,z} (куда смотрит); .hp, .maxHp, .alive .teleport(x,y,z); .damage(n); .kill(); .heal(n); .respawn() .setSpeed(mul); .setJumpPower(mul); .setGravityMul(mul); .setInputBlocked(bool) .giveTool(toolId, {equip:true}); .removeTool(id); .setSkin(slug); .setTeam(name) .isKeyDown('w'|'a'|'s'|'d'|'space'|'shift'...); .boostJump(strength); .setDoubleJump(bool) .setCameraMode('first'|'third'|'front'); .playEmote('wave'|'dance'|'cheer'|'sit') === ОБЪЕКТ-НОСИТЕЛЬ game.self (только в скрипте на объекте) === .ref ('primitive:N'); .position -> {x,y,z}; .kind .onTouch(fn) — игрок коснулся; .onUntouch(fn) — отошёл .onClick(fn) — клик мышью по объекту .onInteract(fn, {text:'Открыть', key:'e', distance:4, holdDuration:0}) — действие по клавише E .move(x,y,z); .rotateY(rad); .setColor('#hex'); .setVisible(bool); .setCollide(bool) .setLabel(text, {color,height}); .clearLabel(); .delete() === СЦЕНА game.scene === spawn(type, opts) -> объект. type: 'cube'|'sphere'|'cylinder'|'cone'|'pyramid'|'torus'|'wedge'|'plane' (примитивы), 'model:ID', 'block:ID', 'vehicle:car', 'light:point', 'billboard', 'trigger'. opts: {x,y,z, sx,sy,sz, rotationX,rotationY,rotationZ, color:'#hex', material:'matte'|'neon'|'metal'|'glass'|'studs', name, anchored:true(висит)/false(падает), canCollide, visible, mass, lifetime(сек до авто-удаления)} delete(ref); move(ref,x,y,z); setRotation(ref,rx,ry,rz); setColor(ref,'#hex'); setMaterial(ref,name); setVisible(ref,bool); setCollide(ref,bool); setOpacity(ref,0..1); setScale(ref,sx,sy,sz) setLabel(ref,text,opts); clearLabel(ref); setData(ref,key,val); getData(ref,key) find(name)->[...]; findOne(name)->объект|null; all('primitive'|'model'|'block') tag(ref,tag); untag(ref,tag); hasTag(ref,tag); getTagged(tag)->[refs] clone(ref,{dx,dy,dz}); spawnParticles('fire'|'smoke'|'sparks'|'magic'|'explosion'|'confetti', {x,y,z}, {duration,count,color}) setSkybox({preset:'clear-summer-day'|'sunset'|'starry-night'|'space'|'cloudy'}); setFog({color,density}); setClouds({enabled,cover,speed}) spawnNpc(modelType, {x,y,z,hp,name,speed}) -> npc. npc: .position, .hp, .follow('player'|ref), .moveTo(x,z), .stop(), .say(text,sec), .damage(n), .setSpeed(s), .onDeath(fn), .remove() === ОБЪЕКТ-ПРОКСИ (то, что вернул spawn/findOne) === obj.ref; obj.position; obj.color='#hex'; obj.material='neon'; obj.scale=2; obj.opacity=0.5; obj.visible=false; obj.canCollide=false; obj.position={x,y,z} obj.move(x,y,z); obj.rotateY(rad); obj.onTouch(fn); obj.onClick(fn); obj.onInteract(fn,opts); obj.clone({dx,dy,dz}); obj.destroy(); obj.tween(props,opts); obj.setLabel(t,opts); obj.addTag(t) === HUD game.ui === .score = N (счётчик в углу); .timer = N (таймер mm:ss); .showText('текст', сек) (крупно по центру) .set(id, text, {x,y,color,size}) (своя метка, x/y в %); .remove(id); .clear() === GUI (кнопки/панели на экране) game.gui === create('button'|'text'|'frame'|'image', {name,x,y,w,h,text,bg:'#hex',color,fontSize,borderRadius}) -> id onClick(id, fn); update(id, {text,...}); show(id); hide(id); remove(id); tween(id, props, {duration}) === ТАЙМЕРЫ И КАДРЫ === game.onTick(fn) — каждый кадр, fn(dt) dt=сек с прошлого кадра game.after(сек, fn) -> id — один раз через N сек game.every(сек, fn) -> id — повторять каждые N сек game.cancel(id) — отменить таймер game.tween(ref, props, {duration, easing:'linear'|'ease'|'bounce'|'elastic', yoyo, repeat:-1, onDone}) — анимация свойств (props: x,y,z, rotationX/Y/Z, sx/sy/sz, color, opacity) === СОБЫТИЯ === game.onKey('w', fn); game.onKeyUp('w', fn); game.onClick(fn) (fn({point,target})) game.onPlayerDied(fn); game.onHpChange(fn); game.onPlayerJump(fn); game.onMobKilled(fn) game.broadcast(name, data) — послать сообщение всем скриптам game.onMessage(name, fn) — принять сообщение (fn(data)) === ФИЗИКА game.physics === raycast(origin, dir, {maxDistance}) -> {hit, ref, point, distance} (синхронно, для стрельбы) applyImpulse(ref, {x,y,z}); setVelocity(ref, {x,y,z}); explode({x,y,z}, radius, {damage}); passThrough(refОrTag, bool) === ЭФФЕКТЫ game.fx === damageFloater(posОrRef, value, {isCrit,isHeal,isMiss,color}) — всплывающая цифра урона autoMobFloaters(true) — авто-цифры над всеми мобами beam({from,to,color,width}) -> луч; pointer({from,to,preset:'quest'}); trail(ref,{color,width}) === ЗВУК game.sound === play('coin'|'jump'|'win'|'lose'|'click'|'hit'|'pickup' | 'sound_N', {volume,pitch,at:{x,y,z},loop}) -> {stop()} === КАМЕРА game.camera === shake(amp, sec); setFov(deg); focusOn(ref,{distance,height}); cutscene([{x,y,z}...], {lookAt,segDuration}); reset() === ОКРУЖЕНИЕ game.environment === setSkyColor('#hex'); setFog({enabled,color,density}); setTimeOfDay(0..24) === ИНВЕНТАРЬ / ПРЕДМЕТЫ === game.items.define([{id,name,emoji,rarity:'common'|'rare'|'epic'|'legendary',maxStack,onUseEffect:'heal:50'}]) game.inventory.give(id,count); .take(id,count); .has(id); .open(); .list() === ЛИДЕРБОРД И ДОСТИЖЕНИЯ === game.leaderstats.define('Монеты', {initial:0}); game.leaderstats.me.add('Монеты', 1); .me.set(name,val); .me.get(name) game.achievements.define([{id,name,description,icon,rarity}]); game.achievements.unlock(id) === СОХРАНЕНИЕ (между сессиями) game.save === .merge(namespace, {patch:{поле:знач}, increment:{счётчик:дельта}, max:{рекорд:знач}}) .get(namespace, fn) (fn(data|null)); .increment(ns,key,delta); .leaderboard(ns,key,'desc',fn) === МОДАЛКИ game.modal === .dialog(npcName, [строки], onDone); .confirmation(title, body, onYes, onNo); .lootbox([{name,color,icon,rarity}], onPick); .close() === УТИЛИТЫ === game.log(...) — в консоль; game.random(min,max,целое?); game.clamp(v,min,max); game.lerp(a,b,t); game.distance(a,b) (точки или ref); game.format.time(сек,'mm:ss'); game.format.number(n,'short') === ВАЖНЫЕ ПРАВИЛА === - Повороты в РАДИАНАХ (Math.PI/2 = 90°). - Счётчики/общую логику держи в ОДНОМ глобальном скрипте; объекты шлют game.broadcast (скрипты объектов не видят переменные друг друга). - spawn примитива: тип без префикса ('cube'), модели — с 'model:', машина — 'vehicle:car'. - material только: matte, neon, metal, glass, studs. - Для сбора предмета: game.self.onTouch(()=>{ game.broadcast('coin'); game.self.delete(); }). - Не используй require() и DOM/window — только game.* и обычный JS. ПРИМЕР (килблок-лава, скрипт НА объекте): game.self.onTouch(() => { game.player.kill(); game.ui.showText('Лава!', 2); }); ПРИМЕР (сбор монет, глобальный скрипт): let s = 0; game.ui.score = 0; game.onMessage('coin', () => { s++; game.ui.score = s; game.sound.play('coin'); }); Теперь напиши скрипт под мою задачу (она ниже). Укажи, КУДА его вставить (глобальный или на объект).`; export const DOCS = [ // ════════════════════════════════════════════════════ // РАЗДЕЛ A — ОСНОВЫ // ════════════════════════════════════════════════════ { id: 'basics', icon: 'rocket', title: 'Основы', summary: 'С чего начать: интерфейс редактора, инструменты, первая игра за 5 минут.', sections: [ { id: 'what-is-rublox', title: 'A1. Что такое Рублокс и редактор игр', body: ( <>

Рублокс — это платформа, где можно играть в 3D-игры и создавать свои собственные. Всё работает прямо в браузере: ничего скачивать и устанавливать не нужно.

Редактор игр (его ещё называют Studio) — это место, где ты строишь игру. Ты ставишь блоки и модели, рисуешь кнопки, пишешь скрипты — а потом нажимаешь «Играть» и сразу проверяешь, что получилось.

В Рублоксе можно сделать почти любую игру:

  • паркур и платформеры — прыгай по платформам;
  • гонки — мчись к финишу на время;
  • стрелялки и арены — сражайся с врагами;
  • головоломки и квесты — решай загадки;
  • выживалки и целые RPG с героями и заданиями.

Как устроена эта вика. Разделы A-C научат строить мир и интерфейс. Разделы D-G — писать скрипты (код, который оживляет игру). Раздел H — справочник всех команд. Раздел I — словарик непонятных слов. Раздел J — что делать, если что-то сломалось. Раздел K — 50 готовых игр-уроков.

Не нужно читать всё подряд. Пройди раздел «Основы», а дальше открывай то, что нужно прямо сейчас. ), }, { id: 'editor-interface', title: 'A2. Интерфейс редактора', body: ( <>

Когда ты открываешь игру в редакторе, экран делится на части. Разберём каждую:

  • 1 — Шапка сверху — название игры и кнопки: Настройки, Сохранить, Играть, Опубликовать.
  • 2 — Лента инструментов под шапкой — вкладки Главная / Модель / Игра / Вид. На каждой вкладке свои кнопки и инструменты.
  • 3 — 3D-сцена (вьюпорт) — твой игровой мир в центре экрана. Тут ты всё и строишь.
  • 4 — Правая панель — сверху Иерархия (список всех объектов), снизу Инспектор (свойства того объекта, который ты выделил мышкой).

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

Как двигать камеру в редакторе:

Правая кнопка мыши + движениеосмотреться по сторонам
WASDлететь вперёд / влево / назад / вправо
Колесо мышиприблизить / отдалить
Камера редактора и камера игрока — это разные камеры. То, как ты летаешь по сцене сейчас, не влияет на то, как будет видеть мир игрок. ), }, { id: 'first-game-5min', title: 'A3. Первая игра за 5 минут', body: ( <>

Соберём самую простую игру — площадку, по которой можно ходить. Делай по шагам.

Открой редактор и создай новую игру (или выбери пустой шаблон). На вкладке Главная выбери инструмент Блок. В левой палитре кликни на блок травы — он станет выбранным. Кликай по сцене — блоки будут вставать один за другим. Собери небольшую площадку примерно 6×6 блоков. Перейди на вкладку Игра и выбери Ставить спавн. Кликни на площадку — там появится точка, где игрок начнёт игру. Нажми Играть в шапке. Ты окажешься на своей площадке и сможешь по ней ходить! Нажми Esc, чтобы вернуться в редактор, и Сохранить.

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

добавь по краям площадки стены из блоков, чтобы нельзя было упасть. И поставь в центре несколько блоков-ступенек. ), }, { id: 'creation-tools', title: 'A4. Инструменты: блок, примитив, модель, ландшафт', body: ( <>

На вкладке Главная есть инструменты создания:

  • Блок — ставит кубический блок (трава, камень, дерево...). Блоки ровно встают по сетке — из них удобно строить дома и стены, как из кубиков Лего.
  • Примитив — простая фигура: куб, сфера, цилиндр, конус, плоскость, тор, клин. У примитива можно свободно менять размер по каждой оси и красить в любой цвет.
  • Модель — готовая красивая 3D-модель из библиотеки (дерево, бочка, машина, оружие). Можно загружать и свои модели в формате .glb.
  • Ландшафт — инструмент лепки рельефа: холмы, горы, пещеры. Об этом — раздел B4.
  • Стереть — удаляет блок или объект под курсором.

Когда выбираешь «Блок» или «Примитив», слева открывается палитра — выбери в ней фигуру, а потом кликай по сцене, чтобы её поставить.

Шаг привязки (1.0 / 0.5 / 0.25 / Выкл) — задаёт, насколько мелко объект «прилипает» к сетке, когда ты его двигаешь. Шаг 1.0 — объект двигается крупными шагами, ровно по клеткам. Маленький шаг 0.25 — точнее, но дольше.

Чем отличаются блок и примитив-куб? Блок всегда одного размера и быстро ставится сеткой — он для стройки. Примитив-куб можно растянуть в длинную платформу или тонкую стенку — он для геймплея. ), }, { id: 'gizmo', title: 'A5. Гизмо-манипуляторы: двигать, вращать, масштаб', body: ( <>

Гизмо — это цветные стрелки и кольца, которые появляются на выделенном объекте. Они помогают точно его двигать, поворачивать и менять размер.

Режим гизмо выбирается в ленте инструментов — группа «Манипуляторы»:

Выделитьобычный режим, клик выбирает объект
Двигатьтри стрелки X / Y / Z — тяни, объект едет по оси
Вращатьтри кольца — тяни, объект поворачивается
Масштабкубики на осях — тяни, объект растягивается

Вот как выглядит гизмо «Двигать» на выделенном кубе — три цветные стрелки:

Оси всегда одни и те же и покрашены одинаково:

  • X (красная) — влево / вправо;
  • Y (зелёная) — вверх / вниз;
  • Z (синяя) — вперёд / назад.
Эти же буквы X, Y, Z ты увидишь в скриптах. Когда команда пишет {`{ x: 5, y: 2, z: 0 }`} — это точка в мире: 5 вправо, 2 вверх, 0 вперёд. ), }, { id: 'hierarchy', title: 'A6. Иерархия объектов и папки', body: ( <>

Иерархия — это список всех объектов твоей игры в правой панели. Когда сцена большая, найти нужный куб мышкой трудно — а в списке он всегда под рукой.

Объекты сгруппированы по категориям:

  • Сцена — точка спавна, окружение, свет;
  • Игрок — скин персонажа;
  • Интерфейс — GUI-элементы (кнопки, надписи);
  • Скрипты — твой код.

Имя объекта. У каждого объекта есть имя — его видно в иерархии и можно изменить в инспекторе. Имена очень важны для скриптов: команда game.scene.findOne('Дверь') находит объект по имени. Давай объектам понятные имена: «Дверь», «Монетка1», «Босс».

Папки. Объекты можно складывать в свои папки — например, папка «Уровень 1», папка «Враги». Это как наводить порядок в шкафу: всё на своих полках. Двойной клик по объекту в списке — камера прилетит прямо к нему.

), }, { id: 'hotkeys', title: 'A7. Горячие клавиши', body: ( <>

Горячие клавиши экономят кучу времени:

Ctrl+SСохранить игру
Ctrl+ZОтменить последнее действие
Ctrl+DДублировать выделенный объект
DelУдалить выделенное
RПовернуть объект на 90°
EscСнять выделение / выйти из режима
FНавести камеру на выделенное
Самая полезная привычка — почаще жать Ctrl+S. Сохраняться нужно не «когда закончил», а каждые пару минут. ), }, { id: 'play-mode', title: 'A8. Режим игры: HP, смерть, респаун', body: ( <>

Кнопка Запустить в правой части ленты запускает игру. Сцена «оживает»: включается физика, начинают работать скрипты, появляется HUD (счётчики поверх экрана). Чтобы остановить игру — кнопка Стоп или клавиша Esc.

Управление в игре:

WASDИдти
SpaceПрыжок
ShiftБег
МышьПоворот камеры
ЛКМАтака / стрельба
EВзаимодействовать
15Слот инвентаря
C1-е / 3-е лицо

HP (здоровье) игрока видно в левом верхнем углу. Когда игрок получает урон, полоска краснеет. Если HP падает до нуля — игрок «умирает» и через пару секунд воскресает (респаун) на точке спавна с полным здоровьем.

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

), }, { id: 'save-publish', title: 'A9. Сохранение и публикация', body: ( <>

Сохранение — кнопка Сохранить или Ctrl+S. Игра автоматически сохраняется и сама время от времени, но лучше не лениться и сохранять руками.

Публикация — когда игра готова, нажми Опубликовать. Выбери возрастной рейтинг (6+, 12+, 16+, 18+) и куда отправить:

  • В главную ленту — игру увидят все ученики. Модерация строже.
  • Только в профиле — игра доступна по ссылке и в твоём профиле, но в общей ленте её не будет.

После отправки игру проверит модератор (обычно за 24-48 часов). Игра не должна содержать матов, рекламы, чужого контента (модели из Roblox/Minecraft) и жестокости не по возрасту.

), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ B — ОБЪЕКТЫ СЦЕНЫ // ════════════════════════════════════════════════════ { id: 'objects', icon: 'cube', title: 'Объекты сцены', summary: 'Блоки, примитивы, модели, ландшафт, свет, частицы и свойства объектов.', sections: [ { id: 'blocks', title: 'B1. Блоки и их типы', body: ( <>

Блок — это кубик одного размера. Блоки ровно встают по сетке, и из них удобно строить — как из кубиков Лего. В палитре слева есть разные типы: трава, камень, песок, дерево, кирпич, цветные блоки, блоки со снегом.

Чтобы поставить блок — выбери инструмент Блок, кликни нужный тип в палитре и кликай по сцене. Чтобы убрать — инструмент Стереть.

У блока в инспекторе можно включить/выключить столкновение (твёрдый он или сквозь него можно пройти) и видимость.

Невидимый блок с включённым столкновением — это уже готовый «невидимый барьер». Игрок в него упрётся, но не увидит. Так делают границы уровня. ), }, { id: 'primitives', title: 'B2. Примитивы', body: ( <>

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

Кубстены, платформы, ящики
Сферамячи, монетки, планеты
Цилиндрколонны, бочки, трубы
Конусшипы, ёлки, шляпы
Плоскостьтонкий лист — пол, экран
Торкольцо (как бублик)
Клиннаклонная фигура — пандус

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

Чтобы в скрипте было легко найти примитив — дай ему понятное имя в инспекторе. Например, «Платформа1». Потом скрипт найдёт её командой game.scene.findOne('Платформа1'). ), }, { id: 'models', title: 'B3. Готовые модели и импорт своих', body: ( <>

Модель — готовая красивая 3D-фигура: дерево, камень, бочка, машина, оружие, мебель. Их не нужно строить из кубиков — просто бери из библиотеки и ставь на сцену.

Инструмент Модель открывает каталог. Модели разбиты по категориям (природа, город, оружие...).

Свои модели. Можно загрузить собственную 3D-модель в формате .glb. Такие файлы делают в бесплатных редакторах вроде Blender или берут на сайтах с бесплатными моделями. После загрузки твоя модель появится в палитре рядом с остальными.

Не бери модели из других игр (Roblox, Minecraft) — это чужой контент, игру с ним не пропустит модерация. Бери только бесплатные модели «для свободного использования». ), }, { id: 'terrain', title: 'B4. Ландшафт: воксельный и гладкий', body: ( <>

Ландшафт — это рельеф мира: холмы, горы, ямы, пещеры. В Рублоксе два режима:

  • Воксельный — рельеф из маленьких кубиков-вокселей. Получается «ступенчатый», как в Minecraft. Удобно рисовать пещеры и обрывы.
  • Гладкий — рельеф из плавных холмов без ступенек. Похоже на настоящую землю.

Инструменты лепки: поднять, опустить, разгладить, покрасить. Есть и кисти-растения — рисуешь по земле, и сразу вырастают деревья и трава.

Если в игре есть гладкий ландшафт, ставь точку спавна прямо на его поверхности. Если спавн окажется ниже земли — игрок при старте провалится. Высоту земли в скрипте можно узнать командой game.scene.surfaceY(x, z). ), }, { id: 'spawn-checkpoints', title: 'B5. Точка спавна и чекпоинты', body: ( <>

Точка спавна — место, где игрок появляется в начале игры и куда возвращается после смерти. Ставится на вкладке Игра → Ставить спавн. Точка спавна одна.

Чекпоинт (контрольная точка) — промежуточное место сохранения в длинных уровнях. Когда игрок дошёл до чекпоинта, при следующей смерти он воскреснет уже там, а не в самом начале.

Чекпоинт делают скриптом на флажке-объекте: когда игрок касается флажка, скрипт запоминает это место как новый спавн.

{`// Когда игрок коснётся флажка — // это место станет новой точкой возрождения. game.self.onTouch(() => { // game.self.position — координаты самого флажка game.player.setSpawn(game.self.position); game.ui.showText('Чекпоинт сохранён!', 1.5); game.sound.play('pickup'); });`}

Что тут происходит: onTouch срабатывает, когда игрок дотронулся до флажка. setSpawn запоминает точку возрождения. showText показывает надпись на 1.5 секунды. Готово — игрок не начнёт уровень заново.

), }, { id: 'lamps', title: 'B6. Лампы (источники света)', body: ( <>

Лампа — это источник света. Кроме общего солнца, можно поставить точечные лампы, которые освещают всё рядом с собой. Они нужны для пещер, ночных уровней, подсветки важных мест.

У лампы настраиваются цвет, яркость и радиус (как далеко достаёт свет). Лампу можно создать и из скрипта:

{`// Создаём тёплую лампу над сценой game.scene.spawn('light:point', { x: 0, y: 4, z: 0, // где висит лампа color: '#ffdd88', // тёплый жёлтый свет brightness: 2, // яркость range: 12 // радиус освещения });`} ), }, { id: 'particles', title: 'B7. Эмиттеры частиц', body: ( <>

Частицы — это много маленьких летящих точек: искры, дым, огонь, магия. Объект, который их создаёт, называется эмиттер.

Частицы делают игру живой: костёр дымит, при победе летит конфетти, у портала кружится магия. Из скрипта это команда game.scene.spawnParticles:

{`// Залп конфетти над центром сцены game.scene.spawnParticles( 'confetti', // тип эффекта { x: 0, y: 3, z: 0 }, // где появятся частицы { duration: 2, count: 3 } // длительность и густота );`}

Типы эффектов: fire (огонь), smoke (дым), sparks (искры), magic (магия), explosion (взрыв), confetti (конфетти).

), }, { id: 'triggers', title: 'B8. Триггеры', body: ( <>

Триггер — невидимая зона, которая что-то запускает, когда игрок в неё входит. Например: игрок зашёл в зону — открылась дверь, заиграла музыка, появился враг.

Как сделать триггер:

Поставь примитив-куб нужного размера — это и будет зона. В инспекторе выключи столкновение (чтобы игрок проходил сквозь) и можно выключить видимость (чтобы зону не было видно). Повесь на этот куб скрипт, который ловит касание игрока: {`// Игрок вошёл в зону — показываем надпись game.self.onTouch(() => { game.ui.showText('Ты вошёл в опасную зону!', 2); }); // Игрок вышел из зоны game.self.onUntouch(() => { game.ui.showText('Ты в безопасности', 1.5); });`} ), }, { id: 'object-properties', title: 'B9. Свойства объекта: цвет, материал, физика, замок', body: ( <>

Когда ты выделяешь объект, в Инспекторе (правая панель снизу) появляются его свойства:

Цветзакрасить примитив в любой цвет
Материалобычный, металл, стекло, неон — как объект блестит
Текстураналожить свою картинку на поверхность
Столкновениетвёрдый объект или сквозь него можно пройти
Видимостьпоказать или спрятать объект
Закреплёнесли выключить — объект падает под действием физики
Замок (Lock)заблокировать, чтобы случайно не сдвинуть
Свойство «Закреплён» — частая причина бага «объект провалился сквозь пол». Для платформ, стен и декораций всегда оставляй «Закреплён» включённым. Падать должны только ящики и мячи, которым это нужно. ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ C — ИНТЕРФЕЙС ИГРЫ (GUI) // ════════════════════════════════════════════════════ { id: 'gui', icon: 'window', title: 'Интерфейс игры', summary: 'GUI: кнопки, надписи, поля ввода, меню и счётчики поверх экрана.', sections: [ { id: 'gui-what-is', title: 'C1. Что такое GUI и редактор интерфейса', body: ( <>

GUI (читается «гуи») — это интерфейс игры: всё, что нарисовано поверх 3D-сцены. Кнопки, надписи, счётчик очков, полоска здоровья, меню — это всё GUI.

GUI не находится внутри игрового мира — он «приклеен» к экрану. Куда бы ни смотрел игрок, кнопка остаётся на том же месте экрана.

В редакторе есть визуальный редактор UI: ты перетаскиваешь элементы мышкой, ставишь их в нужное место, меняешь цвет и текст — и сразу видишь результат. Открыть его можно через инструмент Интерфейс.

Не путай GUI и HUD. GUI — это элементы, которые ты сам нарисовал в редакторе интерфейса. HUD — стандартные счётчики (game.ui.score, game.ui.timer), которые рисует сама игра по команде из скрипта. ), }, { id: 'gui-elements', title: 'C2. Контейнер, надпись, кнопка, поле ввода, картинка', body: ( <>

Из чего собирается интерфейс:

  • Контейнер (Frame) — прямоугольник-коробка. Сам по себе это просто фон, но внутрь него кладут другие элементы. Контейнер — основа любого меню.
  • Надпись (Label) — текст на экране. Счёт, имя игрока, подсказки.
  • Кнопка (Button) — на неё можно нажать. По клику в скрипте срабатывает действие.
  • Поле ввода (TextBox) — сюда игрок печатает текст или число. Например, ввести код от двери.
  • Картинка (Image) — изображение: иконка, логотип, фон меню.

Имя элемента. Как и у объектов сцены, у GUI-элемента есть имя. Скрипт находит элемент по имени командой game.gui.find('Кнопка старта'). Давай кнопкам и надписям понятные имена.

), }, { id: 'gui-script', title: 'C3. Как оживить кнопку скриптом', body: ( <>

Нарисованная кнопка сама по себе ничего не делает — нужен скрипт.

{`// Скрипт висит на кнопке. // game.self — это сама кнопка. game.self.onClick(() => { game.ui.showText('Кнопка нажата!', 2); game.sound.play('click'); });`}
} lua={{`-- Скрипт висит на кнопке (TextButton) -- script.Parent — это сама кнопка. local btn = script.Parent btn.MouseButton1Click:Connect(function() print("Кнопка нажата!") end)`}} />

Можно и наоборот — управлять кнопкой из глобального скрипта:

{`// Находим кнопку по имени и вешаем на неё клик const btnId = game.gui.find('Кнопка старта'); game.gui.onClick(btnId, () => { game.ui.showText('Игра началась!', 2); game.gui.hide(btnId); // спрятать кнопку после нажатия });`}} lua={{`local Players = game:GetService("Players") local player = Players.LocalPlayer local gui = player:WaitForChild("PlayerGui") -- Находим кнопку по имени (она лежит где-то в PlayerGui) local btn = gui:FindFirstChild("Кнопка старта", true) btn.MouseButton1Click:Connect(function() print("Игра началась!") btn.Visible = false -- спрятать кнопку end)`}} />

JS: game.gui.find ищет элемент по имени. game.gui.onClick вешает действие, game.gui.hide прячет.

Lua: gui:FindFirstChild(name, true) ищет рекурсивно (третий аргумент true = во вложенных). MouseButton1Click — стандартный сигнал клика на TextButton. btn.Visible = false прячет элемент.

), }, { id: 'gui-textbox', title: 'C4. Поле ввода: дверь по коду', body: ( <>

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

{`// Игрок вводит код. Правильный код — 1234. const boxId = game.gui.find('Поле кода'); game.gui.onSubmit(boxId, (text) => { if (text === '1234') { game.ui.showText('Верно! Дверь открыта', 2); const door = game.scene.findOne('Дверь'); game.tween(door, { y: 8 }, { duration: 1 }); } else { game.ui.showText('Неверный код', 1.5); } });`}} lua={{`local Players = game:GetService("Players") local TweenService = game:GetService("TweenService") local player = Players.LocalPlayer local gui = player:WaitForChild("PlayerGui") -- Находим TextBox по имени local box = gui:FindFirstChild("Поле кода", true) -- FocusLost срабатывает когда игрок нажал Enter или ушёл с поля. -- Первый параметр enterPressed = true только если был Enter. box.FocusLost:Connect(function(enterPressed) if not enterPressed then return end if box.Text == "1234" then print("Верно! Дверь открыта") local door = workspace:WaitForChild("Дверь") local goal = { Position = door.Position + Vector3.new(0, 8, 0) } TweenService:Create(door, TweenInfo.new(1), goal):Play() else print("Неверный код") end end)`}} />

JS-разбор: onSubmit даёт переменную text — то, что напечатал игрок. if (text === '1234') — проверяем код.

Lua-разбор: на TextBox сигнал FocusLost срабатывает когда поле теряет фокус (Enter или клик мимо). Текст лежит в box.Text.

Кавычки "1234" означают, что это текст, а не число. Игрок печатает в поле всегда текст, поэтому и сравнивать нужно с текстом. ), }, { id: 'gui-styles', title: 'C5. Стили и загрузка картинок', body: ( <>

У каждого GUI-элемента в инспекторе настраивается внешний вид: цвет фона и прозрачность, граница (рамка, её цвет и толщина), скругление углов (большое скругление делает кнопку «таблеткой»), тень (мягкая тень под элементом), цвет и размер текста.

В элемент Картинка можно загрузить своё изображение с компьютера (PNG или JPG): логотип игры, иконку кнопки, фон главного меню.

Хороший интерфейс — это аккуратный интерфейс. Один стиль для всех кнопок, один шрифт, выровненные отступы. Картинки бери небольшие — огромные файлы делают игру тяжёлой. ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ D — СКРИПТЫ — ОСНОВЫ // ════════════════════════════════════════════════════ { id: 'scripts-basics', icon: 'code', title: 'Скрипты — основы', summary: 'Самый важный раздел: что такое скрипт, переменные, события, таймеры.', sections: [ { id: 'js-or-lua', title: 'D0. Скриптинг: JS или Lua — что выбрать?', body: ( <>

В Рублоксе можно писать скрипты на двух языках: JavaScript и Lua. Оба работают одинаково хорошо. Игра не отличает их между собой — внутри одного проекта одни скрипты могут быть на JS, другие на Lua, и они общаются между собой как будто это один язык.

Чем они отличаются?

JavaScriptLua
Где ещё используется Сайты, мобильные приложения, серверы. Самый популярный язык в мире. Roblox, World of Warcraft (моды), многие игры. Простой и быстрый.
Главный API game.* (game.player, game.log, game.scene) game.* в Roblox-стиле (game:GetService("Players"), workspace)
Похож на Roblox-LUA если знаешь Roblox Roblox-Studio — те же команды
Когда выбрать Если планируешь делать сайты и приложения — JS пригодится везде. Если играешь в Roblox и видел там скрипты — Lua тебе знаком.

Один и тот же пример на двух языках:

Когда игрок касается синего блока — печатаем «Привет».

{`// JS — глобальный скрипт game.onTouch('синий-блок', (player) => { game.log('Привет, ' + player.name + '!'); });`}} lua={{`-- Lua — скрипт на самом блоке local part = script.Parent part.Touched:Connect(function(hit) local player = game.Players:GetPlayerFromCharacter(hit.Parent) if player then print("Привет, " .. player.Name .. "!") end end)`}} />

Видишь — оба варианта делают одно и то же. Но запись отличается. JS короче для простых вещей через game.onTouch, Lua даёт точный Roblox-стиль через :Connect и события.

Что выбирать новичку?

Если ты совсем новичок — бери JavaScript. Команды game.* в JS короче и проще читать. Большая часть уроков в этой вике написана с примерами на JS — все они работают, просто копируй. Если ты уже играл в Roblox и видел там скрипты — бери Lua. Команды почти один в один как в Roblox Studio: game:GetService, :Connect, workspace, script.Parent.

Можно ли менять язык в одном скрипте?

Да. В редакторе скрипта вверху есть две кнопки — JS и Lua. Просто нажми на нужную. Твой код на текущем языке сохранится, а на другом языке откроется пустой шаблон или то, что ты писал там раньше. Никогда ничего не теряется.

Создай новый скрипт. Напиши пару строк на JS. Нажми кнопку Lua — JS-код спрячется, появится Lua-шаблон. Напиши там что-то. Нажми JS обратно — твой JS-код вернётся. Магия!

А что под капотом?

JS-скрипты исполняются в WebWorker — это отдельный поток в браузере, чтобы скрипт не тормозил саму игру. Lua-скрипты исполняются через wasmoon — Lua-интерпретатор, скомпилированный в WebAssembly. Оба варианта работают на любом устройстве без установки.

), }, { id: 'what-is-script', title: 'D1. Что такое скрипт и как его создать', body: ( <>

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

Скрипты пишут на одном из двух языков: JavaScript или Lua. Оба работают одинаково. Подробнее про выбор языка — статья D0 выше. В этом уроке покажем пример на обоих языках — переключай вкладки, чтобы видеть нужный.

Как создать первый скрипт:

В иерархии (правая панель) найди категорию Скрипты и нажми кнопку «+». Откроется окно кода. Вверху выбери язык (кнопки JS/Lua) и напиши одну строку: {`game.log('Привет! Игра запустилась.');`}} lua={{`print("Привет! Игра запустилась.")`}} /> Нажми Играть. Внизу справа открой Консоль — там появится твоё сообщение.

Это твой первый работающий скрипт.

Команда game.log(...) печатает в консоль. Каждая команда в JavaScript заканчивается точкой с запятой ; — как точка в предложении. Текст пишут в кавычках: 'привет'. } lua={ Команда print(...) печатает в консоль. В Lua точку с запятой ставить не нужно. Текст пишут в кавычках: "привет" или 'привет'. Lua использует .. (две точки) для склейки текста: "А=" .. 5. } /> ), }, { id: 'global-vs-object', title: 'D2. Глобальный скрипт и скрипт на объекте', body: ( <>

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

Глобальный скрипт — это «мозг игры». Он не привязан ни к чему. Запускается один раз при старте. В нём пишут общие правила: подсчёт очков, таймер уровня, проверку победы.

Скрипт на объекте относится к конкретному кубу, модели или кнопке. Внутри такого скрипта работает волшебное слово (своё для каждого языка):

{`// JS: game.self — это и есть тот объект, на котором висит скрипт game.self.onClick(() => { game.log('Кликнули по мне!'); });`} } lua={<> {`-- Lua: script.Parent — это и есть тот объект, на котором висит скрипт local part = script.Parent part.Touched:Connect(function(hit) print("Касание объекта " .. part.Name) end)`} } />

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

Простое правило: если в коде урока есть game.self — это скрипт на объекте. Если game.self нет — скрипт глобальный. Плашка в начале каждого урока всегда подскажет. } lua={ Простое правило: если в коде есть script.Parent — это скрипт на объекте. Если только game — глобальный. В Lua глобальные скрипты обычно работают со списком игроков: game:GetService("Players"). } /> ), }, { id: 'variables', title: 'D3. Переменные — память скрипта', body: ( <>

Переменная — это «коробочка с именем», в которой скрипт хранит значение. Например, количество очков, имя игрока, выбранный уровень.

{`// JS: создаём переменную через let let score = 0; // Меняем значение score = score + 10; // теперь в score лежит 10 score = score + 5; // теперь 15 game.log('Очков:', score); // напечатает: Очков: 15`}} lua={{`-- Lua: создаём переменную через local local score = 0 -- Меняем значение score = score + 10 -- теперь в score лежит 10 score = score + 5 -- теперь 15 print("Очков:", score) -- напечатает: Очков: 15`}} /> let — это слово «создать переменную». Пишут его только один раз, когда коробочку заводят. Дальше меняют значение уже без let.

} lua={

local — это слово «создать переменную внутри скрипта». Пишут его только один раз. Если опустить local — переменная станет глобальной (доступна всем скриптам), это редко нужно.

} />

В переменную можно класть не только числа:

{`let name = 'Герой'; // текст — в кавычках let isWin = false; // да/нет — true или false let coinCount = 0; // число — без кавычек`}} lua={{`local name = "Герой" -- текст — в кавычках local isWin = false -- да/нет — true или false local coinCount = 0 -- число — без кавычек`}} /> Если значение никогда не меняется — вместо let можно писать const («постоянная»). Например, найденную один раз дверь: const door = game.scene.findOne('Дверь'); } lua={ В Lua нет отдельного слова для «не-меняющейся» переменной — всё через local. Если хочешь явно показать что значение не меняется, пиши имя ЗАГЛАВНЫМИ: local DOOR = workspace.Дверь. Это договорённость, а не правило Lua. } /> ), }, { id: 'game-object', title: 'D4. Объект game — главный инструмент', body: ( <>

В каждом скрипте есть одно главное волшебное слово — game. Через него ты управляешь всей игрой. Но «отделы» у JS и Lua разные:

game.playerуправление игроком
game.sceneобъекты сцены
game.uiсчётчики и текст на экране
game.guiкнопки и меню
game.soundзвуки
game.physicsлучи, импульсы, взрывы
game.selfобъект-носитель скрипта

Запись через точку читается слева направо. game.player.teleport(0, 5, 0) читается так: «у игры, у игрока, выполни телепорт в точку 0, 5, 0».

} lua={<>
workspace3D-объекты сцены
game:GetService("Players")список игроков
game.Workspaceто же что workspace
script.Parentобъект-носитель скрипта
game:GetService("RunService")каждый кадр (Heartbeat)
game:GetService("UserInputService")клавиши и мышь
game:GetService("TweenService")плавные движения

Знак : (двоеточие) в Lua — это вызов метода объекта. game:GetService("Players") читается так: «у game вызови GetService и дай ему текст Players».

Точка . — это доступ к полю объекта. workspace.Floor.BrickColor — у workspace взять Floor, у него взять BrickColor.

} />

Полный список всех команд — в Справочнике (раздел H). Не нужно его заучивать: при наборе кода редактор сам показывает подсказки.

), }, { id: 'log-console', title: 'D5. log/print, консоль, отладка', body: ( <>

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

Команда game.log(...) печатает в консоль что угодно. Это главный инструмент отладки — проверки, что код работает правильно:

} lua={

Команда print(...) печатает в консоль что угодно. Это главный инструмент отладки — проверки, что код работает правильно:

} /> {`let score = 0; score = score + 10; game.log('Очки сейчас:', score); // Очки сейчас: 10 let pos = game.player.position; game.log('Игрок стоит в точке:', pos);`}} lua={{`local score = 0 score = score + 10 print("Очки сейчас:", score) -- Очки сейчас: 10 local pos = game.Players.LocalPlayer.Character.HumanoidRootPart.Position print("Игрок стоит в точке:", pos)`}} /> Если игра ведёт себя странно — расставь game.log по коду и посмотри, какие значения печатаются. Так ты увидишь, где именно что-то пошло не так.

} lua={

Если игра ведёт себя странно — расставь print по коду и посмотри, какие значения печатаются. Так ты увидишь, где именно что-то пошло не так.

} /> Если в скрипте опечатка — текст ошибки появится в Консоли красным, и там же будет написан номер строки с ошибкой. Всегда заглядывай в Консоль первым делом. ), }, { id: 'events', title: 'D6. События: тик, клавиши, клик, касание', body: ( <>

Событие — это «что-то случилось». Скрипт может ждать событие и реагировать на него. Самые важные:

game.onTick(fn)каждый кадр (60 раз в секунду) game.onKey('space', fn)игрок нажал клавишу game.self.onClick(fn)игрок кликнул по объекту game.self.onTouch(fn)игрок коснулся объекта } lua={
RunService.Heartbeat:Connect(fn)каждый кадр
UserInputService.InputBegan:Connect(fn)любая клавиша
part.ClickDetector.MouseClick:Connect(fn)клик по объекту
part.Touched:Connect(fn)касание объекта
} />

Пример — куб, который исчезает по клику:

{`game.self.onClick(() => { game.self.delete(); // удалить сам себя game.log('Куб удалён!'); });`}} lua={{`local part = script.Parent local clickDetector = Instance.new("ClickDetector") clickDetector.Parent = part clickDetector.MouseClick:Connect(function(player) part:Destroy() -- удалить сам себя print("Куб удалён!") end)`}} /> Что такое {`() => { ... }`}? Это «функция» — набор команд, упакованных вместе. Команды внутри фигурных скобок выполнятся не сразу, а только когда случится событие. То есть «когда кликнули — тогда удалить и напечатать».

} lua={

Что такое function() ... end? Это «функция» — набор команд, упакованных вместе. Команды между function() и end выполнятся не сразу, а только когда случится событие. То есть «когда кликнули — тогда удалить и напечатать». Метод :Connect «подключает» функцию к событию.

} /> onTick выполняется ОЧЕНЬ часто — 60 раз в секунду. Не делай внутри него тяжёлых вещей. Подробнее об этой ошибке — раздел J4. } lua={ Heartbeat выполняется ОЧЕНЬ часто — 60 раз в секунду. Не делай внутри тяжёлых вычислений. Подробнее об этой ошибке — раздел J4. } /> ), }, { id: 'conditions', title: 'D7. Условия: if / else', body: ( <>

Условие — это развилка: «если что-то верно — сделай одно, иначе — другое». В обоих языках это слова if («если») и else («иначе»), но запись чуть отличается.

{`let coins = 7; if (coins >= 10) { game.ui.showText('Хватает на покупку!', 2); } else { game.ui.showText('Нужно больше монет', 2); }`}} lua={{`local coins = 7 if coins >= 10 then print("Хватает на покупку!") else print("Нужно больше монет") end`}} />

Тут проверяется: coins {'>'}= 10 — «монет 10 или больше?». Сейчас монет 7, значит условие неверно, и сработает ветка else.

Знаки сравнения:

a === ba равно b a !== ba не равно b a {'>'} ba больше b a {'<'} ba меньше b a {'>'}= ba больше или равно b a {'<'}= ba меньше или равно b } lua={
a == ba равно b
a ~= ba не равно b
a {'>'} ba больше b
a {'<'} ba меньше b
a {'>'}= ba больше или равно b
a {'<'}= ba меньше или равно b
} /> В JS для проверки «равно» пишут три знака равенства ===, а не один. Один знак = — это «положить значение в переменную», совсем другое действие. И не равно — это !==. } lua={ В Lua для проверки «равно» пишут два знака равенства ==. А «не равно» — это ~= (тильда + равно). Запомни этот значок — он встречается только в Lua. } /> ), }, { id: 'timers', title: 'D8. Таймеры: задержка, повтор, отмена', body: ( <>

Таймеры запускают команды не сразу, а потом:

  • game.after(сек, fn) — выполнить один раз через несколько секунд;
  • game.every(сек, fn) — выполнять снова и снова каждые несколько секунд;
  • game.cancel(id) — остановить таймер.
  • } lua={
    • task.delay(сек, fn) — выполнить один раз через несколько секунд;
    • task.wait(сек) — приостановить скрипт на N секунд (внутри цикла или функции);
    • Для повторяющихся таймеров — обычный цикл while true do task.wait(1); ... end в отдельной корутине через task.spawn.
    } /> {`// Через 3 секунды показать текст game.after(3, () => { game.ui.showText('Игра началась!', 2); }); // Каждую секунду прибавлять очко. // every возвращает номер таймера — запомним его. const ticker = game.every(1, () => { game.ui.score = (game.ui.score || 0) + 1; }); // Через 10 секунд остановить начисление очков game.after(10, () => { game.cancel(ticker); game.ui.showText('Время вышло!', 2); });`}} lua={{`-- Через 3 секунды показать текст task.delay(3, function() print("Игра началась!") end) -- Каждую секунду прибавлять очко. -- Запускаем в отдельной корутине чтобы не блокировать скрипт. local score = 0 local running = true task.spawn(function() while running do task.wait(1) score = score + 1 end end) -- Через 10 секунд остановить начисление очков task.delay(10, function() running = false print("Время вышло! Набрано очков:", score) end)`}} /> Запись (game.ui.score || 0) читается так: «возьми счёт, а если его ещё нет — возьми 0». Это защита от ошибки в самом начале, когда счётчик ещё пустой.

    } lua={

    В Lua переменная running — флаг работы цикла. Когда нужно остановить таймер, ставим running = false, и цикл сам завершится после task.wait(1). Это проще, чем хранить номер таймера.

    } /> ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ E — СКРИПТЫ — ДВИЖЕНИЕ И АНИМАЦИЯ // ════════════════════════════════════════════════════ { id: 'scripts-motion', icon: 'run', title: 'Движение и анимация', summary: 'Управление игроком, плавные твины, спавн и перемещение объектов.', sections: [ { id: 'player-control', title: 'E1. Управление игроком: скорость, прыжок, гравитация', body: ( <>

    Скриптом можно менять, как двигается игрок.

    В JS используем команды-«множители»: 1 — обычно, 2 — в два раза сильнее, 0.5 — в два раза слабее.

    game.player.setSpeed(mul)скорость бега
    game.player.setJumpPower(mul)сила прыжка
    game.player.setGravityMul(mul)сила притяжения
    game.player.setDoubleJump(true)двойной прыжок
    game.player.teleport(x,y,z)мгновенно переставить
    } lua={<>

    В Lua скорость и прыжок — это прямые значения в Humanoid (не множители). По умолчанию WalkSpeed=16, JumpPower=50.

    humanoid.WalkSpeed = 32скорость (16 = норма)
    humanoid.JumpPower = 80сила прыжка (50 = норма)
    workspace.Gravity = 100гравитация (196 = норма)
    humanoid:ChangeState(Enum.HumanoidStateType.Jumping)прыгнуть
    hrp.CFrame = CFrame.new(x,y,z)телепорт
    } />

    Пример — «зелье скорости» при касании сферы:

    {`game.self.onTouch(() => { // ускоряем игрока в 2 раза game.player.setSpeed(2); game.ui.showText('Скорость x2 на 5 секунд!', 2); game.sound.play('pickup'); // зелье исчезает game.self.delete(); // через 5 секунд скорость снова обычная game.after(5, () => { game.player.setSpeed(1); }); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local humanoid = hit.Parent:FindFirstChild("Humanoid") if not humanoid then return end -- ускоряем игрока в 2 раза (16 → 32) humanoid.WalkSpeed = 32 print("Скорость x2 на 5 секунд!") -- зелье исчезает part:Destroy() -- через 5 секунд скорость обратно норма task.delay(5, function() humanoid.WalkSpeed = 16 end) end)`}} /> Не забывай возвращать скорость обратно. Иначе игрок останется быстрым навсегда — а это может сломать твой уровень. ), }, { id: 'player-animations', title: 'E2. Анимации-эмоции персонажа', body: ( <>

    Персонаж умеет показывать эмоции.

    Команда game.player.playAnimation(имя) проигрывает анимацию: 'wave' (помахать), 'dance' (танец), 'cheer' (радость), 'sit' (сесть).

    } lua={

    В Lua анимации проигрываются через Animator на Humanoid'е. Roblox-стиль: создать Animation-объект, вызвать Animator:LoadAnimation(anim), потом track:Play().

    } /> {`// При победе персонаж радуется game.player.playAnimation('cheer'); // Через 3 секунды перестать game.after(3, () => { game.player.stopAnimation(); });`}} lua={{`local Players = game:GetService("Players") local player = Players.LocalPlayer local humanoid = player.Character:WaitForChild("Humanoid") local animator = humanoid:FindFirstChildOfClass("Animator") -- Создаём анимацию (упрощённый шаблон — в реальности нужен AnimationId) local anim = Instance.new("Animation") -- anim.AnimationId = "rbxassetid://..." -- свой Animation ID local track = animator:LoadAnimation(anim) track:Play() -- Через 3 секунды перестать task.delay(3, function() track:Stop() end)`}} /> ), }, { id: 'tweens', title: 'E3. Твины — плавные движения', body: ( <>

    Твин — это плавное изменение чего-либо за время. Если просто переставить объект — он телепортируется рывком. А твин плавно доедет из точки в точку.

    Команда: game.tween(объект, что менять, настройки)

    {`// Находим платформу-лифт по имени const lift = game.scene.findOne('Лифт'); // Платформа за 2 секунды плавно поднимается на высоту 10 game.tween(lift, { y: 10 }, { duration: 2, // длительность в секундах easing: 'ease' // характер движения });`}

    Полезные настройки твина:

    durationсколько секунд длится
    easing'linear' / 'ease' / 'bounce'
    repeatсколько раз повторить
    yoyo: trueдвигаться туда-обратно
    onDoneчто сделать, когда твин закончится
    {`// Платформа вечно ездит вверх-вниз const plat = game.scene.findOne('Качалка'); game.tween(plat, { y: 8 }, { duration: 2, yoyo: true, repeat: 999 });`} } lua={<>

    В Lua используется TweenService — встроенный сервис Roblox-стиля. Создаёшь TweenInfo и Tween, вызываешь Play.

    {`local TweenService = game:GetService("TweenService") -- Находим платформу-лифт по имени local lift = workspace:WaitForChild("Лифт") -- Настройка анимации: 2 сек, плавно local info = TweenInfo.new(2, Enum.EasingStyle.Quad) -- Что менять: новая Position (поднимаем на 10 вверх) local goal = { Position = lift.Position + Vector3.new(0, 10, 0) } -- Создаём и запускаем твин local tween = TweenService:Create(lift, info, goal) tween:Play()`}

    Полезные настройки TweenInfo:

    1-й арг — секундысколько длится
    EasingStyleLinear / Quad / Bounce / Elastic
    EasingDirectionIn / Out / InOut
    repeatCountсколько раз повторить (-1 = бесконечно)
    reversestrue = туда-обратно
    tween.Completed:Connectсобытие «закончен»
    {`-- Платформа вечно ездит вверх-вниз local plat = workspace:WaitForChild("Качалка") local info = TweenInfo.new( 2, -- секунды Enum.EasingStyle.Quad, -- плавно Enum.EasingDirection.InOut, -1, -- бесконечно true -- туда-обратно ) local goal = { Position = plat.Position + Vector3.new(0, 8, 0) } TweenService:Create(plat, info, goal):Play()`} } /> ), }, { id: 'spawn-delete', title: 'E4. Спавн и удаление объектов', body: ( <>

    Спавн — создание нового объекта прямо во время игры.

    Команда game.scene.spawn(тип, настройки):

    {`// Создаём золотую монетку-сферу const coin = game.scene.spawn('primitive:sphere', { x: 5, y: 1, z: 0, // где появится color: '#ffd700' // золотой цвет }); game.log('Создали монетку, её адрес:', coin);`}

    Тип бывает 'block:трава', 'primitive:cube', 'model:tree'. Команда возвращает ref — «адрес» объекта, по которому к нему можно обращаться.

    Удаление объекта:

    {`game.scene.delete(coin); // сразу game.scene.deleteAfter(coin, 3); // через 3 секунды`} Запоминай ref в переменную. Без адреса ты потом не сможешь объект ни подвинуть, ни удалить. } lua={<>

    Команда Instance.new("Part") создаёт новый Part:

    {`-- Создаём золотую монетку-сферу local coin = Instance.new("Part") coin.Shape = Enum.PartType.Ball coin.Size = Vector3.new(1, 1, 1) coin.Position = Vector3.new(5, 1, 0) coin.BrickColor = BrickColor.new("Bright yellow") coin.Anchored = true coin.Parent = workspace print("Создали монетку:", coin)`}

    Чтобы объект появился в игре — обязательно ставь .Parent = workspace. Anchored = true — чтобы не падал.

    Удаление объекта:

    {`coin:Destroy() -- сразу -- через 3 секунды game:GetService("Debris"):AddItem(coin, 3)`} Сохраняй ссылку на объект в переменную (local coin = ...). Без неё ты потом не сможешь объект ни подвинуть, ни удалить. } /> ), }, { id: 'move-objects', title: 'E5. Перемещение объектов', body: ( <>

    Передвинуть объект скриптом можно несколькими способами:

    game.scene.move(ref,x,y,z)мгновенно переставить game.scene.rotate(ref,угол)повернуть game.self.move(x,y,z)скрипт двигает сам себя game.tween(...)плавное перемещение (E3) } lua={
    part.Position = Vector3.new(x,y,z)мгновенно переставить
    part.CFrame = part.CFrame * CFrame.Angles(0, math.rad(45), 0)повернуть
    script.Parent.Position = ...скрипт двигает сам себя
    TweenService:Create(...)плавное перемещение (E3)
    } />

    Пример — дверь уезжает вверх и освобождает проход:

    {`const door = game.scene.findOne('Дверь'); // плавно поднимаем дверь на 6 единиц вверх game.tween(door, { y: 6 }, { duration: 1 });`}} lua={{`local TweenService = game:GetService("TweenService") local door = workspace:WaitForChild("Дверь") -- плавно поднимаем дверь на 6 единиц вверх local goal = { Position = door.Position + Vector3.new(0, 6, 0) } TweenService:Create(door, TweenInfo.new(1), goal):Play()`}} /> ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ F — СКРИПТЫ — ИГРОВАЯ ЛОГИКА // ════════════════════════════════════════════════════ { id: 'scripts-logic', icon: 'target', title: 'Игровая логика', summary: 'HP и урон, физика, теги, взаимодействие по E, связи между объектами.', sections: [ { id: 'player-hp', title: 'F1. HP игрока: урон, лечение, смерть, чекпоинт', body: ( <>

    Команды для здоровья игрока:

    game.player.hpтекущее здоровье game.player.damage(n)нанести урон game.player.heal(n)вылечить game.player.kill()мгновенно убить game.player.respawn()воскресить на спавне game.player.setSpawn(точка)новая точка возрождения } lua={
    humanoid.Healthтекущее здоровье
    humanoid:TakeDamage(n)нанести урон
    humanoid.Health = humanoid.Health + nвылечить
    humanoid.Health = 0мгновенно убить
    player:LoadCharacter()воскресить
    player.RespawnLocation = spawnновая точка возрождения
    } />

    Пример 1 — шипы наносят урон:

    {`game.self.onTouch(() => { game.player.damage(20); // отнять 20 здоровья game.sound.play('hit'); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local humanoid = hit.Parent:FindFirstChild("Humanoid") if humanoid then humanoid:TakeDamage(20) -- отнять 20 здоровья end end)`}} />

    Пример 2 — аптечка лечит:

    {`game.self.onTouch(() => { game.player.heal(50); // добавить 50 здоровья game.ui.showText('+50 HP', 1.5); game.self.delete(); // аптечка исчезает });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local humanoid = hit.Parent:FindFirstChild("Humanoid") if not humanoid then return end -- добавить 50 здоровья, но не выше MaxHealth humanoid.Health = math.min(humanoid.Health + 50, humanoid.MaxHealth) print("+50 HP") part:Destroy() -- аптечка исчезает end)`}} /> ), }, { id: 'physics', title: 'F2. Физика: raycast, импульсы, взрывы', body: ( <>

    Отдел game.physics отвечает за «настоящую» физику:

    • raycast(откуда, куда, опции) — луч для стрельбы;
    • applyImpulse(ref, сила) — толкнуть объект;
    • explode(точка, радиус, опции) — взрыв.
    } lua={<>

    В Lua для физики используется workspace и стандартный Roblox API:

    • workspace:Raycast(origin, dir, params) — луч;
    • part:ApplyImpulse(Vector3) — толкнуть Part;
    • Instance.new("Explosion") — создать взрыв.
    } />

    Пример — стрельба лучом из камеры игрока:

    {`game.onClick(() => { const p = game.player.position; const hit = game.physics.raycast( { x: p.x, y: p.y + 1.5, z: p.z }, // откуда (от головы) game.player.forward, // куда (взгляд) { maxDistance: 50 } ); if (hit.hit) { game.log('Попал в объект:', hit.ref); game.sound.play('hit'); } });`}} lua={{`local UIS = game:GetService("UserInputService") local Players = game:GetService("Players") local player = Players.LocalPlayer local mouse = player:GetMouse() UIS.InputBegan:Connect(function(input) if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end local hrp = player.Character.HumanoidRootPart local origin = hrp.Position + Vector3.new(0, 1.5, 0) local direction = (mouse.Hit.Position - origin).Unit * 50 local raycastResult = workspace:Raycast(origin, direction) if raycastResult then print("Попал в объект:", raycastResult.Instance.Name) end end)`}} /> hit.hit — попал ли луч во что-нибудь. hit.ref — адрес объекта.

    } lua={

    raycastResult равно nil если луч ни во что не попал. Иначе у него есть поля .Instance (что попало), .Position (точка попадания), .Normal (нормаль поверхности).

    } /> ), }, { id: 'attributes', title: 'F3. Атрибуты объектов', body: ( <>

    Атрибут — это значение, которое ты «приклеиваешь» к объекту. Например, сколько здоровья у конкретного врага или сколько монет стоит товар.

    {`// При старте игры запоминаем цену прямо на товаре game.scene.setData(game.self.ref, 'price', 50); // Когда игрок кликает по товару — читаем цену game.self.onClick(() => { const price = game.scene.getData(game.self.ref, 'price'); game.ui.showText('Этот товар стоит ' + price + ' монет', 2); });`}} lua={{`local part = script.Parent -- При старте игры запоминаем цену прямо на товаре part:SetAttribute("Price", 50) -- Когда игрок кликает — читаем цену local clickDetector = Instance.new("ClickDetector", part) clickDetector.MouseClick:Connect(function(player) local price = part:GetAttribute("Price") print("Этот товар стоит " .. price .. " монет") end)`}} />

    Чем атрибут лучше обычной переменной? Переменная одна на весь скрипт. А атрибут — свой у каждого объекта. Один и тот же скрипт можно повесить на 10 разных товаров, и у каждого будет своя цена.

    ), }, { id: 'tags', title: 'F4. Теги объектов', body: ( <>

    Тег — это «ярлык» на объекте. Удобно ставить сразу на много объектов и потом одной командой находить их все.

    game.scene.tag(ref, 'звезда')повесить тег game.scene.untag(ref, 'звезда')снять тег game.scene.hasTag(ref, 'звезда')есть ли тег game.scene.getTagged('звезда')все объекты с тегом } lua={
    CollectionService:AddTag(part, "звезда")повесить тег
    CollectionService:RemoveTag(part, "звезда")снять тег
    CollectionService:HasTag(part, "звезда")есть ли тег
    CollectionService:GetTagged("звезда")все объекты с тегом
    } />

    Пример — игра «собери все звёзды»:

    {`// Этот скрипт висит на звезде. game.scene.tag(game.self.ref, 'звезда'); game.self.onTouch(() => { game.self.delete(); game.sound.play('coin'); const left = game.scene.getTagged('звезда').length; if (left === 0) { game.ui.showText('Все звёзды собраны! Победа!', 3); } else { game.ui.showText('Осталось звёзд: ' + left, 1.5); } });`}} lua={{`local CS = game:GetService("CollectionService") local part = script.Parent -- Помечаем звезду тегом CS:AddTag(part, "звезда") part.Touched:Connect(function() part:Destroy() local left = #CS:GetTagged("звезда") if left == 0 then print("Все звёзды собраны! Победа!") else print("Осталось звёзд: " .. left) end end)`}} /> Снятие тега убирает только ярлык. Цвет, размер и другие свойства объекта при этом не меняются. ), }, { id: 'proximity', title: 'F5. Взаимодействие по клавише E (ProximityPrompt)', body: ( <>

    Часто игра просит «подойди и нажми E»: открыть сундук, поговорить с торговцем, дёрнуть рычаг.

    {`game.self.onInteract(() => { game.ui.showText('Сундук открыт!', 2); game.scene.spawnParticles('sparks', game.self.position, { duration: 1 }); game.sound.play('pickup'); }, { text: 'Открыть сундук', // подсказка над объектом distance: 4 // на сколько метров подойти });`}} lua={{`local part = script.Parent -- ProximityPrompt — стандартный Roblox-способ local prompt = Instance.new("ProximityPrompt") prompt.ActionText = "Открыть" prompt.ObjectText = "Сундук" prompt.MaxActivationDistance = 4 prompt.KeyboardKeyCode = Enum.KeyCode.E prompt.Parent = part prompt.Triggered:Connect(function(player) print("Сундук открыт!") -- Можно создать эффект частиц или проиграть звук end)`}} />

    Когда игрок подойдёт на расстояние взаимодействия, над объектом появится подсказка с текстом. Нажатие E запустит функцию.

    ), }, { id: 'billboard', title: 'F6. Billboard-метки над объектами', body: ( <>

    Billboard — это текст-табличка, которая висит над объектом в 3D-мире и всегда повёрнута к игроку. Так показывают имена врагов, их HP, названия мест.

    {`// npc — это адрес созданного NPC. game.scene.setLabel(npc.ref, 'Торговец Боб', { color: '#ffffff', height: 2.5 // на 2.5 метра над объектом }); // Позже можно убрать табличку game.scene.clearLabel(npc.ref);`}} lua={{`-- BillboardGui в Roblox — это GUI поверх Part local part = workspace:WaitForChild("NPC") local billboard = Instance.new("BillboardGui") billboard.Size = UDim2.new(4, 0, 1, 0) billboard.StudsOffset = Vector3.new(0, 2.5, 0) -- над объектом billboard.Parent = part local label = Instance.new("TextLabel") label.BackgroundTransparency = 1 label.Size = UDim2.new(1, 0, 1, 0) label.Text = "Торговец Боб" label.TextColor3 = Color3.new(1, 1, 1) label.TextScaled = true label.Parent = billboard -- Позже можно убрать табличку -- billboard:Destroy()`}} /> ), }, { id: 'pass-through', title: 'F7. Проходимость объектов', body: ( <>

    Иногда стена должна стать проходимой — призрачная стена, секретный проход, исчезающий мост.

    {`game.self.onClick(() => { game.physics.passThrough(game.self.ref, true); game.scene.setOpacity(game.self.ref, 0.3); // полупрозрачная game.ui.showText('Секретный проход открыт!', 2); });`}} lua={{`local part = script.Parent local clickDetector = Instance.new("ClickDetector", part) clickDetector.MouseClick:Connect(function(player) part.CanCollide = false -- игрок проходит насквозь part.Transparency = 0.7 -- полупрозрачная (0=видна, 1=невидима) print("Секретный проход открыт!") end)`}} /> Если сделать стену снова твёрдой, пока игрок стоит внутри неё — игра аккуратно вытолкнет его наружу, он не застрянет. ), }, { id: 'constraints', title: 'F8. Связи: склейка, петля, пружина', body: ( <>

    Связи (constraints) соединяют объекты, чтобы они двигались вместе или по правилам физики.

    • Склейка (weld) — намертво приклеивает один объект к другому;
    • Петля (hinge) — объект вращается вокруг оси, как дверь или качели;
    • Пружина (spring) — объект упруго колеблется, как батут.

    Пример — качели на петле:

    {`const swing = game.scene.findOne('Качели'); const h = game.constraints.hinge(swing, { pivotX: 0, pivotZ: 0, angle: 30 }); // раскачиваем в другую сторону каждую секунду let dir = -30; game.every(1, () => { h.setAngle(dir); dir = -dir; });`}} lua={{`-- В Roblox HingeConstraint — стандартный способ local swing = workspace:WaitForChild("Качели") local mount = workspace:WaitForChild("Опора") -- неподвижная точка -- Attachment'ы (точки крепления) local a0 = Instance.new("Attachment", mount) local a1 = Instance.new("Attachment", swing) local hinge = Instance.new("HingeConstraint") hinge.Attachment0 = a0 hinge.Attachment1 = a1 hinge.ActuatorType = Enum.ActuatorType.Servo hinge.ServoMaxTorque = 10000 hinge.AngularSpeed = 2 hinge.Parent = swing -- Раскачиваем local dir = 30 while true do hinge.TargetAngle = dir task.wait(1) dir = -dir end`}} /> ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ G — СКРИПТЫ — БОЛЬШИЕ СИСТЕМЫ // ════════════════════════════════════════════════════ { id: 'scripts-systems', icon: 'gear', title: 'Большие системы', summary: 'NPC, инвентарь и оружие, звук, камера и катсцены, мультиплеер.', sections: [ { id: 'npc', title: 'G1. NPC: создание, движение, диалоги', body: ( <>

    NPC (неигровой персонаж) — это житель твоей игры: торговец, враг, проводник.

    В JS используем game.scene.spawnNpc(модель, опции):

    {`// Создаём NPC по имени Боб const bob = game.scene.spawnNpc('character-a', { x: 5, y: 0, z: 0, name: 'Боб', hp: 100, speed: 3 }); bob.say('Привет, путник!', 3); // реплика над головой bob.moveTo(10, 0); // идёт в точку`}

    Что умеет NPC:

    moveTo(x, z)идти в точку
    follow('player')гнаться за игроком
    stop()остановиться
    say(текст, сек)реплика над головой
    damage(n)нанести урон NPC
    remove()убрать
    onDeath(fn)при гибели

    Пример — враг гонится за игроком:

    {`const enemy = game.scene.spawnNpc('character-b', { x: 0, y: 0, z: 20, name: 'Враг', hp: 50, speed: 2 }); enemy.follow('player'); enemy.onDeath(() => { game.ui.showText('Враг побеждён!', 2); });`} } lua={<>

    В Lua NPC — это обычный Model с Humanoid внутри. Движение делается через humanoid:MoveTo(point). Реплики — через ChatService или BillboardGui.

    {`-- NPC модель должна лежать в Workspace. -- Внутри Model должны быть Part'ы и Humanoid. local npc = workspace:WaitForChild("Боб") local humanoid = npc:WaitForChild("Humanoid") local hrp = npc:WaitForChild("HumanoidRootPart") -- Реплика над головой через BillboardGui local function say(text, duration) local bg = Instance.new("BillboardGui") bg.Size = UDim2.new(4, 0, 1, 0) bg.StudsOffset = Vector3.new(0, 3, 0) bg.Parent = npc.Head or hrp local label = Instance.new("TextLabel", bg) label.Size = UDim2.new(1, 0, 1, 0) label.BackgroundTransparency = 1 label.TextColor3 = Color3.new(1, 1, 1) label.TextScaled = true label.Text = text task.delay(duration, function() bg:Destroy() end) end say("Привет, путник!", 3) -- Идёт в точку humanoid:MoveTo(Vector3.new(10, hrp.Position.Y, 0))`}

    Враг гонится за игроком:

    {`local enemy = workspace:WaitForChild("Враг") local humanoid = enemy.Humanoid local Players = game:GetService("Players") -- Каждые 0.5 сек обновляем цель — позицию игрока task.spawn(function() while enemy.Parent do local player = Players:GetPlayers()[1] if player and player.Character then local target = player.Character.HumanoidRootPart.Position humanoid:MoveTo(target) end task.wait(0.5) end end) -- При гибели humanoid.Died:Connect(function() print("Враг побеждён!") end)`} } /> ), }, { id: 'inventory-tools', title: 'G2. Инвентарь и инструменты', body: ( <>

    Инвентарь — сумка предметов. Инструмент — предмет, который игрок берёт в руку: меч, фонарик.

    {`// Выдать игроку меч прямо в руку game.player.giveTool('sword', { name: 'Стальной меч', equip: true }); // Ловим, когда игрок применил инструмент (ЛКМ) game.player.onToolUse((e) => { game.log('Игрок применил:', e.tool); });`}

    Команды game.inventory: add, remove, has, list.

    } lua={<> {`-- В Roblox инструмент — это Tool-инстанс в Backpack игрока. local Players = game:GetService("Players") local player = Players.LocalPlayer -- Создаём меч local sword = Instance.new("Tool") sword.Name = "Стальной меч" sword.RequiresHandle = false -- упрощённо без Handle-Part sword.Parent = player.Backpack -- Сразу взять в руку (переложить в Character) sword.Parent = player.Character -- Ловим применение (ЛКМ или активация) sword.Activated:Connect(function() print("Игрок применил меч!") end)`}

    Инвентарь игрока = его Backpack (Roblox-сервис). Чтобы посмотреть что есть: player.Backpack:GetChildren().

    } />

    Пример — игра «ключ и сундук»:

    {`game.self.onInteract(() => { if (game.inventory.has('Ключ')) { game.ui.showText('Сундук открыт!', 2); game.inventory.remove('Ключ'); } else { game.ui.showText('Нужен ключ', 1.5); } }, { text: 'Открыть', distance: 4 });`}} lua={{`local part = script.Parent local prompt = Instance.new("ProximityPrompt") prompt.ActionText = "Открыть" prompt.MaxActivationDistance = 4 prompt.Parent = part prompt.Triggered:Connect(function(player) -- Ищем ключ в Backpack local key = player.Backpack:FindFirstChild("Ключ") if not key then key = player.Character and player.Character:FindFirstChild("Ключ") end if key then print("Сундук открыт!") key:Destroy() -- ключ потрачен else print("Нужен ключ") end end)`}} /> ), }, { id: 'sound', title: 'G3. Звук: свои звуки и 3D-позиционный звук', body: ( <>

    Звук оживляет игру.

    В JS — команда game.sound.play(id, опции):

    {`// Готовые звуки-пресеты game.sound.play('coin'); game.sound.play('win'); game.sound.play('jump'); game.sound.play('hit'); // Свой загруженный звук, потише game.sound.play('sound_1', { volume: 0.7 });`}

    Пресеты: jump, pickup, win, lose, click, hit, coin.

    3D-звук — опция at привязывает звук к точке в мире, тише с расстоянием.

    {`game.sound.play('sound_2', { at: { x: 0, y: 1, z: 0 }, loop: true });`} } lua={<>

    В Lua используется Sound-инстанс:

    {`-- Простой звук — играет везде одинаково local sound = Instance.new("Sound") sound.SoundId = "rbxassetid://9120386436" -- свой ID sound.Volume = 0.7 sound.Parent = workspace sound:Play()`}

    3D-звук — родителем ставим Part в мире. Sound автоматически становится позиционным.

    {`-- Звук костра — слышен близко local campfire = workspace.Костёр local sound = Instance.new("Sound") sound.SoundId = "rbxassetid://..." sound.RollOffMaxDistance = 30 -- метры до полной тишины sound.Looped = true sound.Parent = campfire -- родитель = Part → 3D-звук sound:Play()`} } /> Звук обязателен — игра без звука кажется «мёртвой». Но не запускай длинную музыку в начале — это тормозит старт. Звуки вешай на события: прыжок, попадание, победа. ), }, { id: 'camera', title: 'G4. Камера: FOV, привязка, катсцены', body: ( <>

    В JS — отдел game.camera:

    setFov(градусы)угол обзора
    shake(сила, сек)тряска камеры
    focusOn(ref)навести на объект
    cutscene(точки, опции)пролёт камеры
    reset()вернуть игроку
    {`// Облёт уровня при старте game.camera.cutscene([ { x: 0, y: 20, z: -30 }, { x: 0, y: 15, z: 0 }, { x: 0, y: 10, z: 30 } ], { segDuration: 2 }); game.onCutsceneDone(() => { game.ui.showText('Поехали!', 2); });`} } lua={<>

    В Lua — стандартный Roblox Camera через Workspace.CurrentCamera:

    camera.FieldOfView = 90угол обзора
    camera.CameraType = Enum.CameraType.Scriptableотключить авто-следование
    camera.CFrame = CFrame.new(pos, look)поставить камеру
    TweenService:Create(camera, ...)плавный пролёт
    {`local TweenService = game:GetService("TweenService") local camera = workspace.CurrentCamera -- Отключаем авто-следование за игроком camera.CameraType = Enum.CameraType.Scriptable -- Облёт через 3 точки за 6 секунд (3 этапа по 2 сек) local points = { Vector3.new(0, 20, -30), Vector3.new(0, 15, 0), Vector3.new(0, 10, 30), } for _, point in ipairs(points) do local goal = { CFrame = CFrame.new(point, Vector3.new(0, 5, 0)) } local tween = TweenService:Create(camera, TweenInfo.new(2), goal) tween:Play() tween.Completed:Wait() end -- Вернуть камеру игроку camera.CameraType = Enum.CameraType.Custom print("Поехали!")`} } /> ), }, { id: 'beam-trail', title: 'G5. Лучи и следы (Beam и Trail)', body: ( <>

    Beam — светящаяся линия между двумя точками (лазер, мост света), Trail — шлейф за движущимся объектом (след за ракетой).

    {`// Лазер между двумя башнями const t1 = game.scene.findOne('Башня1'); const t2 = game.scene.findOne('Башня2'); const laser = game.fx.beam({ from: t1, to: t2, color: '#ff3344', width: 0.3 });`}} lua={{`-- В Roblox Beam — это инстанс на Attachment'е local t1 = workspace:WaitForChild("Башня1") local t2 = workspace:WaitForChild("Башня2") -- Attachment'ы — точки на Part'ах local a0 = Instance.new("Attachment", t1) local a1 = Instance.new("Attachment", t2) local beam = Instance.new("Beam") beam.Attachment0 = a0 beam.Attachment1 = a1 beam.Color = ColorSequence.new(Color3.fromRGB(255, 51, 68)) beam.Width0 = 0.3 beam.Width1 = 0.3 beam.LightEmission = 1 beam.Parent = t1`}} /> ), }, { id: 'multiplayer', title: 'G6. Мультиплеер: игроки, комната, команды', body: ( <>

    В Рублоксе можно сделать игру на несколько игроков.

    В JS — отделы game.players, game.room, game.teams:

    {`// Общий счёт команды — виден всем игрокам game.room.set('totalScore', 0); // когда счёт меняется — обновляем надпись game.room.onChange('totalScore', (val) => { game.ui.set('score', 'Счёт команды: ' + val); }); game.log('Игроков:', game.players.count()); game.onPlayerJoin((p) => { game.ui.showText(p.name + ' присоединился!', 2); });`} } lua={<>

    В Lua — стандартный Roblox-стиль:

    {`local Players = game:GetService("Players") local Teams = game:GetService("Teams") local ReplicatedStorage = game:GetService("ReplicatedStorage") -- Общий счёт команды через NumberValue в ReplicatedStorage -- (виден всем игрокам через .Changed) local totalScore = Instance.new("NumberValue") totalScore.Name = "TotalScore" totalScore.Value = 0 totalScore.Parent = ReplicatedStorage totalScore.Changed:Connect(function(newValue) print("Счёт команды:", newValue) end) print("Игроков:", #Players:GetPlayers()) -- Когда новый игрок зашёл Players.PlayerAdded:Connect(function(player) print(player.Name .. " присоединился!") end)`}

    Команды (Teams):

    {`-- Команды создают в Teams сервисе или скриптом local redTeam = Instance.new("Team") redTeam.Name = "Red" redTeam.TeamColor = BrickColor.new("Bright red") redTeam.AutoAssignable = true redTeam.Parent = game:GetService("Teams") -- Назначить игрока в команду Players.PlayerAdded:Connect(function(player) player.Team = redTeam end)`} } /> ), }, { id: 'leaderstats', title: 'G7. Лидерборды и достижения', body: ( <>

    Лидерборд — таблица очков справа-сверху (как в Roblox).

    {`game.leaderstats.define('Монеты', { initial: 0, icon: 'coin' }); game.leaderstats.define('Уровень', { initial: 1 }); game.leaderstats.me.add('Монеты', 5); game.leaderstats.me.set('Уровень', 2); const c = game.leaderstats.me.get('Монеты');`}

    Достижения:

    {`game.achievements.define([ { id: 'first_coin', name: 'Первая монетка', icon: 'coin', rarity: 'common' }, { id: 'rich', name: 'Богач', description: '100 монет', icon: 'trophy', rarity: 'legendary' } ]); game.achievements.unlock('first_coin'); game.achievements.bindToStat('rich', 'Монеты', 100);`} } lua={<>

    В Lua — стандартный Roblox-паттерн: создаём папку leaderstats в Player с IntValue внутри:

    {`local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) -- Папка leaderstats — Roblox автоматически показывает её в HUD local stats = Instance.new("Folder") stats.Name = "leaderstats" stats.Parent = player -- Стат "Монеты" local coins = Instance.new("IntValue") coins.Name = "Монеты" coins.Value = 0 coins.Parent = stats -- Стат "Уровень" local level = Instance.new("IntValue") level.Name = "Уровень" level.Value = 1 level.Parent = stats end) -- Добавить монеты текущему игроку local function addCoins(player, amount) local stats = player:FindFirstChild("leaderstats") if stats then stats.Монеты.Value = stats.Монеты.Value + amount end end`}

    Папка с именем leaderstats на игроке — магическое имя в Roblox. Любые IntValue/NumberValue/StringValue внутри неё автоматически попадают в HUD справа сверху.

    } /> Лидерборд и достижения сохраняются в БД и подтягиваются при следующем входе игрока (DataStoreService в Roblox). ), }, { id: 'damage-floaters', title: 'G8. Облачка урона (damage floaters)', body: ( <>

    Всплывающие цифры урона над врагом — как в RPG.

    В JS это готовая команда:

    {`game.fx.autoMobFloaters(true); // авто для всех мобов // или вручную game.fx.damageFloater(enemy.position, 25); game.fx.damageFloater(enemy.position, 100, { isCrit: true }); game.fx.damageFloater('player', 30, { isHeal: true }); game.fx.damageFloater(pos, 0, { isMiss: true });`} } lua={<>

    В Lua делаем сами через BillboardGui + TweenService:

    {`local TweenService = game:GetService("TweenService") local function showDamage(position, amount, isCrit) -- Невидимый Part-якорь в нужной точке local anchor = Instance.new("Part") anchor.Anchored = true anchor.CanCollide = false anchor.Transparency = 1 anchor.Size = Vector3.new(0.1, 0.1, 0.1) anchor.Position = position + Vector3.new(0, 2, 0) anchor.Parent = workspace -- BillboardGui над якорем local bg = Instance.new("BillboardGui", anchor) bg.Size = UDim2.new(3, 0, 1, 0) bg.AlwaysOnTop = true local label = Instance.new("TextLabel", bg) label.Size = UDim2.new(1, 0, 1, 0) label.BackgroundTransparency = 1 label.Text = "-" .. amount label.TextColor3 = isCrit and Color3.fromRGB(255, 200, 0) -- жёлтый крит or Color3.fromRGB(255, 80, 80) -- красный обычный label.TextScaled = true label.Font = Enum.Font.GothamBold -- Анимируем вверх + исчезание local goal = { Position = anchor.Position + Vector3.new(0, 4, 0) } TweenService:Create(anchor, TweenInfo.new(1), goal):Play() task.delay(1, function() anchor:Destroy() end) end -- Пример использования: showDamage(workspace.Враг.Position, 25, false) showDamage(workspace.Враг.Position, 100, true) -- крит`} } /> ), }, { id: 'items-inventory', title: 'G9. Предметы и инвентарь с редкостями', body: ( <>

    В JS — готовый отдел game.items и game.inventory:

    {`game.items.define([ { id: 'berry', name: 'Ягоды', emoji: '🍓', rarity: 'common', maxStack: 16 }, { id: 'potion', name: 'Зелье', emoji: '🧪', rarity: 'rare', maxStack: 8, onUseEffect: 'heal:50' }, { id: 'sword', name: 'Меч', emoji: '⚔️', rarity: 'legendary', maxStack: 1 }, ]); game.inventory.give('sword', 1); game.inventory.give('berry', 5);`}

    Сбор предмета с земли:

    {`game.self.onInteract(() => { game.inventory.give('berry', 2); game.self.delete(); }, { text: 'Собрать', distance: 3 });`} Редкости: common (серый), uncommon (зелёный), rare (голубой), epic (фиолетовый), legendary (золотой). Окно инвентаря — клавиша I, drag-drop, ПКМ-меню. } lua={<>

    В Roblox инвентарь — это Backpack игрока с Tool'ами, плюс свои IntValue'ы для подсчёта стаков. Готового «инвентаря с редкостями» нет — собирается из частей:

    {`-- Пример: ягоды как IntValue в leaderstats local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) local stats = Instance.new("Folder") stats.Name = "leaderstats" stats.Parent = player local berries = Instance.new("IntValue", stats) berries.Name = "Ягоды" berries.Value = 0 end) -- Сбор ягод (скрипт на собираемом Part) local part = script.Parent local prompt = Instance.new("ProximityPrompt") prompt.ActionText = "Собрать" prompt.Parent = part prompt.Triggered:Connect(function(player) local berries = player.leaderstats and player.leaderstats:FindFirstChild("Ягоды") if berries then berries.Value = berries.Value + 2 part:Destroy() end end)`}

    Для полноценной системы с редкостями, иконками и UI окном — надо собирать ScreenGui вручную (по статье C). Это много кода — проще использовать JS-вариант с готовым game.items.

    } /> ), }, { id: 'sky-environment', title: 'G10. Небо, облака, туман, время суток', body: ( <>

    Пресеты неба одной командой:

    {`game.scene.setSkybox({ preset: 'sunset' }); // пресеты: clear-summer-day | lowpoly-roblox | cloudy | sunset | starry-night | space game.scene.setClouds({ enabled: true, cover: 0.5, speed: 0.02 }); game.scene.setFog({ color: '#dddddd', density: 0.006 }); game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3); game.environment.setSkyColor('#0a1024'); game.environment.setTimeOfDay(0); // ночь game.environment.setTimeOfDay(12); // полдень`} } lua={<>

    В Roblox небо — это инстансы Sky и Atmosphere в Lighting:

    {`local Lighting = game:GetService("Lighting") -- Sky-инстанс с собственными текстурами local sky = Instance.new("Sky") sky.SkyboxBk = "rbxassetid://..." -- задняя грань sky.SkyboxFt = "rbxassetid://..." -- передняя sky.SkyboxLf = "rbxassetid://..." -- левая sky.SkyboxRt = "rbxassetid://..." -- правая sky.SkyboxUp = "rbxassetid://..." -- верх sky.SkyboxDn = "rbxassetid://..." -- низ sky.Parent = Lighting -- Туман Lighting.FogColor = Color3.fromRGB(221, 221, 221) Lighting.FogStart = 50 Lighting.FogEnd = 500 -- Atmosphere (мгла, плотность) local atmosphere = Instance.new("Atmosphere") atmosphere.Density = 0.3 atmosphere.Color = Color3.fromRGB(199, 199, 199) atmosphere.Parent = Lighting -- Время суток (часы и минуты от полуночи) Lighting:SetMinutesAfterMidnight(12 * 60) -- полдень Lighting:SetMinutesAfterMidnight(0) -- полночь`} } /> ), }, { id: 'modal-menu-loading', title: 'G11. Диалоги, меню, экран загрузки', body: ( <>

    Диалог NPC:

    {`game.modal.dialog('Староста', [ 'Привет, путник!', 'Собери 10 монет и возвращайся.', ], () => game.ui.showText('Квест начат!', 2));`}

    Окно Да/Нет и лутбокс:

    {`game.modal.confirmation('Выход', 'Точно выйти?', () => game.player.respawn(), null); game.modal.lootbox([ { name: 'Меч', color: '#f0ad4e', rarity: 'legendary' }, { name: 'Щит', color: '#5bc0de', rarity: 'rare' }, ], (item) => game.ui.showText('Выпал: ' + item.name, 3));`}

    Экран загрузки:

    {`game.loading.show({ style: 'ken-burns', placeName: 'Глава 2 — Шахта', duration: 2 }); game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`} } lua={<>

    В Roblox/Lua нет готовых модалок — всё собирается через ScreenGui с Frame'ами. Это много кода (~30-100 строк на диалог), но полностью кастомизируется.

    {`-- Простейший диалог: ScreenGui + Frame + TextLabel + Button local Players = game:GetService("Players") local player = Players.LocalPlayer local gui = player:WaitForChild("PlayerGui") local function showDialog(speaker, lines, onDone) local screen = Instance.new("ScreenGui", gui) local frame = Instance.new("Frame", screen) frame.Size = UDim2.new(0.6, 0, 0.25, 0) frame.Position = UDim2.new(0.2, 0, 0.65, 0) frame.BackgroundColor3 = Color3.new(0, 0, 0) frame.BackgroundTransparency = 0.4 local nameLabel = Instance.new("TextLabel", frame) nameLabel.Size = UDim2.new(1, 0, 0.2, 0) nameLabel.Text = speaker nameLabel.TextColor3 = Color3.fromRGB(255, 220, 100) nameLabel.BackgroundTransparency = 1 local textLabel = Instance.new("TextLabel", frame) textLabel.Size = UDim2.new(1, -20, 0.6, 0) textLabel.Position = UDim2.new(0, 10, 0.2, 0) textLabel.TextColor3 = Color3.new(1, 1, 1) textLabel.BackgroundTransparency = 1 textLabel.TextWrapped = true local idx = 1 local function showLine() textLabel.Text = lines[idx] end local btn = Instance.new("TextButton", frame) btn.Size = UDim2.new(0.3, 0, 0.15, 0) btn.Position = UDim2.new(0.65, 0, 0.82, 0) btn.Text = "Дальше" btn.MouseButton1Click:Connect(function() idx = idx + 1 if idx > #lines then screen:Destroy() if onDone then onDone() end else showLine() end end) showLine() end showDialog("Староста", { "Привет, путник!", "Собери 10 монет и возвращайся.", }, function() print("Квест начат!") end)`} } /> ), }, { id: 'vehicles-menu', title: 'G12. Машины и главное меню', body: ( <>

    Машина, на которой можно ездить (вход hold-F, WASD руль):

    {`game.scene.spawn('vehicle:car', { x: 0, y: 1, z: 0, name: 'Тачка' }); game.onVehicleEnter(() => game.ui.showText('За рулём! WASD — ехать', 2)); game.onVehicleExit(() => game.ui.showText('Вышел', 1));`}

    Главное меню:

    {`game.mainMenu.show({ title: 'МОЯ ИГРА', camera: 'orbit', playButtonText: 'ИГРАТЬ', patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] }, onPlay: () => game.ui.showText('Поехали!', 2) });`} } lua={<>

    В Roblox машина — это сложная Model с VehicleSeat внутри. Когда игрок садится в VehicleSeat — у него появляются .Throttle и .Steer свойства от WASD автоматически:

    {`-- VehicleSeat внутри Model local seat = workspace:WaitForChild("Тачка"):WaitForChild("VehicleSeat") -- Слушаем игрока в кресле seat:GetPropertyChangedSignal("Occupant"):Connect(function() if seat.Occupant then print("За рулём! WASD — ехать") else print("Вышел") end end) -- Throttle (W/S) и Steer (A/D) — автоматически в seat.Throttle и seat.Steer -- Применяй их к скорости/повороту в RunService.Heartbeat`}

    Главное меню — собирается через ScreenGui (см. C2-C3), или используется готовый StarterGui от Roblox.

    } /> ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ H — СПРАВОЧНИК game.* // ════════════════════════════════════════════════════ { id: 'reference', icon: 'book', title: 'Справочник game.*', summary: 'Шпаргалка: все команды game.* списком по отделам.', sections: [ { id: 'cheatsheet', title: 'H1. Шпаргалка — все команды списком', body: ( <>

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

    Игрок

    game.player.positionпозиция игрока {`{x,y,z}`} game.player.hp / maxHpздоровье и максимум game.player.aliveжив ли игрок game.player.forwardкуда смотрит game.player.teleport(x,y,z)телепорт game.player.damage(n) / heal(n)урон / лечение game.player.kill() / respawn()убить / воскресить game.player.setSpawn(точка)новая точка возрождения game.player.setSpeed(mul)скорость (множитель) game.player.setJumpPower(mul)прыжок (множитель) game.player.setGravityMul(mul)гравитация (множитель) game.player.setDoubleJump(on)двойной прыжок game.player.playAnimation(имя)эмоция game.player.giveTool(тип,опции)инструмент в руку game.player.isKeyDown(клавиша)зажата ли клавиша } lua={
    hrp.Positionпозиция (Vector3)
    humanoid.Health / MaxHealthздоровье
    humanoid.Health {'>'} 0жив ли
    camera.CFrame.LookVectorкуда смотрит
    hrp.CFrame = CFrame.new(x,y,z)телепорт
    humanoid:TakeDamage(n) / humanoid.Health += nурон / лечение
    humanoid.Health = 0 / player:LoadCharacter()убить / воскресить
    player.RespawnLocation = spawnточка возрождения
    humanoid.WalkSpeed = Nскорость (16 = норма)
    humanoid.JumpPower = Nсила прыжка (50 = норма)
    workspace.Gravity = Nгравитация (196 = норма)
    humanoid:ChangeState(Jumping)прыгнуть
    animator:LoadAnimation(anim):Play()анимация
    Instance.new("Tool",player.Character)инструмент в руку
    UserInputService:IsKeyDown(key)зажата ли клавиша
    } />

    Объекты сцены

    game.scene.spawn(тип,опции)создать объект → ref game.scene.delete(ref)удалить game.scene.deleteAfter(ref,сек)удалить через N секунд game.scene.move(ref,x,y,z)переместить game.scene.rotate(ref,угол)повернуть game.scene.setColor(ref,цвет)цвет game.scene.setCollide(ref,да)твёрдость game.scene.setVisible(ref,да)видимость game.scene.setOpacity(ref,0..1)прозрачность game.scene.find(имя) / findOne(имя)поиск по имени game.scene.all(тип)все объекты типа game.scene.setData/getDataатрибуты game.scene.tag/untag/hasTagтеги game.scene.getTagged(тег)все объекты с тегом game.scene.setLabel/clearLabelметка над объектом game.scene.spawnNpc(модель,опции)создать NPC game.scene.spawnParticles(тип,...)частицы } lua={
    Instance.new("Part", workspace)создать объект
    part:Destroy()удалить
    Debris:AddItem(part, N)удалить через N секунд
    part.Position = Vector3.new(x,y,z)переместить
    part.Orientation = Vector3.new(...)повернуть
    part.Color = Color3.fromRGB(...)цвет
    part.CanCollide = true/falseтвёрдость
    part.Transparency = 1невидимость (0=видно)
    part.Transparency = 0.4полупрозрачность
    workspace:FindFirstChild("Имя") / workspace.Имяпоиск по имени
    CollectionService:GetTagged("тег")все объекты с тегом
    part:SetAttribute/GetAttributeатрибуты
    CollectionService:AddTag/RemoveTag/HasTagтеги
    CollectionService:GetTagged(tag)все объекты с тегом
    BillboardGui + TextLabelметка над объектом
    Model + Humanoid + AnimNPC (вручную)
    Instance.new("ParticleEmitter", part)частицы
    } />

    Объект-носитель скрипта

    game.self.ref / positionадрес и позиция game.self.onClick(fn)клик по объекту game.self.onTouch(fn)игрок коснулся game.self.onUntouch(fn)игрок вышел game.self.onInteract(fn,опции)взаимодействие по E game.self.move(x,y,z)переместить себя game.self.delete()удалить себя game.self.setText(t)сменить текст } lua={
    script.Parent / .Positionсам объект и его позиция
    ClickDetector.MouseClick:Connectклик по объекту
    part.Touched:Connectигрок коснулся
    part.TouchEnded:Connectигрок вышел
    ProximityPrompt.Triggered:Connectвзаимодействие по E
    part.Position = Vector3.new(x,y,z)переместить
    part:Destroy()удалить
    textLabel.Text = "..."сменить текст (для GUI)
    } />

    HUD: счётчики и текст

    game.ui.score / timerсчётчики в углу game.ui.showText(текст,сек)текст по центру game.ui.set(id,текст,опции)своя метка game.ui.remove(id) / clear()убрать метку / всё } lua={
    leaderstats папка + IntValueсчётчики в углу (HUD автомат)
    ScreenGui + TextLabel (центр)текст по центру
    label.Text = "..."обновить метку
    label:Destroy() / screen:Destroy()убрать метку / всё
    } />

    GUI: кнопки и меню

    game.gui.find(имя) / get(id)найти элемент game.gui.update(id,patch)изменить свойства game.gui.show(id) / hide(id)показать / скрыть game.gui.onClick(id,fn)клик по кнопке game.gui.onSubmit(id,fn)ввод в поле завершён } lua={
    gui:FindFirstChild(имя, true)найти элемент
    elem.Text = "..." / прямая запись свойствизменить свойства
    elem.Visible = true/falseпоказать / скрыть
    button.MouseButton1Click:Connectклик по кнопке
    textbox.FocusLost:Connect(fn)ввод завершён
    } />

    Физика, эффекты, связи

    game.physics.raycast(...)луч — во что попал game.physics.applyImpulse(...)толкнуть объект game.physics.explode(...)взрыв game.physics.passThrough(...)проходимость game.fx.beam(опции)светящийся луч game.fx.trail(ref,опции)след за объектом game.fx.damageFloater(...)цифры урона game.constraints.weld(a,b)склейка game.constraints.hinge(...)петля game.constraints.spring(...)пружина } lua={
    workspace:Raycast(origin,dir,params)луч — во что попал
    part:ApplyImpulse(Vector3)толкнуть объект
    Instance.new("Explosion", workspace)взрыв
    part.CanCollide = falseпроходимость
    Instance.new("Beam") + Attachmentsсветящийся луч
    Instance.new("Trail") + Attachmentsслед за объектом
    BillboardGui + TweenServiceцифры урона (вручную)
    Instance.new("WeldConstraint")склейка
    Instance.new("HingeConstraint")петля
    Instance.new("SpringConstraint")пружина
    } />

    Камера и звук

    game.camera.setFov(град)угол обзора game.camera.shake(сила,сек)тряска game.camera.cutscene(...)пролёт камеры game.camera.reset()вернуть камеру игроку game.sound.play(id,опции)проиграть звук } lua={
    workspace.CurrentCamera.FieldOfView = Nугол обзора
    camera.CFrame = CFrame.new(...) + рандомтряска (вручную)
    camera.CameraType = Scriptable + TweenServiceпролёт камеры
    camera.CameraType = Customвернуть игроку
    Instance.new("Sound"):Play()проиграть звук
    } />

    События и таймеры

    game.onTick(fn)каждый кадр game.onKey/onKeyUp(клавиша,fn)клавиатура game.onClick(fn)клик в игре game.after(сек,fn)через N секунд game.every(сек,fn)каждые N секунд game.cancel(id)отменить таймер game.tween(ref,св-ва,опции)плавная анимация } lua={
    RunService.Heartbeat:Connect(fn)каждый кадр
    UserInputService.InputBegan/Endedклавиатура
    mouse.Button1Down:Connect(fn)клик в игре
    task.delay(сек, fn)через N секунд
    task.spawn(function() while ... task.wait(N) end end)каждые N секунд
    connection:Disconnect()отменить подписку
    TweenService:Create(obj, info, goal):Play()плавная анимация
    } />

    Утилиты

    game.random(min,max)случайное число game.distance(a,b)расстояние между точками game.clamp(v,min,max)зажать в границах game.lerp(a,b,t)плавный переход game.log(...)в консоль game.broadcast/onMessageсообщения между скриптами } lua={
    math.random(min,max)случайное число
    (a - b).Magnitudeрасстояние между Vector3
    math.clamp(v,min,max)зажать в границах
    a + (b-a)*t или Vector3:Lerp(other,t)плавный переход
    print(...) / warn(...)в консоль
    BindableEvent:Fire + .Event:Connectсообщения между скриптами
    } />

    Мультиплеер, лидерборды, команды

    game.players.all() / count() / me()список игроков game.room.set/get/onChangeобщее состояние комнаты game.teams.*команды game.leaderstats.define(имя,опции)объявить стат game.leaderstats.me.add/set/getтекущему игроку game.achievements.define/unlockдостижения game.save.merge/getсохранение прогресса game.onPlayerJoin/Leave(fn)игрок зашёл / ушёл } lua={
    Players:GetPlayers() / #Players:GetPlayers() / Players.LocalPlayerсписок / число / я
    ReplicatedStorage + Value + .Changedобщее состояние
    Teams сервис + Instance.new("Team")команды
    Instance.new("Folder","leaderstats")+IntValueлидерборд
    stats.Имя.Value = Nобновить стат
    BadgeService:AwardBadge(uid, id)достижения (badges)
    DataStoreService:GetAsync/SetAsyncсохранение прогресса
    Players.PlayerAdded:Connect / PlayerRemovingигрок зашёл / ушёл
    } />

    Небо, освещение, инвентарь, модалки

    game.scene.setSkybox/fadeToпресеты неба game.scene.setFog/setCloudsтуман и облака game.environment.setTimeOfDay(0..24)время суток game.items.define(список)описать предметы game.inventory.give/remove/has/listинвентарь game.modal.dialog/confirmation/lootboxмодальные окна game.mainMenu.show/hideглавное меню game.loading.show/onHideэкран загрузки } lua={
    Lighting + Sky / Atmosphereпресеты неба (вручную)
    Lighting.FogColor / FogEnd / Atmosphereтуман и облака
    Lighting:SetMinutesAfterMidnight(N)время суток
    Свои Tool'ы в ServerStorageпредметы (вручную)
    player.Backpack:GetChildren() / Tool.Parent = Backpackинвентарь
    ScreenGui + Frame + Buttonмодалки (вручную, см. G11)
    ScreenGui + Frameглавное меню (вручную)
    ReplicatedFirst + loading screenэкран загрузки
    } /> ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ I — ГЛОССАРИЙ // ════════════════════════════════════════════════════ { id: 'glossary', icon: 'glossary', title: 'Глоссарий', summary: 'Словарик: все непонятные слова из вики простым языком.', sections: [ { id: 'terms', title: 'I1. Термины простым языком', body: ( <>

    Словарик слов, которые встречаются в вики:

    ПримитивПростая 3D-фигура: куб, сфера, цилиндр. Главный строительный материал.
    МодельГотовая красивая 3D-фигура из библиотеки (дерево, машина).
    БлокКубик одного размера, ровно встаёт по сетке.
    СценаВесь игровой мир — всё, что ты построил.
    ВьюпортОкно с 3D-сценой в центре редактора.
    ГизмоЦветные стрелки и кольца для перемещения объектов.
    ИерархияСписок всех объектов игры в правой панели.
    ИнспекторПанель со свойствами выделенного объекта.
    СкриптНабор команд (код), который оживляет игру.
    JavaScriptЯзык программирования, на котором пишут скрипты.
    Глобальный скриптСкрипт-«мозг», не привязан к объекту, запускается один раз.
    Скрипт на объектеСкрипт конкретного объекта, в нём работает game.self.
    Переменная«Коробочка с именем» — скрипт хранит в ней значение.
    ФункцияНабор команд, который выполнится, когда его позовут.
    Событие«Что-то случилось» — клик, касание, нажатие клавиши.
    Условие (if)Развилка: если что-то верно — сделай одно, иначе — другое.
    СпавнПоявление: точка спавна — где появляется игрок; «заспавнить» — создать объект.
    РеспаунВоскрешение игрока после смерти.
    ЧекпоинтКонтрольная точка — место, откуда игрок воскреснет.
    HPЗдоровье игрока. При HP=0 он умирает.
    ref«Адрес» объекта. Команда spawn возвращает ref, по нему обращаются к объекту.
    ТвинПлавное изменение свойства за время (плавное движение).
    ТегЯрлык на объекте. По тегу можно найти все помеченные объекты.
    АтрибутСвоё значение, приклеенное к объекту (через setData).
    КонстрейнтСвязь между объектами: склейка, петля, пружина.
    ЭмиттерОбъект, создающий частицы (искры, дым, огонь).
    ЧастицыМного маленьких летящих точек для эффектов.
    GUIИнтерфейс: кнопки, надписи, меню поверх 3D-сцены.
    HUDСчётчики и индикаторы поверх экрана (счёт, HP).
    NPCНеигровой персонаж: торговец, враг, проводник.
    ТриггерНевидимая зона, которая что-то запускает при входе игрока.
    RaycastНевидимый луч — узнать, во что он попал (для стрельбы).
    FOVУгол обзора камеры. Больше — «шире» видно.
    КатсценаВидеовставка: камера сама пролетает по точкам.
    МультиплеерИгра на нескольких игроков в одной комнате.
    МодерацияПроверка игры перед публикацией в общей ленте.
    ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ J — ЧАСТЫЕ ОШИБКИ // ════════════════════════════════════════════════════ { id: 'mistakes', icon: 'bug', title: 'Частые ошибки', summary: 'Что делать, если скрипт не работает или игра ведёт себя странно.', sections: [ { id: 'script-not-working', title: 'J1. Скрипт не работает или не сохраняется', body: ( <>

    Первым делом — проверь Консоль. Если в скрипте опечатка, её текст появится в Консоли красным — там написано, в какой строке ошибка.

    Самые частые опечатки:

    • забыли точку с запятой ; в конце команды;
    • не закрыли скобку — открывающих и закрывающих ( ), {`{ }`} должно быть поровну;
    • имя команды с ошибкой: game.player.teelport вместо teleport;
    • русская буква вместо английской (с виду одинаковы: русская с и английская c).

    После правки скрипта — сохрани игру (Ctrl+S) и перезапусти Play.

    ), }, { id: 'fell-through-floor', title: 'J2. Объект провалился сквозь пол', body: (

    Если объект не закреплён (свойство «Закреплён» выключено), он падает под действием физики. Для платформ, стен и декораций включи «Закреплён» в инспекторе — тогда объект будет висеть на месте. Падать должны только те объекты, которым это нужно (ящики, мячи).

    ), }, { id: 'findone-null', title: 'J3. findOne вернул «ничего» (null) на старте', body: ( <>

    Если позвать game.scene.findOne(...) в самой первой строке скрипта — объект может быть ещё не готов, и команда вернёт null («ничего»). Потом обращение к этому null сломает скрипт.

    Решение: ищи объект не на старте, а внутри onTick или после небольшой задержки:

    {`let door = null; game.onTick(() => { // ищем дверь, пока не найдём if (!door) door = game.scene.findOne('Дверь'); // ... работаем с door, когда он уже найден });`} ), }, { id: 'ui-set-every-frame', title: 'J4. game.ui.set каждый кадр — игра лагает', body: ( <>

    onTick выполняется 60 раз в секунду. Если внутри него на каждом кадре звать game.ui.set(...), интерфейс будет обновляться слишком часто и игра начнёт тормозить.

    Решение: обновляй интерфейс только когда значение реально изменилось:

    {`let lastScore = -1; game.onTick(() => { const s = game.ui.score || 0; if (s !== lastScore) { // значение изменилось? game.ui.set('hud', 'Счёт: ' + s); lastScore = s; } });`} ), }, { id: 'other-mistakes', title: 'J5. Прочие типичные грабли', body: (
    • Враг идёт сквозь стены — проверь, что у стен включено «Столкновение».
    • Звук не играет — проверь громкость (volume) и что звук загружен. Не запускай длинный звук в самом начале — это тормозит старт.
    • Кнопка не нажимается — проверь, что на неё повешен game.gui.onClick(...) с правильным id, и что у элемента правильное имя.
    • Объект не двигается твином — у твина свойство должно быть числом ({`{ y: 10 }`}), а первым аргументом — настоящий ref объекта.
    • Скрипт на объекте, но game.self пустой — значит скрипт не привязан. Выдели объект и пересоздай скрипт на нём, либо укажи носителя в настройках скрипта.
    • Игрок застрял в стене — не делай стену твёрдой, пока игрок внутри. Используй passThrough аккуратно.
    ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ — СКРИПТЫ: ГОТОВЫЕ РЕЦЕПТЫ // ════════════════════════════════════════════════════ { id: 'recipes', icon: 'code', title: 'Скрипты: рецепты', summary: 'Готовые мини-скрипты, которые можно скопировать и вставить: килблок, сбор предметов, исчезновение и телепорт при касании, кнопки, таймеры, все свойства примитивов.', sections: [ { id: 'recipes-howto', title: 'S1. Куда писать скрипты', body: ( <>

    В Рублоксе есть два вида скриптов:

    • Глобальный — создаётся в иерархии в категории «Скрипты» (кнопка «+»). Запускается один раз при старте игры. В нём управляешь всей сценой через game.scene, game.player, game.ui.
    • На объекте — вешается прямо на примитив/модель/блок. В нём работает game.self — это сам объект-носитель. Удобно для «этот блок убивает», «этот сундук открывается».
    В рецептах ниже над каждым примером написано, куда его писать. Просто скопируй код и вставь в нужный скрипт.

    Полезное: game.log(...) печатает в консоль (значок «Консоль» внизу) — для отладки. game.player.position — где сейчас игрок ({'{x, y, z}'}).

    ), }, { id: 'recipes-touch', title: 'S2. Касание объекта', body: ( <>

    Самое частое событие — игрок коснулся объекта.

    {`// Игрок наступил на объект — показать надпись и звук game.self.onTouch(() => { game.ui.showText('Ты коснулся плиты!', 2); game.sound.play('click'); }); // Когда игрок ушёл с объекта game.self.onUntouch(() => { game.ui.showText('Отошёл', 1); });`}} lua={{`local part = script.Parent -- Игрок наступил part.Touched:Connect(function(hit) local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent) if player then print("Ты коснулся плиты!") end end) -- Игрок ушёл part.TouchEnded:Connect(function(hit) local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent) if player then print("Отошёл") end end)`}} />

    Подписаться на чужой объект из глобального скрипта:

    {`const trap = game.scene.findOne('Ловушка'); trap.onTouch(() => game.player.damage(20));`}} lua={{`local trap = workspace:WaitForChild("Ловушка") trap.Touched:Connect(function(hit) local humanoid = hit.Parent:FindFirstChild("Humanoid") if humanoid then humanoid:TakeDamage(20) end end)`}} /> ), }, { id: 'recipes-killblock', title: 'S3. Килблок — урон и смерть при касании', body: ( <>

    Килблок — объект, который наносит урон или мгновенно убивает при касании (лава, шипы, кислота).

    {`// Мгновенная смерть при касании game.self.onTouch(() => { game.player.kill(); game.ui.showText('Ты сгорел в лаве!', 2); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local humanoid = hit.Parent:FindFirstChild("Humanoid") if humanoid then humanoid.Health = 0 -- мгновенная смерть end end)`}} />

    Если хочешь не убивать сразу, а наносить урон:

    {`game.self.onTouch(() => { game.player.damage(25); game.camera.shake(0.2, 0.3); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local humanoid = hit.Parent:FindFirstChild("Humanoid") if humanoid then humanoid:TakeDamage(25) end end)`}} />

    Постоянный урон, пока игрок стоит в зоне:

    {`let inside = false; game.self.onTouch(() => { inside = true; }); game.self.onUntouch(() => { inside = false; }); game.every(0.5, () => { if (inside) game.player.damage(5); });`}} lua={{`local part = script.Parent local inside = {} -- humanoid → true part.Touched:Connect(function(hit) local h = hit.Parent:FindFirstChild("Humanoid") if h then inside[h] = true end end) part.TouchEnded:Connect(function(hit) local h = hit.Parent:FindFirstChild("Humanoid") if h then inside[h] = nil end end) -- Урон каждые 0.5 сек пока стоит while true do task.wait(0.5) for h in pairs(inside) do if h.Parent then h:TakeDamage(5) end end end`}} /> Сделай красный неоновый куб, повесь на него скрипт смерти — получится лава. Поставь его в проёме как преграду. ), }, { id: 'recipes-disappear', title: 'S4. Исчезновение при касании (сбор монет)', body: ( <>

    Предмет исчезает при касании — основа сбора монет.

    {`game.self.onTouch(() => { game.sound.play('coin'); game.self.delete(); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local h = hit.Parent:FindFirstChild("Humanoid") if h then part:Destroy() end end)`}} />

    Со счётчиком: монетка увеличивает leaderstats игрока:

    {`game.self.onTouch(() => { game.broadcast('coin'); game.self.delete(); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent) if not player then return end -- Прибавить монетку в leaderstats local stats = player:FindFirstChild("leaderstats") if stats and stats:FindFirstChild("Монеты") then stats.Монеты.Value = stats.Монеты.Value + 1 end part:Destroy() end)`}} />

    JS: глобальный скрипт принимает broadcast и считает:

    {`let score = 0; game.ui.score = 0; game.onMessage('coin', () => { score = score + 1; game.ui.score = score; if (score >= 10) game.ui.showText('Собрал все!', 3); });`}} lua={{`-- В Lua счёт уже в leaderstats игрока (см. код на монетке выше). -- Проверим достижение цели в глобальном скрипте: local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) -- Создаём leaderstats папку при заходе local stats = Instance.new("Folder", player) stats.Name = "leaderstats" local coins = Instance.new("IntValue", stats) coins.Name = "Монеты" coins.Value = 0 coins.Changed:Connect(function(newVal) if newVal >= 10 then print("Собрал все!") end end) end)`}} /> ), }, { id: 'recipes-teleport', title: 'S5. Телепорт и смена позиции при касании', body: ( <>

    При касании переместить игрока (портал) или сдвинуть сам объект.

    Портал — телепорт игрока:

    {`game.self.onTouch(() => { game.player.teleport(0, 20, 50); game.sound.play('win'); game.camera.shake(0.15, 0.2); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local h = hit.Parent:FindFirstChild("Humanoid") if not h then return end local hrp = hit.Parent:FindFirstChild("HumanoidRootPart") if hrp then hrp.CFrame = CFrame.new(0, 20, 50) end end)`}} />

    Сдвинуть сам объект при касании (опустить мост):

    {`let opened = false; game.self.onTouch(() => { if (opened) return; opened = true; const p = game.self.position; game.self.move(p.x, p.y - 3, p.z); });`}} lua={{`local part = script.Parent local opened = false part.Touched:Connect(function(hit) if opened then return end if not hit.Parent:FindFirstChild("Humanoid") then return end opened = true part.Position = part.Position - Vector3.new(0, 3, 0) end)`}} />

    Плавно сдвинуть — через TweenService:

    {`const p = game.self.position; game.self.onTouch(() => { game.tween(game.self.ref, { x: p.x + 4 }, { duration: 1, easing: 'ease' }); });`}} lua={{`local TweenService = game:GetService("TweenService") local part = script.Parent part.Touched:Connect(function(hit) if not hit.Parent:FindFirstChild("Humanoid") then return end local goal = { Position = part.Position + Vector3.new(4, 0, 0) } TweenService:Create(part, TweenInfo.new(1), goal):Play() end)`}} /> ), }, { id: 'recipes-primitive-props', title: 'S6. Все свойства примитивов из скрипта', body: ( <>

    Любой примитив можно создать и менять из скрипта.

    Создать примитив:

    {`const box = game.scene.spawn('cube', { x: 0, y: 2, z: 0, sx: 2, sy: 1, sz: 3, rotationX: 0, rotationY: 0.8, rotationZ: 0, color: '#ff5533', material: 'neon', name: 'МойКуб', anchored: true, canCollide: true, visible: true, mass: 5, });`}} lua={{`local box = Instance.new("Part") box.Name = "МойКуб" box.Shape = Enum.PartType.Block box.Size = Vector3.new(2, 1, 3) box.Position = Vector3.new(0, 2, 0) box.Orientation = Vector3.new(0, math.deg(0.8), 0) -- градусы box.Color = Color3.fromRGB(255, 85, 51) box.Material = Enum.Material.Neon box.Anchored = true box.CanCollide = true box.Transparency = 0 -- Если Anchored=false: box.Mass читается, не задаётся. -- Управляется через PhysicalProperties и Density. box.Parent = workspace`}} />

    Типы примитивов:

    {`'cube' 'sphere' 'cylinder' 'cone' 'pyramid' 'torus' 'wedge' 'cornerwedge' 'plane'`}} lua={{`Enum.PartType.Block / Ball / Cylinder / Wedge / CornerWedge -- Для cone/pyramid/torus используются MeshPart или SpecialMesh: local sphere = Instance.new("Part") sphere.Shape = Enum.PartType.Ball -- сфера`}} />

    Менять свойства существующего объекта:

    {`game.scene.setColor(box, '#00ff88'); game.scene.setMaterial(box, 'glass'); game.scene.setVisible(box, false); game.scene.setCollide(box, false); game.scene.setOpacity(box, 0.4); game.scene.setScale(box, 3, 1, 1); game.scene.move(box, 5, 2, 0); game.scene.setRotation(box, 0, 1.57, 0); game.scene.setLabel(box, 'Привет!', { color:'#fff', height: 2.5 }); // Или через прокси: const obj = game.scene.findOne('МойКуб'); obj.color = '#ffd700'; obj.material = 'metal'; obj.scale = 2; obj.opacity = 0.5; obj.visible = false; obj.canCollide = false; obj.position = { x: 0, y: 10, z: 0 }; obj.rotateY(1.57); obj.destroy();`}} lua={{`-- Прямое присваивание свойств Part box.Color = Color3.fromRGB(0, 255, 136) box.Material = Enum.Material.Glass box.Transparency = 0.6 -- 0=видно, 1=невидимо box.CanCollide = false box.Size = Vector3.new(3, 1, 1) box.Position = Vector3.new(5, 2, 0) box.Orientation = Vector3.new(0, 90, 0) -- Скрыть: Transparency = 1 (или Parent = nil) box:Destroy() -- удалить`}} /> Радианы: поворот в радианах. 90° = Math.PI/2 ≈ 1.57. } lua={ Градусы: Orientation в градусах (не радианах). Для CFrame.Angles — в радианах: math.rad(90). } /> ), }, { id: 'recipes-anim', title: 'S7. Движение, вращение, мигание', body: ( <>

    Вращающийся объект (монета, портал):

    {`let angle = 0; game.onTick((dt) => { angle = angle + dt * 2; game.self.rotateY(angle); });`}} lua={{`local RunService = game:GetService("RunService") local part = script.Parent local angle = 0 RunService.Heartbeat:Connect(function(dt) angle = angle + dt * 2 part.CFrame = CFrame.new(part.Position) * CFrame.Angles(0, angle, 0) end)`}} />

    Парение вверх-вниз:

    {`const start = game.self.position; let t = 0; game.onTick((dt) => { t = t + dt; const dy = Math.sin(t * 2) * 0.4; game.self.move(start.x, start.y + dy, start.z); });`}} lua={{`local RunService = game:GetService("RunService") local part = script.Parent local startPos = part.Position local t = 0 RunService.Heartbeat:Connect(function(dt) t = t + dt local dy = math.sin(t * 2) * 0.4 part.Position = Vector3.new(startPos.X, startPos.Y + dy, startPos.Z) end)`}} />

    Пульсация размера:

    {`game.tween(game.self.ref, { sy: 1.4 }, { duration: 0.6, easing: 'ease', yoyo: true, repeat: -1 });`}} lua={{`local TweenService = game:GetService("TweenService") local part = script.Parent local origSize = part.Size local info = TweenInfo.new( 0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, -1, -- бесконечно true -- yoyo (туда-обратно) ) local goal = { Size = Vector3.new(origSize.X, origSize.Y * 1.4, origSize.Z) } TweenService:Create(part, info, goal):Play()`}} />

    Мигание цветом:

    {`let on = false; game.every(0.5, () => { on = !on; game.self.setColor(on ? '#ff0000' : '#330000'); });`}} lua={{`local part = script.Parent local on = false while true do task.wait(0.5) on = not on part.Color = on and Color3.fromRGB(255, 0, 0) or Color3.fromRGB(51, 0, 0) end`}} /> ), }, { id: 'recipes-button-door', title: 'S8. Кнопка по E и дверь', body: ( <>

    Взаимодействие по клавише E:

    {`game.self.onInteract(() => { game.ui.showText('Открыто!', 2); game.broadcast('open-door'); }, { text: 'Открыть', key: 'e', distance: 4 });`}} lua={{`local part = script.Parent local prompt = Instance.new("ProximityPrompt") prompt.ActionText = "Открыть" prompt.MaxActivationDistance = 4 prompt.KeyboardKeyCode = Enum.KeyCode.E prompt.Parent = part -- BindableEvent для оповещения "open-door" local doorEvent = workspace:FindFirstChild("DoorOpenEvent") or Instance.new("BindableEvent", workspace) doorEvent.Name = "DoorOpenEvent" prompt.Triggered:Connect(function(player) print("Открыто!") doorEvent:Fire() end)`}} />

    На двери:

    {`const closed = game.self.position; game.onMessage('open-door', () => { game.tween(game.self.ref, { y: closed.y + 4 }, { duration: 1, easing: 'ease' }); game.self.setCollide(false); });`}} lua={{`local TweenService = game:GetService("TweenService") local door = script.Parent local closedPos = door.Position local doorEvent = workspace:WaitForChild("DoorOpenEvent") doorEvent.Event:Connect(function() local goal = { Position = closedPos + Vector3.new(0, 4, 0) } TweenService:Create(door, TweenInfo.new(1), goal):Play() door.CanCollide = false end)`}} /> holdDuration: 1 в onInteract / prompt.HoldDuration = 1 в Roblox — держать E одну секунду. ), }, { id: 'recipes-gui-timer', title: 'S9. HUD надписи, таймер, кнопки', body: ( <>

    HUD-надписи:

    {`game.ui.score = 0; // счётчик в углу game.ui.score = 50; game.ui.timer = 60; // таймер game.ui.showText('Старт!', 2); game.ui.set('hp', 'Жизни: 3', { x: 50, y: 90, color: '#fff' }); game.ui.remove('hp');`}} lua={{`-- В Roblox HUD = leaderstats папка (см. G7) или свой ScreenGui local Players = game:GetService("Players") local player = Players.LocalPlayer local gui = player:WaitForChild("PlayerGui") -- Своя метка по центру local screen = Instance.new("ScreenGui", gui) local label = Instance.new("TextLabel", screen) label.Size = UDim2.new(0.4, 0, 0.1, 0) label.Position = UDim2.new(0.3, 0, 0.4, 0) label.Text = "Старт!" label.TextScaled = true label.BackgroundTransparency = 0.5 task.delay(2, function() screen:Destroy() end)`}} />

    Обратный отсчёт:

    {`let time = 30; game.ui.timer = time; const id = game.every(1, () => { time = time - 1; game.ui.timer = time; if (time <= 0) { game.cancel(id); game.ui.showText('Время вышло!', 3); game.player.kill(); } });`}} lua={{`local time = 30 while time > 0 do task.wait(1) time = time - 1 print("Осталось: " .. time) end print("Время вышло!") -- Убить локального игрока local player = game:GetService("Players").LocalPlayer if player.Character and player.Character:FindFirstChild("Humanoid") then player.Character.Humanoid.Health = 0 end`}} />

    Кнопка GUI:

    {`const btn = game.gui.create('button', { name: 'start', text: 'НАЧАТЬ', x: 50, y: 80, w: 20, h: 8, bg: '#3a6ee0', color: '#fff' }); game.gui.onClick(btn, () => { game.ui.showText('Поехали!', 2); game.gui.hide(btn); });`}} lua={{`local player = game:GetService("Players").LocalPlayer local gui = player:WaitForChild("PlayerGui") local screen = Instance.new("ScreenGui", gui) local btn = Instance.new("TextButton", screen) btn.Size = UDim2.new(0.2, 0, 0.08, 0) btn.Position = UDim2.new(0.4, 0, 0.8, 0) btn.Text = "НАЧАТЬ" btn.BackgroundColor3 = Color3.fromRGB(58, 110, 224) btn.TextColor3 = Color3.new(1, 1, 1) btn.MouseButton1Click:Connect(function() print("Поехали!") btn.Visible = false end)`}} /> ), }, { id: 'recipes-spawn-fall', title: 'S10. Спавн, падение, проверка падения вниз', body: ( <>

    Спавнить объекты с неба каждую секунду:

    {`game.every(1, () => { const x = game.random(-10, 10); game.scene.spawn('sphere', { x: x, y: 20, z: 0, color: '#ffd700', material: 'neon', anchored: false, lifetime: 8 }); });`}} lua={{`local Debris = game:GetService("Debris") while true do task.wait(1) local x = math.random(-10, 10) local ball = Instance.new("Part") ball.Shape = Enum.PartType.Ball ball.Size = Vector3.new(1, 1, 1) ball.Position = Vector3.new(x, 20, 0) ball.Color = Color3.fromRGB(255, 215, 0) ball.Material = Enum.Material.Neon ball.Anchored = false -- падает ball.Parent = workspace Debris:AddItem(ball, 8) -- удалить через 8 сек end`}} />

    Игрок упал вниз:

    {`game.onTick(() => { if (game.player.position.y < -10) { game.player.respawn(); game.ui.showText('Упал!', 2); } });`}} lua={{`local Players = game:GetService("Players") local RunService = game:GetService("RunService") RunService.Heartbeat:Connect(function() local player = Players.LocalPlayer if not player.Character then return end local hrp = player.Character:FindFirstChild("HumanoidRootPart") if hrp and hrp.Position.Y < -10 then player:LoadCharacter() -- респавн print("Упал!") end end)`}} />

    Финиш:

    {`game.self.onTouch(() => { game.ui.showText('ПОБЕДА!', 4); game.sound.play('win'); game.player.setInputBlocked(true); });`}} lua={{`local part = script.Parent part.Touched:Connect(function(hit) local h = hit.Parent:FindFirstChild("Humanoid") if not h then return end print("ПОБЕДА!") h.WalkSpeed = 0 -- заморозить h.JumpPower = 0 end)`}} /> ), }, { id: 'recipes-npc-enemy', title: 'S11. Враг, который идёт за игроком', body: ( <>

    NPC/враг, преследующий игрока:

    {`const enemy = game.scene.spawnNpc('zombie', { x: 10, y: 0, z: 10, hp: 100, name: 'Зомби', speed: 3 }); enemy.follow('player'); enemy.say('Хочу тебя поймать!', 3); enemy.onDeath(() => { game.ui.showText('Враг повержен!', 2); }); // Урон когда близко game.every(0.5, () => { const d = game.distance(enemy.position, game.player.position); if (d < 2) game.player.damage(10); });`}} lua={{`-- Враг должен быть Model с Humanoid и HumanoidRootPart в workspace. -- Например workspace.Зомби. local Players = game:GetService("Players") local enemy = workspace:WaitForChild("Зомби") local humanoid = enemy:WaitForChild("Humanoid") local hrp = enemy:WaitForChild("HumanoidRootPart") -- Преследование игрока task.spawn(function() while enemy.Parent and humanoid.Health > 0 do local player = Players:GetPlayers()[1] if player and player.Character then local target = player.Character:FindFirstChild("HumanoidRootPart") if target then humanoid:MoveTo(target.Position) end end task.wait(0.5) end end) humanoid.Died:Connect(function() print("Враг повержен!") end) -- Урон когда близко task.spawn(function() while enemy.Parent and humanoid.Health > 0 do task.wait(0.5) local player = Players:GetPlayers()[1] if player and player.Character then local target = player.Character:FindFirstChild("HumanoidRootPart") local playerHum = player.Character:FindFirstChild("Humanoid") if target and playerHum then local dist = (target.Position - hrp.Position).Magnitude if dist < 2 then playerHum:TakeDamage(10) end end end end end)`}} /> ), }, { id: 'recipes-save', title: 'S12. Сохранение прогресса и лидерборд', body: ( <>

    Лидерборд:

    {`game.leaderstats.define('Монеты', { initial: 0 }); game.leaderstats.me.add('Монеты', 1);`}} lua={{`-- leaderstats: см. G7 local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) local stats = Instance.new("Folder", player) stats.Name = "leaderstats" local coins = Instance.new("IntValue", stats) coins.Name = "Монеты" coins.Value = 0 end) -- Прибавить монетку (например, при сборе) local function addCoin(player, amount) local stats = player:FindFirstChild("leaderstats") if stats then stats.Монеты.Value = stats.Монеты.Value + amount end end`}} />

    Сохранение между сессиями:

    {`game.save.merge('progress', { patch: { level: 3 }, increment: { coins: 10 }, max: { bestScore: 5000 } }); game.save.get('progress', (data) => { if (data) { game.ui.showText('С возвращением! Уровень ' + data.level, 3); } });`}} lua={{`-- В Roblox сохранение через DataStoreService (требует онлайн-игру) local DataStoreService = game:GetService("DataStoreService") local progress = DataStoreService:GetDataStore("Progress") local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) -- Прочитать при входе local success, data = pcall(function() return progress:GetAsync(player.UserId) end) if success and data then print("С возвращением! Уровень " .. (data.level or 1)) -- Применить прогресс: leaderstats.Монеты.Value = data.coins и т.п. end end) Players.PlayerRemoving:Connect(function(player) -- Сохранить при выходе local data = { level = 3, coins = 10, bestScore = 5000, } pcall(function() progress:SetAsync(player.UserId, data) end) end)`}} /> Собери всё вместе: монетки добавляются в leaderstats → глобальный скрипт раз в N монет вызывает save.merge или DataStore:SetAsync. Получится игра с прогрессом. ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ — СОВМЕСТНОЕ РЕДАКТИРОВАНИЕ (Team Create) // ════════════════════════════════════════════════════ { id: 'collab', icon: 'users', title: 'Вместе с друзьями', summary: 'Совместное редактирование игры в реальном времени: приглашай друзей, стройте сцену вдвоём, видьте курсоры и правки друг друга.', sections: [ { id: 'collab-what', title: 'V1. Что такое совместное редактирование', body: ( <>

    Совместное редактирование (Team Create) — это когда одну игру в студии редактируют несколько человек одновременно. Ты строишь дом, друг в это же время ставит деревья — и каждый видит правки другого вживую, без перезагрузки страницы.

    Что синхронизируется в реальном времени:

    • Блоки — поставил/удалил блок, друг сразу видит;
    • Примитивы — добавил, подвинул, повернул, изменил цвет или размер;
    • Модели — добавил из Тулбокса или удалил;
    • Курсоры друзей — где сейчас «мышка» каждого соавтора (цветная точка с ником);
    • Кто онлайн — список соавторов с цветными аватарками вверху сцены.
    Это как Google Документы, только для 3D-игр. Удобно делать игру в команде, помогать ученику или показывать, как что устроено — прямо в его проекте. ), }, { id: 'collab-invite', title: 'V2. Как пригласить друга', body: ( <>

    Открой свою игру в студии и перейди на вкладку Игра в верхней панели. Там, в группе «Вместе», есть кнопка Пригласить.

    Нажми Пригласить — ссылка-приглашение автоматически скопируется в буфер обмена (появится уведомление). Отправь эту ссылку другу (в чат, мессенджер — как угодно). Друг открывает ссылку — и попадает в ту же сцену. Теперь вы редактируете вместе. На кнопке появится счётчик: «Вместе (2)». Приглашать может только автор игры. Ссылка действует 24 часа. Друг должен быть зарегистрирован на Рублоксе и войти в свой аккаунт. ), }, { id: 'collab-how', title: 'V3. Как это работает и правила', body: ( <>

    Блокировка объекта. Пока один соавтор выделил объект и двигает его — этот объект заблокирован для других (никто другой не сможет его двигать одновременно). Так правки не конфликтуют. Как только выделение снято — объект снова свободен.

    Кто сохраняет. Игру в базу сохраняет автор (владелец проекта). Приглашённые друзья видят пометку «Совместное редактирование» вместо кнопок Сохранить и Опубликовать — это правильно: они помогают строить, а управляет игрой автор.

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

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

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

    ), }, ], }, // ════════════════════════════════════════════════════ // РАЗДЕЛ — КОНТЕКСТ ДЛЯ НЕЙРОНКИ (AI) // ════════════════════════════════════════════════════ { id: 'ai-context', icon: 'lightbulb', title: 'Контекст для нейронки', summary: 'Готовый текст со всем API скриптов Рублокса. Скопируй его целиком, вставь в ChatGPT/нейросеть — и она будет писать тебе рабочие скрипты под твою задачу.', sections: [ { id: 'ai-howto', title: 'AI1. Как писать скрипты с нейросетью', body: ( <>

    Хочешь, чтобы скрипт написала нейросеть (ChatGPT, DeepSeek, Claude и т.п.)? Проблема в том, что нейросеть не знает устройство Рублокса — придумает несуществующие команды. Решение простое:

    Открой статью «AI2. Контекст — скопируй в нейросеть» ниже. Выдели и скопируй весь текст из серого блока (он описывает все команды Рублокса). Вставь его в нейросеть первым сообщением. Затем добавь свою задачу, например: «Напиши скрипт: при касании синего куба игрок прыгает высоко и играет звук». Нейросеть выдаст готовый код. Скопируй его в скрипт в студии (глобальный или на объекте — она подскажет куда). Если нейросеть всё равно ошиблась в команде — скинь ей текст ошибки из «Консоли» студии, она исправит. И всегда проверяй результат запуском игры. ), }, { id: 'ai-context-text', title: 'AI2. Контекст — скопируй в нейросеть', body: ( <>

    Выдели весь текст ниже и скопируй (Ctrl+A внутри блока или мышью), затем вставь в нейросеть перед своим вопросом:

    {AI_CONTEXT} ), }, ], }, ];