feat(studio): +25 готовых механик из Вики (вся партия 3 — все остальные)
Добавлены все оставшиеся механики из TOOLBOX_KITS_FROM_WIKI.md: Мир: зона опасности, шипы, светофор, грядка-урожай, падающие предметы. Интерфейс: счётчик очков, HP-бар, дверь по коду (textbox), метка с именем, обратный отсчёт, 3D-стрелка-указатель. Эффекты: костёр (particles fire), магнит монет. NPC и бой (новая категория): преследователь, торговец (modal.dialog), мишень, враг с HP, волна врагов, диалог/кат-сцена, машина (vehicle:car). Экономика (новая категория): магазин-кнопка, кликер, ключ+замок. +2 категории китов (NPC и бой, Экономика). Всего ~37 китов. Опущены «Главное меню» и «Экран загрузки» — требуют целой сцены, не «1 клик». Все 45 скриптов прошли синтаксис-проверку, билд зелёный. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f270854795
commit
ed7310a532
@ -20,6 +20,8 @@ export const KIT_CATEGORIES = [
|
||||
{ id: 'world', label: 'Мир' },
|
||||
{ id: 'ui', label: 'Интерфейс' },
|
||||
{ id: 'fx', label: 'Эффекты' },
|
||||
{ id: 'npc', label: 'NPC и бой' },
|
||||
{ id: 'economy', label: 'Экономика' },
|
||||
];
|
||||
|
||||
export const GAMEPLAY_KITS = [
|
||||
@ -424,6 +426,388 @@ game.self.onTouch(() => {
|
||||
game.after(0.25, () => { game.self.setColor('#6f8bff'); cd = false; });
|
||||
});` }],
|
||||
},
|
||||
|
||||
// ===== Партия 3 из Вики (остальные механики) =====
|
||||
|
||||
// --- Мир ---
|
||||
{
|
||||
id: 'damage-zone',
|
||||
name: 'Зона опасности',
|
||||
desc: 'Невидимая зона: пока игрок внутри — теряет здоровье. (Вики: «Зона опасности»)',
|
||||
icon: 'warning', category: 'world',
|
||||
prims: [{ type: 'cube', x: 0, y: 2, z: 0, sx: 6, sy: 4, sz: 6, color: '#ff3344', material: 'glass', canCollide: false, name: 'Зона опасности' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Зона урона: внутри — 10 HP/сек.
|
||||
let inside = false, t = null;
|
||||
game.self.onTouch(() => { if (inside) return; inside = true;
|
||||
const tick = () => { if (!inside) return; game.player.damage(10);
|
||||
game.ui.set('dz', '☠ Опасно! -10 HP', { x:50, y:78, anchor:'bottom', color:'#ff5555', size:18 });
|
||||
t = game.after(1, tick); }; tick(); });
|
||||
game.self.onUntouch(() => { inside = false; if (t) game.cancel(t); game.ui.set('dz',''); });` }],
|
||||
},
|
||||
{
|
||||
id: 'spikes-trap',
|
||||
name: 'Шипы-ловушка',
|
||||
desc: 'Ряд острых шипов: наступишь — мгновенный урон. (Вики: «Полоса препятствий»)',
|
||||
icon: 'warning', category: 'world',
|
||||
prims: [
|
||||
{ type: 'cube', x: 0, y: 0.1, z: 0, sx: 4, sy: 0.2, sz: 1.5, color: '#555', material: 'metal', name: 'Основание шипов' },
|
||||
{ type: 'cone', x: -1.2, y: 0.7, z: 0, sx: 0.6, sy: 1.2, sz: 0.6, color: '#cccccc', material: 'metal', canCollide: false, name: 'Шип 1' },
|
||||
{ type: 'cone', x: 0, y: 0.7, z: 0, sx: 0.6, sy: 1.2, sz: 0.6, color: '#cccccc', material: 'metal', canCollide: false, name: 'Шип 2' },
|
||||
{ type: 'cone', x: 1.2, y: 0.7, z: 0, sx: 0.6, sy: 1.2, sz: 0.6, color: '#cccccc', material: 'metal', canCollide: false, name: 'Шип 3' },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Шипы: касание → урон + отброс.
|
||||
let cd = false;
|
||||
game.self.onTouch(() => { if (cd) return; cd = true;
|
||||
game.player.damage(34);
|
||||
game.ui.set('sp', '🗡 Ой! -34 HP', { x:50, y:78, anchor:'bottom', color:'#ff5555', size:18 });
|
||||
game.after(1.2, () => { game.ui.set('sp',''); cd = false; }); });` }],
|
||||
},
|
||||
{
|
||||
id: 'traffic-light',
|
||||
name: 'Светофор',
|
||||
desc: 'Светофор переключает красный/жёлтый/зелёный по таймеру. (Вики: «Светофор»)',
|
||||
icon: 'light', category: 'world',
|
||||
prims: [
|
||||
{ type: 'cube', x: 0, y: 3, z: 0, sx: 1.2, sy: 4, sz: 1.2, color: '#2a2a2a', material: 'matte', name: 'Корпус светофора' },
|
||||
{ type: 'sphere', x: 0, y: 4.2, z: 0.6, sx: 0.8, sy: 0.8, sz: 0.4, color: '#5a0000', material: 'neon', canCollide: false, name: 'Красный' },
|
||||
{ type: 'sphere', x: 0, y: 3.3, z: 0.6, sx: 0.8, sy: 0.8, sz: 0.4, color: '#5a5a00', material: 'neon', canCollide: false, name: 'Жёлтый' },
|
||||
{ type: 'sphere', x: 0, y: 2.4, z: 0.6, sx: 0.8, sy: 0.8, sz: 0.4, color: '#005a00', material: 'neon', canCollide: false, name: 'Зелёный' },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Светофор: переключение фаз каждые 2 сек.
|
||||
const R = game.scene.findOne('Красный'), Y = game.scene.findOne('Жёлтый'), G = game.scene.findOne('Зелёный');
|
||||
const phases = [['#ff0000','#5a5a00','#005a00'], ['#5a0000','#ffcc00','#005a00'], ['#5a0000','#5a5a00','#00ff00']];
|
||||
let p = 0;
|
||||
function show(){ const c = phases[p]; if(R) R.color = c[0]; if(Y) Y.color = c[1]; if(G) G.color = c[2]; }
|
||||
show();
|
||||
game.every(2, () => { p = (p+1) % phases.length; show(); });` }],
|
||||
},
|
||||
{
|
||||
id: 'harvest-plant',
|
||||
name: 'Грядка с урожаем',
|
||||
desc: 'Растение растёт, по клику собираешь урожай (+10 монет) и оно вырастает заново. (Вики: «Сбор урожая»)',
|
||||
icon: 'plant', category: 'world',
|
||||
prims: [
|
||||
{ type: 'cube', x: 0, y: 0.2, z: 0, sx: 2, sy: 0.4, sz: 2, color: '#6b4a2e', material: 'matte', name: 'Грядка' },
|
||||
{ type: 'sphere', x: 0, y: 0.8, z: 0, sx: 1, sy: 1, sz: 1, color: '#3f9a48', material: 'matte', name: 'Урожай' },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Грядка: урожай растёт, по взаимодействию — сбор +10 монет.
|
||||
const plant = game.scene.findOne('Урожай');
|
||||
let ripe = true;
|
||||
game.self.onInteract(() => {
|
||||
if (!ripe) { game.ui.set('h','Ещё не созрело...', {x:50,y:80,anchor:'bottom',color:'#bbb',size:16}); return; }
|
||||
ripe = false;
|
||||
game.broadcast('coins', { add: 10 });
|
||||
if (plant) plant.visible = false;
|
||||
game.ui.set('h','🌾 Собрано! +10 монет', {x:50,y:80,anchor:'bottom',color:'#ffd23a',size:18});
|
||||
game.after(3, () => { if (plant) plant.visible = true; ripe = true; game.ui.set('h',''); });
|
||||
}, { text: 'Собрать урожай', key: 'e', distance: 4 });` }],
|
||||
},
|
||||
{
|
||||
id: 'falling-objects',
|
||||
name: 'Падающие предметы',
|
||||
desc: 'Из этой точки с неба сыплются кубики — лови их или уворачивайся. (Вики: «Поймай падающее»)',
|
||||
icon: 'box', category: 'world',
|
||||
prims: [{ type: 'sphere', x: 0, y: 8, z: 0, sx: 0.6, sy: 0.6, sz: 0.6, color: '#4d6bff', material: 'neon', canCollide: false, name: 'Тучка-источник' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Каждые 1.5с роняет куб из позиции источника.
|
||||
const p = game.self.position;
|
||||
game.every(1.5, () => {
|
||||
game.scene.spawn('primitive:cube', {
|
||||
x: p.x + (Math.random()-0.5)*6, y: p.y, z: p.z + (Math.random()-0.5)*6,
|
||||
sx: 0.6, sy: 0.6, sz: 0.6, color: '#ffaa33', anchored: false, canCollide: true, lifetime: 6 });
|
||||
});` }],
|
||||
},
|
||||
|
||||
// --- Интерфейс ---
|
||||
{
|
||||
id: 'score-counter',
|
||||
name: 'Счётчик очков',
|
||||
desc: 'Счёт очков в HUD. Другие механики шлют game.broadcast("score",{add:N}). (Вики: «Собери монетки»)',
|
||||
icon: 'star', category: 'ui',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Счётчик очков. Прибавить: game.broadcast('score', { add: 1 });
|
||||
let score = 0;
|
||||
function show(){ game.ui.set('score', '⭐ ' + score, { x:8, y:6, anchor:'top', color:'#ffd23a', size:22 }); }
|
||||
show();
|
||||
game.onMessage('score', (m) => { score += (m && m.add) ? m.add : 1; show(); });` }],
|
||||
},
|
||||
{
|
||||
id: 'hp-bar',
|
||||
name: 'Полоска здоровья',
|
||||
desc: 'Показывает HP игрока в углу экрана, обновляется при уроне/лечении. (Вики: «Лава-пол»)',
|
||||
icon: 'warning', category: 'ui',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// HP-индикатор игрока в HUD.
|
||||
function show(){ const hp = Math.max(0, Math.round(game.player.hp));
|
||||
game.ui.set('hp', '❤ ' + hp, { x:8, y:12, anchor:'top', color: hp>30?'#36d57a':'#ff4444', size:22 }); }
|
||||
show();
|
||||
game.every(0.3, show);` }],
|
||||
},
|
||||
{
|
||||
id: 'code-door',
|
||||
name: 'Дверь по коду',
|
||||
desc: 'Поле ввода: введи правильный код (1234) — дверь открывается. (Вики: «Дверь по коду»)',
|
||||
icon: 'keypad', category: 'ui',
|
||||
prims: [{ type: 'cube', x: 0, y: 2, z: 0, sx: 0.5, sy: 4, sz: 3, color: '#5a6478', material: 'metal', name: 'Дверь-код' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Дверь по коду 1234. Поле ввода снизу.
|
||||
const CODE = '1234';
|
||||
const inp = game.gui.create('textbox', { id:'codein', x:50, y:88, w:24, h:8, anchor:'center', placeholder:'Код...', textSize:20 });
|
||||
game.ui.set('codehint', 'Введи код двери (1234) и нажми Enter', {x:50,y:80,anchor:'bottom',color:'#fff',size:16});
|
||||
let opened = false;
|
||||
const p0 = game.self.position;
|
||||
game.gui.onSubmit('codein', (text) => {
|
||||
if (opened) return;
|
||||
if (String(text).trim() === CODE) { opened = true; game.self.move(p0.x, p0.y-4.2, p0.z);
|
||||
game.ui.set('codehint', '✓ Открыто!', {x:50,y:80,anchor:'bottom',color:'#36d57a',size:18}); }
|
||||
else game.ui.set('codehint', '✗ Неверный код', {x:50,y:80,anchor:'bottom',color:'#ff5555',size:18});
|
||||
});` }],
|
||||
},
|
||||
{
|
||||
id: 'name-label',
|
||||
name: 'Метка с именем',
|
||||
desc: 'Над объектом висит табличка с текстом (имя/HP). (Вики: «Имена над врагами»)',
|
||||
icon: 'tag', category: 'ui',
|
||||
prims: [{ type: 'cube', x: 0, y: 1, z: 0, sx: 1.5, sy: 2, sz: 1.5, color: '#c83030', material: 'matte', name: 'Объект с меткой' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Метка-табличка над объектом.
|
||||
game.self.setLabel('Враг ❤ 100', { color: '#ffffff', bg: '#c83030' });` }],
|
||||
},
|
||||
{
|
||||
id: 'countdown',
|
||||
name: 'Обратный отсчёт',
|
||||
desc: 'Таймер обратного отсчёта в HUD. По нулю — событие (тут просто сообщение). (Вики: «продержись N секунд»)',
|
||||
icon: 'clock', category: 'ui',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Обратный отсчёт 30 секунд.
|
||||
let left = 30;
|
||||
game.ui.set('cd', '⏳ ' + left, { x:50, y:6, anchor:'top', color:'#fff', size:26 });
|
||||
const id = game.every(1, () => {
|
||||
left--; game.ui.set('cd', '⏳ ' + left, { x:50, y:6, anchor:'top', color: left<=5?'#ff4444':'#fff', size:26 });
|
||||
if (left <= 0) { game.cancel(id); game.ui.set('cd', '⏰ Время вышло!', { x:50, y:42, anchor:'center', color:'#ff4444', size:40 }); }
|
||||
});` }],
|
||||
},
|
||||
|
||||
// --- Эффекты ---
|
||||
{
|
||||
id: 'fire-emitter',
|
||||
name: 'Костёр (огонь)',
|
||||
desc: 'Источник частиц огня — горит постоянно. (Палитра эффектов)',
|
||||
icon: 'sparkles', category: 'fx',
|
||||
prims: [{ type: 'cylinder', x: 0, y: 0.2, z: 0, sx: 1.2, sy: 0.4, sz: 1.2, color: '#3a2a1a', material: 'matte', name: 'Костёр' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Постоянный огонь из точки костра.
|
||||
const p = game.self.position;
|
||||
function fire(){ game.scene.spawnParticles('fire', { x:p.x, y:p.y+0.5, z:p.z }, { duration: 1.5, count: 40 }); }
|
||||
fire(); game.every(1.2, fire);` }],
|
||||
},
|
||||
{
|
||||
id: 'magnet-coins',
|
||||
name: 'Магнит монет',
|
||||
desc: 'Монета сама летит к игроку, когда он подходит близко. (Вики: «Магнит монет»)',
|
||||
icon: 'circle', category: 'fx',
|
||||
prims: [{ type: 'cylinder', x: 0, y: 1, z: 0, sx: 0.8, sy: 0.2, sz: 0.8, color: '#ffd23a', material: 'metal', name: 'Монета' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Монета летит к игроку, если он ближе 6 м; коснулся — +1 монета.
|
||||
let taken = false;
|
||||
game.onTick(() => {
|
||||
if (taken) return;
|
||||
const me = game.self.position, pl = game.player.position;
|
||||
const dx = pl.x-me.x, dy = pl.y-me.y, dz = pl.z-me.z;
|
||||
const d = Math.sqrt(dx*dx+dy*dy+dz*dz);
|
||||
if (d < 1.2) { taken = true; game.broadcast('coins', { add: 1 }); game.self.setVisible(false); return; }
|
||||
if (d < 6) { game.self.move(me.x+dx*0.12, me.y+dy*0.12, me.z+dz*0.12); }
|
||||
});` }],
|
||||
},
|
||||
|
||||
// --- NPC и бой ---
|
||||
{
|
||||
id: 'npc-chaser',
|
||||
name: 'NPC-преследователь',
|
||||
desc: 'Враг бежит за игроком по всему уровню. (Вики: «Преследователь»)',
|
||||
icon: 'chase', category: 'npc',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Спавним NPC, который преследует игрока.
|
||||
const enemy = game.scene.spawnNpc('robot', { x: 8, z: 8, name: 'Охотник', speed: 4 });
|
||||
if (enemy && enemy.follow) enemy.follow('player');` }],
|
||||
},
|
||||
{
|
||||
id: 'npc-trader',
|
||||
name: 'Торговец (диалог)',
|
||||
desc: 'Фигура торговца: подойди, нажми E — открывается диалог. (Вики: «Торговец»)',
|
||||
icon: 'trader', category: 'npc',
|
||||
prims: [
|
||||
{ type: 'cylinder', x: 0, y: 1, z: 0, sx: 1.2, sy: 2, sz: 1.2, color: '#3a6ea5', material: 'matte', name: 'Торговец' },
|
||||
{ type: 'sphere', x: 0, y: 2.3, z: 0, sx: 0.9, sy: 0.9, sz: 0.9, color: '#e8c8a0', material: 'matte', canCollide: false, name: 'Голова торговца' },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Торговец с диалогом по E.
|
||||
game.self.setLabel('Торговец Боб', { color:'#fff', bg:'#3a6ea5' });
|
||||
game.self.onInteract(() => {
|
||||
game.modal.dialog('Торговец Боб', [
|
||||
'Привет, путник! Заходи за товарами.',
|
||||
'У меня лучшие мечи во всём королевстве!',
|
||||
'Возвращайся, когда накопишь монет.',
|
||||
]);
|
||||
}, { text: 'Поговорить', key: 'e', distance: 4 });` }],
|
||||
},
|
||||
{
|
||||
id: 'shooting-target',
|
||||
name: 'Мишень для стрельбы',
|
||||
desc: 'Кликни по мишени — +10 очков, мишень исчезает и появляется снова. (Вики: «Тир»)',
|
||||
icon: 'crosshair', category: 'npc',
|
||||
prims: [
|
||||
{ type: 'cylinder', x: 0, y: 2, z: 0, sx: 0.3, sy: 2, sz: 2, color: '#ffffff', material: 'matte', name: 'Мишень' },
|
||||
{ type: 'cylinder', x: 0.2, y: 2, z: 0, sx: 0.1, sy: 1.2, sz: 1.2, color: '#ff3333', material: 'matte', canCollide: false, name: 'Кольцо мишени' },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Мишень: клик → +10 очков, прячется на 1.5с.
|
||||
let active = true;
|
||||
game.self.onClick(() => {
|
||||
if (!active) return; active = false;
|
||||
game.broadcast('score', { add: 10 });
|
||||
game.self.setVisible(false);
|
||||
game.ui.set('hit', '🎯 +10!', { x:50, y:75, anchor:'bottom', color:'#36d57a', size:20 });
|
||||
game.after(1.5, () => { game.self.setVisible(true); active = true; game.ui.set('hit',''); });
|
||||
});` }],
|
||||
},
|
||||
{
|
||||
id: 'enemy-hp',
|
||||
name: 'Враг с HP',
|
||||
desc: 'Враг с полоской здоровья над головой. Кликай — урон, при нуле HP погибает. (Вики: «Имена над врагами», «босс»)',
|
||||
icon: 'boss', category: 'npc',
|
||||
prims: [{ type: 'cube', x: 0, y: 1.5, z: 0, sx: 1.5, sy: 3, sz: 1.5, color: '#7a2030', material: 'matte', name: 'Враг' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Враг с HP: клик → -20 HP, метка обновляется, при 0 — исчезает.
|
||||
let hp = 100;
|
||||
function lbl(){ game.self.setLabel('Враг ❤ ' + hp, { color:'#fff', bg:'#7a2030' }); }
|
||||
lbl();
|
||||
game.self.onClick(() => {
|
||||
hp -= 20; if (hp <= 0) { game.self.setVisible(false); game.broadcast('score', { add: 50 });
|
||||
game.ui.set('kill', '💀 Враг повержен! +50', { x:50, y:75, anchor:'bottom', color:'#ffd23a', size:18 }); return; }
|
||||
lbl();
|
||||
});` }],
|
||||
},
|
||||
{
|
||||
id: 'enemy-wave',
|
||||
name: 'Волна врагов',
|
||||
desc: 'Спавнер: выпускает врагов волнами по таймеру. (Вики: «Выживание от волн», tower defense)',
|
||||
icon: 'zombie', category: 'npc',
|
||||
prims: [{ type: 'cylinder', x: 0, y: 0.15, z: 0, sx: 2, sy: 0.3, sz: 2, color: '#7a2030', material: 'neon', name: 'Портал врагов' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Каждые 5с спавнит 2 врагов из точки портала, они идут к игроку.
|
||||
const p = game.self.position;
|
||||
function wave(){
|
||||
for (let i=0;i<2;i++){ const e = game.scene.spawnNpc('robot', { x:p.x+(Math.random()-0.5)*2, z:p.z+(Math.random()-0.5)*2, name:'Враг', speed:3 });
|
||||
if (e && e.follow) e.follow('player'); }
|
||||
}
|
||||
game.after(2, wave); game.every(5, wave);` }],
|
||||
},
|
||||
|
||||
// --- Экономика ---
|
||||
{
|
||||
id: 'shop-button',
|
||||
name: 'Магазин (кнопка покупки)',
|
||||
desc: 'GUI-кнопка магазина: покупка предмета за 50 монет (если хватает). (Вики: «Магазин»)',
|
||||
icon: 'cart', category: 'economy',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Кнопка магазина: купить за 50 монет.
|
||||
let coins = 100; // локальный баланс кита (для демо)
|
||||
game.ui.set('bal', '🪙 ' + coins, { x:92, y:6, anchor:'top', color:'#ffd23a', size:22 });
|
||||
game.onMessage('coins', (m) => { coins += (m&&m.add)?m.add:0; game.ui.set('bal','🪙 '+coins,{x:92,y:6,anchor:'top',color:'#ffd23a',size:22}); });
|
||||
game.gui.create('button', { id:'buybtn', x:50, y:90, w:26, h:9, anchor:'center', text:'Купить меч — 50 🪙',
|
||||
bgGradient:{ stops:['#ffe066','#e0a000'], angle:90 }, textColor:'#3a2a00', textSize:18, fontWeight:800, borderRadius:12 });
|
||||
game.gui.onClick('buybtn', () => {
|
||||
if (coins >= 50) { game.broadcast('coins', { add: -50 }); game.ui.set('shopmsg','✓ Куплено!',{x:50,y:80,anchor:'bottom',color:'#36d57a',size:18}); }
|
||||
else game.ui.set('shopmsg','✗ Мало монет',{x:50,y:80,anchor:'bottom',color:'#ff5555',size:18});
|
||||
game.after(2, () => game.ui.set('shopmsg',''));
|
||||
});` }],
|
||||
},
|
||||
{
|
||||
id: 'clicker-button',
|
||||
name: 'Кликер',
|
||||
desc: 'GUI-кнопка: кликай и копи очки. (Вики: «Кликер»)',
|
||||
icon: 'click', category: 'economy',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Кликер: кнопка по центру, клик → +1 очко.
|
||||
let n = 0;
|
||||
function show(){ game.ui.set('clk', '👆 ' + n, { x:50, y:20, anchor:'center', color:'#fff', size:36 }); }
|
||||
show();
|
||||
game.gui.create('button', { id:'clickbtn', x:50, y:55, w:30, h:14, anchor:'center', text:'КЛИК!',
|
||||
bgGradient:{ stops:['#6f8bff','#3a4ed0'], angle:90 }, textColor:'#fff', textSize:32, fontWeight:900, borderRadius:18,
|
||||
hover:{ scale:1.05 }, active:{ scale:0.94 } });
|
||||
game.gui.onClick('clickbtn', () => { n++; show(); });` }],
|
||||
},
|
||||
{
|
||||
id: 'key-lock',
|
||||
name: 'Ключ и замок',
|
||||
desc: 'Подбери ключ, затем открой запертую дверь. Без ключа дверь не открывается. (Вики: «Ключ и сундук»)',
|
||||
icon: 'key', category: 'economy',
|
||||
prims: [
|
||||
{ type: 'cube', x: 0, y: 1, z: 0, sx: 0.4, sy: 0.8, sz: 0.4, color: '#ffd23a', material: 'metal', name: 'Ключ' },
|
||||
{ type: 'cube', x: 6, y: 2, z: 0, sx: 0.5, sy: 4, sz: 3, color: '#6b4423', material: 'matte', name: 'Запертая дверь' },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Ключ (этот объект) подбирается касанием → открывает «Запертую дверь».
|
||||
let hasKey = false;
|
||||
game.self.onTouch(() => {
|
||||
if (hasKey) return; hasKey = true;
|
||||
game.self.setVisible(false);
|
||||
game.ui.set('key', '🔑 Ключ найден! Иди к двери (E).', {x:50,y:80,anchor:'bottom',color:'#ffd23a',size:18});
|
||||
});
|
||||
const door = game.scene.findOne('Запертая дверь');
|
||||
if (door && door.onInteract) {
|
||||
let opened = false;
|
||||
door.onInteract(() => {
|
||||
if (!hasKey) { game.ui.set('key','🔒 Заперто. Нужен ключ.',{x:50,y:80,anchor:'bottom',color:'#ff5555',size:18}); return; }
|
||||
if (opened) return; opened = true;
|
||||
const dp = door.position; door.move(dp.x, dp.y-4.2, dp.z);
|
||||
game.ui.set('key','✓ Дверь открыта!',{x:50,y:80,anchor:'bottom',color:'#36d57a',size:18});
|
||||
}, { text:'Открыть дверь', key:'e', distance:4 });
|
||||
}` }],
|
||||
},
|
||||
|
||||
// --- Из готовых игр (g5) ---
|
||||
{
|
||||
id: 'spawn-car',
|
||||
name: 'Машина (сядь за руль)',
|
||||
desc: 'Готовый автомобиль: подойди, держи F — садись за руль, WASD — едь. (Вики: «Такси-симулятор»)',
|
||||
icon: 'car', category: 'npc',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Спавн машины, на которой можно ездить.
|
||||
game.scene.spawn('vehicle:car', { x: 0, y: 0.5, z: 5, model: 'car-sedan', color: '#c83030',
|
||||
name: 'Авто', params: { maxSpeed: 18, turnSpeed: 1.7, enginePower: 20 } });
|
||||
game.ui.set('carhint', 'Подойди к машине и держи F — за руль!', {x:50,y:90,anchor:'bottom',color:'#fff',size:16});` }],
|
||||
},
|
||||
{
|
||||
id: 'cutscene-dialog',
|
||||
name: 'Диалог (кат-сцена)',
|
||||
desc: 'Объект, по взаимодействию показывает диалог по строкам. (Вики: «Тайна старого сундука»)',
|
||||
icon: 'scroll', category: 'npc',
|
||||
prims: [{ type: 'cube', x: 0, y: 0.6, z: 0, sx: 1.4, sy: 1.2, sz: 1, color: '#8a6a3a', material: 'matte', name: 'Рассказчик' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Диалог по строкам через готовый game.modal.dialog.
|
||||
const lines = ['Давным-давно здесь стоял замок...', 'Его охранял древний страж.', 'Найди три ключа, чтобы войти!'];
|
||||
game.self.onInteract(() => {
|
||||
game.modal.dialog('Рассказчик', lines);
|
||||
}, { text:'Поговорить', key:'e', distance:4 });` }],
|
||||
},
|
||||
{
|
||||
id: 'guide-arrow',
|
||||
name: '3D-стрелка-указатель',
|
||||
desc: 'Стрелка-подсказка «иди сюда» ведёт игрока к цели. (Вики: «Туториал — собери монетки»)',
|
||||
icon: 'flag', category: 'ui',
|
||||
prims: [{ type: 'cylinder', x: 0, y: 1, z: 0, sx: 1, sy: 2, sz: 1, color: '#ffd23a', material: 'neon', name: 'Цель-указатель' }],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Стрелка от игрока к этому объекту-цели.
|
||||
const arrow = game.fx.pointer({ from: 'player', to: game.self, preset: 'guide' });
|
||||
game.self.onTouch(() => { if (arrow && arrow.remove) arrow.remove();
|
||||
game.ui.set('arr','✓ Дошёл!', {x:50,y:80,anchor:'bottom',color:'#36d57a',size:18}); });` }],
|
||||
},
|
||||
];
|
||||
|
||||
/** Найти кит по id. */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user