studio/src/community/docsData.jsx
min defb1d80c1 feat(wiki): LangTabs в раздел H — справочник для обоих языков
Каждая из 11 таблиц-разделов H1 теперь имеет JS и Lua колонки:

- Игрок: game.player.* vs humanoid/hrp/player.*
- Объекты сцены: game.scene.* vs Instance.new + workspace.*
- script-носитель: game.self vs script.Parent
- HUD: game.ui.* vs leaderstats + ScreenGui
- GUI: game.gui.* vs MouseButton1Click/FocusLost
- Физика/эффекты: game.physics/fx/constraints vs Raycast/Beam/Trail
- Камера/звук: game.camera/sound vs CurrentCamera + Sound
- События/таймеры: game.onTick/onKey vs Heartbeat/UIS/task.delay
- Утилиты: game.random/distance vs math.*/Vector3.Magnitude
- Мультиплеер: game.players/teams/leaderstats vs Players/Teams/Folder
- Окружение: game.environment/items/modal/menu vs Lighting/Backpack/ScreenGui
2026-06-09 03:01:52 +03:00

5010 lines
292 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 — короткое описание для карточки на главной вики.
*
* Хелперы для оформления:
* - <Code> — код-блок (тёмный, моноширинный)
* - <ScriptKind kind="global|object" on="..."> — плашка «куда писать скрипт»
* - <Step n="1"> — шаг инструкции
* - <Note> — жёлтая плашка-подсказка
* - <Try> — зелёная плашка «попробуй сам»
*
* Контент написан для детей 5 класса+. Каждый пример — рабочий код,
* который можно скопировать в свою игру. Эмодзи в UI не используются —
* только SVG-иконки (см. docsIcons.jsx).
*/
// ── Код-блок ──────────────────────────────────────────────────────
export const Code = ({ children }) => (
<pre className="docCode"><code>{children}</code></pre>
);
// ── Плашка «куда писать скрипт» ───────────────────────────────────
// kind="global" — глобальный скрипт (создаётся в категории «Скрипты»)
// kind="object" — скрипт привязан к объекту (передай on="название объекта")
export const ScriptKind = ({ kind, on }) => {
if (kind === 'object') {
return (
<div className="docScriptKind docScriptKind--object">
<span className="docScriptKind__ico"><DocIcon name="pin" size={18} /></span>
<span>
<b>Куда писать:</b> этот скрипт нужно <b>повесить на объект</b>
{on ? <> на <b>{on}</b></> : null}. Выдели объект на сцене
и создай скрипт прямо на нём. Тогда внутри скрипта работает
слово <code>game.self</code> это и есть твой объект.
</span>
</div>
);
}
return (
<div className="docScriptKind docScriptKind--global">
<span className="docScriptKind__ico"><DocIcon name="globeIcon" size={18} /></span>
<span>
<b>Куда писать:</b> это <b>глобальный скрипт</b>. Создай его
в иерархии в категории <b>Скрипты</b> (кнопка «+»). Он не привязан
ни к какому объекту и запускается один раз при старте игры.
</span>
</div>
);
};
// ── Шаг инструкции ────────────────────────────────────────────────
export const Step = ({ n, children }) => (
<div className="docStep">
<span className="docStep__num">{n}</span>
<div className="docStep__body">{children}</div>
</div>
);
// ── Жёлтая плашка-подсказка ───────────────────────────────────────
export const Note = ({ children }) => (
<div className="docNote">
<span className="docNote__ico"><DocIcon name="lightbulb" size={17} /></span>
<div>{children}</div>
</div>
);
// ── Зелёная плашка «попробуй сам» ─────────────────────────────────
export const Try = ({ children }) => (
<div className="docTry">
<span className="docTry__ico"><DocIcon name="target" size={17} /></span>
<div>
<b>Попробуй сам:</b> {children}
</div>
</div>
);
// ── Скриншот интерфейса с подписью ────────────────────────────────
// Версия вики-ассетов (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 }) => (
<figure className={'docShot' + (wide ? ' docShot--wide' : '')}>
<img src={wikiUrl(src)} alt={caption || ''} loading="lazy" />
{caption && <figcaption>{caption}</figcaption>}
</figure>
);
// ══════════════════════════════════════════════════════════════════
// 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: (
<>
<p>
<b>Рублокс</b> это платформа, где можно играть в 3D-игры
и создавать свои собственные. Всё работает прямо в браузере:
ничего скачивать и устанавливать не нужно.
</p>
<p>
<b>Редактор игр</b> (его ещё называют Studio) это место,
где ты строишь игру. Ты ставишь блоки и модели, рисуешь
кнопки, пишешь скрипты а потом нажимаешь «Играть»
и сразу проверяешь, что получилось.
</p>
<p>В Рублоксе можно сделать почти любую игру:</p>
<ul>
<li>паркур и платформеры прыгай по платформам;</li>
<li>гонки мчись к финишу на время;</li>
<li>стрелялки и арены сражайся с врагами;</li>
<li>головоломки и квесты решай загадки;</li>
<li>выживалки и целые RPG с героями и заданиями.</li>
</ul>
<p>
<b>Как устроена эта вика.</b> Разделы A-C научат строить
мир и интерфейс. Разделы D-G писать скрипты (код, который
оживляет игру). Раздел H справочник всех команд. Раздел I
словарик непонятных слов. Раздел J что делать, если
что-то сломалось. Раздел K 50 готовых игр-уроков.
</p>
<Note>
Не нужно читать всё подряд. Пройди раздел «Основы»,
а дальше открывай то, что нужно прямо сейчас.
</Note>
</>
),
},
{
id: 'editor-interface',
title: 'A2. Интерфейс редактора',
body: (
<>
<p>
Когда ты открываешь игру в редакторе, экран делится
на части. Разберём каждую:
</p>
<Shot src="a2-overview.png" wide
caption="Четыре главные зоны редактора" />
<ul>
<li>
<b>1 Шапка сверху</b> название игры и кнопки:
<kbd className="kbd">Настройки</kbd>,
<kbd className="kbd">Сохранить</kbd>,
<kbd className="kbd">Играть</kbd>,
<kbd className="kbd">Опубликовать</kbd>.
</li>
<li>
<b>2 Лента инструментов</b> под шапкой вкладки
<i> Главная / Модель / Игра / Вид</i>. На каждой вкладке
свои кнопки и инструменты.
</li>
<li>
<b>3 3D-сцена (вьюпорт)</b> твой игровой мир
в центре экрана. Тут ты всё и строишь.
</li>
<li>
<b>4 Правая панель</b> — сверху <i>Иерархия</i> (список
всех объектов), снизу <i>Инспектор</i> (свойства того
объекта, который ты выделил мышкой).
</li>
</ul>
<p>
Когда ты выбираешь инструмент «Блок» или «Примитив»,
слева открывается ещё одна <b>палитра</b> там лежат
фигуры, которые можно ставить на сцену.
</p>
<Shot src="a2-topbar.png"
caption="Шапка: название игры и главные кнопки" />
<Shot src="a2-ribbon.png" wide
caption="Лента инструментов — все инструменты создания" />
<p><b>Как двигать камеру в редакторе:</b></p>
<table className="docTable">
<tbody>
<tr><td>Правая кнопка мыши + движение</td><td>осмотреться по сторонам</td></tr>
<tr><td><kbd className="kbd">W</kbd><kbd className="kbd">A</kbd><kbd className="kbd">S</kbd><kbd className="kbd">D</kbd></td><td>лететь вперёд / влево / назад / вправо</td></tr>
<tr><td>Колесо мыши</td><td>приблизить / отдалить</td></tr>
</tbody>
</table>
<Note>
Камера редактора и камера игрока это <b>разные</b> камеры.
То, как ты летаешь по сцене сейчас, не влияет на то, как
будет видеть мир игрок.
</Note>
</>
),
},
{
id: 'first-game-5min',
title: 'A3. Первая игра за 5 минут',
body: (
<>
<p>
Соберём самую простую игру площадку, по которой можно
ходить. Делай по шагам.
</p>
<Step n="1">
Открой редактор и создай <b>новую игру</b>
(или выбери пустой шаблон).
</Step>
<Step n="2">
На вкладке <b>Главная</b> выбери инструмент
<kbd className="kbd">Блок</kbd>. В левой палитре кликни
на блок травы он станет выбранным.
</Step>
<Step n="3">
Кликай по сцене блоки будут вставать один за другим.
Собери небольшую площадку примерно 6×6 блоков.
</Step>
<Step n="4">
Перейди на вкладку <b>Игра</b> и выбери
<kbd className="kbd">Ставить спавн</kbd>. Кликни
на площадку там появится точка, где игрок начнёт игру.
</Step>
<Step n="5">
Нажми <kbd className="kbd">Играть</kbd> в шапке. Ты
окажешься на своей площадке и сможешь по ней ходить!
</Step>
<Step n="6">
Нажми <kbd className="kbd">Esc</kbd>, чтобы вернуться
в редактор, и <kbd className="kbd">Сохранить</kbd>.
</Step>
<p>
Поздравляем это уже работающая игра. Дальше ты добавишь
в неё препятствия, врагов, монетки и логику.
</p>
<Try>
добавь по краям площадки стены из блоков, чтобы нельзя
было упасть. И поставь в центре несколько блоков-ступенек.
</Try>
</>
),
},
{
id: 'creation-tools',
title: 'A4. Инструменты: блок, примитив, модель, ландшафт',
body: (
<>
<p>На вкладке <b>Главная</b> есть инструменты создания:</p>
<Shot src="a4-tools.png"
caption="Инструменты создания: Блок, Примитив, Модель" />
<ul>
<li>
<b>Блок</b> ставит кубический блок (трава, камень,
дерево...). Блоки ровно встают по сетке из них удобно
строить дома и стены, как из кубиков Лего.
</li>
<li>
<b>Примитив</b> простая фигура: куб, сфера, цилиндр,
конус, плоскость, тор, клин. У примитива можно свободно
менять размер по каждой оси и красить в любой цвет.
</li>
<li>
<b>Модель</b> готовая красивая 3D-модель из библиотеки
(дерево, бочка, машина, оружие). Можно загружать и свои
модели в формате <code>.glb</code>.
</li>
<li>
<b>Ландшафт</b> инструмент лепки рельефа: холмы, горы,
пещеры. Об этом раздел B4.
</li>
<li>
<b>Стереть</b> удаляет блок или объект под курсором.
</li>
</ul>
<p>
Когда выбираешь «Блок» или «Примитив», слева открывается
<b> палитра</b> выбери в ней фигуру, а потом кликай
по сцене, чтобы её поставить.
</p>
<Shot src="a4-palette.png"
caption="Палитра примитивов — выбранная фигура подсвечена синим" />
<p>
<b>Шаг привязки</b> (1.0 / 0.5 / 0.25 / Выкл) задаёт,
насколько мелко объект «прилипает» к сетке, когда ты его
двигаешь. Шаг 1.0 объект двигается крупными шагами,
ровно по клеткам. Маленький шаг 0.25 точнее, но дольше.
</p>
<Shot src="a4-snap.png"
caption="Переключатель шага привязки" />
<Note>
Чем отличаются блок и примитив-куб? Блок всегда одного
размера и быстро ставится сеткой он для стройки.
Примитив-куб можно растянуть в длинную платформу или
тонкую стенку он для геймплея.
</Note>
</>
),
},
{
id: 'gizmo',
title: 'A5. Гизмо-манипуляторы: двигать, вращать, масштаб',
body: (
<>
<p>
<b>Гизмо</b> это цветные стрелки и кольца, которые
появляются на выделенном объекте. Они помогают точно
его двигать, поворачивать и менять размер.
</p>
<p>
Режим гизмо выбирается в ленте инструментов группа
«Манипуляторы»:
</p>
<Shot src="a5-gizmo-tools.png"
caption="Кнопки гизмо: Выделить, Двигать, Вращать, Масштаб" />
<table className="docTable">
<tbody>
<tr><td><b>Выделить</b></td><td>обычный режим, клик выбирает объект</td></tr>
<tr><td><b>Двигать</b></td><td>три стрелки X / Y / Z тяни, объект едет по оси</td></tr>
<tr><td><b>Вращать</b></td><td>три кольца тяни, объект поворачивается</td></tr>
<tr><td><b>Масштаб</b></td><td>кубики на осях тяни, объект растягивается</td></tr>
</tbody>
</table>
<p>
Вот как выглядит гизмо «Двигать» на выделенном кубе
три цветные стрелки:
</p>
<Shot src="a5-gizmo.png"
caption="Гизмо «Двигать»: стрелки X (красная), Y (зелёная), Z (синяя)" />
<p>
Оси всегда одни и те же и покрашены одинаково:
</p>
<ul>
<li><b>X</b> (красная) — влево / вправо;</li>
<li><b>Y</b> (зелёная) — вверх / вниз;</li>
<li><b>Z</b> (синяя) — вперёд / назад.</li>
</ul>
<Note>
Эти же буквы X, Y, Z ты увидишь в скриптах. Когда команда
пишет <code>{`{ x: 5, y: 2, z: 0 }`}</code> это точка
в мире: 5 вправо, 2 вверх, 0 вперёд.
</Note>
</>
),
},
{
id: 'hierarchy',
title: 'A6. Иерархия объектов и папки',
body: (
<>
<p>
<b>Иерархия</b> это список всех объектов твоей игры
в правой панели. Когда сцена большая, найти нужный куб
мышкой трудно а в списке он всегда под рукой.
</p>
<Shot src="a6-hierarchy.png"
caption="Правая панель: сверху иерархия объектов, снизу инспектор свойств" />
<p>Объекты сгруппированы по категориям:</p>
<ul>
<li><b>Сцена</b> точка спавна, окружение, свет;</li>
<li><b>Игрок</b> скин персонажа;</li>
<li><b>Интерфейс</b> GUI-элементы (кнопки, надписи);</li>
<li><b>Скрипты</b> твой код.</li>
</ul>
<p>
<b>Имя объекта.</b> У каждого объекта есть имя его видно
в иерархии и можно изменить в инспекторе. Имена очень важны
для скриптов: команда <code>game.scene.findOne('Дверь')</code>
находит объект <b>по имени</b>. Давай объектам понятные
имена: «Дверь», «Монетка1», «Босс».
</p>
<p>
<b>Папки.</b> Объекты можно складывать в свои папки
например, папка «Уровень 1», папка «Враги». Это как
наводить порядок в шкафу: всё на своих полках. Двойной
клик по объекту в списке камера прилетит прямо к нему.
</p>
</>
),
},
{
id: 'hotkeys',
title: 'A7. Горячие клавиши',
body: (
<>
<p>Горячие клавиши экономят кучу времени:</p>
<table className="docTable">
<tbody>
<tr><td><kbd className="kbd">Ctrl</kbd>+<kbd className="kbd">S</kbd></td><td>Сохранить игру</td></tr>
<tr><td><kbd className="kbd">Ctrl</kbd>+<kbd className="kbd">Z</kbd></td><td>Отменить последнее действие</td></tr>
<tr><td><kbd className="kbd">Ctrl</kbd>+<kbd className="kbd">D</kbd></td><td>Дублировать выделенный объект</td></tr>
<tr><td><kbd className="kbd">Del</kbd></td><td>Удалить выделенное</td></tr>
<tr><td><kbd className="kbd">R</kbd></td><td>Повернуть объект на 90°</td></tr>
<tr><td><kbd className="kbd">Esc</kbd></td><td>Снять выделение / выйти из режима</td></tr>
<tr><td><kbd className="kbd">F</kbd></td><td>Навести камеру на выделенное</td></tr>
</tbody>
</table>
<Note>
Самая полезная привычка почаще жать
<kbd className="kbd">Ctrl</kbd>+<kbd className="kbd">S</kbd>.
Сохраняться нужно не «когда закончил», а каждые пару минут.
</Note>
</>
),
},
{
id: 'play-mode',
title: 'A8. Режим игры: HP, смерть, респаун',
body: (
<>
<p>
Кнопка <kbd className="kbd">Запустить</kbd> в правой части
ленты запускает игру. Сцена «оживает»: включается физика,
начинают работать скрипты, появляется HUD (счётчики поверх
экрана). Чтобы остановить игру кнопка
<kbd className="kbd">Стоп</kbd> или клавиша
<kbd className="kbd">Esc</kbd>.
</p>
<Shot src="a8-play.png"
caption="Кнопка «Запустить» — проверить игру; «Стоп» — вернуться в редактор" />
<p><b>Управление в игре:</b></p>
<table className="docTable">
<tbody>
<tr><td><kbd className="kbd">W</kbd><kbd className="kbd">A</kbd><kbd className="kbd">S</kbd><kbd className="kbd">D</kbd></td><td>Идти</td></tr>
<tr><td><kbd className="kbd">Space</kbd></td><td>Прыжок</td></tr>
<tr><td><kbd className="kbd">Shift</kbd></td><td>Бег</td></tr>
<tr><td>Мышь</td><td>Поворот камеры</td></tr>
<tr><td>ЛКМ</td><td>Атака / стрельба</td></tr>
<tr><td><kbd className="kbd">E</kbd></td><td>Взаимодействовать</td></tr>
<tr><td><kbd className="kbd">1</kbd><kbd className="kbd">5</kbd></td><td>Слот инвентаря</td></tr>
<tr><td><kbd className="kbd">C</kbd></td><td>1-е / 3-е лицо</td></tr>
</tbody>
</table>
<p>
<b>HP (здоровье)</b> игрока видно в левом верхнем углу.
Когда игрок получает урон, полоска краснеет. Если HP падает
до нуля игрок «умирает» и через пару секунд
<b> воскресает (респаун)</b> на точке спавна с полным
здоровьем.
</p>
<p>
Всем этим можно управлять из скриптов: наносить урон,
лечить, ставить чекпоинты. Об этом раздел F1.
</p>
</>
),
},
{
id: 'save-publish',
title: 'A9. Сохранение и публикация',
body: (
<>
<p>
<b>Сохранение</b> кнопка <kbd className="kbd">Сохранить</kbd>
или <kbd className="kbd">Ctrl</kbd>+<kbd className="kbd">S</kbd>.
Игра автоматически сохраняется и сама время от времени,
но лучше не лениться и сохранять руками.
</p>
<p>
<b>Публикация</b> когда игра готова, нажми
<kbd className="kbd">Опубликовать</kbd>. Выбери
возрастной рейтинг (6+, 12+, 16+, 18+) и куда отправить:
</p>
<ul>
<li>
<b>В главную ленту</b> игру увидят все ученики.
Модерация строже.
</li>
<li>
<b>Только в профиле</b> игра доступна по ссылке
и в твоём профиле, но в общей ленте её не будет.
</li>
</ul>
<p>
После отправки игру проверит модератор (обычно за 24-48
часов). Игра <b>не должна</b> содержать матов, рекламы,
чужого контента (модели из Roblox/Minecraft) и жестокости
не по возрасту.
</p>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ B — ОБЪЕКТЫ СЦЕНЫ
// ════════════════════════════════════════════════════
{
id: 'objects',
icon: 'cube',
title: 'Объекты сцены',
summary: 'Блоки, примитивы, модели, ландшафт, свет, частицы и свойства объектов.',
sections: [
{
id: 'blocks',
title: 'B1. Блоки и их типы',
body: (
<>
<p>
<b>Блок</b> это кубик одного размера. Блоки ровно
встают по сетке, и из них удобно строить как из кубиков
Лего. В палитре слева есть разные типы: трава, камень,
песок, дерево, кирпич, цветные блоки, блоки со снегом.
</p>
<p>
Чтобы поставить блок выбери инструмент
<kbd className="kbd">Блок</kbd>, кликни нужный тип
в палитре и кликай по сцене. Чтобы убрать инструмент
<kbd className="kbd">Стереть</kbd>.
</p>
<Shot src="b1-blocks.png"
caption="Палитра блоков — типы сгруппированы по вкладкам" />
<p>
У блока в инспекторе можно включить/выключить
<i> столкновение</i> (твёрдый он или сквозь него можно
пройти) и <i>видимость</i>.
</p>
<Note>
Невидимый блок с включённым столкновением это уже
готовый «невидимый барьер». Игрок в него упрётся,
но не увидит. Так делают границы уровня.
</Note>
</>
),
},
{
id: 'primitives',
title: 'B2. Примитивы',
body: (
<>
<p>
<b>Примитив</b> простая 3D-фигура. В отличие от блока,
у примитива можно свободно менять размер по каждой оси
и красить в любой цвет. Виды:
</p>
<table className="docTable">
<tbody>
<tr><td><b>Куб</b></td><td>стены, платформы, ящики</td></tr>
<tr><td><b>Сфера</b></td><td>мячи, монетки, планеты</td></tr>
<tr><td><b>Цилиндр</b></td><td>колонны, бочки, трубы</td></tr>
<tr><td><b>Конус</b></td><td>шипы, ёлки, шляпы</td></tr>
<tr><td><b>Плоскость</b></td><td>тонкий лист пол, экран</td></tr>
<tr><td><b>Тор</b></td><td>кольцо (как бублик)</td></tr>
<tr><td><b>Клин</b></td><td>наклонная фигура пандус</td></tr>
</tbody>
</table>
<p>
Примитивы главный «строительный материал» для игр.
Из растянутых кубов делают платформы для паркура,
из сфер собираемые монетки, из конусов смертельные
шипы.
</p>
<Note>
Чтобы в скрипте было легко найти примитив дай ему
понятное имя в инспекторе. Например, «Платформа1». Потом
скрипт найдёт её командой
<code> game.scene.findOne('Платформа1')</code>.
</Note>
</>
),
},
{
id: 'models',
title: 'B3. Готовые модели и импорт своих',
body: (
<>
<p>
<b>Модель</b> готовая красивая 3D-фигура: дерево, камень,
бочка, машина, оружие, мебель. Их не нужно строить из
кубиков просто бери из библиотеки и ставь на сцену.
</p>
<p>
Инструмент <kbd className="kbd">Модель</kbd> открывает
каталог. Модели разбиты по категориям (природа, город,
оружие...).
</p>
<Shot src="b3-models.png" wide
caption="Тулбокс — библиотека готовых 3D-моделей" />
<p>
<b>Свои модели.</b> Можно загрузить собственную 3D-модель
в формате <code>.glb</code>. Такие файлы делают в бесплатных
редакторах вроде Blender или берут на сайтах с бесплатными
моделями. После загрузки твоя модель появится в палитре
рядом с остальными.
</p>
<Note>
Не бери модели из других игр (Roblox, Minecraft)
это чужой контент, игру с ним не пропустит модерация.
Бери только бесплатные модели «для свободного использования».
</Note>
</>
),
},
{
id: 'terrain',
title: 'B4. Ландшафт: воксельный и гладкий',
body: (
<>
<p>
<b>Ландшафт</b> это рельеф мира: холмы, горы, ямы, пещеры.
В Рублоксе два режима:
</p>
<ul>
<li>
<b>Воксельный</b> рельеф из маленьких кубиков-вокселей.
Получается «ступенчатый», как в Minecraft. Удобно
рисовать пещеры и обрывы.
</li>
<li>
<b>Гладкий</b> рельеф из плавных холмов без ступенек.
Похоже на настоящую землю.
</li>
</ul>
<p>
Инструменты лепки: <i>поднять</i>, <i>опустить</i>,
<i> разгладить</i>, <i>покрасить</i>. Есть и кисти-растения
рисуешь по земле, и сразу вырастают деревья и трава.
</p>
<Note>
Если в игре есть гладкий ландшафт, ставь точку спавна
прямо на его поверхности. Если спавн окажется ниже земли
игрок при старте провалится. Высоту земли в скрипте можно
узнать командой <code>game.scene.surfaceY(x, z)</code>.
</Note>
</>
),
},
{
id: 'spawn-checkpoints',
title: 'B5. Точка спавна и чекпоинты',
body: (
<>
<p>
<b>Точка спавна</b> место, где игрок появляется в начале
игры и куда возвращается после смерти. Ставится на вкладке
<i> Игра Ставить спавн</i>. Точка спавна одна.
</p>
<p>
<b>Чекпоинт (контрольная точка)</b> промежуточное место
сохранения в длинных уровнях. Когда игрок дошёл до чекпоинта,
при следующей смерти он воскреснет уже там, а не в самом
начале.
</p>
<p>
Чекпоинт делают скриптом на флажке-объекте: когда игрок
касается флажка, скрипт запоминает это место как новый спавн.
</p>
<ScriptKind kind="object" on="флажок-чекпоинт" />
<Code>{`// Когда игрок коснётся флажка —
// это место станет новой точкой возрождения.
game.self.onTouch(() => {
// game.self.position — координаты самого флажка
game.player.setSpawn(game.self.position);
game.ui.showText('Чекпоинт сохранён!', 1.5);
game.sound.play('pickup');
});`}</Code>
<p>
Что тут происходит: <code>onTouch</code> срабатывает,
когда игрок дотронулся до флажка. <code>setSpawn</code>
запоминает точку возрождения. <code>showText</code>
показывает надпись на 1.5 секунды. Готово игрок
не начнёт уровень заново.
</p>
</>
),
},
{
id: 'lamps',
title: 'B6. Лампы (источники света)',
body: (
<>
<p>
<b>Лампа</b> это источник света. Кроме общего солнца,
можно поставить точечные лампы, которые освещают всё рядом
с собой. Они нужны для пещер, ночных уровней, подсветки
важных мест.
</p>
<p>
У лампы настраиваются <b>цвет</b>, <b>яркость</b>
и <b>радиус</b> (как далеко достаёт свет). Лампу можно
создать и из скрипта:
</p>
<ScriptKind kind="global" />
<Code>{`// Создаём тёплую лампу над сценой
game.scene.spawn('light:point', {
x: 0, y: 4, z: 0, // где висит лампа
color: '#ffdd88', // тёплый жёлтый свет
brightness: 2, // яркость
range: 12 // радиус освещения
});`}</Code>
</>
),
},
{
id: 'particles',
title: 'B7. Эмиттеры частиц',
body: (
<>
<p>
<b>Частицы</b> это много маленьких летящих точек:
искры, дым, огонь, магия. Объект, который их создаёт,
называется <b>эмиттер</b>.
</p>
<p>
Частицы делают игру живой: костёр дымит, при победе летит
конфетти, у портала кружится магия. Из скрипта это команда
<code> game.scene.spawnParticles</code>:
</p>
<ScriptKind kind="global" />
<Code>{`// Залп конфетти над центром сцены
game.scene.spawnParticles(
'confetti', // тип эффекта
{ x: 0, y: 3, z: 0 }, // где появятся частицы
{ duration: 2, count: 3 } // длительность и густота
);`}</Code>
<p>
Типы эффектов: <code>fire</code> (огонь),
<code> smoke</code> (дым), <code>sparks</code> (искры),
<code> magic</code> (магия), <code>explosion</code> (взрыв),
<code> confetti</code> (конфетти).
</p>
</>
),
},
{
id: 'triggers',
title: 'B8. Триггеры',
body: (
<>
<p>
<b>Триггер</b> невидимая зона, которая что-то запускает,
когда игрок в неё входит. Например: игрок зашёл в зону
открылась дверь, заиграла музыка, появился враг.
</p>
<p><b>Как сделать триггер:</b></p>
<Step n="1">
Поставь примитив-куб нужного размера это и будет зона.
</Step>
<Step n="2">
В инспекторе <b>выключи столкновение</b> (чтобы игрок
проходил сквозь) и можно выключить <b>видимость</b>
(чтобы зону не было видно).
</Step>
<Step n="3">
Повесь на этот куб скрипт, который ловит касание игрока:
</Step>
<ScriptKind kind="object" on="куб-триггер" />
<Code>{`// Игрок вошёл в зону — показываем надпись
game.self.onTouch(() => {
game.ui.showText('Ты вошёл в опасную зону!', 2);
});
// Игрок вышел из зоны
game.self.onUntouch(() => {
game.ui.showText('Ты в безопасности', 1.5);
});`}</Code>
</>
),
},
{
id: 'object-properties',
title: 'B9. Свойства объекта: цвет, материал, физика, замок',
body: (
<>
<p>
Когда ты выделяешь объект, в <b>Инспекторе</b> (правая
панель снизу) появляются его свойства:
</p>
<Shot src="b9-inspector.png"
caption="Инспектор свойств примитива: позиция, размер, цвет, материал" />
<table className="docTable">
<tbody>
<tr><td><b>Цвет</b></td><td>закрасить примитив в любой цвет</td></tr>
<tr><td><b>Материал</b></td><td>обычный, металл, стекло, неон как объект блестит</td></tr>
<tr><td><b>Текстура</b></td><td>наложить свою картинку на поверхность</td></tr>
<tr><td><b>Столкновение</b></td><td>твёрдый объект или сквозь него можно пройти</td></tr>
<tr><td><b>Видимость</b></td><td>показать или спрятать объект</td></tr>
<tr><td><b>Закреплён</b></td><td>если выключить объект падает под действием физики</td></tr>
<tr><td><b>Замок (Lock)</b></td><td>заблокировать, чтобы случайно не сдвинуть</td></tr>
</tbody>
</table>
<Note>
Свойство «Закреплён» частая причина бага «объект
провалился сквозь пол». Для платформ, стен и декораций
всегда оставляй «Закреплён» включённым. Падать должны
только ящики и мячи, которым это нужно.
</Note>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ C — ИНТЕРФЕЙС ИГРЫ (GUI)
// ════════════════════════════════════════════════════
{
id: 'gui',
icon: 'window',
title: 'Интерфейс игры',
summary: 'GUI: кнопки, надписи, поля ввода, меню и счётчики поверх экрана.',
sections: [
{
id: 'gui-what-is',
title: 'C1. Что такое GUI и редактор интерфейса',
body: (
<>
<p>
<b>GUI</b> (читается «гуи») это интерфейс игры:
всё, что нарисовано поверх 3D-сцены. Кнопки, надписи,
счётчик очков, полоска здоровья, меню это всё GUI.
</p>
<p>
GUI не находится внутри игрового мира он «приклеен»
к экрану. Куда бы ни смотрел игрок, кнопка остаётся
на том же месте экрана.
</p>
<p>
В редакторе есть <b>визуальный редактор UI</b>: ты
перетаскиваешь элементы мышкой, ставишь их в нужное место,
меняешь цвет и текст и сразу видишь результат. Открыть
его можно через инструмент <kbd className="kbd">Интерфейс</kbd>.
</p>
<Note>
Не путай <b>GUI</b> и <b>HUD</b>. GUI это элементы,
которые ты сам нарисовал в редакторе интерфейса.
HUD стандартные счётчики (<code>game.ui.score</code>,
<code> game.ui.timer</code>), которые рисует сама игра
по команде из скрипта.
</Note>
</>
),
},
{
id: 'gui-elements',
title: 'C2. Контейнер, надпись, кнопка, поле ввода, картинка',
body: (
<>
<p>Из чего собирается интерфейс:</p>
<Shot src="c2-gui-palette.png"
caption="Палитра элементов интерфейса — перетащи на экран игры" />
<ul>
<li>
<b>Контейнер (Frame)</b> прямоугольник-коробка.
Сам по себе это просто фон, но внутрь него кладут
другие элементы. Контейнер основа любого меню.
</li>
<li>
<b>Надпись (Label)</b> текст на экране. Счёт, имя
игрока, подсказки.
</li>
<li>
<b>Кнопка (Button)</b> на неё можно нажать. По клику
в скрипте срабатывает действие.
</li>
<li>
<b>Поле ввода (TextBox)</b> сюда игрок печатает текст
или число. Например, ввести код от двери.
</li>
<li>
<b>Картинка (Image)</b> изображение: иконка, логотип,
фон меню.
</li>
</ul>
<p>
<b>Имя элемента.</b> Как и у объектов сцены, у GUI-элемента
есть имя. Скрипт находит элемент по имени командой
<code> game.gui.find('Кнопка старта')</code>. Давай
кнопкам и надписям понятные имена.
</p>
</>
),
},
{
id: 'gui-script',
title: 'C3. Как оживить кнопку скриптом',
body: (
<>
<p>
Нарисованная кнопка сама по себе ничего не делает
нужен скрипт.
</p>
<ScriptKind kind="object" on="кнопку" />
<LangTabs
js={<Code>{`// Скрипт висит на кнопке.
// game.self — это сама кнопка.
game.self.onClick(() => {
game.ui.showText('Кнопка нажата!', 2);
game.sound.play('click');
});`}</Code>}
lua={<Code>{`-- Скрипт висит на кнопке (TextButton)
-- script.Parent — это сама кнопка.
local btn = script.Parent
btn.MouseButton1Click:Connect(function()
print("Кнопка нажата!")
end)`}</Code>}
/>
<p>Можно и наоборот управлять кнопкой из глобального скрипта:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`// Находим кнопку по имени и вешаем на неё клик
const btnId = game.gui.find('Кнопка старта');
game.gui.onClick(btnId, () => {
game.ui.showText('Игра началась!', 2);
game.gui.hide(btnId); // спрятать кнопку после нажатия
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>
JS: <code>game.gui.find</code> ищет элемент по имени.
<code> game.gui.onClick</code> вешает действие, <code>game.gui.hide</code> прячет.
</p>
<p>
Lua: <code>gui:FindFirstChild(name, true)</code> ищет рекурсивно
(третий аргумент <code>true</code> = во вложенных).
<code> MouseButton1Click</code> стандартный сигнал клика на TextButton.
<code> btn.Visible = false</code> прячет элемент.
</p>
</>
),
},
{
id: 'gui-textbox',
title: 'C4. Поле ввода: дверь по коду',
body: (
<>
<p>
<b>Поле ввода</b> позволяет игроку напечатать ответ.
Когда он нажмёт Enter, скрипт получает введённый текст.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`// Игрок вводит код. Правильный код — 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);
}
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>
JS-разбор: <code>onSubmit</code> даёт переменную
<code> text</code> то, что напечатал игрок.
<code> if (text === '1234')</code> проверяем код.
</p>
<p>
Lua-разбор: на TextBox сигнал <code>FocusLost</code>
срабатывает когда поле теряет фокус (Enter или клик мимо).
Текст лежит в <code>box.Text</code>.
</p>
<Note>
Кавычки <code>"1234"</code> означают, что это
<b> текст</b>, а не число. Игрок печатает в поле всегда
текст, поэтому и сравнивать нужно с текстом.
</Note>
</>
),
},
{
id: 'gui-styles',
title: 'C5. Стили и загрузка картинок',
body: (
<>
<p>
У каждого GUI-элемента в инспекторе настраивается внешний
вид: <b>цвет фона</b> и прозрачность, <b>граница</b>
(рамка, её цвет и толщина), <b>скругление углов</b>
(большое скругление делает кнопку «таблеткой»),
<b> тень</b> (мягкая тень под элементом),
<b> цвет и размер текста</b>.
</p>
<p>
В элемент <b>Картинка</b> можно загрузить своё изображение
с компьютера (PNG или JPG): логотип игры, иконку кнопки,
фон главного меню.
</p>
<Note>
Хороший интерфейс это аккуратный интерфейс. Один стиль
для всех кнопок, один шрифт, выровненные отступы. Картинки
бери небольшие огромные файлы делают игру тяжёлой.
</Note>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ D — СКРИПТЫ — ОСНОВЫ
// ════════════════════════════════════════════════════
{
id: 'scripts-basics',
icon: 'code',
title: 'Скрипты — основы',
summary: 'Самый важный раздел: что такое скрипт, переменные, события, таймеры.',
sections: [
{
id: 'js-or-lua',
title: 'D0. Скриптинг: JS или Lua — что выбрать?',
body: (
<>
<p>
В Рублоксе можно писать скрипты <b>на двух языках</b>:
<b> JavaScript</b> и <b>Lua</b>. Оба работают одинаково
хорошо. Игра не отличает их между собой внутри одного
проекта одни скрипты могут быть на JS, другие на Lua,
и они общаются между собой как будто это один язык.
</p>
<p><b>Чем они отличаются?</b></p>
<table className="docTable">
<thead>
<tr><th></th><th>JavaScript</th><th>Lua</th></tr>
</thead>
<tbody>
<tr>
<td><b>Где ещё используется</b></td>
<td>Сайты, мобильные приложения, серверы.
Самый популярный язык в мире.</td>
<td>Roblox, World of Warcraft (моды),
многие игры. Простой и быстрый.</td>
</tr>
<tr>
<td><b>Главный API</b></td>
<td><code>game.*</code> (game.player,
game.log, game.scene)</td>
<td><code>game.*</code> в Roblox-стиле
(game:GetService("Players"), workspace)</td>
</tr>
<tr>
<td><b>Похож на</b></td>
<td>Roblox-LUA если знаешь Roblox</td>
<td>Roblox-Studio те же команды</td>
</tr>
<tr>
<td><b>Когда выбрать</b></td>
<td>Если планируешь делать сайты и приложения
JS пригодится везде.</td>
<td>Если играешь в Roblox и видел там скрипты
Lua тебе знаком.</td>
</tr>
</tbody>
</table>
<p><b>Один и тот же пример на двух языках:</b></p>
<p>Когда игрок касается синего блока печатаем «Привет».</p>
<LangTabs
js={<Code>{`// JS — глобальный скрипт
game.onTouch('синий-блок', (player) => {
game.log('Привет, ' + player.name + '!');
});`}</Code>}
lua={<Code>{`-- 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)`}</Code>}
/>
<p>
Видишь оба варианта делают <b>одно и то же</b>. Но
запись отличается. JS короче для простых вещей через
<code>game.onTouch</code>, Lua даёт точный Roblox-стиль
через <code>:Connect</code> и события.
</p>
<p><b>Что выбирать новичку?</b></p>
<Note>
Если ты <b>совсем новичок</b> бери <b>JavaScript</b>.
Команды <code>game.*</code> в JS короче и проще читать.
Большая часть уроков в этой вике написана с примерами
на JS все они работают, просто копируй.
</Note>
<Note>
Если ты <b>уже играл в Roblox</b> и видел там скрипты
бери <b>Lua</b>. Команды почти один в один как
в Roblox Studio: <code>game:GetService</code>,
<code>:Connect</code>, <code>workspace</code>,
<code>script.Parent</code>.
</Note>
<p><b>Можно ли менять язык в одном скрипте?</b></p>
<p>
Да. В редакторе скрипта вверху есть две кнопки
<b>JS</b> и <b>Lua</b>. Просто нажми на нужную.
Твой код на текущем языке <b>сохранится</b>, а
на другом языке откроется пустой шаблон или то, что
ты писал там раньше. Никогда ничего не теряется.
</p>
<Try>
Создай новый скрипт. Напиши пару строк на JS. Нажми
кнопку <b>Lua</b> JS-код спрячется, появится
Lua-шаблон. Напиши там что-то. Нажми <b>JS</b>
обратно твой JS-код вернётся. Магия!
</Try>
<p><b>А что под капотом?</b></p>
<p>
JS-скрипты исполняются в <code>WebWorker</code>
это отдельный поток в браузере, чтобы скрипт не
тормозил саму игру. Lua-скрипты исполняются через
<b> wasmoon</b> Lua-интерпретатор, скомпилированный
в WebAssembly. Оба варианта работают на любом
устройстве без установки.
</p>
</>
),
},
{
id: 'what-is-script',
title: 'D1. Что такое скрипт и как его создать',
body: (
<>
<p>
<b>Скрипт</b> это набор команд, который оживляет игру.
Блоки и модели сами по себе просто стоят. Чтобы монетка
собиралась, дверь открывалась, а враг гнался за игроком
нужен скрипт.
</p>
<p>
Скрипты пишут на одном из двух языков: <b>JavaScript</b>
или <b>Lua</b>. Оба работают одинаково. Подробнее про
выбор языка статья <b>D0</b> выше. В этом уроке покажем
пример на обоих языках переключай вкладки, чтобы видеть
нужный.
</p>
<p><b>Как создать первый скрипт:</b></p>
<Step n="1">
В иерархии (правая панель) найди категорию <b>Скрипты</b>
и нажми кнопку «+».
</Step>
<Shot src="d1-scripts-tree.png"
caption="Категория «Скрипты» в иерархии объектов" />
<Step n="2">
Откроется окно кода. Вверху выбери язык (кнопки JS/Lua)
и напиши одну строку:
</Step>
<LangTabs
js={<Code>{`game.log('Привет! Игра запустилась.');`}</Code>}
lua={<Code>{`print("Привет! Игра запустилась.")`}</Code>}
/>
<Shot src="d5-script-editor.png" wide
caption="Окно редактора кода скрипта" />
<Step n="3">
Нажми <kbd className="kbd">Играть</kbd>. Внизу справа
открой <b>Консоль</b> там появится твоё сообщение.
</Step>
<p>
Это твой первый работающий скрипт.
</p>
<LangTabs
js={<Note>
Команда <code>game.log(...)</code> печатает в консоль.
Каждая команда в JavaScript заканчивается точкой с запятой
<code> ;</code> как точка в предложении. Текст пишут
в кавычках: <code>'привет'</code>.
</Note>}
lua={<Note>
Команда <code>print(...)</code> печатает в консоль.
В Lua точку с запятой ставить <b>не нужно</b>.
Текст пишут в кавычках: <code>"привет"</code> или
<code> 'привет'</code>. Lua использует <code>..</code>
(две точки) для склейки текста: <code>"А=" .. 5</code>.
</Note>}
/>
</>
),
},
{
id: 'global-vs-object',
title: 'D2. Глобальный скрипт и скрипт на объекте',
body: (
<>
<p>
Это очень важно понять с самого начала. Скрипты бывают
<b> двух видов</b>, и в каждом уроке вики написано, какой
именно нужен.
</p>
<ScriptKind kind="global" />
<p>
Глобальный скрипт это «мозг игры». Он не привязан
ни к чему. Запускается один раз при старте. В нём пишут
общие правила: подсчёт очков, таймер уровня, проверку
победы.
</p>
<ScriptKind kind="object" on="любой объект" />
<p>
Скрипт на объекте относится к конкретному кубу, модели
или кнопке. Внутри такого скрипта работает волшебное слово
(своё для каждого языка):
</p>
<LangTabs
js={<>
<Code>{`// JS: game.self — это и есть тот объект, на котором висит скрипт
game.self.onClick(() => {
game.log('Кликнули по мне!');
});`}</Code>
</>}
lua={<>
<Code>{`-- Lua: script.Parent — это и есть тот объект, на котором висит скрипт
local part = script.Parent
part.Touched:Connect(function(hit)
print("Касание объекта " .. part.Name)
end)`}</Code>
</>}
/>
<p>
<b>Как привязать скрипт к объекту:</b> выдели объект
на сцене, потом создай скрипт он автоматически привяжется
к выделенному объекту. Или укажи носителя в настройках
скрипта.
</p>
<LangTabs
js={<Note>
Простое правило: если в коде урока есть
<code> game.self</code> это скрипт <b>на объекте</b>.
Если <code>game.self</code> нет скрипт <b>глобальный</b>.
Плашка в начале каждого урока всегда подскажет.
</Note>}
lua={<Note>
Простое правило: если в коде есть
<code> script.Parent</code> это скрипт <b>на объекте</b>.
Если только <code>game</code> глобальный.
В Lua глобальные скрипты обычно работают со списком
игроков: <code>game:GetService("Players")</code>.
</Note>}
/>
</>
),
},
{
id: 'variables',
title: 'D3. Переменные — память скрипта',
body: (
<>
<p>
<b>Переменная</b> это «коробочка с именем», в которой
скрипт хранит значение. Например, количество очков,
имя игрока, выбранный уровень.
</p>
<LangTabs
js={<Code>{`// JS: создаём переменную через let
let score = 0;
// Меняем значение
score = score + 10; // теперь в score лежит 10
score = score + 5; // теперь 15
game.log('Очков:', score); // напечатает: Очков: 15`}</Code>}
lua={<Code>{`-- Lua: создаём переменную через local
local score = 0
-- Меняем значение
score = score + 10 -- теперь в score лежит 10
score = score + 5 -- теперь 15
print("Очков:", score) -- напечатает: Очков: 15`}</Code>}
/>
<LangTabs
js={<p>
<code>let</code> это слово «создать переменную». Пишут
его только <b>один раз</b>, когда коробочку заводят. Дальше
меняют значение уже без <code>let</code>.
</p>}
lua={<p>
<code>local</code> это слово «создать переменную внутри
скрипта». Пишут его только <b>один раз</b>. Если опустить
<code> local</code> переменная станет глобальной (доступна
всем скриптам), это редко нужно.
</p>}
/>
<p>В переменную можно класть не только числа:</p>
<LangTabs
js={<Code>{`let name = 'Герой'; // текст — в кавычках
let isWin = false; // да/нет — true или false
let coinCount = 0; // число — без кавычек`}</Code>}
lua={<Code>{`local name = "Герой" -- текст — в кавычках
local isWin = false -- да/нет — true или false
local coinCount = 0 -- число — без кавычек`}</Code>}
/>
<LangTabs
js={<Note>
Если значение <b>никогда</b> не меняется вместо
<code> let</code> можно писать <code>const</code>
(«постоянная»). Например, найденную один раз дверь:
<code> const door = game.scene.findOne('Дверь');</code>
</Note>}
lua={<Note>
В Lua нет отдельного слова для «не-меняющейся» переменной
всё через <code>local</code>. Если хочешь явно показать
что значение не меняется, пиши имя ЗАГЛАВНЫМИ:
<code> local DOOR = workspace.Дверь</code>.
Это договорённость, а не правило Lua.
</Note>}
/>
</>
),
},
{
id: 'game-object',
title: 'D4. Объект game — главный инструмент',
body: (
<>
<p>
В каждом скрипте есть одно главное волшебное слово
<code> game</code>. Через него ты управляешь всей игрой.
Но «отделы» у JS и Lua разные:
</p>
<LangTabs
js={<>
<table className="docTable">
<tbody>
<tr><td><code>game.player</code></td><td>управление игроком</td></tr>
<tr><td><code>game.scene</code></td><td>объекты сцены</td></tr>
<tr><td><code>game.ui</code></td><td>счётчики и текст на экране</td></tr>
<tr><td><code>game.gui</code></td><td>кнопки и меню</td></tr>
<tr><td><code>game.sound</code></td><td>звуки</td></tr>
<tr><td><code>game.physics</code></td><td>лучи, импульсы, взрывы</td></tr>
<tr><td><code>game.self</code></td><td>объект-носитель скрипта</td></tr>
</tbody>
</table>
<p>
Запись через точку читается слева направо.
<code> game.player.teleport(0, 5, 0)</code> читается так:
«у <b>игры</b>, у <b>игрока</b>, выполни <b>телепорт</b>
в точку 0, 5, 0».
</p>
</>}
lua={<>
<table className="docTable">
<tbody>
<tr><td><code>workspace</code></td><td>3D-объекты сцены</td></tr>
<tr><td><code>game:GetService("Players")</code></td><td>список игроков</td></tr>
<tr><td><code>game.Workspace</code></td><td>то же что workspace</td></tr>
<tr><td><code>script.Parent</code></td><td>объект-носитель скрипта</td></tr>
<tr><td><code>game:GetService("RunService")</code></td><td>каждый кадр (Heartbeat)</td></tr>
<tr><td><code>game:GetService("UserInputService")</code></td><td>клавиши и мышь</td></tr>
<tr><td><code>game:GetService("TweenService")</code></td><td>плавные движения</td></tr>
</tbody>
</table>
<p>
Знак <code>:</code> (двоеточие) в Lua это вызов
метода объекта. <code>game:GetService("Players")</code>
читается так: «у <b>game</b> вызови <b>GetService</b>
и дай ему текст <b>Players</b>».
</p>
<p>
Точка <code>.</code> это доступ к полю объекта.
<code> workspace.Floor.BrickColor</code> у workspace
взять Floor, у него взять BrickColor.
</p>
</>}
/>
<p>
Полный список всех команд в Справочнике (раздел H). Не нужно
его заучивать: при наборе кода редактор сам показывает подсказки.
</p>
</>
),
},
{
id: 'log-console',
title: 'D5. log/print, консоль, отладка',
body: (
<>
<p>
<b>Консоль</b> окошко в правом нижнем углу редактора.
Туда выводятся все сообщения и ошибки скриптов.
</p>
<LangTabs
js={<p>
Команда <code>game.log(...)</code> печатает в консоль
что угодно. Это главный инструмент <b>отладки</b>
проверки, что код работает правильно:
</p>}
lua={<p>
Команда <code>print(...)</code> печатает в консоль
что угодно. Это главный инструмент <b>отладки</b>
проверки, что код работает правильно:
</p>}
/>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`let score = 0;
score = score + 10;
game.log('Очки сейчас:', score); // Очки сейчас: 10
let pos = game.player.position;
game.log('Игрок стоит в точке:', pos);`}</Code>}
lua={<Code>{`local score = 0
score = score + 10
print("Очки сейчас:", score) -- Очки сейчас: 10
local pos = game.Players.LocalPlayer.Character.HumanoidRootPart.Position
print("Игрок стоит в точке:", pos)`}</Code>}
/>
<LangTabs
js={<p>
Если игра ведёт себя странно расставь
<code> game.log</code> по коду и посмотри, какие значения
печатаются. Так ты увидишь, где именно что-то пошло не так.
</p>}
lua={<p>
Если игра ведёт себя странно расставь
<code> print</code> по коду и посмотри, какие значения
печатаются. Так ты увидишь, где именно что-то пошло не так.
</p>}
/>
<Note>
Если в скрипте опечатка текст ошибки появится
в Консоли <b>красным</b>, и там же будет написан номер
строки с ошибкой. Всегда заглядывай в Консоль первым делом.
</Note>
</>
),
},
{
id: 'events',
title: 'D6. События: тик, клавиши, клик, касание',
body: (
<>
<p>
<b>Событие</b> это «что-то случилось». Скрипт может
ждать событие и реагировать на него. Самые важные:
</p>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.onTick(fn)</code></td><td>каждый кадр (60 раз в секунду)</td></tr>
<tr><td><code>game.onKey('space', fn)</code></td><td>игрок нажал клавишу</td></tr>
<tr><td><code>game.self.onClick(fn)</code></td><td>игрок кликнул по объекту</td></tr>
<tr><td><code>game.self.onTouch(fn)</code></td><td>игрок коснулся объекта</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>RunService.Heartbeat:Connect(fn)</code></td><td>каждый кадр</td></tr>
<tr><td><code>UserInputService.InputBegan:Connect(fn)</code></td><td>любая клавиша</td></tr>
<tr><td><code>part.ClickDetector.MouseClick:Connect(fn)</code></td><td>клик по объекту</td></tr>
<tr><td><code>part.Touched:Connect(fn)</code></td><td>касание объекта</td></tr>
</tbody>
</table>}
/>
<p>Пример куб, который исчезает по клику:</p>
<ScriptKind kind="object" on="куб" />
<LangTabs
js={<Code>{`game.self.onClick(() => {
game.self.delete(); // удалить сам себя
game.log('Куб удалён!');
});`}</Code>}
lua={<Code>{`local part = script.Parent
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function(player)
part:Destroy() -- удалить сам себя
print("Куб удалён!")
end)`}</Code>}
/>
<LangTabs
js={<p>
<b>Что такое <code>{`() => { ... }`}</code>?</b> Это
«функция» набор команд, упакованных вместе. Команды
внутри фигурных скобок выполнятся <b>не сразу</b>, а только
когда случится событие. То есть «когда кликнули тогда
удалить и напечатать».
</p>}
lua={<p>
<b>Что такое <code>function() ... end</code>?</b> Это
«функция» набор команд, упакованных вместе. Команды
между <code>function()</code> и <code>end</code> выполнятся
<b> не сразу</b>, а только когда случится событие. То есть
«когда кликнули тогда удалить и напечатать».
Метод <code>:Connect</code> «подключает» функцию
к событию.
</p>}
/>
<LangTabs
js={<Note>
<code>onTick</code> выполняется ОЧЕНЬ часто 60 раз
в секунду. Не делай внутри него тяжёлых вещей. Подробнее
об этой ошибке раздел J4.
</Note>}
lua={<Note>
<code>Heartbeat</code> выполняется ОЧЕНЬ часто 60 раз
в секунду. Не делай внутри тяжёлых вычислений. Подробнее
об этой ошибке раздел J4.
</Note>}
/>
</>
),
},
{
id: 'conditions',
title: 'D7. Условия: if / else',
body: (
<>
<p>
<b>Условие</b> это развилка: «<b>если</b> что-то верно
сделай одно, <b>иначе</b> другое». В обоих языках это
слова <code>if</code> («если») и <code>else</code>
(«иначе»), но запись чуть отличается.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`let coins = 7;
if (coins >= 10) {
game.ui.showText('Хватает на покупку!', 2);
} else {
game.ui.showText('Нужно больше монет', 2);
}`}</Code>}
lua={<Code>{`local coins = 7
if coins >= 10 then
print("Хватает на покупку!")
else
print("Нужно больше монет")
end`}</Code>}
/>
<p>
Тут проверяется: <code>coins {'>'}= 10</code> «монет
10 или больше?». Сейчас монет 7, значит условие неверно,
и сработает ветка <code>else</code>.
</p>
<p><b>Знаки сравнения:</b></p>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>a === b</code></td><td>a равно b</td></tr>
<tr><td><code>a !== b</code></td><td>a не равно b</td></tr>
<tr><td><code>a {'>'} b</code></td><td>a больше b</td></tr>
<tr><td><code>a {'<'} b</code></td><td>a меньше b</td></tr>
<tr><td><code>a {'>'}= b</code></td><td>a больше или равно b</td></tr>
<tr><td><code>a {'<'}= b</code></td><td>a меньше или равно b</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>a == b</code></td><td>a равно b</td></tr>
<tr><td><code>a ~= b</code></td><td>a не равно b</td></tr>
<tr><td><code>a {'>'} b</code></td><td>a больше b</td></tr>
<tr><td><code>a {'<'} b</code></td><td>a меньше b</td></tr>
<tr><td><code>a {'>'}= b</code></td><td>a больше или равно b</td></tr>
<tr><td><code>a {'<'}= b</code></td><td>a меньше или равно b</td></tr>
</tbody>
</table>}
/>
<LangTabs
js={<Note>
В JS для проверки «равно» пишут <b>три</b> знака равенства
<code> ===</code>, а не один. Один знак <code>=</code>
это «положить значение в переменную», совсем другое
действие. И не равно это <code>!==</code>.
</Note>}
lua={<Note>
В Lua для проверки «равно» пишут <b>два</b> знака равенства
<code> ==</code>. А «не равно» это <code>~=</code>
(тильда + равно). Запомни этот значок он встречается
только в Lua.
</Note>}
/>
</>
),
},
{
id: 'timers',
title: 'D8. Таймеры: задержка, повтор, отмена',
body: (
<>
<p>
Таймеры запускают команды <b>не сразу, а потом</b>:
</p>
<LangTabs
js={<ul>
<li>
<code>game.after(сек, fn)</code> выполнить
<b> один раз</b> через несколько секунд;
</li>
<li>
<code>game.every(сек, fn)</code> выполнять
<b> снова и снова</b> каждые несколько секунд;
</li>
<li>
<code>game.cancel(id)</code> остановить таймер.
</li>
</ul>}
lua={<ul>
<li>
<code>task.delay(сек, fn)</code> выполнить
<b> один раз</b> через несколько секунд;
</li>
<li>
<code>task.wait(сек)</code> приостановить скрипт
на N секунд (внутри цикла или функции);
</li>
<li>
Для повторяющихся таймеров обычный цикл
<code> while true do task.wait(1); ... end</code>
в отдельной корутине через <code>task.spawn</code>.
</li>
</ul>}
/>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`// Через 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);
});`}</Code>}
lua={<Code>{`-- Через 3 секунды показать текст
task.delay(3, function()
print("Игра началась!")
end)
-- Каждую секунду прибавлять очко.
-- Запускаем в отдельной корутине чтобы не блокировать скрипт.
local score = 0
local running = true
task.spawn(function()
while running do
task.wait(1)
score = score + 1
end
end)
-- Через 10 секунд остановить начисление очков
task.delay(10, function()
running = false
print("Время вышло! Набрано очков:", score)
end)`}</Code>}
/>
<LangTabs
js={<p>
Запись <code>(game.ui.score || 0)</code> читается так:
«возьми счёт, а если его ещё нет возьми 0». Это защита
от ошибки в самом начале, когда счётчик ещё пустой.
</p>}
lua={<p>
В Lua переменная <code>running</code> флаг работы цикла.
Когда нужно остановить таймер, ставим <code>running = false</code>,
и цикл сам завершится после <code>task.wait(1)</code>.
Это проще, чем хранить номер таймера.
</p>}
/>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ E — СКРИПТЫ — ДВИЖЕНИЕ И АНИМАЦИЯ
// ════════════════════════════════════════════════════
{
id: 'scripts-motion',
icon: 'run',
title: 'Движение и анимация',
summary: 'Управление игроком, плавные твины, спавн и перемещение объектов.',
sections: [
{
id: 'player-control',
title: 'E1. Управление игроком: скорость, прыжок, гравитация',
body: (
<>
<p>Скриптом можно менять, как двигается игрок.</p>
<LangTabs
js={<>
<p>В JS используем команды-«множители»: 1 обычно,
2 в два раза сильнее, 0.5 в два раза слабее.</p>
<table className="docTable">
<tbody>
<tr><td><code>game.player.setSpeed(mul)</code></td><td>скорость бега</td></tr>
<tr><td><code>game.player.setJumpPower(mul)</code></td><td>сила прыжка</td></tr>
<tr><td><code>game.player.setGravityMul(mul)</code></td><td>сила притяжения</td></tr>
<tr><td><code>game.player.setDoubleJump(true)</code></td><td>двойной прыжок</td></tr>
<tr><td><code>game.player.teleport(x,y,z)</code></td><td>мгновенно переставить</td></tr>
</tbody>
</table>
</>}
lua={<>
<p>В Lua скорость и прыжок это <b>прямые значения</b>
в Humanoid (не множители). По умолчанию WalkSpeed=16,
JumpPower=50.</p>
<table className="docTable">
<tbody>
<tr><td><code>humanoid.WalkSpeed = 32</code></td><td>скорость (16 = норма)</td></tr>
<tr><td><code>humanoid.JumpPower = 80</code></td><td>сила прыжка (50 = норма)</td></tr>
<tr><td><code>workspace.Gravity = 100</code></td><td>гравитация (196 = норма)</td></tr>
<tr><td><code>humanoid:ChangeState(Enum.HumanoidStateType.Jumping)</code></td><td>прыгнуть</td></tr>
<tr><td><code>hrp.CFrame = CFrame.new(x,y,z)</code></td><td>телепорт</td></tr>
</tbody>
</table>
</>}
/>
<p>Пример «зелье скорости» при касании сферы:</p>
<ScriptKind kind="object" on="сфера-зелье" />
<LangTabs
js={<Code>{`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);
});
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<Note>
Не забывай возвращать скорость обратно. Иначе игрок
останется быстрым навсегда а это может сломать твой уровень.
</Note>
</>
),
},
{
id: 'player-animations',
title: 'E2. Анимации-эмоции персонажа',
body: (
<>
<p>Персонаж умеет показывать эмоции.</p>
<LangTabs
js={<p>
Команда <code>game.player.playAnimation(имя)</code> проигрывает
анимацию: <code>'wave'</code> (помахать),
<code> 'dance'</code> (танец), <code>'cheer'</code>
(радость), <code>'sit'</code> (сесть).
</p>}
lua={<p>
В Lua анимации проигрываются через Animator на Humanoid'е.
Roblox-стиль: создать Animation-объект, вызвать
<code> Animator:LoadAnimation(anim)</code>,
потом <code>track:Play()</code>.
</p>}
/>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`// При победе персонаж радуется
game.player.playAnimation('cheer');
// Через 3 секунды перестать
game.after(3, () => {
game.player.stopAnimation();
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
{
id: 'tweens',
title: 'E3. Твины плавные движения',
body: (
<>
<p>
<b>Твин</b> — это плавное изменение чего-либо за время.
Если просто переставить объект — он телепортируется рывком.
А твин <b>плавно</b> доедет из точки в точку.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>Команда: <code>game.tween(объект, что менять, настройки)</code></p>
<Code>{`// Находим платформу-лифт по имени
const lift = game.scene.findOne('Лифт');
// Платформа за 2 секунды плавно поднимается на высоту 10
game.tween(lift, { y: 10 }, {
duration: 2, // длительность в секундах
easing: 'ease' // характер движения
});`}</Code>
<p><b>Полезные настройки твина:</b></p>
<table className="docTable">
<tbody>
<tr><td><code>duration</code></td><td>сколько секунд длится</td></tr>
<tr><td><code>easing</code></td><td>'linear' / 'ease' / 'bounce'</td></tr>
<tr><td><code>repeat</code></td><td>сколько раз повторить</td></tr>
<tr><td><code>yoyo: true</code></td><td>двигаться туда-обратно</td></tr>
<tr><td><code>onDone</code></td><td>что сделать, когда твин закончится</td></tr>
</tbody>
</table>
<Code>{`// Платформа вечно ездит вверх-вниз
const plat = game.scene.findOne('Качалка');
game.tween(plat, { y: 8 }, {
duration: 2,
yoyo: true,
repeat: 999
});`}</Code>
</>}
lua={<>
<p>В Lua используется <b>TweenService</b> — встроенный
сервис Roblox-стиля. Создаёшь TweenInfo и Tween, вызываешь Play.</p>
<Code>{`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()`}</Code>
<p><b>Полезные настройки TweenInfo:</b></p>
<table className="docTable">
<tbody>
<tr><td>1-й арг — секунды</td><td>сколько длится</td></tr>
<tr><td>EasingStyle</td><td>Linear / Quad / Bounce / Elastic</td></tr>
<tr><td>EasingDirection</td><td>In / Out / InOut</td></tr>
<tr><td>repeatCount</td><td>сколько раз повторить (-1 = бесконечно)</td></tr>
<tr><td>reverses</td><td>true = туда-обратно</td></tr>
<tr><td>tween.Completed:Connect</td><td>событие «закончен»</td></tr>
</tbody>
</table>
<Code>{`-- Платформа вечно ездит вверх-вниз
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()`}</Code>
</>}
/>
</>
),
},
{
id: 'spawn-delete',
title: 'E4. Спавн и удаление объектов',
body: (
<>
<p>
<b>Спавн</b> — создание нового объекта прямо во время игры.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>Команда <code>game.scene.spawn(тип, настройки)</code>:</p>
<Code>{`// Создаём золотую монетку-сферу
const coin = game.scene.spawn('primitive:sphere', {
x: 5, y: 1, z: 0, // где появится
color: '#ffd700' // золотой цвет
});
game.log('Создали монетку, её адрес:', coin);`}</Code>
<p>
Тип бывает <code>'block:трава'</code>,
<code> 'primitive:cube'</code>, <code>'model:tree'</code>.
Команда <b>возвращает ref</b> — «адрес» объекта,
по которому к нему можно обращаться.
</p>
<p><b>Удаление объекта:</b></p>
<Code>{`game.scene.delete(coin); // сразу
game.scene.deleteAfter(coin, 3); // через 3 секунды`}</Code>
<Note>
Запоминай <code>ref</code> в переменную. Без адреса
ты потом не сможешь объект ни подвинуть, ни удалить.
</Note>
</>}
lua={<>
<p>Команда <code>Instance.new("Part")</code> создаёт новый Part:</p>
<Code>{`-- Создаём золотую монетку-сферу
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)`}</Code>
<p>
Чтобы объект появился в игре — обязательно ставь
<code> .Parent = workspace</code>.
<code> Anchored = true</code> — чтобы не падал.
</p>
<p><b>Удаление объекта:</b></p>
<Code>{`coin:Destroy() -- сразу
-- через 3 секунды
game:GetService("Debris"):AddItem(coin, 3)`}</Code>
<Note>
Сохраняй ссылку на объект в переменную (<code>local coin = ...</code>).
Без неё ты потом не сможешь объект ни подвинуть, ни удалить.
</Note>
</>}
/>
</>
),
},
{
id: 'move-objects',
title: 'E5. Перемещение объектов',
body: (
<>
<p>Передвинуть объект скриптом можно несколькими способами:</p>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.scene.move(ref,x,y,z)</code></td><td>мгновенно переставить</td></tr>
<tr><td><code>game.scene.rotate(ref,угол)</code></td><td>повернуть</td></tr>
<tr><td><code>game.self.move(x,y,z)</code></td><td>скрипт двигает сам себя</td></tr>
<tr><td><code>game.tween(...)</code></td><td>плавное перемещение (E3)</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>part.Position = Vector3.new(x,y,z)</code></td><td>мгновенно переставить</td></tr>
<tr><td><code>part.CFrame = part.CFrame * CFrame.Angles(0, math.rad(45), 0)</code></td><td>повернуть</td></tr>
<tr><td><code>script.Parent.Position = ...</code></td><td>скрипт двигает сам себя</td></tr>
<tr><td><code>TweenService:Create(...)</code></td><td>плавное перемещение (E3)</td></tr>
</tbody>
</table>}
/>
<p>Пример — дверь уезжает вверх и освобождает проход:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`const door = game.scene.findOne('Дверь');
// плавно поднимаем дверь на 6 единиц вверх
game.tween(door, { y: 6 }, { duration: 1 });`}</Code>}
lua={<Code>{`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()`}</Code>}
/>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ F — СКРИПТЫ — ИГРОВАЯ ЛОГИКА
// ════════════════════════════════════════════════════
{
id: 'scripts-logic',
icon: 'target',
title: 'Игровая логика',
summary: 'HP и урон, физика, теги, взаимодействие по E, связи между объектами.',
sections: [
{
id: 'player-hp',
title: 'F1. HP игрока: урон, лечение, смерть, чекпоинт',
body: (
<>
<p>Команды для здоровья игрока:</p>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.player.hp</code></td><td>текущее здоровье</td></tr>
<tr><td><code>game.player.damage(n)</code></td><td>нанести урон</td></tr>
<tr><td><code>game.player.heal(n)</code></td><td>вылечить</td></tr>
<tr><td><code>game.player.kill()</code></td><td>мгновенно убить</td></tr>
<tr><td><code>game.player.respawn()</code></td><td>воскресить на спавне</td></tr>
<tr><td><code>game.player.setSpawn(точка)</code></td><td>новая точка возрождения</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>humanoid.Health</code></td><td>текущее здоровье</td></tr>
<tr><td><code>humanoid:TakeDamage(n)</code></td><td>нанести урон</td></tr>
<tr><td><code>humanoid.Health = humanoid.Health + n</code></td><td>вылечить</td></tr>
<tr><td><code>humanoid.Health = 0</code></td><td>мгновенно убить</td></tr>
<tr><td><code>player:LoadCharacter()</code></td><td>воскресить</td></tr>
<tr><td><code>player.RespawnLocation = spawn</code></td><td>новая точка возрождения</td></tr>
</tbody>
</table>}
/>
<p><b>Пример 1 — шипы наносят урон:</b></p>
<ScriptKind kind="object" on="шипы (конус)" />
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.damage(20); // отнять 20 здоровья
game.sound.play('hit');
});`}</Code>}
lua={<Code>{`local part = script.Parent
part.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then
humanoid:TakeDamage(20) -- отнять 20 здоровья
end
end)`}</Code>}
/>
<p><b>Пример 2 — аптечка лечит:</b></p>
<ScriptKind kind="object" on="аптечку" />
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.heal(50); // добавить 50 здоровья
game.ui.showText('+50 HP', 1.5);
game.self.delete(); // аптечка исчезает
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
{
id: 'physics',
title: 'F2. Физика: raycast, импульсы, взрывы',
body: (
<>
<LangTabs
js={<>
<p>Отдел <code>game.physics</code> отвечает за «настоящую» физику:</p>
<ul>
<li><code>raycast(откуда, куда, опции)</code> — луч для стрельбы;</li>
<li><code>applyImpulse(ref, сила)</code> — толкнуть объект;</li>
<li><code>explode(точка, радиус, опции)</code> — взрыв.</li>
</ul>
</>}
lua={<>
<p>В Lua для физики используется <code>workspace</code> и стандартный Roblox API:</p>
<ul>
<li><code>workspace:Raycast(origin, dir, params)</code> — луч;</li>
<li><code>part:ApplyImpulse(Vector3)</code> — толкнуть Part;</li>
<li><code>Instance.new("Explosion")</code> — создать взрыв.</li>
</ul>
</>}
/>
<p><b>Пример — стрельба лучом из камеры игрока:</b></p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`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');
}
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<LangTabs
js={<p>
<code>hit.hit</code> — попал ли луч во что-нибудь.
<code> hit.ref</code> — адрес объекта.
</p>}
lua={<p>
<code>raycastResult</code> равно <code>nil</code> если
луч ни во что не попал. Иначе у него есть поля
<code> .Instance</code> (что попало),
<code> .Position</code> (точка попадания),
<code> .Normal</code> (нормаль поверхности).
</p>}
/>
</>
),
},
{
id: 'attributes',
title: 'F3. Атрибуты объектов',
body: (
<>
<p>
<b>Атрибут</b> — это значение, которое ты «приклеиваешь»
к объекту. Например, сколько здоровья у конкретного врага
или сколько монет стоит товар.
</p>
<ScriptKind kind="object" on="товар в магазине" />
<LangTabs
js={<Code>{`// При старте игры запоминаем цену прямо на товаре
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);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>
Чем атрибут лучше обычной переменной? Переменная одна
на весь скрипт. А атрибут — свой у каждого объекта.
Один и тот же скрипт можно повесить на 10 разных товаров,
и у каждого будет своя цена.
</p>
</>
),
},
{
id: 'tags',
title: 'F4. Теги объектов',
body: (
<>
<p>
<b>Тег</b> — это «ярлык» на объекте. Удобно ставить сразу на
много объектов и потом одной командой находить их все.
</p>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.scene.tag(ref, 'звезда')</code></td><td>повесить тег</td></tr>
<tr><td><code>game.scene.untag(ref, 'звезда')</code></td><td>снять тег</td></tr>
<tr><td><code>game.scene.hasTag(ref, 'звезда')</code></td><td>есть ли тег</td></tr>
<tr><td><code>game.scene.getTagged('звезда')</code></td><td>все объекты с тегом</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>CollectionService:AddTag(part, "звезда")</code></td><td>повесить тег</td></tr>
<tr><td><code>CollectionService:RemoveTag(part, "звезда")</code></td><td>снять тег</td></tr>
<tr><td><code>CollectionService:HasTag(part, "звезда")</code></td><td>есть ли тег</td></tr>
<tr><td><code>CollectionService:GetTagged("звезда")</code></td><td>все объекты с тегом</td></tr>
</tbody>
</table>}
/>
<p><b>Пример — игра «собери все звёзды»:</b></p>
<ScriptKind kind="object" on="каждую звезду" />
<LangTabs
js={<Code>{`// Этот скрипт висит на звезде.
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);
}
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<Note>
Снятие тега убирает только ярлык. Цвет, размер и другие
свойства объекта при этом не меняются.
</Note>
</>
),
},
{
id: 'proximity',
title: 'F5. Взаимодействие по клавише E (ProximityPrompt)',
body: (
<>
<p>
Часто игра просит «подойди и нажми E»: открыть сундук,
поговорить с торговцем, дёрнуть рычаг.
</p>
<ScriptKind kind="object" on="сундук" />
<LangTabs
js={<Code>{`game.self.onInteract(() => {
game.ui.showText('Сундук открыт!', 2);
game.scene.spawnParticles('sparks',
game.self.position, { duration: 1 });
game.sound.play('pickup');
}, {
text: 'Открыть сундук', // подсказка над объектом
distance: 4 // на сколько метров подойти
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>
Когда игрок подойдёт на расстояние взаимодействия, над
объектом появится подсказка с текстом. Нажатие
<kbd className="kbd">E</kbd> запустит функцию.
</p>
</>
),
},
{
id: 'billboard',
title: 'F6. Billboard-метки над объектами',
body: (
<>
<p>
<b>Billboard</b> — это текст-табличка, которая висит
над объектом в 3D-мире и всегда повёрнута к игроку.
Так показывают имена врагов, их HP, названия мест.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`// npc — это адрес созданного NPC.
game.scene.setLabel(npc.ref, 'Торговец Боб', {
color: '#ffffff',
height: 2.5 // на 2.5 метра над объектом
});
// Позже можно убрать табличку
game.scene.clearLabel(npc.ref);`}</Code>}
lua={<Code>{`-- 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()`}</Code>}
/>
</>
),
},
{
id: 'pass-through',
title: 'F7. Проходимость объектов',
body: (
<>
<p>
Иногда стена должна стать проходимой — призрачная стена,
секретный проход, исчезающий мост.
</p>
<ScriptKind kind="object" on="секретную стену" />
<LangTabs
js={<Code>{`game.self.onClick(() => {
game.physics.passThrough(game.self.ref, true);
game.scene.setOpacity(game.self.ref, 0.3); // полупрозрачная
game.ui.showText('Секретный проход открыт!', 2);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<Note>
Если сделать стену снова твёрдой, пока игрок стоит внутри
неё — игра аккуратно вытолкнет его наружу, он не застрянет.
</Note>
</>
),
},
{
id: 'constraints',
title: 'F8. Связи: склейка, петля, пружина',
body: (
<>
<p>
<b>Связи (constraints)</b> соединяют объекты, чтобы они
двигались вместе или по правилам физики.
</p>
<ul>
<li><b>Склейка (weld)</b> — намертво приклеивает один объект к другому;</li>
<li><b>Петля (hinge)</b> — объект вращается вокруг оси, как дверь или качели;</li>
<li><b>Пружина (spring)</b> — объект упруго колеблется, как батут.</li>
</ul>
<p><b>Пример — качели на петле:</b></p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`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;
});`}</Code>}
lua={<Code>{`-- В 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`}</Code>}
/>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ G — СКРИПТЫ — БОЛЬШИЕ СИСТЕМЫ
// ════════════════════════════════════════════════════
{
id: 'scripts-systems',
icon: 'gear',
title: 'Большие системы',
summary: 'NPC, инвентарь и оружие, звук, камера и катсцены, мультиплеер.',
sections: [
{
id: 'npc',
title: 'G1. NPC: создание, движение, диалоги',
body: (
<>
<p>
<b>NPC</b> (неигровой персонаж) — это житель твоей игры:
торговец, враг, проводник.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS используем <code>game.scene.spawnNpc(модель, опции)</code>:</p>
<Code>{`// Создаём 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); // идёт в точку`}</Code>
<p><b>Что умеет NPC:</b></p>
<table className="docTable">
<tbody>
<tr><td><code>moveTo(x, z)</code></td><td>идти в точку</td></tr>
<tr><td><code>follow('player')</code></td><td>гнаться за игроком</td></tr>
<tr><td><code>stop()</code></td><td>остановиться</td></tr>
<tr><td><code>say(текст, сек)</code></td><td>реплика над головой</td></tr>
<tr><td><code>damage(n)</code></td><td>нанести урон NPC</td></tr>
<tr><td><code>remove()</code></td><td>убрать</td></tr>
<tr><td><code>onDeath(fn)</code></td><td>при гибели</td></tr>
</tbody>
</table>
<p><b>Пример враг гонится за игроком:</b></p>
<Code>{`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);
});`}</Code>
</>}
lua={<>
<p>
В Lua NPC это обычный Model с Humanoid внутри.
Движение делается через <code>humanoid:MoveTo(point)</code>.
Реплики через ChatService или BillboardGui.
</p>
<Code>{`-- 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))`}</Code>
<p><b>Враг гонится за игроком:</b></p>
<Code>{`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)`}</Code>
</>}
/>
</>
),
},
{
id: 'inventory-tools',
title: 'G2. Инвентарь и инструменты',
body: (
<>
<p>
<b>Инвентарь</b> сумка предметов. <b>Инструмент</b>
предмет, который игрок берёт в руку: меч, фонарик.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<Code>{`// Выдать игроку меч прямо в руку
game.player.giveTool('sword', {
name: 'Стальной меч',
equip: true
});
// Ловим, когда игрок применил инструмент (ЛКМ)
game.player.onToolUse((e) => {
game.log('Игрок применил:', e.tool);
});`}</Code>
<p>
Команды <code>game.inventory</code>: <code>add</code>,
<code> remove</code>, <code>has</code>, <code>list</code>.
</p>
</>}
lua={<>
<Code>{`-- В 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)`}</Code>
<p>
Инвентарь игрока = его <code>Backpack</code> (Roblox-сервис).
Чтобы посмотреть что есть: <code>player.Backpack:GetChildren()</code>.
</p>
</>}
/>
<p><b>Пример игра «ключ и сундук»:</b></p>
<ScriptKind kind="object" on="сундук" />
<LangTabs
js={<Code>{`game.self.onInteract(() => {
if (game.inventory.has('Ключ')) {
game.ui.showText('Сундук открыт!', 2);
game.inventory.remove('Ключ');
} else {
game.ui.showText('Нужен ключ', 1.5);
}
}, { text: 'Открыть', distance: 4 });`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
{
id: 'sound',
title: 'G3. Звук: свои звуки и 3D-позиционный звук',
body: (
<>
<p>Звук оживляет игру.</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS команда <code>game.sound.play(id, опции)</code>:</p>
<Code>{`// Готовые звуки-пресеты
game.sound.play('coin');
game.sound.play('win');
game.sound.play('jump');
game.sound.play('hit');
// Свой загруженный звук, потише
game.sound.play('sound_1', { volume: 0.7 });`}</Code>
<p>
Пресеты: <code>jump</code>, <code>pickup</code>,
<code> win</code>, <code>lose</code>, <code>click</code>,
<code> hit</code>, <code>coin</code>.
</p>
<p>
<b>3D-звук</b> опция <code>at</code> привязывает
звук к точке в мире, тише с расстоянием.
</p>
<Code>{`game.sound.play('sound_2', {
at: { x: 0, y: 1, z: 0 },
loop: true
});`}</Code>
</>}
lua={<>
<p>В Lua используется <code>Sound</code>-инстанс:</p>
<Code>{`-- Простой звук — играет везде одинаково
local sound = Instance.new("Sound")
sound.SoundId = "rbxassetid://9120386436" -- свой ID
sound.Volume = 0.7
sound.Parent = workspace
sound:Play()`}</Code>
<p>
<b>3D-звук</b> родителем ставим Part в мире.
Sound автоматически становится позиционным.
</p>
<Code>{`-- Звук костра — слышен близко
local campfire = workspace.Костёр
local sound = Instance.new("Sound")
sound.SoundId = "rbxassetid://..."
sound.RollOffMaxDistance = 30 -- метры до полной тишины
sound.Looped = true
sound.Parent = campfire -- родитель = Part → 3D-звук
sound:Play()`}</Code>
</>}
/>
<Note>
Звук обязателен игра без звука кажется «мёртвой».
Но не запускай длинную музыку в начале это тормозит старт.
Звуки вешай на события: прыжок, попадание, победа.
</Note>
</>
),
},
{
id: 'camera',
title: 'G4. Камера: FOV, привязка, катсцены',
body: (
<>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS отдел <code>game.camera</code>:</p>
<table className="docTable">
<tbody>
<tr><td><code>setFov(градусы)</code></td><td>угол обзора</td></tr>
<tr><td><code>shake(сила, сек)</code></td><td>тряска камеры</td></tr>
<tr><td><code>focusOn(ref)</code></td><td>навести на объект</td></tr>
<tr><td><code>cutscene(точки, опции)</code></td><td>пролёт камеры</td></tr>
<tr><td><code>reset()</code></td><td>вернуть игроку</td></tr>
</tbody>
</table>
<Code>{`// Облёт уровня при старте
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);
});`}</Code>
</>}
lua={<>
<p>В Lua стандартный Roblox Camera через Workspace.CurrentCamera:</p>
<table className="docTable">
<tbody>
<tr><td><code>camera.FieldOfView = 90</code></td><td>угол обзора</td></tr>
<tr><td><code>camera.CameraType = Enum.CameraType.Scriptable</code></td><td>отключить авто-следование</td></tr>
<tr><td><code>camera.CFrame = CFrame.new(pos, look)</code></td><td>поставить камеру</td></tr>
<tr><td><code>TweenService:Create(camera, ...)</code></td><td>плавный пролёт</td></tr>
</tbody>
</table>
<Code>{`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("Поехали!")`}</Code>
</>}
/>
</>
),
},
{
id: 'beam-trail',
title: 'G5. Лучи и следы (Beam и Trail)',
body: (
<>
<p>
<b>Beam</b> светящаяся линия между двумя точками
(лазер, мост света), <b>Trail</b> шлейф за движущимся
объектом (след за ракетой).
</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`// Лазер между двумя башнями
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
});`}</Code>}
lua={<Code>{`-- В 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`}</Code>}
/>
</>
),
},
{
id: 'multiplayer',
title: 'G6. Мультиплеер: игроки, комната, команды',
body: (
<>
<p>В Рублоксе можно сделать игру на несколько игроков.</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS отделы <code>game.players</code>, <code>game.room</code>, <code>game.teams</code>:</p>
<Code>{`// Общий счёт команды — виден всем игрокам
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);
});`}</Code>
</>}
lua={<>
<p>В Lua стандартный Roblox-стиль:</p>
<Code>{`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)`}</Code>
<p><b>Команды (Teams):</b></p>
<Code>{`-- Команды создают в 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)`}</Code>
</>}
/>
</>
),
},
{
id: 'leaderstats',
title: 'G7. Лидерборды и достижения',
body: (
<>
<p>
<b>Лидерборд</b> таблица очков справа-сверху (как в Roblox).
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<Code>{`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('Монеты');`}</Code>
<p><b>Достижения</b>:</p>
<Code>{`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);`}</Code>
</>}
lua={<>
<p>В Lua стандартный Roblox-паттерн: создаём папку
<code> leaderstats</code> в Player с IntValue внутри:</p>
<Code>{`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`}</Code>
<p>
Папка с именем <code>leaderstats</code> на игроке
магическое имя в Roblox. Любые IntValue/NumberValue/StringValue
внутри неё автоматически попадают в HUD справа сверху.
</p>
</>}
/>
<Note>
Лидерборд и достижения сохраняются в БД и подтягиваются при
следующем входе игрока (DataStoreService в Roblox).
</Note>
</>
),
},
{
id: 'damage-floaters',
title: 'G8. Облачка урона (damage floaters)',
body: (
<>
<p>
Всплывающие <b>цифры урона</b> над врагом как в RPG.
</p>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS это готовая команда:</p>
<Code>{`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 });`}</Code>
</>}
lua={<>
<p>В Lua делаем сами через BillboardGui + TweenService:</p>
<Code>{`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) -- крит`}</Code>
</>}
/>
</>
),
},
{
id: 'items-inventory',
title: 'G9. Предметы и инвентарь с редкостями',
body: (
<>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>В JS готовый отдел <code>game.items</code> и <code>game.inventory</code>:</p>
<Code>{`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);`}</Code>
<p>Сбор предмета с земли:</p>
<Code>{`game.self.onInteract(() => {
game.inventory.give('berry', 2);
game.self.delete();
}, { text: 'Собрать', distance: 3 });`}</Code>
<Note>
Редкости: common (серый), uncommon (зелёный), rare (голубой),
epic (фиолетовый), legendary (золотой). Окно инвентаря
клавиша <kbd className="kbd">I</kbd>, drag-drop, ПКМ-меню.
</Note>
</>}
lua={<>
<p>
В Roblox инвентарь это <code>Backpack</code> игрока с Tool'ами,
плюс свои <code>IntValue</code>'ы для подсчёта стаков.
Готового «инвентаря с редкостями» нет собирается из частей:
</p>
<Code>{`-- Пример: ягоды как 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)`}</Code>
<p>
Для полноценной системы с редкостями, иконками и UI окном
надо собирать ScreenGui вручную (по статье C). Это много кода
проще использовать JS-вариант с готовым <code>game.items</code>.
</p>
</>}
/>
</>
),
},
{
id: 'sky-environment',
title: 'G10. Небо, облака, туман, время суток',
body: (
<>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p>Пресеты неба одной командой:</p>
<Code>{`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); // полдень`}</Code>
</>}
lua={<>
<p>В Roblox небо это инстансы <code>Sky</code> и <code>Atmosphere</code>
в Lighting:</p>
<Code>{`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) -- полночь`}</Code>
</>}
/>
</>
),
},
{
id: 'modal-menu-loading',
title: 'G11. Диалоги, меню, экран загрузки',
body: (
<>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p><b>Диалог NPC</b>:</p>
<Code>{`game.modal.dialog('Староста', [
'Привет, путник!',
'Собери 10 монет и возвращайся.',
], () => game.ui.showText('Квест начат!', 2));`}</Code>
<p><b>Окно Да/Нет</b> и <b>лутбокс</b>:</p>
<Code>{`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));`}</Code>
<p><b>Экран загрузки</b>:</p>
<Code>{`game.loading.show({
style: 'ken-burns',
placeName: 'Глава 2 — Шахта',
duration: 2
});
game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`}</Code>
</>}
lua={<>
<p>
В Roblox/Lua нет готовых модалок всё собирается через
<b> ScreenGui</b> с Frame'ами. Это много кода (~30-100 строк
на диалог), но полностью кастомизируется.
</p>
<Code>{`-- Простейший диалог: 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)`}</Code>
</>}
/>
</>
),
},
{
id: 'vehicles-menu',
title: 'G12. Машины и главное меню',
body: (
<>
<ScriptKind kind="global" />
<LangTabs
js={<>
<p><b>Машина</b>, на которой можно ездить (вход hold-F, WASD руль):</p>
<Code>{`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));`}</Code>
<p><b>Главное меню</b>:</p>
<Code>{`game.mainMenu.show({
title: 'МОЯ ИГРА',
camera: 'orbit',
playButtonText: 'ИГРАТЬ',
patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] },
onPlay: () => game.ui.showText('Поехали!', 2)
});`}</Code>
</>}
lua={<>
<p>
В Roblox машина это сложная Model с VehicleSeat
внутри. Когда игрок садится в VehicleSeat у него
появляются <code>.Throttle</code> и <code>.Steer</code>
свойства от WASD автоматически:
</p>
<Code>{`-- 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`}</Code>
<p>
Главное меню собирается через ScreenGui (см. C2-C3),
или используется готовый StarterGui от Roblox.
</p>
</>}
/>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ H — СПРАВОЧНИК game.*
// ════════════════════════════════════════════════════
{
id: 'reference',
icon: 'book',
title: 'Справочник game.*',
summary: 'Шпаргалка: все команды game.* списком по отделам.',
sections: [
{
id: 'cheatsheet',
title: 'H1. Шпаргалка — все команды списком',
body: (
<>
<p>
Здесь собраны все команды по отделам. Это шпаргалка
не нужно её запоминать, держи под рукой. Переключатель
сверху меняет язык.
</p>
<h4 className="docRefHead"><DocIcon name="run" size={17} /> Игрок</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.player.position</code></td><td>позиция игрока {`{x,y,z}`}</td></tr>
<tr><td><code>game.player.hp</code> / <code>maxHp</code></td><td>здоровье и максимум</td></tr>
<tr><td><code>game.player.alive</code></td><td>жив ли игрок</td></tr>
<tr><td><code>game.player.forward</code></td><td>куда смотрит</td></tr>
<tr><td><code>game.player.teleport(x,y,z)</code></td><td>телепорт</td></tr>
<tr><td><code>game.player.damage(n)</code> / <code>heal(n)</code></td><td>урон / лечение</td></tr>
<tr><td><code>game.player.kill()</code> / <code>respawn()</code></td><td>убить / воскресить</td></tr>
<tr><td><code>game.player.setSpawn(точка)</code></td><td>новая точка возрождения</td></tr>
<tr><td><code>game.player.setSpeed(mul)</code></td><td>скорость (множитель)</td></tr>
<tr><td><code>game.player.setJumpPower(mul)</code></td><td>прыжок (множитель)</td></tr>
<tr><td><code>game.player.setGravityMul(mul)</code></td><td>гравитация (множитель)</td></tr>
<tr><td><code>game.player.setDoubleJump(on)</code></td><td>двойной прыжок</td></tr>
<tr><td><code>game.player.playAnimation(имя)</code></td><td>эмоция</td></tr>
<tr><td><code>game.player.giveTool(тип,опции)</code></td><td>инструмент в руку</td></tr>
<tr><td><code>game.player.isKeyDown(клавиша)</code></td><td>зажата ли клавиша</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>hrp.Position</code></td><td>позиция (Vector3)</td></tr>
<tr><td><code>humanoid.Health</code> / <code>MaxHealth</code></td><td>здоровье</td></tr>
<tr><td><code>humanoid.Health {'>'} 0</code></td><td>жив ли</td></tr>
<tr><td><code>camera.CFrame.LookVector</code></td><td>куда смотрит</td></tr>
<tr><td><code>hrp.CFrame = CFrame.new(x,y,z)</code></td><td>телепорт</td></tr>
<tr><td><code>humanoid:TakeDamage(n)</code> / <code>humanoid.Health += n</code></td><td>урон / лечение</td></tr>
<tr><td><code>humanoid.Health = 0</code> / <code>player:LoadCharacter()</code></td><td>убить / воскресить</td></tr>
<tr><td><code>player.RespawnLocation = spawn</code></td><td>точка возрождения</td></tr>
<tr><td><code>humanoid.WalkSpeed = N</code></td><td>скорость (16 = норма)</td></tr>
<tr><td><code>humanoid.JumpPower = N</code></td><td>сила прыжка (50 = норма)</td></tr>
<tr><td><code>workspace.Gravity = N</code></td><td>гравитация (196 = норма)</td></tr>
<tr><td><code>humanoid:ChangeState(Jumping)</code></td><td>прыгнуть</td></tr>
<tr><td><code>animator:LoadAnimation(anim):Play()</code></td><td>анимация</td></tr>
<tr><td><code>Instance.new("Tool",player.Character)</code></td><td>инструмент в руку</td></tr>
<tr><td><code>UserInputService:IsKeyDown(key)</code></td><td>зажата ли клавиша</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="cube" size={17} /> Объекты сцены</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.scene.spawn(тип,опции)</code></td><td>создать объект ref</td></tr>
<tr><td><code>game.scene.delete(ref)</code></td><td>удалить</td></tr>
<tr><td><code>game.scene.deleteAfter(ref,сек)</code></td><td>удалить через N секунд</td></tr>
<tr><td><code>game.scene.move(ref,x,y,z)</code></td><td>переместить</td></tr>
<tr><td><code>game.scene.rotate(ref,угол)</code></td><td>повернуть</td></tr>
<tr><td><code>game.scene.setColor(ref,цвет)</code></td><td>цвет</td></tr>
<tr><td><code>game.scene.setCollide(ref,да)</code></td><td>твёрдость</td></tr>
<tr><td><code>game.scene.setVisible(ref,да)</code></td><td>видимость</td></tr>
<tr><td><code>game.scene.setOpacity(ref,0..1)</code></td><td>прозрачность</td></tr>
<tr><td><code>game.scene.find(имя)</code> / <code>findOne(имя)</code></td><td>поиск по имени</td></tr>
<tr><td><code>game.scene.all(тип)</code></td><td>все объекты типа</td></tr>
<tr><td><code>game.scene.setData/getData</code></td><td>атрибуты</td></tr>
<tr><td><code>game.scene.tag/untag/hasTag</code></td><td>теги</td></tr>
<tr><td><code>game.scene.getTagged(тег)</code></td><td>все объекты с тегом</td></tr>
<tr><td><code>game.scene.setLabel/clearLabel</code></td><td>метка над объектом</td></tr>
<tr><td><code>game.scene.spawnNpc(модель,опции)</code></td><td>создать NPC</td></tr>
<tr><td><code>game.scene.spawnParticles(тип,...)</code></td><td>частицы</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>Instance.new("Part", workspace)</code></td><td>создать объект</td></tr>
<tr><td><code>part:Destroy()</code></td><td>удалить</td></tr>
<tr><td><code>Debris:AddItem(part, N)</code></td><td>удалить через N секунд</td></tr>
<tr><td><code>part.Position = Vector3.new(x,y,z)</code></td><td>переместить</td></tr>
<tr><td><code>part.Orientation = Vector3.new(...)</code></td><td>повернуть</td></tr>
<tr><td><code>part.Color = Color3.fromRGB(...)</code></td><td>цвет</td></tr>
<tr><td><code>part.CanCollide = true/false</code></td><td>твёрдость</td></tr>
<tr><td><code>part.Transparency = 1</code></td><td>невидимость (0=видно)</td></tr>
<tr><td><code>part.Transparency = 0.4</code></td><td>полупрозрачность</td></tr>
<tr><td><code>workspace:FindFirstChild("Имя")</code> / <code>workspace.Имя</code></td><td>поиск по имени</td></tr>
<tr><td><code>CollectionService:GetTagged("тег")</code></td><td>все объекты с тегом</td></tr>
<tr><td><code>part:SetAttribute/GetAttribute</code></td><td>атрибуты</td></tr>
<tr><td><code>CollectionService:AddTag/RemoveTag/HasTag</code></td><td>теги</td></tr>
<tr><td><code>CollectionService:GetTagged(tag)</code></td><td>все объекты с тегом</td></tr>
<tr><td><code>BillboardGui + TextLabel</code></td><td>метка над объектом</td></tr>
<tr><td>Model + Humanoid + Anim</td><td>NPC (вручную)</td></tr>
<tr><td><code>Instance.new("ParticleEmitter", part)</code></td><td>частицы</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="pin" size={17} /> Объект-носитель скрипта</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.self.ref</code> / <code>position</code></td><td>адрес и позиция</td></tr>
<tr><td><code>game.self.onClick(fn)</code></td><td>клик по объекту</td></tr>
<tr><td><code>game.self.onTouch(fn)</code></td><td>игрок коснулся</td></tr>
<tr><td><code>game.self.onUntouch(fn)</code></td><td>игрок вышел</td></tr>
<tr><td><code>game.self.onInteract(fn,опции)</code></td><td>взаимодействие по E</td></tr>
<tr><td><code>game.self.move(x,y,z)</code></td><td>переместить себя</td></tr>
<tr><td><code>game.self.delete()</code></td><td>удалить себя</td></tr>
<tr><td><code>game.self.setText(t)</code></td><td>сменить текст</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>script.Parent</code> / <code>.Position</code></td><td>сам объект и его позиция</td></tr>
<tr><td><code>ClickDetector.MouseClick:Connect</code></td><td>клик по объекту</td></tr>
<tr><td><code>part.Touched:Connect</code></td><td>игрок коснулся</td></tr>
<tr><td><code>part.TouchEnded:Connect</code></td><td>игрок вышел</td></tr>
<tr><td><code>ProximityPrompt.Triggered:Connect</code></td><td>взаимодействие по E</td></tr>
<tr><td><code>part.Position = Vector3.new(x,y,z)</code></td><td>переместить</td></tr>
<tr><td><code>part:Destroy()</code></td><td>удалить</td></tr>
<tr><td><code>textLabel.Text = "..."</code></td><td>сменить текст (для GUI)</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="window" size={17} /> HUD: счётчики и текст</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.ui.score</code> / <code>timer</code></td><td>счётчики в углу</td></tr>
<tr><td><code>game.ui.showText(текст,сек)</code></td><td>текст по центру</td></tr>
<tr><td><code>game.ui.set(id,текст,опции)</code></td><td>своя метка</td></tr>
<tr><td><code>game.ui.remove(id)</code> / <code>clear()</code></td><td>убрать метку / всё</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>leaderstats папка + IntValue</code></td><td>счётчики в углу (HUD автомат)</td></tr>
<tr><td><code>ScreenGui + TextLabel (центр)</code></td><td>текст по центру</td></tr>
<tr><td><code>label.Text = "..."</code></td><td>обновить метку</td></tr>
<tr><td><code>label:Destroy()</code> / <code>screen:Destroy()</code></td><td>убрать метку / всё</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="palette" size={17} /> GUI: кнопки и меню</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.gui.find(имя)</code> / <code>get(id)</code></td><td>найти элемент</td></tr>
<tr><td><code>game.gui.update(id,patch)</code></td><td>изменить свойства</td></tr>
<tr><td><code>game.gui.show(id)</code> / <code>hide(id)</code></td><td>показать / скрыть</td></tr>
<tr><td><code>game.gui.onClick(id,fn)</code></td><td>клик по кнопке</td></tr>
<tr><td><code>game.gui.onSubmit(id,fn)</code></td><td>ввод в поле завершён</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>gui:FindFirstChild(имя, true)</code></td><td>найти элемент</td></tr>
<tr><td><code>elem.Text = "..."</code> / прямая запись свойств</td><td>изменить свойства</td></tr>
<tr><td><code>elem.Visible = true/false</code></td><td>показать / скрыть</td></tr>
<tr><td><code>button.MouseButton1Click:Connect</code></td><td>клик по кнопке</td></tr>
<tr><td><code>textbox.FocusLost:Connect(fn)</code></td><td>ввод завершён</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="target" size={17} /> Физика, эффекты, связи</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.physics.raycast(...)</code></td><td>луч во что попал</td></tr>
<tr><td><code>game.physics.applyImpulse(...)</code></td><td>толкнуть объект</td></tr>
<tr><td><code>game.physics.explode(...)</code></td><td>взрыв</td></tr>
<tr><td><code>game.physics.passThrough(...)</code></td><td>проходимость</td></tr>
<tr><td><code>game.fx.beam(опции)</code></td><td>светящийся луч</td></tr>
<tr><td><code>game.fx.trail(ref,опции)</code></td><td>след за объектом</td></tr>
<tr><td><code>game.fx.damageFloater(...)</code></td><td>цифры урона</td></tr>
<tr><td><code>game.constraints.weld(a,b)</code></td><td>склейка</td></tr>
<tr><td><code>game.constraints.hinge(...)</code></td><td>петля</td></tr>
<tr><td><code>game.constraints.spring(...)</code></td><td>пружина</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>workspace:Raycast(origin,dir,params)</code></td><td>луч во что попал</td></tr>
<tr><td><code>part:ApplyImpulse(Vector3)</code></td><td>толкнуть объект</td></tr>
<tr><td><code>Instance.new("Explosion", workspace)</code></td><td>взрыв</td></tr>
<tr><td><code>part.CanCollide = false</code></td><td>проходимость</td></tr>
<tr><td><code>Instance.new("Beam") + Attachments</code></td><td>светящийся луч</td></tr>
<tr><td><code>Instance.new("Trail") + Attachments</code></td><td>след за объектом</td></tr>
<tr><td>BillboardGui + TweenService</td><td>цифры урона (вручную)</td></tr>
<tr><td><code>Instance.new("WeldConstraint")</code></td><td>склейка</td></tr>
<tr><td><code>Instance.new("HingeConstraint")</code></td><td>петля</td></tr>
<tr><td><code>Instance.new("SpringConstraint")</code></td><td>пружина</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="camera" size={17} /> Камера и звук</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.camera.setFov(град)</code></td><td>угол обзора</td></tr>
<tr><td><code>game.camera.shake(сила,сек)</code></td><td>тряска</td></tr>
<tr><td><code>game.camera.cutscene(...)</code></td><td>пролёт камеры</td></tr>
<tr><td><code>game.camera.reset()</code></td><td>вернуть камеру игроку</td></tr>
<tr><td><code>game.sound.play(id,опции)</code></td><td>проиграть звук</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>workspace.CurrentCamera.FieldOfView = N</code></td><td>угол обзора</td></tr>
<tr><td><code>camera.CFrame = CFrame.new(...)</code> + рандом</td><td>тряска (вручную)</td></tr>
<tr><td><code>camera.CameraType = Scriptable + TweenService</code></td><td>пролёт камеры</td></tr>
<tr><td><code>camera.CameraType = Custom</code></td><td>вернуть игроку</td></tr>
<tr><td><code>Instance.new("Sound"):Play()</code></td><td>проиграть звук</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="gear" size={17} /> События и таймеры</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.onTick(fn)</code></td><td>каждый кадр</td></tr>
<tr><td><code>game.onKey/onKeyUp(клавиша,fn)</code></td><td>клавиатура</td></tr>
<tr><td><code>game.onClick(fn)</code></td><td>клик в игре</td></tr>
<tr><td><code>game.after(сек,fn)</code></td><td>через N секунд</td></tr>
<tr><td><code>game.every(сек,fn)</code></td><td>каждые N секунд</td></tr>
<tr><td><code>game.cancel(id)</code></td><td>отменить таймер</td></tr>
<tr><td><code>game.tween(ref,св-ва,опции)</code></td><td>плавная анимация</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>RunService.Heartbeat:Connect(fn)</code></td><td>каждый кадр</td></tr>
<tr><td><code>UserInputService.InputBegan/Ended</code></td><td>клавиатура</td></tr>
<tr><td><code>mouse.Button1Down:Connect(fn)</code></td><td>клик в игре</td></tr>
<tr><td><code>task.delay(сек, fn)</code></td><td>через N секунд</td></tr>
<tr><td><code>task.spawn(function() while ... task.wait(N) end end)</code></td><td>каждые N секунд</td></tr>
<tr><td><code>connection:Disconnect()</code></td><td>отменить подписку</td></tr>
<tr><td><code>TweenService:Create(obj, info, goal):Play()</code></td><td>плавная анимация</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="code" size={17} /> Утилиты</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.random(min,max)</code></td><td>случайное число</td></tr>
<tr><td><code>game.distance(a,b)</code></td><td>расстояние между точками</td></tr>
<tr><td><code>game.clamp(v,min,max)</code></td><td>зажать в границах</td></tr>
<tr><td><code>game.lerp(a,b,t)</code></td><td>плавный переход</td></tr>
<tr><td><code>game.log(...)</code></td><td>в консоль</td></tr>
<tr><td><code>game.broadcast/onMessage</code></td><td>сообщения между скриптами</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>math.random(min,max)</code></td><td>случайное число</td></tr>
<tr><td><code>(a - b).Magnitude</code></td><td>расстояние между Vector3</td></tr>
<tr><td><code>math.clamp(v,min,max)</code></td><td>зажать в границах</td></tr>
<tr><td><code>a + (b-a)*t</code> или <code>Vector3:Lerp(other,t)</code></td><td>плавный переход</td></tr>
<tr><td><code>print(...)</code> / <code>warn(...)</code></td><td>в консоль</td></tr>
<tr><td><code>BindableEvent:Fire</code> + <code>.Event:Connect</code></td><td>сообщения между скриптами</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="users" size={17} /> Мультиплеер, лидерборды, команды</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.players.all() / count() / me()</code></td><td>список игроков</td></tr>
<tr><td><code>game.room.set/get/onChange</code></td><td>общее состояние комнаты</td></tr>
<tr><td><code>game.teams.*</code></td><td>команды</td></tr>
<tr><td><code>game.leaderstats.define(имя,опции)</code></td><td>объявить стат</td></tr>
<tr><td><code>game.leaderstats.me.add/set/get</code></td><td>текущему игроку</td></tr>
<tr><td><code>game.achievements.define/unlock</code></td><td>достижения</td></tr>
<tr><td><code>game.save.merge/get</code></td><td>сохранение прогресса</td></tr>
<tr><td><code>game.onPlayerJoin/Leave(fn)</code></td><td>игрок зашёл / ушёл</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>Players:GetPlayers()</code> / <code>#Players:GetPlayers()</code> / <code>Players.LocalPlayer</code></td><td>список / число / я</td></tr>
<tr><td><code>ReplicatedStorage + Value + .Changed</code></td><td>общее состояние</td></tr>
<tr><td><code>Teams сервис + Instance.new("Team")</code></td><td>команды</td></tr>
<tr><td><code>Instance.new("Folder","leaderstats")+IntValue</code></td><td>лидерборд</td></tr>
<tr><td><code>stats.Имя.Value = N</code></td><td>обновить стат</td></tr>
<tr><td><code>BadgeService:AwardBadge(uid, id)</code></td><td>достижения (badges)</td></tr>
<tr><td><code>DataStoreService:GetAsync/SetAsync</code></td><td>сохранение прогресса</td></tr>
<tr><td><code>Players.PlayerAdded:Connect</code> / <code>PlayerRemoving</code></td><td>игрок зашёл / ушёл</td></tr>
</tbody>
</table>}
/>
<h4 className="docRefHead"><DocIcon name="sparkle" size={17} /> Небо, освещение, инвентарь, модалки</h4>
<LangTabs
js={<table className="docTable">
<tbody>
<tr><td><code>game.scene.setSkybox/fadeTo</code></td><td>пресеты неба</td></tr>
<tr><td><code>game.scene.setFog/setClouds</code></td><td>туман и облака</td></tr>
<tr><td><code>game.environment.setTimeOfDay(0..24)</code></td><td>время суток</td></tr>
<tr><td><code>game.items.define(список)</code></td><td>описать предметы</td></tr>
<tr><td><code>game.inventory.give/remove/has/list</code></td><td>инвентарь</td></tr>
<tr><td><code>game.modal.dialog/confirmation/lootbox</code></td><td>модальные окна</td></tr>
<tr><td><code>game.mainMenu.show/hide</code></td><td>главное меню</td></tr>
<tr><td><code>game.loading.show/onHide</code></td><td>экран загрузки</td></tr>
</tbody>
</table>}
lua={<table className="docTable">
<tbody>
<tr><td><code>Lighting + Sky / Atmosphere</code></td><td>пресеты неба (вручную)</td></tr>
<tr><td><code>Lighting.FogColor / FogEnd / Atmosphere</code></td><td>туман и облака</td></tr>
<tr><td><code>Lighting:SetMinutesAfterMidnight(N)</code></td><td>время суток</td></tr>
<tr><td>Свои Tool'ы в ServerStorage</td><td>предметы (вручную)</td></tr>
<tr><td><code>player.Backpack:GetChildren()</code> / <code>Tool.Parent = Backpack</code></td><td>инвентарь</td></tr>
<tr><td>ScreenGui + Frame + Button</td><td>модалки (вручную, см. G11)</td></tr>
<tr><td>ScreenGui + Frame</td><td>главное меню (вручную)</td></tr>
<tr><td><code>ReplicatedFirst + loading screen</code></td><td>экран загрузки</td></tr>
</tbody>
</table>}
/>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ I — ГЛОССАРИЙ
// ════════════════════════════════════════════════════
{
id: 'glossary',
icon: 'glossary',
title: 'Глоссарий',
summary: 'Словарик: все непонятные слова из вики простым языком.',
sections: [
{
id: 'terms',
title: 'I1. Термины простым языком',
body: (
<>
<p>Словарик слов, которые встречаются в вики:</p>
<table className="docTable">
<tbody>
<tr><td><b>Примитив</b></td><td>Простая 3D-фигура: куб, сфера, цилиндр. Главный строительный материал.</td></tr>
<tr><td><b>Модель</b></td><td>Готовая красивая 3D-фигура из библиотеки (дерево, машина).</td></tr>
<tr><td><b>Блок</b></td><td>Кубик одного размера, ровно встаёт по сетке.</td></tr>
<tr><td><b>Сцена</b></td><td>Весь игровой мир — всё, что ты построил.</td></tr>
<tr><td><b>Вьюпорт</b></td><td>Окно с 3D-сценой в центре редактора.</td></tr>
<tr><td><b>Гизмо</b></td><td>Цветные стрелки и кольца для перемещения объектов.</td></tr>
<tr><td><b>Иерархия</b></td><td>Список всех объектов игры в правой панели.</td></tr>
<tr><td><b>Инспектор</b></td><td>Панель со свойствами выделенного объекта.</td></tr>
<tr><td><b>Скрипт</b></td><td>Набор команд (код), который оживляет игру.</td></tr>
<tr><td><b>JavaScript</b></td><td>Язык программирования, на котором пишут скрипты.</td></tr>
<tr><td><b>Глобальный скрипт</b></td><td>Скрипт-«мозг», не привязан к объекту, запускается один раз.</td></tr>
<tr><td><b>Скрипт на объекте</b></td><td>Скрипт конкретного объекта, в нём работает game.self.</td></tr>
<tr><td><b>Переменная</b></td><td>«Коробочка с именем» — скрипт хранит в ней значение.</td></tr>
<tr><td><b>Функция</b></td><td>Набор команд, который выполнится, когда его позовут.</td></tr>
<tr><td><b>Событие</b></td><td>«Что-то случилось» — клик, касание, нажатие клавиши.</td></tr>
<tr><td><b>Условие (if)</b></td><td>Развилка: если что-то верно — сделай одно, иначе — другое.</td></tr>
<tr><td><b>Спавн</b></td><td>Появление: точка спавна — где появляется игрок; «заспавнить» — создать объект.</td></tr>
<tr><td><b>Респаун</b></td><td>Воскрешение игрока после смерти.</td></tr>
<tr><td><b>Чекпоинт</b></td><td>Контрольная точка — место, откуда игрок воскреснет.</td></tr>
<tr><td><b>HP</b></td><td>Здоровье игрока. При HP=0 он умирает.</td></tr>
<tr><td><b>ref</b></td><td>«Адрес» объекта. Команда spawn возвращает ref, по нему обращаются к объекту.</td></tr>
<tr><td><b>Твин</b></td><td>Плавное изменение свойства за время (плавное движение).</td></tr>
<tr><td><b>Тег</b></td><td>Ярлык на объекте. По тегу можно найти все помеченные объекты.</td></tr>
<tr><td><b>Атрибут</b></td><td>Своё значение, приклеенное к объекту (через setData).</td></tr>
<tr><td><b>Констрейнт</b></td><td>Связь между объектами: склейка, петля, пружина.</td></tr>
<tr><td><b>Эмиттер</b></td><td>Объект, создающий частицы (искры, дым, огонь).</td></tr>
<tr><td><b>Частицы</b></td><td>Много маленьких летящих точек для эффектов.</td></tr>
<tr><td><b>GUI</b></td><td>Интерфейс: кнопки, надписи, меню поверх 3D-сцены.</td></tr>
<tr><td><b>HUD</b></td><td>Счётчики и индикаторы поверх экрана (счёт, HP).</td></tr>
<tr><td><b>NPC</b></td><td>Неигровой персонаж: торговец, враг, проводник.</td></tr>
<tr><td><b>Триггер</b></td><td>Невидимая зона, которая что-то запускает при входе игрока.</td></tr>
<tr><td><b>Raycast</b></td><td>Невидимый луч — узнать, во что он попал (для стрельбы).</td></tr>
<tr><td><b>FOV</b></td><td>Угол обзора камеры. Больше — «шире» видно.</td></tr>
<tr><td><b>Катсцена</b></td><td>Видеовставка: камера сама пролетает по точкам.</td></tr>
<tr><td><b>Мультиплеер</b></td><td>Игра на нескольких игроков в одной комнате.</td></tr>
<tr><td><b>Модерация</b></td><td>Проверка игры перед публикацией в общей ленте.</td></tr>
</tbody>
</table>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ J — ЧАСТЫЕ ОШИБКИ
// ════════════════════════════════════════════════════
{
id: 'mistakes',
icon: 'bug',
title: 'Частые ошибки',
summary: 'Что делать, если скрипт не работает или игра ведёт себя странно.',
sections: [
{
id: 'script-not-working',
title: 'J1. Скрипт не работает или не сохраняется',
body: (
<>
<p>
<b>Первым делом — проверь Консоль.</b> Если в скрипте
опечатка, её текст появится в Консоли красным — там
написано, в какой строке ошибка.
</p>
<p>Самые частые опечатки:</p>
<ul>
<li>забыли точку с запятой <code>;</code> в конце команды;</li>
<li>
не закрыли скобку — открывающих и закрывающих
<code> ( )</code>, <code>{`{ }`}</code> должно быть
поровну;
</li>
<li>
имя команды с ошибкой: <code>game.player.teelport</code>
вместо <code>teleport</code>;
</li>
<li>
русская буква вместо английской (с виду одинаковы:
русская <code>с</code> и английская <code>c</code>).
</li>
</ul>
<p>
После правки скрипта — <b>сохрани игру</b>
(<kbd className="kbd">Ctrl</kbd>+<kbd className="kbd">S</kbd>)
и перезапусти Play.
</p>
</>
),
},
{
id: 'fell-through-floor',
title: 'J2. Объект провалился сквозь пол',
body: (
<p>
Если объект <b>не закреплён</b> (свойство «Закреплён»
выключено), он падает под действием физики. Для платформ,
стен и декораций <b>включи «Закреплён»</b> в инспекторе —
тогда объект будет висеть на месте. Падать должны только те
объекты, которым это нужно (ящики, мячи).
</p>
),
},
{
id: 'findone-null',
title: 'J3. findOne вернул «ничего» (null) на старте',
body: (
<>
<p>
Если позвать <code>game.scene.findOne(...)</code> в самой
первой строке скрипта — объект может быть ещё не готов,
и команда вернёт <code>null</code> («ничего»). Потом
обращение к этому <code>null</code> сломает скрипт.
</p>
<p>
<b>Решение:</b> ищи объект не на старте, а внутри
<code> onTick</code> или после небольшой задержки:
</p>
<Code>{`let door = null;
game.onTick(() => {
// ищем дверь, пока не найдём
if (!door) door = game.scene.findOne('Дверь');
// ... работаем с door, когда он уже найден
});`}</Code>
</>
),
},
{
id: 'ui-set-every-frame',
title: 'J4. game.ui.set каждый кадр игра лагает',
body: (
<>
<p>
<code>onTick</code> выполняется 60 раз в секунду. Если
внутри него на каждом кадре звать <code>game.ui.set(...)</code>,
интерфейс будет обновляться слишком часто и игра начнёт
тормозить.
</p>
<p>
<b>Решение:</b> обновляй интерфейс только когда значение
реально изменилось:
</p>
<Code>{`let lastScore = -1;
game.onTick(() => {
const s = game.ui.score || 0;
if (s !== lastScore) { // значение изменилось?
game.ui.set('hud', 'Счёт: ' + s);
lastScore = s;
}
});`}</Code>
</>
),
},
{
id: 'other-mistakes',
title: 'J5. Прочие типичные грабли',
body: (
<ul>
<li>
<b>Враг идёт сквозь стены</b> — проверь, что у стен
включено «Столкновение».
</li>
<li>
<b>Звук не играет</b> — проверь громкость
(<code>volume</code>) и что звук загружен. Не запускай
длинный звук в самом начале — это тормозит старт.
</li>
<li>
<b>Кнопка не нажимается</b> — проверь, что на неё повешен
<code> game.gui.onClick(...)</code> с правильным id,
и что у элемента правильное имя.
</li>
<li>
<b>Объект не двигается твином</b> — у твина свойство
должно быть числом (<code>{`{ y: 10 }`}</code>), а первым
аргументом — настоящий <code>ref</code> объекта.
</li>
<li>
<b>Скрипт на объекте, но game.self пустой</b> — значит
скрипт не привязан. Выдели объект и пересоздай скрипт
на нём, либо укажи носителя в настройках скрипта.
</li>
<li>
<b>Игрок застрял в стене</b> — не делай стену твёрдой,
пока игрок внутри. Используй <code>passThrough</code>
аккуратно.
</li>
</ul>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ — СКРИПТЫ: ГОТОВЫЕ РЕЦЕПТЫ
// ════════════════════════════════════════════════════
{
id: 'recipes',
icon: 'code',
title: 'Скрипты: рецепты',
summary: 'Готовые мини-скрипты, которые можно скопировать и вставить: килблок, сбор предметов, исчезновение и телепорт при касании, кнопки, таймеры, все свойства примитивов.',
sections: [
{
id: 'recipes-howto',
title: 'S1. Куда писать скрипты',
body: (
<>
<p>В Рублоксе есть <b>два вида скриптов</b>:</p>
<ul>
<li><b>Глобальный</b> — создаётся в иерархии в категории
<b> «Скрипты»</b> (кнопка «+»). Запускается один раз при
старте игры. В нём управляешь всей сценой через
<code> game.scene</code>, <code>game.player</code>, <code>game.ui</code>.</li>
<li><b>На объекте</b> — вешается прямо на примитив/модель/блок.
В нём работает <code>game.self</code> — это сам объект-носитель.
Удобно для «этот блок убивает», «этот сундук открывается».</li>
</ul>
<Note>
В рецептах ниже над каждым примером написано, <b>куда его
писать</b>. Просто скопируй код и вставь в нужный скрипт.
</Note>
<p>
Полезное: <code>game.log(...)</code> печатает в консоль (значок
«Консоль» внизу) — для отладки. <code>game.player.position</code>
— где сейчас игрок (<code>{'{x, y, z}'}</code>).
</p>
</>
),
},
{
id: 'recipes-touch',
title: 'S2. Касание объекта',
body: (
<>
<p>Самое частое событие — <b>игрок коснулся объекта</b>.</p>
<ScriptKind kind="object" on="любой объект (плита, зона, предмет)" />
<LangTabs
js={<Code>{`// Игрок наступил на объект — показать надпись и звук
game.self.onTouch(() => {
game.ui.showText('Ты коснулся плиты!', 2);
game.sound.play('click');
});
// Когда игрок ушёл с объекта
game.self.onUntouch(() => {
game.ui.showText('Отошёл', 1);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>Подписаться на <b>чужой</b> объект из глобального скрипта:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`const trap = game.scene.findOne('Ловушка');
trap.onTouch(() => game.player.damage(20));`}</Code>}
lua={<Code>{`local trap = workspace:WaitForChild("Ловушка")
trap.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then humanoid:TakeDamage(20) end
end)`}</Code>}
/>
</>
),
},
{
id: 'recipes-killblock',
title: 'S3. Килблок урон и смерть при касании',
body: (
<>
<p>
<b>Килблок</b> — объект, который наносит урон или мгновенно
убивает при касании (лава, шипы, кислота).
</p>
<ScriptKind kind="object" on="блок-ловушку (лава, шипы)" />
<LangTabs
js={<Code>{`// Мгновенная смерть при касании
game.self.onTouch(() => {
game.player.kill();
game.ui.showText('Ты сгорел в лаве!', 2);
});`}</Code>}
lua={<Code>{`local part = script.Parent
part.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then
humanoid.Health = 0 -- мгновенная смерть
end
end)`}</Code>}
/>
<p>Если хочешь не убивать сразу, а наносить урон:</p>
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.damage(25);
game.camera.shake(0.2, 0.3);
});`}</Code>}
lua={<Code>{`local part = script.Parent
part.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then humanoid:TakeDamage(25) end
end)`}</Code>}
/>
<p><b>Постоянный урон</b>, пока игрок стоит в зоне:</p>
<LangTabs
js={<Code>{`let inside = false;
game.self.onTouch(() => { inside = true; });
game.self.onUntouch(() => { inside = false; });
game.every(0.5, () => {
if (inside) game.player.damage(5);
});`}</Code>}
lua={<Code>{`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`}</Code>}
/>
<Try>
Сделай красный неоновый куб, повесь на него скрипт смерти —
получится лава. Поставь его в проёме как преграду.
</Try>
</>
),
},
{
id: 'recipes-disappear',
title: 'S4. Исчезновение при касании (сбор монет)',
body: (
<>
<p>
Предмет <b>исчезает</b> при касании — основа сбора монет.
</p>
<ScriptKind kind="object" on="монетку / собираемый предмет" />
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.sound.play('coin');
game.self.delete();
});`}</Code>}
lua={<Code>{`local part = script.Parent
part.Touched:Connect(function(hit)
local h = hit.Parent:FindFirstChild("Humanoid")
if h then
part:Destroy()
end
end)`}</Code>}
/>
<p>Со <b>счётчиком</b>: монетка увеличивает leaderstats игрока:</p>
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.broadcast('coin');
game.self.delete();
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>JS: глобальный скрипт принимает broadcast и считает:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`let score = 0;
game.ui.score = 0;
game.onMessage('coin', () => {
score = score + 1;
game.ui.score = score;
if (score >= 10) game.ui.showText('Собрал все!', 3);
});`}</Code>}
lua={<Code>{`-- В 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)`}</Code>}
/>
</>
),
},
{
id: 'recipes-teleport',
title: 'S5. Телепорт и смена позиции при касании',
body: (
<>
<p>
При касании <b>переместить игрока</b> (портал) или
<b> сдвинуть сам объект</b>.
</p>
<p><b>Портал</b> — телепорт игрока:</p>
<ScriptKind kind="object" on="портал" />
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.player.teleport(0, 20, 50);
game.sound.play('win');
game.camera.shake(0.15, 0.2);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p><b>Сдвинуть сам объект</b> при касании (опустить мост):</p>
<LangTabs
js={<Code>{`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);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p><b>Плавно</b> сдвинуть — через TweenService:</p>
<LangTabs
js={<Code>{`const p = game.self.position;
game.self.onTouch(() => {
game.tween(game.self.ref, { x: p.x + 4 },
{ duration: 1, easing: 'ease' });
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
{
id: 'recipes-primitive-props',
title: 'S6. Все свойства примитивов из скрипта',
body: (
<>
<p>
Любой примитив можно <b>создать</b> и <b>менять</b> из скрипта.
</p>
<ScriptKind kind="global" />
<p><b>Создать примитив</b>:</p>
<LangTabs
js={<Code>{`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,
});`}</Code>}
lua={<Code>{`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`}</Code>}
/>
<p>Типы примитивов:</p>
<LangTabs
js={<Code>{`'cube' 'sphere' 'cylinder' 'cone' 'pyramid' 'torus' 'wedge' 'cornerwedge' 'plane'`}</Code>}
lua={<Code>{`Enum.PartType.Block / Ball / Cylinder / Wedge / CornerWedge
-- Для cone/pyramid/torus используются MeshPart или SpecialMesh:
local sphere = Instance.new("Part")
sphere.Shape = Enum.PartType.Ball -- сфера`}</Code>}
/>
<p><b>Менять свойства</b> существующего объекта:</p>
<LangTabs
js={<Code>{`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();`}</Code>}
lua={<Code>{`-- Прямое присваивание свойств 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() -- удалить`}</Code>}
/>
<LangTabs
js={<Note>
<b>Радианы:</b> поворот в радианах. 90° = <code>Math.PI/2</code> ≈ 1.57.
</Note>}
lua={<Note>
<b>Градусы:</b> Orientation в градусах (не радианах).
Для CFrame.Angles — в радианах: <code>math.rad(90)</code>.
</Note>}
/>
</>
),
},
{
id: 'recipes-anim',
title: 'S7. Движение, вращение, мигание',
body: (
<>
<p><b>Вращающийся объект</b> (монета, портал):</p>
<ScriptKind kind="object" on="вращающийся объект" />
<LangTabs
js={<Code>{`let angle = 0;
game.onTick((dt) => {
angle = angle + dt * 2;
game.self.rotateY(angle);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p><b>Парение вверх-вниз</b>:</p>
<LangTabs
js={<Code>{`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);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p><b>Пульсация размера</b>:</p>
<LangTabs
js={<Code>{`game.tween(game.self.ref, { sy: 1.4 }, {
duration: 0.6, easing: 'ease', yoyo: true, repeat: -1
});`}</Code>}
lua={<Code>{`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()`}</Code>}
/>
<p><b>Мигание цветом</b>:</p>
<LangTabs
js={<Code>{`let on = false;
game.every(0.5, () => {
on = !on;
game.self.setColor(on ? '#ff0000' : '#330000');
});`}</Code>}
lua={<Code>{`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`}</Code>}
/>
</>
),
},
{
id: 'recipes-button-door',
title: 'S8. Кнопка по E и дверь',
body: (
<>
<p><b>Взаимодействие по клавише E</b>:</p>
<ScriptKind kind="object" on="кнопку / рычаг / сундук" />
<LangTabs
js={<Code>{`game.self.onInteract(() => {
game.ui.showText('Открыто!', 2);
game.broadcast('open-door');
}, { text: 'Открыть', key: 'e', distance: 4 });`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p>На двери:</p>
<ScriptKind kind="object" on="дверь" />
<LangTabs
js={<Code>{`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);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<Note>
<code>holdDuration: 1</code> в onInteract / <code>prompt.HoldDuration = 1</code>
в Roblox — держать E одну секунду.
</Note>
</>
),
},
{
id: 'recipes-gui-timer',
title: 'S9. HUD надписи, таймер, кнопки',
body: (
<>
<p><b>HUD-надписи</b>:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`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');`}</Code>}
lua={<Code>{`-- В 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)`}</Code>}
/>
<p><b>Обратный отсчёт</b>:</p>
<LangTabs
js={<Code>{`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();
}
});`}</Code>}
lua={<Code>{`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`}</Code>}
/>
<p><b>Кнопка GUI</b>:</p>
<LangTabs
js={<Code>{`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);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
{
id: 'recipes-spawn-fall',
title: 'S10. Спавн, падение, проверка падения вниз',
body: (
<>
<p><b>Спавнить объекты с неба</b> каждую секунду:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`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
});
});`}</Code>}
lua={<Code>{`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`}</Code>}
/>
<p><b>Игрок упал вниз</b>:</p>
<LangTabs
js={<Code>{`game.onTick(() => {
if (game.player.position.y < -10) {
game.player.respawn();
game.ui.showText('Упал!', 2);
}
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
<p><b>Финиш</b>:</p>
<ScriptKind kind="object" on="финишную плиту" />
<LangTabs
js={<Code>{`game.self.onTouch(() => {
game.ui.showText('ПОБЕДА!', 4);
game.sound.play('win');
game.player.setInputBlocked(true);
});`}</Code>}
lua={<Code>{`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)`}</Code>}
/>
</>
),
},
{
id: 'recipes-npc-enemy',
title: 'S11. Враг, который идёт за игроком',
body: (
<>
<p><b>NPC/враг</b>, преследующий игрока:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`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);
});`}</Code>}
lua={<Code>{`-- Враг должен быть 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)`}</Code>}
/>
</>
),
},
{
id: 'recipes-save',
title: 'S12. Сохранение прогресса и лидерборд',
body: (
<>
<p><b>Лидерборд</b>:</p>
<ScriptKind kind="global" />
<LangTabs
js={<Code>{`game.leaderstats.define('Монеты', { initial: 0 });
game.leaderstats.me.add('Монеты', 1);`}</Code>}
lua={<Code>{`-- 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`}</Code>}
/>
<p><b>Сохранение между сессиями</b>:</p>
<LangTabs
js={<Code>{`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);
}
});`}</Code>}
lua={<Code>{`-- В 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)`}</Code>}
/>
<Try>
Собери всё вместе: монетки добавляются в leaderstats →
глобальный скрипт раз в N монет вызывает save.merge
или DataStore:SetAsync. Получится игра с прогрессом.
</Try>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ — СОВМЕСТНОЕ РЕДАКТИРОВАНИЕ (Team Create)
// ════════════════════════════════════════════════════
{
id: 'collab',
icon: 'users',
title: 'Вместе с друзьями',
summary: 'Совместное редактирование игры в реальном времени: приглашай друзей, стройте сцену вдвоём, видьте курсоры и правки друг друга.',
sections: [
{
id: 'collab-what',
title: 'V1. Что такое совместное редактирование',
body: (
<>
<p>
<b>Совместное редактирование (Team Create)</b> — это когда
одну игру в студии редактируют <b>несколько человек
одновременно</b>. Ты строишь дом, друг в это же время
ставит деревья — и каждый видит правки другого
<b> вживую</b>, без перезагрузки страницы.
</p>
<p>Что синхронизируется в реальном времени:</p>
<ul>
<li><b>Блоки</b> — поставил/удалил блок, друг сразу видит;</li>
<li><b>Примитивы</b> — добавил, подвинул, повернул, изменил
цвет или размер;</li>
<li><b>Модели</b> — добавил из Тулбокса или удалил;</li>
<li><b>Курсоры друзей</b> — где сейчас «мышка» каждого
соавтора (цветная точка с ником);</li>
<li><b>Кто онлайн</b> — список соавторов с цветными
аватарками вверху сцены.</li>
</ul>
<Shot src="collab-scene.png" wide
caption="Сцена при совместном редактировании: вверху — кто онлайн (аватарки «У» и «М», всего 2), на сцене — курсор соавтора «УтреннийРозмарин4633» (оранжевая точка с ником) и блок, который он только что поставил." />
<Note>
Это как Google Документы, только для 3D-игр. Удобно делать
игру в команде, помогать ученику или показывать, как что
устроено — прямо в его проекте.
</Note>
</>
),
},
{
id: 'collab-invite',
title: 'V2. Как пригласить друга',
body: (
<>
<p>
Открой свою игру в студии и перейди на вкладку
<kbd className="kbd">Игра</kbd> в верхней панели. Там, в
группе <b>«Вместе»</b>, есть кнопка
<kbd className="kbd">Пригласить</kbd>.
</p>
<Shot src="collab-button.png" wide
caption="Вкладка «Игра» → группа «Вместе» → кнопка «Пригласить». Когда соавторы подключатся, на ней появится счётчик «Вместе (2)»." />
<Step n={1}>
Нажми <kbd className="kbd">Пригласить</kbd> — ссылка-приглашение
автоматически скопируется в буфер обмена (появится
уведомление).
</Step>
<Step n={2}>
Отправь эту ссылку другу (в чат, мессенджер — как угодно).
</Step>
<Step n={3}>
Друг открывает ссылку — и попадает в <b>ту же сцену</b>.
Теперь вы редактируете вместе. На кнопке появится счётчик:
<b> «Вместе (2)»</b>.
</Step>
<Note>
Приглашать может только <b>автор игры</b>. Ссылка действует
24 часа. Друг должен быть зарегистрирован на Рублоксе и
войти в свой аккаунт.
</Note>
</>
),
},
{
id: 'collab-how',
title: 'V3. Как это работает и правила',
body: (
<>
<p>
<b>Блокировка объекта.</b> Пока один соавтор выделил объект
и двигает его — этот объект <b>заблокирован</b> для других
(никто другой не сможет его двигать одновременно). Так
правки не конфликтуют. Как только выделение снято — объект
снова свободен.
</p>
<p>
<b>Кто сохраняет.</b> Игру в базу сохраняет <b>автор</b>
(владелец проекта). Приглашённые друзья видят пометку
«Совместное редактирование» вместо кнопок Сохранить и
Опубликовать — это правильно: они помогают строить, а
управляет игрой автор.
</p>
<p>
<b>Цвета.</b> У каждого соавтора свой цвет — им подсвечены
его курсор и аватарка, чтобы было понятно, кто что делает.
</p>
<Note>
Если друг ненадолго потерял связь (вылетел интернет) — у
него есть несколько секунд, чтобы переподключиться и
продолжить с того же места.
</Note>
<p>
<b>Совет:</b> договоритесь заранее, кто какую часть карты
делает (например, один — здания, другой — ландшафт и
декор) — так работать вместе быстрее и без накладок.
</p>
</>
),
},
],
},
// ════════════════════════════════════════════════════
// РАЗДЕЛ — КОНТЕКСТ ДЛЯ НЕЙРОНКИ (AI)
// ════════════════════════════════════════════════════
{
id: 'ai-context',
icon: 'lightbulb',
title: 'Контекст для нейронки',
summary: 'Готовый текст со всем API скриптов Рублокса. Скопируй его целиком, вставь в ChatGPT/нейросеть и она будет писать тебе рабочие скрипты под твою задачу.',
sections: [
{
id: 'ai-howto',
title: 'AI1. Как писать скрипты с нейросетью',
body: (
<>
<p>
Хочешь, чтобы скрипт написала <b>нейросеть</b> (ChatGPT,
DeepSeek, Claude и т.п.)? Проблема в том, что нейросеть <b>не
знает</b> устройство Рублокса — придумает несуществующие
команды. Решение простое:
</p>
<Step n={1}>
Открой статью <b>«AI2. Контекст — скопируй в нейросеть»</b> ниже.
</Step>
<Step n={2}>
<b>Выдели и скопируй весь текст</b> из серого блока (он
описывает все команды Рублокса).
</Step>
<Step n={3}>
Вставь его в нейросеть <b>первым сообщением</b>. Затем добавь
свою задачу, например: «Напиши скрипт: при касании синего куба
игрок прыгает высоко и играет звук».
</Step>
<Step n={4}>
Нейросеть выдаст готовый код. Скопируй его в скрипт в студии
(глобальный или на объекте — она подскажет куда).
</Step>
<Note>
Если нейросеть всё равно ошиблась в команде — скинь ей текст
ошибки из «Консоли» студии, она исправит. И всегда проверяй
результат запуском игры.
</Note>
</>
),
},
{
id: 'ai-context-text',
title: 'AI2. Контекст скопируй в нейросеть',
body: (
<>
<p>
<b>Выдели весь текст ниже и скопируй</b> (Ctrl+A внутри блока
или мышью), затем вставь в нейросеть перед своим вопросом:
</p>
<Code>{AI_CONTEXT}</Code>
</>
),
},
],
},
];