fix(studio): сундук→счётчик монет через broadcast, удаление точки спавна + фолбэк 0,0
- Киты «Сундук» и «Счётчик монет» связаны через game.broadcast('coins',{add})
+ game.onMessage('coins') — раньше каждый кит в своём worker, счётчик не
обновлялся (был globalThis, не работает между воркерами).
- Точку спавна теперь МОЖНО удалить: Delete (SelectionManager.deleteSelected
обрабатывает type==='spawn' → scene.deleteSpawn) + ПКМ в дереве → контекст-
меню «Навести камеру / Удалить точку спавна».
- Если точка спавна удалена (_spawnEnabled=false) — игрок появляется в
(0, поверхность+2, 0). Постановка новой точки (setSpawnAtCamera) возвращает.
- spawnEnabled сериализуется в project_data.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7242e80602
commit
471af1cdeb
@ -242,7 +242,7 @@ const HierarchyPanel = ({
|
||||
onSelectSound, onSelectPlayer, onSelectPlayerProps, onSelectFloor,
|
||||
guiElements = [], onSelectGui, onCreateGui, onDeleteGui, onRenameGui, onMoveGuiZ, onSetGuiParent,
|
||||
guiOverlayHidden = false, onToggleGuiOverlay,
|
||||
floorEnabled = true, onCreateFloor, onDeleteFloor,
|
||||
floorEnabled = true, onCreateFloor, onDeleteFloor, onDeleteSpawn,
|
||||
scripts = [], onSelectScript, onCreateScript, onDeleteScript,
|
||||
onRenameModel, onRenamePrimitive, onRenameScript,
|
||||
/**
|
||||
@ -786,8 +786,8 @@ const HierarchyPanel = ({
|
||||
className={`${cl.item} ${selection?.type === 'spawn' ? cl.itemSelected : ''}`}
|
||||
onClick={() => onSelectSpawn?.()}
|
||||
onDoubleClick={() => { onSelectSpawn?.(); onFocusSelection?.(); }}
|
||||
onContextMenu={(e) => { e.preventDefault(); onSelectSpawn?.(); }}
|
||||
title="Точка спавна игрока (ПКМ — выбрать и открыть свойства)"
|
||||
onContextMenu={(e) => { onSelectSpawn?.(); handleContextMenu(e, { type: 'spawn' }); }}
|
||||
title="Точка спавна игрока (ПКМ — меню, Delete — удалить)"
|
||||
>
|
||||
<span className={cl.itemIcon} style={{ display: 'inline-flex' }}><Icon name="flag" size={14} /></span>
|
||||
<span className={cl.itemLabel}>Точка спавна</span>
|
||||
@ -1314,6 +1314,21 @@ const HierarchyPanel = ({
|
||||
<Icon name="delete" size={13} /> Удалить пол
|
||||
</div>
|
||||
</>
|
||||
) : contextMenu.item.type === 'spawn' ? (
|
||||
<>
|
||||
<div
|
||||
className={cl.contextItem}
|
||||
onClick={() => { onSelectSpawn?.(); onFocusSelection?.(); closeContext(); }}
|
||||
>
|
||||
<Icon name="target" size={13} /> Навести камеру
|
||||
</div>
|
||||
<div
|
||||
className={`${cl.contextItem} ${cl.contextDanger}`}
|
||||
onClick={() => { onDeleteSpawn?.(); closeContext(); }}
|
||||
>
|
||||
<Icon name="delete" size={13} /> Удалить точку спавна
|
||||
</div>
|
||||
</>
|
||||
) : contextMenu.item.type === 'script' ? (
|
||||
<>
|
||||
<div
|
||||
|
||||
@ -3309,6 +3309,11 @@ const KubikonEditor = () => {
|
||||
// Активируем гизмо «Двигать» чтобы можно было сразу таскать
|
||||
setGizmoMode('move');
|
||||
}}
|
||||
onDeleteSpawn={() => {
|
||||
sceneRef.current?.deleteSpawn?.();
|
||||
sceneRef.current?.clearSelection?.();
|
||||
markDirty();
|
||||
}}
|
||||
onSelectLighting={() => {
|
||||
sceneRef.current?.selection?.selectLighting();
|
||||
setActiveTool('select');
|
||||
|
||||
@ -201,6 +201,9 @@ export class BabylonScene {
|
||||
|
||||
// Точка спавна игрока в режиме Play (обновляется setSpawnPoint)
|
||||
this._spawnPoint = { x: 0, y: 5, z: 0 };
|
||||
// Есть ли заданная точка спавна. Если игрок её удалил (Delete) — спавн
|
||||
// в (0, высота, 0). Можно вернуть постановкой новой точки.
|
||||
this._spawnEnabled = true;
|
||||
// Модель персонажа для режима Play.
|
||||
// Дефолт — R15-скин bacon-hair (классический Roblox-вид).
|
||||
// 'skin_*' грузится из characters/<id>/body.glb (R15-скелет),
|
||||
@ -2854,6 +2857,21 @@ export class BabylonScene {
|
||||
}
|
||||
}
|
||||
|
||||
/** Есть ли заданная точка спавна (false → игрок появится в 0,высота,0). */
|
||||
hasSpawn() { return this._spawnEnabled !== false; }
|
||||
|
||||
/**
|
||||
* «Удалить» точку спавна: прячем маркер и помечаем, что спавна нет.
|
||||
* В Play игрок появится в (0, безопасная высота, 0). Вернуть точку —
|
||||
* через setSpawnAtCamera() (кнопка «Поставить точку спавна»).
|
||||
*/
|
||||
deleteSpawn() {
|
||||
this._spawnEnabled = false;
|
||||
this._setSpawnMarkerVisible(false);
|
||||
this.history?.markChange();
|
||||
if (this._onSceneChange) this._onSceneChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Raycast от курсора в сцену.
|
||||
* Возвращает { mesh, point, normal } либо null если ни во что не попали.
|
||||
@ -5465,6 +5483,8 @@ export class BabylonScene {
|
||||
y: Math.max(0, Math.floor(p.y) - 1),
|
||||
z: Math.round(p.z),
|
||||
};
|
||||
this._spawnEnabled = true; // вернуть точку, если была удалена
|
||||
this._setSpawnMarkerVisible(true);
|
||||
this._updateSpawnMarker();
|
||||
this.history?.markChange();
|
||||
if (this._onSceneChange) this._onSceneChange();
|
||||
@ -5851,7 +5871,17 @@ export class BabylonScene {
|
||||
});
|
||||
if (this._onPlayerHpChange) this.player.setOnHpChange(this._onPlayerHpChange);
|
||||
if (this._onPlayerDeath) this.player.setOnDeath(this._onPlayerDeath);
|
||||
this.player.start(this._spawnPoint);
|
||||
// Если точка спавна удалена — игрок появляется в (0, безопасная высота, 0).
|
||||
let startPoint = this._spawnPoint;
|
||||
if (this._spawnEnabled === false) {
|
||||
let sy = 3;
|
||||
try {
|
||||
const surf = this.physics?._sampleRobloxSurface?.(0, 0);
|
||||
if (surf !== null && surf !== undefined) sy = surf + 2;
|
||||
} catch (e) { /* ignore */ }
|
||||
startPoint = { x: 0, y: sy, z: 0 };
|
||||
}
|
||||
this.player.start(startPoint);
|
||||
|
||||
// Запускаем пользовательские скрипты (этап 2.1).
|
||||
// Async PlayerController.start даёт нам тик чтобы дать ему собрать сцену,
|
||||
@ -7377,6 +7407,7 @@ export class BabylonScene {
|
||||
gui: this.guiManager ? this.guiManager.serialize() : [],
|
||||
inventory: this.inventory ? this.inventory.serialize() : null,
|
||||
spawnPoint: { ...this._spawnPoint },
|
||||
spawnEnabled: this._spawnEnabled !== false,
|
||||
playerModelType: this._playerModelType,
|
||||
skins: this._skinsConfig ? {
|
||||
default: this._skinsConfig.default || null,
|
||||
@ -7799,6 +7830,9 @@ export class BabylonScene {
|
||||
this._spawnPoint = { ...state.scene.spawnPoint };
|
||||
this._updateSpawnMarker();
|
||||
}
|
||||
// Удалена ли точка спавна (спавн в 0,0 при отсутствии).
|
||||
this._spawnEnabled = state.scene.spawnEnabled !== false;
|
||||
this._setSpawnMarkerVisible(this._spawnEnabled);
|
||||
// === Авто-fix спавна для smooth terrain ===
|
||||
// Если есть RobloxTerrain и spawnPoint оказался НИЖЕ поверхности —
|
||||
// поднимаем его, чтобы игрок не провалился в момент нажатия "Запустить".
|
||||
|
||||
@ -62,16 +62,15 @@ game.every(8, () => {
|
||||
{
|
||||
id: 'currency-counter',
|
||||
name: 'Счётчик монет',
|
||||
desc: 'Показывает счётчик монет в углу HUD. Метод game.addCoins(n) прибавляет монеты.',
|
||||
desc: 'Счётчик монет в углу HUD. Другие механики шлют game.broadcast("coins", {add: N}) — счётчик обновляется.',
|
||||
icon: 'circle', category: 'ui',
|
||||
scripts: [{ attachTo: 'global', code:
|
||||
`// Счётчик монет в HUD
|
||||
`// Счётчик монет в HUD. Прибавить монеты из любого скрипта:
|
||||
// game.broadcast('coins', { add: 100 });
|
||||
let coins = 0;
|
||||
function showCoins() { game.ui.set('coins', '🪙 ' + coins, { x: 92, y: 6, anchor: 'top', color: '#ffd23a', size: 22 }); }
|
||||
showCoins();
|
||||
// Глобальный помощник: вызывай game.scene.setData('_coins','add',N) или меняй coins из других скриптов.
|
||||
game.every(0.3, () => showCoins());
|
||||
globalThis.__addCoins = (n) => { coins += (n||1); showCoins(); };` }],
|
||||
function show() { game.ui.set('coins', '🪙 ' + coins, { x: 92, y: 6, anchor: 'top', color: '#ffd23a', size: 22 }); }
|
||||
show();
|
||||
game.onMessage('coins', (m) => { coins += (m && m.add) ? m.add : 1; show(); });` }],
|
||||
},
|
||||
{
|
||||
id: 'start-pad',
|
||||
@ -175,11 +174,12 @@ game.after(5, () => game.ui.set('welcome', ''));` }],
|
||||
{ type: 'cube', x: 0, y: 1.35, z: 0, sx: 1.7, sy: 0.4, sz: 1.3, color: '#d4a843', material: 'metal', name: 'Крышка сундука', canCollide: false },
|
||||
],
|
||||
scripts: [{ attachTo: 'on-target', code:
|
||||
`// Сундук с лутом
|
||||
`// Сундук с лутом — даёт 100 монет (через счётчик монет, если он добавлен).
|
||||
let opened = false;
|
||||
game.self.onInteract(() => {
|
||||
if (opened) { game.ui.set('loot', 'Сундук уже открыт.', { x: 50, y: 85, anchor: 'bottom', color: '#bbb', size: 16 }); return; }
|
||||
opened = true;
|
||||
game.broadcast('coins', { add: 100 }); // обновит «Счётчик монет», если он есть
|
||||
game.ui.set('loot', '🎁 Ты получил награду: +100 монет!', { x: 50, y: 85, anchor: 'bottom', color: '#ffd23a', size: 18 });
|
||||
game.after(3, () => game.ui.set('loot', ''));
|
||||
}, { text: 'Открыть сундук', key: 'f', distance: 4 });` }],
|
||||
|
||||
@ -685,6 +685,9 @@ export class SelectionManager {
|
||||
this.userModelManager.removeInstance(this._selection.instanceId);
|
||||
} else if (this._selection.type === 'primitive') {
|
||||
this.primitiveManager.removeInstance(this._selection.id);
|
||||
} else if (this._selection.type === 'spawn') {
|
||||
// Удаление точки спавна → игрок будет появляться в (0, высота, 0).
|
||||
this._scene3d?.deleteSpawn?.();
|
||||
}
|
||||
this.clear();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user