fix(studio): дверь-код закрывается по E, NPC ходит с анимацией, торговец=NPC, тени короче
1) Дверь по коду: открытую можно закрыть по E (onInteract) → снова вводить код. 2) NPC: процедурная анимация ходьбы (покачивание по Y + наклон корпуса) для Kenney-моделей — раньше скользили без анимации. 3) Торговец переделан в NPC-персонажа (spawnNpc character-a) + невидимый триггер с диалогом по E (вместо примитивов). 4) Тени: убрана «полоса через всю карту» — autoCalcDepthBounds off, shadowMaxZ 90/60 (было 200/120), lambda 0.6, frustumEdgeFalloff 12. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fe8b6b5b38
commit
9903719f9d
@ -1712,9 +1712,9 @@ export class BabylonScene {
|
|||||||
const csm = new CascadedShadowGenerator(size, this._sunLight);
|
const csm = new CascadedShadowGenerator(size, this._sunLight);
|
||||||
csm.numCascades = numCascades;
|
csm.numCascades = numCascades;
|
||||||
csm.stabilizeCascades = true;
|
csm.stabilizeCascades = true;
|
||||||
csm.lambda = 0.8;
|
csm.lambda = 0.6; // меньше — каскады равномернее, нет вытянутого дальнего
|
||||||
csm.cascadeBlendPercentage = 0.07;
|
csm.cascadeBlendPercentage = 0.1;
|
||||||
csm.shadowMaxZ = (q === 'high') ? 200 : 120;
|
csm.shadowMaxZ = (q === 'high') ? 90 : 60; // тени только вблизи (убирает «полосу через всю карту»)
|
||||||
csm.bias = PCF_BIAS;
|
csm.bias = PCF_BIAS;
|
||||||
csm.normalBias = PCF_NORMAL_BIAS;
|
csm.normalBias = PCF_NORMAL_BIAS;
|
||||||
csm.usePercentageCloserFiltering = true;
|
csm.usePercentageCloserFiltering = true;
|
||||||
@ -1722,10 +1722,10 @@ export class BabylonScene {
|
|||||||
? ShadowGenerator.QUALITY_HIGH
|
? ShadowGenerator.QUALITY_HIGH
|
||||||
: ShadowGenerator.QUALITY_MEDIUM;
|
: ShadowGenerator.QUALITY_MEDIUM;
|
||||||
csm.darkness = 0.4;
|
csm.darkness = 0.4;
|
||||||
csm.autoCalcDepthBounds = true;
|
// autoCalcDepthBounds растягивал дальний каскад → длинная тонкая
|
||||||
// Плавное затухание тени у края каскада — убирает «полосу-хвост»
|
// тень-полоса персонажа на весь пол. Выключаем + фикс. дальность.
|
||||||
// тени персонажа на весь пол при движении (баг 2026-06-05).
|
csm.autoCalcDepthBounds = false;
|
||||||
csm.frustumEdgeFalloff = 8;
|
csm.frustumEdgeFalloff = 12;
|
||||||
this._shadowGenerator = csm;
|
this._shadowGenerator = csm;
|
||||||
} else {
|
} else {
|
||||||
// Обычный ShadowGenerator. Поднял разрешение для soft до 2048.
|
// Обычный ShadowGenerator. Поднял разрешение для soft до 2048.
|
||||||
|
|||||||
@ -631,10 +631,17 @@ game.gui.onSubmit('codein', (text) => {
|
|||||||
if (String(text).trim() === CODE){
|
if (String(text).trim() === CODE){
|
||||||
opened = true; target = Math.PI/2; // плавно распахнуть
|
opened = true; target = Math.PI/2; // плавно распахнуть
|
||||||
game.gui.remove('codein');
|
game.gui.remove('codein');
|
||||||
game.ui.set('codehint','✓ Открыто!', {x:50,y:78,anchor:'bottom',color:'#36d57a',size:18});
|
game.ui.set('codehint','✓ Открыто! Нажми E чтобы закрыть.', {x:50,y:78,anchor:'bottom',color:'#36d57a',size:16});
|
||||||
game.after(2, () => game.ui.set('codehint','',{}));
|
game.after(2.5, () => 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 → снова можно вводить код.
|
||||||
|
game.self.onInteract(() => {
|
||||||
|
if (!opened) return;
|
||||||
|
opened = false; near = false; target = 0; // плавно закрыть
|
||||||
|
game.ui.set('codehint','🔒 Дверь закрыта.', {x:50,y:78,anchor:'bottom',color:'#fff',size:16});
|
||||||
|
game.after(2, () => game.ui.set('codehint','',{}));
|
||||||
|
}, { text: 'Закрыть дверь', key: 'e', distance: 4 });` }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'name-label',
|
id: 'name-label',
|
||||||
@ -706,16 +713,15 @@ if (enemy && enemy.follow) enemy.follow('player');` }],
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'npc-trader',
|
id: 'npc-trader',
|
||||||
name: 'Торговец (диалог)',
|
name: 'Торговец (NPC)',
|
||||||
desc: 'Фигура торговца: подойди, нажми E — открывается диалог. (Вики: «Торговец»)',
|
desc: 'NPC-персонаж торговец: подойди, нажми E — открывается диалог. (Вики: «Торговец»)',
|
||||||
icon: 'trader', category: 'npc',
|
icon: 'trader', category: 'npc',
|
||||||
prims: [
|
// Невидимый prim-триггер держит onInteract; рядом спавнится NPC-персонаж.
|
||||||
{ type: 'cylinder', x: 0, y: 1, z: 0, sx: 1.2, sy: 2, sz: 1.2, color: '#3a6ea5', material: 'matte', name: 'Торговец' },
|
prims: [{ type: 'cube', x: 0, y: 1.5, z: 0, sx: 2, sy: 3, sz: 2, color: '#3a6ea5', material: 'matte', visible: false, canCollide: false, 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:
|
scripts: [{ attachTo: 'on-target', code:
|
||||||
`// Торговец с диалогом по E.
|
`// Торговец — настоящий NPC-персонаж. Триггер (этот объект) держит диалог по E.
|
||||||
game.self.setLabel('Торговец Боб', { color:'#fff', bg:'#3a6ea5' });
|
const p = game.self.position;
|
||||||
|
const npc = game.scene.spawnNpc('character-a', { x: p.x, z: p.z, name: 'Торговец Боб' });
|
||||||
game.self.onInteract(() => {
|
game.self.onInteract(() => {
|
||||||
game.modal.dialog('Торговец Боб', [
|
game.modal.dialog('Торговец Боб', [
|
||||||
'Привет, путник! Заходи за товарами.',
|
'Привет, путник! Заходи за товарами.',
|
||||||
|
|||||||
@ -390,13 +390,19 @@ export class NpcManager {
|
|||||||
if (root._isWorldMatrixFrozen) {
|
if (root._isWorldMatrixFrozen) {
|
||||||
try { root.unfreezeWorldMatrix(); } catch (e) {}
|
try { root.unfreezeWorldMatrix(); } catch (e) {}
|
||||||
}
|
}
|
||||||
root.position.set(npc.x, npc.y, npc.z);
|
// Анимация ходьбы — процедурное покачивание (у Kenney-моделей нет
|
||||||
|
// скелета). Подпрыгивание по Y + лёгкое раскачивание корпуса.
|
||||||
|
if (moving) npc.walkPhase += dt * 10;
|
||||||
|
let bobY = 0, lean = 0;
|
||||||
|
if (moving && !npc.r15Animator) {
|
||||||
|
bobY = Math.abs(Math.sin(npc.walkPhase)) * 0.12; // шаги вверх-вниз
|
||||||
|
lean = Math.sin(npc.walkPhase) * 0.08; // покачивание
|
||||||
|
}
|
||||||
|
root.position.set(npc.x, npc.y + bobY, npc.z);
|
||||||
root.rotation.y = npc.yaw;
|
root.rotation.y = npc.yaw;
|
||||||
|
root.rotation.z = lean;
|
||||||
// data.x/y/z — чтобы scene.find/getPosition видели NPC.
|
// data.x/y/z — чтобы scene.find/getPosition видели NPC.
|
||||||
data.x = npc.x; data.y = npc.y; data.z = npc.z;
|
data.x = npc.x; data.y = npc.y; data.z = npc.z;
|
||||||
|
|
||||||
// Анимация ходьбы — простое покачивание (без R15-скелета у Kenney).
|
|
||||||
if (moving) npc.walkPhase += dt * 6;
|
|
||||||
// R15-NPC (skin_*): процедурная анимация бега/покоя через R15Animator.
|
// R15-NPC (skin_*): процедурная анимация бега/покоя через R15Animator.
|
||||||
if (npc.r15Animator) {
|
if (npc.r15Animator) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user