feat(rbxl): XML-������ ������ .rbxl + Day/Night + Tool/Mouse/Backpack flow #38
@ -211,6 +211,24 @@ export class GameRuntime {
|
||||
try { this._registerRbxlTool(payload); } catch (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 {
|
||||
this._handleCommand(null, cmd, payload);
|
||||
}
|
||||
@ -545,8 +563,12 @@ export class GameRuntime {
|
||||
* Слушает клики ЛКМ → шлёт mouseButton1Down (Tool.Activated fires там). */
|
||||
_registerRbxlTool(payload) {
|
||||
if (!payload || payload.index == null) return;
|
||||
const invUI = this.scene3d?.inventory;
|
||||
if (!invUI) return;
|
||||
// invUI — это новая drag-drop система с defineItem, а не inventory (старая)
|
||||
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 toolName = String(payload.name || `Tool ${payload.index}`);
|
||||
invUI.defineItem({
|
||||
@ -565,6 +587,19 @@ export class GameRuntime {
|
||||
if (!this._rbxlToolHooks) {
|
||||
this._rbxlToolHooks = true;
|
||||
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', () => {
|
||||
const sl = invUI.active;
|
||||
const item = invUI.hotbar[sl];
|
||||
@ -574,9 +609,11 @@ export class GameRuntime {
|
||||
const idx = +item.itemId.slice('rbxlTool_'.length);
|
||||
sb.sendGlobalEvent?.({ type: 'equipTool', index: idx });
|
||||
this._rbxlActiveSlot = sl;
|
||||
this._startRbxlToolParticles();
|
||||
} else if (this._rbxlActiveSlot >= 0) {
|
||||
sb.sendGlobalEvent?.({ type: 'unequipTool' });
|
||||
this._rbxlActiveSlot = -1;
|
||||
this._stopRbxlToolParticles();
|
||||
}
|
||||
});
|
||||
// Клики мыши при экипированном 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. */
|
||||
_raycastFromCamera() {
|
||||
try {
|
||||
@ -624,6 +696,11 @@ export class GameRuntime {
|
||||
console.log('[GameRuntime] stopping', this.sandboxes.length, 'sandboxes');
|
||||
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 они остаются на сцене
|
||||
// и накапливаются при повторных запусках.
|
||||
|
||||
@ -907,6 +907,8 @@ export function registerRobloxShim(lua, opts) {
|
||||
lighting.SetMinutesAfterMidnight = function (m) {
|
||||
lighting._minutes = (Number(m) || 0) % 1440;
|
||||
lighting.ClockTime = lighting._minutes / 60;
|
||||
// Шлём в GameRuntime для обновления реального неба Babylon
|
||||
send('lightingTimeUpdate', { hour: lighting.ClockTime });
|
||||
};
|
||||
lighting.GetMoonDirection = 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.Brightness = 1;
|
||||
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') {
|
||||
inst = newInstance('Mouse', 'Mouse');
|
||||
inst.Button1Down = makeSignal();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user