diff --git a/src/community/docsGamesBuildersLua.js b/src/community/docsGamesBuildersLua.js index a4d3662..2166f9f 100644 --- a/src/community/docsGamesBuildersLua.js +++ b/src/community/docsGamesBuildersLua.js @@ -510,42 +510,52 @@ coinSound.SoundId = "coin"; coinSound.Volume = 1 local winSound = Instance.new("Sound", workspace) winSound.SoundId = "win"; winSound.Volume = 1 --- Каждые 1.5 сек роняем куб -task.spawn(function() - while not won do - task.wait(1.5) - if won then break end - local cube = Instance.new("Part", workspace) - cube.Size = Vector3.new(0.8, 0.8, 0.8) - cube.Position = Vector3.new(math.random(-6, 6), 14, math.random(-6, 6)) - cube.Color = Color3.fromRGB(255, 204, 51) - cube.Material = Enum.Material.Neon - cube.Anchored = false -- падает под действием гравитации +-- Каждые 1.5 сек роняем куб (через Heartbeat — task.spawn не умеет yield) +local RunService = game:GetService("RunService") +local _spawnTimer = 0 - -- Куб исчезает через 6с если не поймали - Debris:AddItem(cube, 6) +local function spawnCube() + -- Используем хелпер __rbxl_spawn_part — он сразу создаёт примитив + -- с правильными свойствами (включая anchored=false → реальная гравитация). + local cube = __rbxl_spawn_part({ + type = "cube", + x = math.random(-6, 6), y = 14, z = math.random(-6, 6), + sx = 0.8, sy = 0.8, sz = 0.8, + color = "#ffcc33", + anchored = false, -- падает + canCollide = true, + }) + if not cube then return end + Debris:AddItem(cube, 6) - -- Ловля: при касании игроком +1 очко - local caught = false - cube.Touched:Connect(function(hit) - if caught or won then return end - local h = hit.Parent and hit.Parent:FindFirstChild("Humanoid") - if not h then return end - caught = true - score = score + 1 - label.Text = "Поймано: " .. score .. " / " .. GOAL - coinSound:Play() - cube:Destroy() - if score >= GOAL then - won = true - winSound:Play() - __rbxl_show_text("Победа! Ты поймал 15 кубов!", 5) - local px = __rbxl_player_x() - local py = __rbxl_player_y() - local pz = __rbxl_player_z() - __rbxl_spawn_particles("confetti", px, py + 3, pz, 3, 3) - end - end) + local caught = false + cube.Touched:Connect(function(hit) + if caught or won then return end + local h = hit.Parent and hit.Parent:FindFirstChild("Humanoid") + if not h then return end + caught = true + score = score + 1 + label.Text = "Поймано: " .. score .. " / " .. GOAL + coinSound:Play() + cube:Destroy() + if score >= GOAL then + won = true + winSound:Play() + __rbxl_show_text("Победа! Ты поймал 15 кубов!", 5) + local px = __rbxl_player_x() + local py = __rbxl_player_y() + local pz = __rbxl_player_z() + __rbxl_spawn_particles("confetti", px, py + 3, pz, 3, 3) + end + end) +end + +RunService.Heartbeat:Connect(function(dt) + if won then return end + _spawnTimer = _spawnTimer + (dt or 0.016) + if _spawnTimer >= 1.5 then + _spawnTimer = 0 + spawnCube() end end)`, }, diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index a431ac5..122086d 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -1826,6 +1826,41 @@ export function registerRobloxShim(lua, opts) { count: Number(count) || 1, }); }); + // Спавн примитива (паритет с JS game.scene.spawn) — кладёт в сцену + // примитив с указанным состоянием (включая anchored/canCollide). Возвращает + // id примитива (число) для дальнейших операций. + let _nextSpawnedId = 800000 + Math.floor(Math.random() * 10000); + global.set('__rbxl_spawn_part', (opts) => { + try { + const id = _nextSpawnedId++; + const o = opts || {}; + send('sceneCreate', { + primId: id, + type: String(o.type || 'cube'), + x: +o.x || 0, y: +o.y || 0, z: +o.z || 0, + sx: +o.sx || 1, sy: +o.sy || 1, sz: +o.sz || 1, + color: o.color || '#A0A0A0', + anchored: o.anchored !== false, + canCollide: o.canCollide !== false, + }); + // Создаём Lua-side представление для скриптов + const fakePrim = { + id, name: o.name || `Spawned_${id}`, + x: +o.x || 0, y: +o.y || 0, z: +o.z || 0, + sx: +o.sx || 1, sy: +o.sy || 1, sz: +o.sz || 1, + color: o.color || '#A0A0A0', + anchored: o.anchored !== false, + canCollide: o.canCollide !== false, + }; + const part = newPart(fakePrim, send); + partById.set(id, part); + return part; + } catch (e) { + // eslint-disable-next-line no-console + console.warn('[__rbxl_spawn_part]', e?.message || e); + return null; + } + }); // Позиция игрока для удобства — отдельные функции для x/y/z, чтобы // wasmoon не оборачивал результат в userdata-proxy. global.set('__rbxl_player_x', () => {