Каждая из 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
5010 lines
292 KiB
JavaScript
5010 lines
292 KiB
JavaScript
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>
|
||
</>
|
||
),
|
||
},
|
||
],
|
||
},
|
||
];
|
||
|
||
|