From ab11ac0b4e987a68af3a6de02afe0455c6ba341d Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 03:27:41 +0300 Subject: [PATCH] =?UTF-8?q?docs(studio):=20=D0=B2=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=E2=80=94=20=D1=80=D0=B5=D1=86=D0=B5=D0=BF=D1=82=D1=8B=20=D1=81?= =?UTF-8?q?=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=BE=D0=B2,=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D0=BA=D1=81=D1=82=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B9=D1=80=D0=BE=D0=BD=D0=BA=D0=B8,=20Team=20Crea?= =?UTF-8?q?te,=20=D0=B0=D0=BA=D1=82=D1=83=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новые разделы: «Скрипты: рецепты» (S1-S12: килблок/касание/исчезновение/телепорт/ свойства примитивов/таймеры/враги/сохранение), «Контекст для нейронки» (полный game-API одним copy-paste блоком для ChatGPT), «Вместе с друзьями» (V1-V3 Team Create). Раздел «Системы» дополнен G7-G12 (лидерборды/floaters/инвентарь/небо/ меню/машины). Иконка users. 80 секций вики. Co-Authored-By: Claude Opus 4.8 --- src/community/docsData.jsx | 906 ++++++++++++++++++++++++++++++++++++ src/community/docsIcons.jsx | 8 + 2 files changed, 914 insertions(+) diff --git a/src/community/docsData.jsx b/src/community/docsData.jsx index a6b1dd3..ae4fcc7 100644 --- a/src/community/docsData.jsx +++ b/src/community/docsData.jsx @@ -104,6 +104,123 @@ export const Shot = ({ src, caption, wide }) => ( // 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 — ОСНОВЫ @@ -2029,6 +2146,167 @@ game.log('Игроков в комнате:', game.players.count()); // когда новый игрок зашёл game.onPlayerJoin((p) => { game.ui.showText(p.name + ' присоединился!', 2); +});`} + + ), + }, + { + id: 'leaderstats', + title: 'G7. Лидерборды и достижения', + body: ( + <> +

+ Лидерборд — таблица очков игроков справа-сверху (как + в Roblox). Объяви стат и меняй значение: +

+ + {`game.leaderstats.define('Монеты', { initial: 0, icon: 'coin' }); +game.leaderstats.define('Уровень', { initial: 1 }); + +game.leaderstats.me.add('Монеты', 5); // +5 текущему игроку +game.leaderstats.me.set('Уровень', 2); // задать значение +const c = game.leaderstats.me.get('Монеты');`} +

+ Достижения — всплывающие ачивки с редкостью и звуком: +

+ {`game.achievements.define([ + { id: 'first_coin', name: 'Первая монетка', description: 'Собери монету', icon: 'coin', rarity: 'common' }, + { id: 'rich', name: 'Богач', description: '100 монет', icon: 'trophy', rarity: 'legendary' } +]); +game.achievements.unlock('first_coin'); +// или авто-разблокировка по статy: +game.achievements.bindToStat('rich', 'Монеты', 100);`} + + Лидерборд и достижения сохраняются в БД и подтягиваются при + следующем входе игрока. + + + ), + }, + { + id: 'damage-floaters', + title: 'G8. Облачка урона (damage floaters)', + body: ( + <> +

+ Всплывающие цифры урона над врагом — как в RPG. Самый + простой способ — авто-режим (цифры над всеми мобами при уроне): +

+ + {`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 }); // промах MISS`} + + ), + }, + { + id: 'items-inventory', + title: 'G9. Предметы и инвентарь с редкостями', + body: ( + <> +

+ Полноценный инвентарь (сетка + хотбар, стаки, редкости). + Сначала опиши предметы, потом выдавай: +

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

Сбор предмета с земли (скрипт на предмете):

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

Кастомное небо одной строкой — пресеты:

+ + {`game.scene.setSkybox({ preset: 'sunset' }); +// пресеты: clear-summer-day | lowpoly-roblox | cloudy | sunset | starry-night | space`} +

Облака, туман и плавный переход:

+ {`game.scene.setClouds({ enabled: true, cover: 0.5, speed: 0.02 }); +game.scene.setFog({ color: '#dddddd', density: 0.006 }); +game.scene.skybox.fadeTo({ preset: 'starry-night' }, 3); // плавно за 3 сек`} +

Простое управление цветом неба и временем суток:

+ {`game.environment.setSkyColor('#0a1024'); // тёмное небо +game.environment.setTimeOfDay(0); // ночь (0..24) +game.environment.setTimeOfDay(12); // полдень`} + + ), + }, + { + id: 'modal-menu-loading', + title: 'G11. Диалоги, меню, экран загрузки', + body: ( + <> +

Диалог NPC построчно:

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

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

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

Экран загрузки при переходе между уровнями:

+ {`game.loading.show({ + style: 'ken-burns', + placeName: 'Глава 2 — Шахта', + studioName: 'Моя студия', + duration: 2 +}); +game.loading.onHide(() => game.ui.showText('Добро пожаловать!', 2));`} + + Стартовый экран загрузки игры настраивается без кода — + см. раздел вики «Экран загрузки» (карточка в разборе игр) и + вкладку «Стартовый экран» в настройках проекта. + + + ), + }, + { + id: 'vehicles-menu', + title: 'G12. Машины и главное меню', + body: ( + <> +

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

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

Главное меню игры с живой камерой и кнопкой ИГРАТЬ:

+ {`game.mainMenu.show({ + title: 'МОЯ ИГРА', + camera: 'orbit', + playButtonText: 'ИГРАТЬ', + patchNotes: { title: 'Что нового', items: ['Добавлены машины', 'Новая карта'] }, + onPlay: () => game.ui.showText('Поехали!', 2) });`} ), @@ -2398,4 +2676,632 @@ game.onTick(() => { }, ], }, + + // ════════════════════════════════════════════════════ + // РАЗДЕЛ — СКРИПТЫ: ГОТОВЫЕ РЕЦЕПТЫ + // ════════════════════════════════════════════════════ + { + id: 'recipes', + icon: 'code', + title: 'Скрипты: рецепты', + summary: 'Готовые мини-скрипты, которые можно скопировать и вставить: килблок, сбор предметов, исчезновение и телепорт при касании, кнопки, таймеры, все свойства примитивов.', + sections: [ + { + id: 'recipes-howto', + title: 'S1. Куда писать скрипты', + body: ( + <> +

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

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

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

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

+ Самое частое событие — игрок коснулся объекта. Вешаем + скрипт на объект и подписываемся через game.self.onTouch. +

+ + {`// Игрок наступил на объект — показать надпись и звук +game.self.onTouch(() => { + game.ui.showText('Ты коснулся плиты!', 2); + game.sound.play('click'); +}); + +// Когда игрок ушёл с объекта +game.self.onUntouch(() => { + game.ui.showText('Отошёл', 1); +});`} +

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

+ + {`const trap = game.scene.findOne('Ловушка'); +trap.onTouch(() => game.player.damage(20));`} + + ), + }, + { + id: 'recipes-killblock', + title: 'S3. Килблок — урон и смерть при касании', + body: ( + <> +

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

+ + {`// Мгновенная смерть при касании +game.self.onTouch(() => { + game.player.kill(); + game.ui.showText('💀 Ты сгорел в лаве!', 2); +});`} +

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

+ {`// Урон 25 при касании (учитывает кадры неуязвимости) +game.self.onTouch(() => { + game.player.damage(25); + game.camera.shake(0.2, 0.3); // лёгкая тряска +});`} +

+ Постоянный урон, пока игрок стоит в зоне (например, + ядовитое облако) — урон каждые 0.5 сек, пока касается: +

+ {`let inside = false; +game.self.onTouch(() => { inside = true; }); +game.self.onUntouch(() => { inside = false; }); +game.every(0.5, () => { + if (inside) game.player.damage(5); +});`} + + Сделай красный неоновый куб, повесь на него скрипт смерти — + получится лава. Поставь его в проёме как преграду. + + + ), + }, + { + id: 'recipes-disappear', + title: 'S4. Исчезновение при касании (сбор монет)', + body: ( + <> +

+ Предмет исчезает, когда игрок его коснулся — основа + сбора монеток, ключей, бонусов. +

+ + {`// Простое исчезновение + звук +game.self.onTouch(() => { + game.sound.play('coin'); + game.self.delete(); +});`} +

+ Со счётчиком: предмет сообщает глобальному скрипту, + тот считает. На монетке: +

+ {`game.self.onTouch(() => { + game.broadcast('coin'); // сообщить всем скриптам + game.self.delete(); +});`} +

В глобальном скрипте — приём и счёт:

+ + {`let score = 0; +game.ui.score = 0; +game.onMessage('coin', () => { + score = score + 1; + game.ui.score = score; // обновить счётчик в углу + if (score >= 10) game.ui.showText('🏆 Собрал все!', 3); +});`} + + Не ставь счётчик на саму монетку — каждая монетка это + свой скрипт, они не видят переменные друг друга. Считай в + одном глобальном скрипте, монетки только шлют + game.broadcast. + + + ), + }, + { + id: 'recipes-teleport', + title: 'S5. Телепорт и смена позиции при касании', + body: ( + <> +

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

+

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

+ + {`game.self.onTouch(() => { + game.player.teleport(0, 20, 50); // x, y, z назначения + game.sound.play('win'); + game.camera.shake(0.15, 0.2); +});`} +

+ Сдвинуть сам объект при касании (например, опустить + мост). game.self.move ставит новую позицию: +

+ {`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); // уехал вниз на 3 м +});`} +

+ Плавно сдвинуть — через game.tween (анимация): +

+ {`// дверь уезжает вбок за 1 секунду +const p = game.self.position; +game.self.onTouch(() => { + game.tween(game.self.ref, { x: p.x + 4 }, { duration: 1, easing: 'ease' }); +});`} + + ), + }, + { + id: 'recipes-primitive-props', + title: 'S6. Все свойства примитивов из скрипта', + body: ( + <> +

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

+ +

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

+ {`const box = game.scene.spawn('cube', { + x: 0, y: 2, z: 0, // позиция + sx: 2, sy: 1, sz: 3, // размер по осям (ширина/высота/глубина) + rotationX: 0, rotationY: 0.8, rotationZ: 0, // поворот в радианах + color: '#ff5533', // цвет (hex) + material: 'neon', // matte | neon | metal | glass | studs + name: 'МойКуб', + anchored: true, // true = висит на месте; false = падает (физика) + canCollide: true, // false = игрок проходит насквозь + visible: true, + mass: 5, // масса (если anchored:false) +});`} +

Типы примитивов для spawn:

+ {`'cube' 'sphere' 'cylinder' 'cone' 'pyramid' 'torus' 'wedge' 'cornerwedge' 'plane'`} +

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

+ {`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); // полупрозрачность (1=видно, 0=невидимо) +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(); // удалить`} + + Радианы: поворот задаётся в радианах, не градусах. + 90° = Math.PI/2 ≈ 1.57, 180° = Math.PI ≈ 3.14. + + + ), + }, + { + id: 'recipes-anim', + title: 'S7. Движение, вращение, мигание (onTick и tween)', + body: ( + <> +

+ Вращающийся объект (монета, портал) — крутим каждый + кадр через game.onTick (dt = время кадра): +

+ + {`let angle = 0; +game.onTick((dt) => { + angle = angle + dt * 2; // скорость вращения + game.self.rotateY(angle); +});`} +

Парение вверх-вниз (плавно качается):

+ {`const start = game.self.position; +let t = 0; +game.onTick((dt) => { + t = t + dt; + const dy = Math.sin(t * 2) * 0.4; // амплитуда 0.4 м + game.self.move(start.x, start.y + dy, start.z); +});`} +

Пульсация размера через tween (бесконечно туда-обратно):

+ {`game.tween(game.self.ref, { sy: 1.4 }, { + duration: 0.6, easing: 'ease', yoyo: true, repeat: -1 +});`} +

Мигание цветом каждые полсекунды:

+ {`let on = false; +game.every(0.5, () => { + on = !on; + game.self.setColor(on ? '#ff0000' : '#330000'); +});`} + + ), + }, + { + id: 'recipes-button-door', + title: 'S8. Кнопка по E и дверь', + body: ( + <> +

+ Взаимодействие по клавише E (как в Roblox ProximityPrompt) + — через game.self.onInteract. Появляется подсказка + «[E] …» когда игрок рядом. +

+ + {`game.self.onInteract(() => { + game.ui.showText('Открыто!', 2); + game.broadcast('open-door'); +}, { text: 'Открыть', key: 'e', distance: 4 });`} +

На двери — глобальный/объектный скрипт, который её открывает:

+ + {`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); // через неё можно пройти +});`} + + holdDuration: 1 в опциях onInteract — держать E + 1 секунду (для важных действий). distance — + с какого расстояния появляется подсказка. + + + ), + }, + { + id: 'recipes-gui-timer', + title: 'S9. Надписи на экране, таймер, кнопки GUI', + body: ( + <> +

HUD-надписи в углу и по центру:

+ + {`game.ui.score = 0; // счётчик «Очки: 0» в углу +game.ui.score = 50; // обновить +game.ui.timer = 60; // таймер mm:ss в углу +game.ui.showText('Старт!', 2); // крупно по центру на 2 сек +game.ui.set('hp', 'Жизни: 3', { x: 50, y: 90, color: '#fff' }); // своя метка +game.ui.remove('hp'); // убрать метку`} +

Обратный отсчёт и проигрыш по времени:

+ {`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(); + } +});`} +

Кнопка на экране (GUI) и обработка клика:

+ {`const btn = game.gui.create('button', { + name: 'start', text: 'НАЧАТЬ', x: 50, y: 80, + w: 20, h: 8, bg: '#3a6ee0', color: '#fff', fontSize: 18, borderRadius: 12 +}); +game.gui.onClick(btn, () => { + game.ui.showText('Поехали!', 2); + game.gui.hide(btn); +});`} + + ), + }, + { + id: 'recipes-spawn-fall', + title: 'S10. Спавн, падение, проверка падения вниз', + body: ( + <> +

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

+ + {`game.every(1, () => { + const x = game.random(-10, 10); + game.scene.spawn('sphere', { + x: x, y: 20, z: 0, + color: '#ffd700', material: 'neon', + anchored: false, // будет падать (физика) + lifetime: 8 // само исчезнет через 8 сек + }); +});`} +

+ Игрок упал вниз (за карту) — вернуть на спавн. Проверяем + высоту каждый кадр: +

+ {`game.onTick(() => { + if (game.player.position.y < -10) { + game.player.respawn(); + game.ui.showText('Упал! Назад на старт.', 2); + } +});`} +

Финиш — дошёл до зоны, победа:

+ + {`game.self.onTouch(() => { + game.ui.showText('🏁 ПОБЕДА!', 4); + game.sound.play('win'); + game.player.setInputBlocked(true); // заморозить управление +});`} + + ), + }, + { + id: 'recipes-npc-enemy', + title: 'S11. Враг, который идёт за игроком', + body: ( + <> +

+ NPC/враг, который преследует игрока и наносит урон. +

+ + {`const enemy = game.scene.spawnNpc('zombie', { + x: 10, y: 0, z: 10, + hp: 100, name: 'Зомби', speed: 3 +}); +enemy.follow('player'); // идти за игроком +enemy.say('Хочу тебя поймать!', 3); + +enemy.onDeath(() => { + game.ui.showText('Враг повержен!', 2); + game.fx.damageFloater(enemy.position, 0, { isHeal: true }); +});`} +

Урон игроку, когда враг близко:

+ {`game.every(0.5, () => { + const d = game.distance(enemy.position, game.player.position); + if (d < 2) game.player.damage(10); +});`} + + Облачка урона над всеми мобами одной строкой: + game.fx.autoMobFloaters(true). + + + ), + }, + { + id: 'recipes-save', + title: 'S12. Сохранение прогресса и лидерборд', + body: ( + <> +

+ Лидерборд (таблица очков справа) — объяви стат и + прибавляй: +

+ + {`game.leaderstats.define('Монеты', { initial: 0 }); +// прибавить текущему игроку: +game.leaderstats.me.add('Монеты', 1);`} +

+ Сохранение между сессиями (прогресс не теряется после + выхода): +

+ {`// записать +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); + } +});`} + + Собери всё вместе: монетки шлют broadcast → глобальный скрипт + считает в leaderstats → раз в N монет сохраняет через + game.save. Получится игра с прогрессом как в настоящем Roblox. + + + ), + }, + ], + }, + + // ════════════════════════════════════════════════════ + // РАЗДЕЛ — СОВМЕСТНОЕ РЕДАКТИРОВАНИЕ (Team Create) + // ════════════════════════════════════════════════════ + { + id: 'collab', + icon: 'users', + title: 'Вместе с друзьями', + summary: 'Совместное редактирование игры в реальном времени: приглашай друзей, стройте сцену вдвоём, видьте курсоры и правки друг друга.', + sections: [ + { + id: 'collab-what', + title: 'V1. Что такое совместное редактирование', + body: ( + <> +

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

+

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

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

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

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

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

+

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

+

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

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

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

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

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

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

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

+ {AI_CONTEXT} + + ), + }, + ], + }, ]; + + diff --git a/src/community/docsIcons.jsx b/src/community/docsIcons.jsx index 5de69c8..f802298 100644 --- a/src/community/docsIcons.jsx +++ b/src/community/docsIcons.jsx @@ -21,6 +21,14 @@ const F = { fill: 'currentColor', stroke: 'none' }; const ICONS = { // ── разделы вики ────────────────────────────────────────── + users: () => ( + <> + + + + + + ), rocket: () => ( <>