fix(rbxl): invUI вместо inventory + Day/Night портирован + искры Sparkles
Что чинит:
1. _registerRbxlTool падал: использовал scene3d.inventory (InventoryManager,
старый, без defineItem). Меняю на scene3d.invUI (новый InventoryUI с
defineItem) — теперь hotbar реально заполняется.
2. Lighting.SetMinutesAfterMidnight теперь шлёт lightingTimeUpdate в
GameRuntime → scene3d.setTimeOfDay(hour). Тротлинг 4 раза/сек.
Roblox Day&Night скрипт теперь визуально меняет небо в нашем плеере!
3. Instance.new('Sparkles') шлёт particleCreated в GameRuntime.
При эквипе Tool — _startRbxlToolParticles() запускает каждые 200мс
burst у позиции игрока (имитация искр из руки).
4. Авто-эквип первого Tool через 100мс после регистрации — юзеру не
нужно нажимать 1, инвентарь не очевиден.
5. stop() корректно гасит интервалы и сбрасывает state.
Эти 4 фикса должны дать Zapper-демке базовое визуальное поведение:
видный hotbar, искры из персонажа, плавная смена дня/ночи.
This commit is contained in:
parent
bb0726b4ad
commit
52724ab9c8
@ -211,6 +211,24 @@ export class GameRuntime {
|
|||||||
try { this._registerRbxlTool(payload); } catch (e) {
|
try { this._registerRbxlTool(payload); } catch (e) {
|
||||||
console.warn('[GameRuntime] toolRegistered failed', e);
|
console.warn('[GameRuntime] toolRegistered failed', e);
|
||||||
}
|
}
|
||||||
|
} else if (cmd === 'lightingTimeUpdate') {
|
||||||
|
// Roblox Lighting:SetMinutesAfterMidnight → Babylon небо.
|
||||||
|
// Скрипты делают это каждый кадр — троттлим до 4 раз/сек.
|
||||||
|
const now = performance.now();
|
||||||
|
if (!this._lastLightUpdate || now - this._lastLightUpdate > 250) {
|
||||||
|
this._lastLightUpdate = now;
|
||||||
|
try {
|
||||||
|
const hour = Number(payload?.hour);
|
||||||
|
if (hour >= 0 && hour < 24) {
|
||||||
|
this.scene3d?.setTimeOfDay?.(hour);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
} else if (cmd === 'particleCreated') {
|
||||||
|
// Roblox Instance.new('Sparkles') — запомнили какие
|
||||||
|
// partlcle-эффекты есть у Tool. При equip покажем у руки.
|
||||||
|
this._rbxlPendingParticles = this._rbxlPendingParticles || [];
|
||||||
|
this._rbxlPendingParticles.push(payload);
|
||||||
} else {
|
} else {
|
||||||
this._handleCommand(null, cmd, payload);
|
this._handleCommand(null, cmd, payload);
|
||||||
}
|
}
|
||||||
@ -545,8 +563,12 @@ export class GameRuntime {
|
|||||||
* Слушает клики ЛКМ → шлёт mouseButton1Down (Tool.Activated fires там). */
|
* Слушает клики ЛКМ → шлёт mouseButton1Down (Tool.Activated fires там). */
|
||||||
_registerRbxlTool(payload) {
|
_registerRbxlTool(payload) {
|
||||||
if (!payload || payload.index == null) return;
|
if (!payload || payload.index == null) return;
|
||||||
const invUI = this.scene3d?.inventory;
|
// invUI — это новая drag-drop система с defineItem, а не inventory (старая)
|
||||||
if (!invUI) return;
|
const invUI = this.scene3d?.invUI;
|
||||||
|
if (!invUI || typeof invUI.defineItem !== 'function') {
|
||||||
|
console.warn('[GameRuntime] invUI not available for tool', payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const itemId = `rbxlTool_${payload.index}`;
|
const itemId = `rbxlTool_${payload.index}`;
|
||||||
const toolName = String(payload.name || `Tool ${payload.index}`);
|
const toolName = String(payload.name || `Tool ${payload.index}`);
|
||||||
invUI.defineItem({
|
invUI.defineItem({
|
||||||
@ -565,6 +587,19 @@ export class GameRuntime {
|
|||||||
if (!this._rbxlToolHooks) {
|
if (!this._rbxlToolHooks) {
|
||||||
this._rbxlToolHooks = true;
|
this._rbxlToolHooks = true;
|
||||||
this._rbxlActiveSlot = -1;
|
this._rbxlActiveSlot = -1;
|
||||||
|
// Авто-эквип первого Tool сразу при регистрации — иначе юзер
|
||||||
|
// не понимает что нажимать. В Roblox StarterPack тоже сразу
|
||||||
|
// в Backpack попадает и юзер жмёт 1 для эквипа.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this._rbxlActiveSlot < 0) {
|
||||||
|
invUI.setActiveHotbar?.(slot);
|
||||||
|
const sb = this._luaUserSandbox;
|
||||||
|
sb?.sendGlobalEvent?.({ type: 'equipTool', index: payload.index });
|
||||||
|
this._rbxlActiveSlot = slot;
|
||||||
|
// Если у Tool были Sparkles — рисуем непрерывно у руки игрока
|
||||||
|
this._startRbxlToolParticles();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
invUI.on('slot', () => {
|
invUI.on('slot', () => {
|
||||||
const sl = invUI.active;
|
const sl = invUI.active;
|
||||||
const item = invUI.hotbar[sl];
|
const item = invUI.hotbar[sl];
|
||||||
@ -574,9 +609,11 @@ export class GameRuntime {
|
|||||||
const idx = +item.itemId.slice('rbxlTool_'.length);
|
const idx = +item.itemId.slice('rbxlTool_'.length);
|
||||||
sb.sendGlobalEvent?.({ type: 'equipTool', index: idx });
|
sb.sendGlobalEvent?.({ type: 'equipTool', index: idx });
|
||||||
this._rbxlActiveSlot = sl;
|
this._rbxlActiveSlot = sl;
|
||||||
|
this._startRbxlToolParticles();
|
||||||
} else if (this._rbxlActiveSlot >= 0) {
|
} else if (this._rbxlActiveSlot >= 0) {
|
||||||
sb.sendGlobalEvent?.({ type: 'unequipTool' });
|
sb.sendGlobalEvent?.({ type: 'unequipTool' });
|
||||||
this._rbxlActiveSlot = -1;
|
this._rbxlActiveSlot = -1;
|
||||||
|
this._stopRbxlToolParticles();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Клики мыши при экипированном Tool — Activated/mouseButton1Down
|
// Клики мыши при экипированном Tool — Activated/mouseButton1Down
|
||||||
@ -602,6 +639,41 @@ export class GameRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Запускает непрерывный эмиттер Sparkles у руки игрока, пока Tool экипирован. */
|
||||||
|
_startRbxlToolParticles() {
|
||||||
|
if (this._rbxlSparkInterval) return;
|
||||||
|
const particles = this._rbxlPendingParticles || [];
|
||||||
|
if (particles.length === 0) return;
|
||||||
|
// RayGun Color3.new(0,0,1) → #0000ff. Берём цвет первой партиклы.
|
||||||
|
const p0 = particles[0] || {};
|
||||||
|
const col = p0.color || [0, 0, 1];
|
||||||
|
const hexCol = '#' + [col[0], col[1], col[2]].map(c => {
|
||||||
|
const v = Math.max(0, Math.min(255, Math.round((Number(c) || 0) * 255)));
|
||||||
|
return v.toString(16).padStart(2, '0');
|
||||||
|
}).join('');
|
||||||
|
// Каждые 200мс — короткий burst у руки игрока (приблизительно)
|
||||||
|
this._rbxlSparkInterval = setInterval(() => {
|
||||||
|
try {
|
||||||
|
const pl = this.scene3d?.player;
|
||||||
|
if (!pl || !pl._pos) return;
|
||||||
|
this.scene3d?._spawnParticleEffect?.({
|
||||||
|
type: 'sparks',
|
||||||
|
position: { x: pl._pos.x + 0.3, y: pl._pos.y + 0.4, z: pl._pos.z + 0.3 },
|
||||||
|
color: hexCol,
|
||||||
|
duration: 0.4,
|
||||||
|
count: 0.5,
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopRbxlToolParticles() {
|
||||||
|
if (this._rbxlSparkInterval) {
|
||||||
|
clearInterval(this._rbxlSparkInterval);
|
||||||
|
this._rbxlSparkInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Простой raycast от камеры — для mouse.Hit. */
|
/** Простой raycast от камеры — для mouse.Hit. */
|
||||||
_raycastFromCamera() {
|
_raycastFromCamera() {
|
||||||
try {
|
try {
|
||||||
@ -624,6 +696,11 @@ export class GameRuntime {
|
|||||||
console.log('[GameRuntime] stopping', this.sandboxes.length, 'sandboxes');
|
console.log('[GameRuntime] stopping', this.sandboxes.length, 'sandboxes');
|
||||||
for (const sb of this.sandboxes) sb.stop();
|
for (const sb of this.sandboxes) sb.stop();
|
||||||
}
|
}
|
||||||
|
// Останавливаем эффекты импортированных Tools
|
||||||
|
this._stopRbxlToolParticles?.();
|
||||||
|
this._rbxlToolHooks = false;
|
||||||
|
this._rbxlActiveSlot = -1;
|
||||||
|
this._rbxlPendingParticles = null;
|
||||||
// Удаляем все объекты, которые скрипты наспавнили через
|
// Удаляем все объекты, которые скрипты наспавнили через
|
||||||
// game.scene.spawn/clone — иначе после Stop они остаются на сцене
|
// game.scene.spawn/clone — иначе после Stop они остаются на сцене
|
||||||
// и накапливаются при повторных запусках.
|
// и накапливаются при повторных запусках.
|
||||||
|
|||||||
@ -907,6 +907,8 @@ export function registerRobloxShim(lua, opts) {
|
|||||||
lighting.SetMinutesAfterMidnight = function (m) {
|
lighting.SetMinutesAfterMidnight = function (m) {
|
||||||
lighting._minutes = (Number(m) || 0) % 1440;
|
lighting._minutes = (Number(m) || 0) % 1440;
|
||||||
lighting.ClockTime = lighting._minutes / 60;
|
lighting.ClockTime = lighting._minutes / 60;
|
||||||
|
// Шлём в GameRuntime для обновления реального неба Babylon
|
||||||
|
send('lightingTimeUpdate', { hour: lighting.ClockTime });
|
||||||
};
|
};
|
||||||
lighting.GetMoonDirection = function () { return new RbxVector3(0, 1, 0); };
|
lighting.GetMoonDirection = function () { return new RbxVector3(0, 1, 0); };
|
||||||
lighting.GetSunDirection = function () { return new RbxVector3(0, 1, 0); };
|
lighting.GetSunDirection = function () { return new RbxVector3(0, 1, 0); };
|
||||||
@ -1283,6 +1285,13 @@ export function registerRobloxShim(lua, opts) {
|
|||||||
inst.Lifetime = { Min: 1, Max: 1 };
|
inst.Lifetime = { Min: 1, Max: 1 };
|
||||||
inst.Brightness = 1;
|
inst.Brightness = 1;
|
||||||
inst.Range = 8;
|
inst.Range = 8;
|
||||||
|
inst.__particleKind = className.toLowerCase();
|
||||||
|
// Шлём событие "создан particle-effect" — GameRuntime может его
|
||||||
|
// привязать к мешу на сцене (например, рукам игрока).
|
||||||
|
send('particleCreated', {
|
||||||
|
kind: inst.__particleKind,
|
||||||
|
color: [inst.Color.R, inst.Color.G, inst.Color.B],
|
||||||
|
});
|
||||||
} else if (className === 'Mouse') {
|
} else if (className === 'Mouse') {
|
||||||
inst = newInstance('Mouse', 'Mouse');
|
inst = newInstance('Mouse', 'Mouse');
|
||||||
inst.Button1Down = makeSignal();
|
inst.Button1Down = makeSignal();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user