From 430b9eddcd7c7027a63d36f59bcb849eed16d486 Mon Sep 17 00:00:00 2001 From: min Date: Tue, 9 Jun 2026 20:22:16 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B8=D0=B3=D1=80=D0=B0=2018=20=C2=AB?= =?UTF-8?q?=D0=9A=D0=B0=D1=87=D0=B5=D0=BB=D0=B8=C2=BB=20Lua=20+=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=BE=D0=BC=D0=BD=D0=B0=D1=8F=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=BA=D0=B0=20=D0=B2=D1=8B=D1=85=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Игра 18: - Падало на WaitForChild + task.spawn + Completed:Wait (yield-across-C). - Полный переписан: Heartbeat-таймер раскачивает swing по синусу с амплитудой 4 и периодом 2.8с (вместо TweenService). - task.delay 0.2с чтобы дождаться появления свинга в scene. - Vector3.new напрямую (без оператора + который не работает). - BindableEvent WinReached + g18_finish.Touched → ev:Fire. - Полный паритет: showText, win Sound, confetti, respawn при y<-3. Модалка выхода: - Заменил window.confirm в KubikonEditor.handleBack на ConfirmModal. - 3 варианта: Сохранить и выйти / Выйти без сохранения / Отмена. - ConfirmModal расширен onCancel prop (отделяет 'cancel-кнопка' от 'клик мимо/Escape'). --- src/community/docsGamesBuildersLua.js | 82 +++++++++++++++++++++------ src/editor/ConfirmModal.jsx | 6 +- src/editor/KubikonEditor.jsx | 28 +++++++-- 3 files changed, 93 insertions(+), 23 deletions(-) diff --git a/src/community/docsGamesBuildersLua.js b/src/community/docsGamesBuildersLua.js index e54f241..9809525 100644 --- a/src/community/docsGamesBuildersLua.js +++ b/src/community/docsGamesBuildersLua.js @@ -1563,25 +1563,75 @@ end)`, // ИГРА 18 — «Качели» // ═══════════════════════════════════════════════════════════════ 'swing': { - g18_main: `-- === ИГРА «КАЧЕЛИ» (Lua) === -local TweenService = game:GetService("TweenService") -local swing = workspace:WaitForChild("Качели") -local startPos = swing.Position + g18_main: `-- === ИГРА «КАЧЕЛИ» — главный скрипт (Lua) === +${SNIPPET_BROADCAST} --- Качаем туда-сюда бесконечно -task.spawn(function() - while true do - local up = TweenService:Create(swing, - TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), - { Position = startPos + Vector3.new(0, 0, 5) }) - up:Play(); up.Completed:Wait() - local down = TweenService:Create(swing, - TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), - { Position = startPos + Vector3.new(0, 0, -5) }) - down:Play(); down.Completed:Wait() +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") +local player = Players.LocalPlayer +local won = false + +__rbxl_show_text("Запрыгни на качели и прокатись!", 3) + +local winSound = Instance.new("Sound", workspace) +winSound.SoundId = "win"; winSound.Volume = 1 + +-- Раскачиваем качели туда-сюда через изменение Position.Z. +-- WaitForChild может зависнуть — берём напрямую с задержкой. +local swing = nil +local startZ = 0 + +task.delay(0.2, function() + swing = workspace:FindFirstChild("Качели") + if swing then + startZ = swing.Position.Z end end) -print("Запрыгни на качающуюся платформу!")`, + +local elapsed = 0 +RunService.Heartbeat:Connect(function(dt) + if won then return end + if not swing then return end + elapsed = elapsed + (dt or 0.016) + -- Синусоидальное качание с амплитудой 4 и периодом ~2.8 сек + local amp = 4 + local period = 2.8 + local offsetZ = amp * math.sin(elapsed * 2 * math.pi / period) + local pos = swing.Position + swing.Position = Vector3.new(pos.X, pos.Y, startZ + offsetZ) + + -- Если упал — респаун + local py = __rbxl_player_y() + if py < -3 then + player:LoadCharacter() + end +end) + +-- Финиш сообщает о победе +local winEvent = getEvent("WinReached") +winEvent.Event:Connect(function() + if won then return end + won = true + winSound:Play() + __rbxl_show_text("Победа! Ты перебрался на качелях!", 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)`, + g18_finish: `-- === Скрипт финиша (Lua) === +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local part = script.Parent +local fired = false + +part.Touched:Connect(function(hit) + if fired then return end + local h = hit.Parent and hit.Parent:FindFirstChild("Humanoid") + if not h then return end + fired = true + local ev = ReplicatedStorage:FindFirstChild("WinReached") + if ev then ev:Fire() end +end)`, }, // ═══════════════════════════════════════════════════════════════ diff --git a/src/editor/ConfirmModal.jsx b/src/editor/ConfirmModal.jsx index e1e12b3..2a4284c 100644 --- a/src/editor/ConfirmModal.jsx +++ b/src/editor/ConfirmModal.jsx @@ -25,8 +25,12 @@ export default function ConfirmModal({ cancelLabel = 'Отмена', confirmTone = 'primary', // 'primary' | 'danger' onConfirm, + onCancel, // если задан — вызывается при клике на «cancel» вместо тихого закрытия onClose, }) { + const handleCancel = () => { + try { onCancel?.(); } finally { onClose?.(); } + }; const confirmBtnRef = useRef(null); useEffect(() => { @@ -119,7 +123,7 @@ export default function ConfirmModal({ gap: 8, }}>