fix(studio): светофор (obj.color по ref), грядка растёт+зреет (obj.scale), кит скрытия HP

1) scene.setColor теперь принимает {ref} (obj.color=hex), не только {id}.
   Светофор переключал цвета через obj.color, но ref игнорировался → не работал.
2) Грядка: добавлен obj.scale (scene.setScale → mesh.scaling). Урожай после
   сбора исчезает, растёт за 5с (scale 0→1) и зреет цветом красный→зелёный,
   при полном размере снова собирается.
3) Кит «HP-бар» теперь сам прячет стандартный HUD HP (setHpVisible false).
   Новый кит «Скрыть стандартный HUD HP» — отдельно прячет дефолтную полосу.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
min 2026-06-05 19:13:02 +03:00
parent 018fce474b
commit 045f892aaa
3 changed files with 60 additions and 10 deletions

View File

@ -2904,13 +2904,29 @@ export class GameRuntime {
return;
}
if (cmd === 'scene.setScale') {
try {
const k = Number(payload?.scale);
if (!Number.isFinite(k) || k < 0) return;
const pm = this.scene3d?.primitiveManager;
const rid = this._resolvePrimitiveId(payload?.id ?? payload?.ref);
const data = (pm && rid != null) ? pm.instances.get(rid) : null;
if (data?.mesh) {
if (data._worldMatrixFrozen) { try { data.mesh.unfreezeWorldMatrix?.(); } catch (e) {} data._worldMatrixFrozen = false; }
data.mesh.scaling.set(k, k, k); // визуальный масштаб от исходного размера
}
} catch (e) {}
return;
}
if (cmd === 'scene.setColor') {
try {
const color = payload?.color;
if (typeof color !== 'string') return;
// Окрашиваемый блок (studs-block): ref вида 'block:x,y,z' →
// меняем per-instance цвет через BlockManager.setBlockColor.
const ref = payload?.id;
// ВАЖНО: obj.color=hex шлёт {ref}, а self.setColor — {id}. Берём оба.
const ref = payload?.id ?? payload?.ref;
if (typeof ref === 'string' && ref.startsWith('block:')) {
const parts = ref.slice(6).split(',').map(Number);
if (parts.length === 3 && parts.every(Number.isFinite)) {
@ -2920,7 +2936,7 @@ export class GameRuntime {
}
const pm = this.scene3d?.primitiveManager;
if (!pm) return;
const rid = this._resolvePrimitiveId(payload?.id);
const rid = this._resolvePrimitiveId(payload?.id ?? payload?.ref);
const data = rid != null ? pm.instances.get(rid) : null;
if (data) {
data.color = color;

View File

@ -494,17 +494,35 @@ game.every(2, () => { p = (p+1) % phases.length; show(); });` }],
{ 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;
let ripe = true; // созрел ли (можно собирать)
let growth = 1; // 0..1 — степень роста
// Плавная интерполяция цвета красный(незрелый)→зелёный(спелый).
function colorAt(g) {
const r = Math.round(0xb0 + (0x3f - 0xb0) * g);
const gr = Math.round(0x40 + (0x9a - 0x40) * g);
const b = Math.round(0x2e + (0x48 - 0x2e) * g);
return '#' + [r,gr,b].map(v => v.toString(16).padStart(2,'0')).join('');
}
game.self.onInteract(() => {
if (!ripe) { game.ui.set('h','Ещё не созрело...', {x:50,y:80,anchor:'bottom',color:'#bbb',size:16}); return; }
ripe = false;
growth = 0;
game.broadcast('coins', { add: 10 });
if (plant) plant.visible = false;
if (plant) { plant.scale = 0.01; plant.color = colorAt(0); }
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 });` }],
game.after(2, () => game.ui.set('h',''));
}, { text: 'Собрать урожай', key: 'e', distance: 4 });
// Рост: за ~5 секунд от 0 до 1.
game.onTick((dt) => {
if (ripe || !plant) return;
growth = Math.min(1, growth + dt / 5);
plant.scale = Math.max(0.05, growth);
plant.color = colorAt(growth);
if (growth >= 1) ripe = true;
});` }],
},
{
id: 'falling-objects',
@ -541,11 +559,21 @@ game.onMessage('score', (m) => { score += (m && m.add) ? m.add : 1; show(); });`
desc: 'Показывает HP игрока в углу экрана, обновляется при уроне/лечении. (Вики: «Лава-пол»)',
icon: 'warning', category: 'ui',
scripts: [{ attachTo: 'global', code:
`// HP-индикатор игрока в HUD.
`// Своя полоска HP. Сначала прячем стандартную, чтобы не дублировалась.
game.hud.setHpVisible(false);
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 }); }
game.ui.set('hp', '❤ ' + hp + ' / 100', { x:50, y:94, anchor:'bottom', color: hp>30?'#36d57a':'#ff4444', size:22 }); }
show();
game.every(0.3, show);` }],
game.every(0.2, show);` }],
},
{
id: 'hide-default-hp',
name: 'Скрыть стандартный HUD HP',
desc: 'Прячет стандартную полосу здоровья сверху — чтобы показать свою. (Свойство игрока game.hud.setHpVisible)',
icon: 'warning', category: 'ui',
scripts: [{ attachTo: 'global', code:
`// Скрыть стандартную полосу здоровья игрока.
game.hud.setHpVisible(false);` }],
},
{
id: 'code-door',

View File

@ -554,6 +554,12 @@ function _getOrCreateInstance(ref, kindHint) {
_send('scene.setColor', { ref, color: String(value) });
return true;
}
if (prop === 'scale') {
// Равномерный визуальный масштаб объекта (1 = исходный размер).
const k = Number(value);
if (Number.isFinite(k) && k >= 0) _send('scene.setScale', { ref, scale: k });
return true;
}
if (prop === 'transparency' || prop === 'opacity') {
const v = Number(value);
if (Number.isFinite(v)) {