feat(lua): __rbxl_show_text + __rbxl_spawn_particles (паритет game.ui/scene)

JS-версия использует game.ui.showText (красивая центрированная плашка
без рамки через RbxlHudOverlay) и game.scene.spawnParticles('confetti').
Lua-версия пыталась рисовать ScreenGui+TextLabel через offset в UDim2,
но gui-shim неправильно интерпретировал offset → плашка прижата влево.
Также конфетти отсутствовали.

Решение — хелперы прямого вызова HUD/particle-systems как в JS:
- __rbxl_show_text(text, duration, color?) → shim шлёт ui.showText →
  GameRuntime → _rbxlHud.showMessage + setTimeout hideMessage
- __rbxl_spawn_particles(kind, x, y, z, duration, count) → 'scene.particles'
- __rbxl_player_pos() → возвращает текущую позицию игрока

Игра 2 переписана: использует __rbxl_show_text для подсказок 'Допрыгай',
'Упал!', 'Победа!' и __rbxl_spawn_particles('confetti', ...) на финише.
This commit is contained in:
min 2026-06-09 12:47:44 +03:00
parent 901c770fdc
commit 660d528ad5
3 changed files with 49 additions and 34 deletions

View File

@ -139,19 +139,8 @@ local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local won = false
-- Подсказка по центру (на 3 секунды)
local hintGui = Instance.new("ScreenGui", player.PlayerGui)
hintGui.Name = "Hint"
local hint = Instance.new("TextLabel", hintGui)
hint.Size = UDim2.new(0, 480, 0, 60)
hint.Position = UDim2.new(0.5, -240, 0.3, 0)
hint.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
hint.BackgroundTransparency = 0.4
hint.TextColor3 = Color3.fromRGB(255, 255, 255)
hint.TextScaled = true
hint.Font = Enum.Font.SourceSansBold
hint.Text = "Допрыгай до зелёной площадки!"
task.delay(3, function() hintGui:Destroy() end)
-- Подсказка по центру (паритет с JS game.ui.showText)
__rbxl_show_text("Допрыгай до зелёной площадки!", 3)
-- Звуки
local loseSound = Instance.new("Sound", workspace)
@ -170,17 +159,7 @@ RunService.Heartbeat:Connect(function()
if hrp and hrp.Position.Y < -3 then
player:LoadCharacter()
loseSound:Play()
local fallGui = Instance.new("ScreenGui", player.PlayerGui)
local fallLabel = Instance.new("TextLabel", fallGui)
fallLabel.Size = UDim2.new(0, 400, 0, 60)
fallLabel.Position = UDim2.new(0.5, -200, 0.35, 0)
fallLabel.BackgroundColor3 = Color3.fromRGB(120, 0, 0)
fallLabel.BackgroundTransparency = 0.3
fallLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
fallLabel.TextScaled = true
fallLabel.Font = Enum.Font.SourceSansBold
fallLabel.Text = "Упал! Пробуй снова."
task.delay(1.5, function() fallGui:Destroy() end)
__rbxl_show_text("Упал! Пробуй снова.", 1.5)
end
end)
@ -190,16 +169,10 @@ finishEvent.Event:Connect(function()
if won then return end
won = true
winSound:Play()
local winGui = Instance.new("ScreenGui", player.PlayerGui)
local winLabel = Instance.new("TextLabel", winGui)
winLabel.Size = UDim2.new(0, 540, 0, 80)
winLabel.Position = UDim2.new(0.5, -270, 0.4, 0)
winLabel.BackgroundColor3 = Color3.fromRGB(0, 120, 0)
winLabel.BackgroundTransparency = 0.2
winLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
winLabel.TextScaled = true
winLabel.Font = Enum.Font.SourceSansBold
winLabel.Text = "Победа! Ты дошёл до финиша!"
__rbxl_show_text("Победа! Ты дошёл до финиша!", 5)
-- Конфетти над игроком (как JS game.scene.spawnParticles)
local pos = __rbxl_player_pos()
__rbxl_spawn_particles("confetti", pos.x, pos.y + 3, pos.z, 3, 3)
end)`,
g2_finish: `-- === Скрипт финиш-зоны (Lua) ===
-- Висит на невидимой зоне над зелёной площадкой.

View File

@ -296,6 +296,23 @@ export class GameRuntime {
this._ensureRbxlHud();
this._rbxlHud.showWin(payload.text || 'WIN!');
} catch (_) {}
} else if (cmd === 'ui.showText') {
// Lua-helper __rbxl_show_text: красивый центрированный
// текст без рамки (паритет с JS game.ui.showText).
try {
this._ensureRbxlHud();
this._rbxlHud.showMessage(payload.text || '');
const dur = Number(payload.duration) || 2;
const t = payload.text || '';
setTimeout(() => {
try {
if (this._rbxlHud._lastMessage === t) {
this._rbxlHud.hideMessage();
}
} catch (_) {}
}, dur * 1000);
try { this._rbxlHud._lastMessage = t; } catch (_) {}
} catch (_) {}
} else if (cmd === 'leaderstatSet') {
// Roblox leaderstats: IntValue.Value меняется → HUD.
try {

View File

@ -1807,6 +1807,31 @@ export function registerRobloxShim(lua, opts) {
global.set('__log', (level, text) => {
send('log', { level: String(level || 'info'), text: String(text || '') });
});
// === Хелперы паритета с JS game.ui / game.scene ===
// Красивый центрированный текст без рамки (как game.ui.showText).
global.set('__rbxl_show_text', (text, duration, color) => {
send('ui.showText', {
text: String(text || ''),
duration: Number(duration) || 2,
color: color || '#ffffff',
});
});
// Эффекты частиц (confetti, sparks и т.п.) — как game.scene.spawnParticles.
global.set('__rbxl_spawn_particles', (kind, x, y, z, duration, count) => {
send('scene.particles', {
kind: String(kind || 'confetti'),
pos: { x: +x, y: +y, z: +z },
duration: Number(duration) || 2,
count: Number(count) || 1,
});
});
// Позиция игрока для удобства (для confetti над головой и т.п.)
global.set('__rbxl_player_pos', () => {
try {
const p = hrp._position || { X: 0, Y: 0, Z: 0 };
return { x: p.X, y: p.Y, z: p.Z };
} catch (_) { return { x: 0, y: 0, z: 0 }; }
});
// Достаём ссылку на Lua-функцию один раз; вызовы безопасны (не doStringSync)
const luaResumeCo = lua.global.get('__rbxl_resume_co');