fix(studio): дверь-код подсказка E только при открытой, NPC=R15-скин (анимация рук/ног), торговец видимый

1) Дверь по коду: подсказка «E закрыть» теперь только когда дверь открыта
   и игрок рядом (через onKey+onTick, без постоянного interact-промпта).
2) NPC-киты используют R15-скин 'skin_roblox-noob' → процедурная анимация
   бега/покоя с руками и ногами (R15Animator), вместо безжизненного покачивания.
3) Торговец-кит: невидимый триггер (insertGameplayKit теперь уважает
   prim.visible=false, раньше хардкод visible:true) + NPC-персонаж рядом —
   синий куб больше не виден.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
min 2026-06-05 19:54:38 +03:00
parent 9903719f9d
commit 26e6306f6e
2 changed files with 22 additions and 15 deletions

View File

@ -800,7 +800,7 @@ const KubikonEditor = () => {
x: px + (p.x || 0), y: py + (p.y != null ? p.y : 1), z: pz + (p.z || 0), x: px + (p.x || 0), y: py + (p.y != null ? p.y : 1), z: pz + (p.z || 0),
sx: p.sx, sy: p.sy, sz: p.sz, sx: p.sx, sy: p.sy, sz: p.sz,
color: p.color, material: p.material, color: p.color, material: p.material,
canCollide: p.canCollide !== false, visible: true, anchored: true, canCollide: p.canCollide !== false, visible: p.visible !== false, anchored: true,
name: p.name, name: p.name,
}); });
if (newId != null) { if (newId != null) {

View File

@ -617,10 +617,15 @@ function place(a){
} }
game.onTick((dt) => { game.onTick((dt) => {
if (cur !== target){ const st=SPEED*dt; cur = Math.abs(target-cur)<=st ? target : cur+Math.sign(target-cur)*st; place(cur); } if (cur !== target){ const st=SPEED*dt; cur = Math.abs(target-cur)<=st ? target : cur+Math.sign(target-cur)*st; place(cur); }
if (opened) return;
// Поле ввода появляется/прячется по дистанции.
const pl = game.player.position; const pl = game.player.position;
const d = Math.sqrt((pl.x-p0.x)**2 + (pl.z-p0.z)**2); const d = Math.sqrt((pl.x-p0.x)**2 + (pl.z-p0.z)**2);
if (opened){
// Дверь открыта: подсказка «E закрыть» только когда игрок рядом.
if (d < RADIUS && !near){ near = true; game.ui.set('codehint','Нажми E чтобы закрыть дверь', {x:50,y:80,anchor:'bottom',color:'#fff',size:16}); }
else if (d >= RADIUS && near){ near = false; game.ui.set('codehint','',{}); }
return;
}
// Дверь закрыта: поле ввода кода по дистанции.
if (d < RADIUS && !near){ near = true; if (d < RADIUS && !near){ near = true;
game.gui.create('textbox', { id:'codein', x:50, y:86, w:24, h:8, anchor:'center', placeholder:'Код...', textSize:20 }); game.gui.create('textbox', { id:'codein', x:50, y:86, w:24, h:8, anchor:'center', placeholder:'Код...', textSize:20 });
game.ui.set('codehint', '🔢 Введи код двери (1234) и нажми Enter', {x:50,y:78,anchor:'bottom',color:'#fff',size:16}); } game.ui.set('codehint', '🔢 Введи код двери (1234) и нажми Enter', {x:50,y:78,anchor:'bottom',color:'#fff',size:16}); }
@ -629,19 +634,21 @@ game.onTick((dt) => {
game.gui.onSubmit('codein', (text) => { game.gui.onSubmit('codein', (text) => {
if (opened) return; if (opened) return;
if (String(text).trim() === CODE){ if (String(text).trim() === CODE){
opened = true; target = Math.PI/2; // плавно распахнуть opened = true; near = false; target = Math.PI/2; // плавно распахнуть
game.gui.remove('codein'); game.gui.remove('codein');
game.ui.set('codehint','✓ Открыто! Нажми E чтобы закрыть.', {x:50,y:78,anchor:'bottom',color:'#36d57a',size:16}); game.ui.set('codehint','✓ Открыто!', {x:50,y:78,anchor:'bottom',color:'#36d57a',size:18});
game.after(2.5, () => game.ui.set('codehint','',{})); game.after(2, () => game.ui.set('codehint','',{}));
} else game.ui.set('codehint','✗ Неверный код', {x:50,y:78,anchor:'bottom',color:'#ff5555',size:18}); } else game.ui.set('codehint','✗ Неверный код', {x:50,y:78,anchor:'bottom',color:'#ff5555',size:18});
}); });
// Закрыть открытую дверь по E → снова можно вводить код. // Закрыть дверь по E (только если открыта и игрок рядом).
game.self.onInteract(() => { game.onKey('e', () => {
if (!opened) return; if (!opened) return;
opened = false; near = false; target = 0; // плавно закрыть const pl = game.player.position;
game.ui.set('codehint','🔒 Дверь закрыта.', {x:50,y:78,anchor:'bottom',color:'#fff',size:16}); if (Math.sqrt((pl.x-p0.x)**2 + (pl.z-p0.z)**2) >= RADIUS) return;
game.after(2, () => game.ui.set('codehint','',{})); opened = false; near = false; target = 0;
}, { text: 'Закрыть дверь', key: 'e', distance: 4 });` }], game.ui.set('codehint','🔒 Дверь закрыта.', {x:50,y:80,anchor:'bottom',color:'#fff',size:16});
game.after(1.5, () => game.ui.set('codehint','',{}));
});` }],
}, },
{ {
id: 'name-label', id: 'name-label',
@ -708,7 +715,7 @@ game.onTick(() => {
icon: 'chase', category: 'npc', icon: 'chase', category: 'npc',
scripts: [{ attachTo: 'global', code: scripts: [{ attachTo: 'global', code:
`// Спавним NPC, который преследует игрока. `// Спавним NPC, который преследует игрока.
const enemy = game.scene.spawnNpc('character-a', { x: 8, z: 8, name: 'Охотник', speed: 4 }); const enemy = game.scene.spawnNpc('skin_roblox-noob', { x: 8, z: 8, name: 'Охотник', speed: 4 });
if (enemy && enemy.follow) enemy.follow('player');` }], if (enemy && enemy.follow) enemy.follow('player');` }],
}, },
{ {
@ -721,7 +728,7 @@ if (enemy && enemy.follow) enemy.follow('player');` }],
scripts: [{ attachTo: 'on-target', code: scripts: [{ attachTo: 'on-target', code:
`// Торговец — настоящий NPC-персонаж. Триггер (этот объект) держит диалог по E. `// Торговец — настоящий NPC-персонаж. Триггер (этот объект) держит диалог по E.
const p = game.self.position; const p = game.self.position;
const npc = game.scene.spawnNpc('character-a', { x: p.x, z: p.z, name: 'Торговец Боб' }); const npc = game.scene.spawnNpc('skin_roblox-noob', { x: p.x, z: p.z, name: 'Торговец Боб' });
game.self.onInteract(() => { game.self.onInteract(() => {
game.modal.dialog('Торговец Боб', [ game.modal.dialog('Торговец Боб', [
'Привет, путник! Заходи за товарами.', 'Привет, путник! Заходи за товарами.',
@ -777,7 +784,7 @@ game.self.onClick(() => {
`// Каждые 5с спавнит 2 врагов из точки портала, они идут к игроку. `// Каждые 5с спавнит 2 врагов из точки портала, они идут к игроку.
const p = game.self.position; const p = game.self.position;
function wave(){ function wave(){
for (let i=0;i<2;i++){ const e = game.scene.spawnNpc('character-a', { x:p.x+(Math.random()-0.5)*2, z:p.z+(Math.random()-0.5)*2, name:'Враг', speed:3 }); for (let i=0;i<2;i++){ const e = game.scene.spawnNpc('skin_roblox-noob', { 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'); } if (e && e.follow) e.follow('player'); }
} }
game.after(2, wave); game.every(5, wave);` }], game.after(2, wave); game.every(5, wave);` }],