diff --git a/src/community/docsGamesBuildersLua.js b/src/community/docsGamesBuildersLua.js index 2d56ae2..db17b2d 100644 --- a/src/community/docsGamesBuildersLua.js +++ b/src/community/docsGamesBuildersLua.js @@ -1277,38 +1277,83 @@ end)`; // ═══════════════════════════════════════════════════════════════ // ИГРА 15 — «Тир» // ═══════════════════════════════════════════════════════════════ - 'shooting-range': { - g15_main: `-- === ИГРА «ТИР» (Lua) === -local UserInputService = game:GetService("UserInputService") -local Players = game:GetService("Players") + 'shooting-range': (function() { + // Мишени имеют id 2, 4, 6, 8, 10, 12, 14, 16 (постамент → нечётный, мишень → чётный) + const TARGET_IDS = [2, 4, 6, 8, 10, 12, 14, 16]; + const TOTAL = TARGET_IDS.length; + const overrides = { + g15_main: `-- === ИГРА «ТИР» — главный скрипт (Lua) === ${SNIPPET_BROADCAST} +local Players = game:GetService("Players") +local player = Players.LocalPlayer local score = 0 -print("Стреляй ЛКМ по красным шарам!") +local TOTAL = ${TOTAL} +local won = false -local ev = getEvent("TargetHit") -ev.Event:Connect(function() +__rbxl_show_text("Кликай по красным мишеням!", 3) + +-- Счётчик в правом верхнем углу +local screenGui = Instance.new("ScreenGui", player.PlayerGui) +local label = Instance.new("TextLabel", screenGui) +label.Size = UDim2.new(0, 220, 0, 50) +label.Position = UDim2.new(1, -240, 0, 20) +label.BackgroundColor3 = Color3.fromRGB(0, 0, 0) +label.BackgroundTransparency = 0.4 +label.TextColor3 = Color3.fromRGB(255, 100, 100) +label.TextScaled = true +label.Font = Enum.Font.SourceSansBold +label.Text = "Мишени: 0 / " .. TOTAL + +local hitSound = Instance.new("Sound", workspace) +hitSound.SoundId = "hit"; hitSound.Volume = 0.7 +local winSound = Instance.new("Sound", workspace) +winSound.SoundId = "win"; winSound.Volume = 1 + +local hitEvent = getEvent("TargetHit") +hitEvent.Event:Connect(function() + if won then return end score = score + 1 - print("Попал! Очки: " .. score) -end) - --- ЛКМ — пускаем луч из камеры -UserInputService.InputBegan:Connect(function(input, gp) - if gp then return end - if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end - local player = Players.LocalPlayer - local mouse = player:GetMouse() - local target = mouse.Target - if target and target:GetAttribute("IsTarget") then - local hitEv = workspace:FindFirstChild("TargetHit") or ev - ev:Fire() - target:Destroy() + label.Text = "Мишени: " .. score .. " / " .. TOTAL + hitSound:Play() + if score >= TOTAL then + 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 end)`, - g15_target: `-- === Скрипт мишени (Lua) === + }; + // Скрипт каждой мишени — ClickDetector + взрыв искр + сообщение + const targetScript = `-- === Скрипт мишени (Lua) === +-- Клик ЛКМ по мишени = выстрел. ClickDetector ловит клик в 3D. +local ReplicatedStorage = game:GetService("ReplicatedStorage") local part = script.Parent -part:SetAttribute("IsTarget", true)`, - }, +local hit = false + +local clickDet = Instance.new("ClickDetector", part) +clickDet.MaxActivationDistance = 50 + +clickDet.MouseClick:Connect(function() + if hit then return end + hit = true + -- Взрыв искр на месте мишени + local pos = part.Position + __rbxl_spawn_particles("explosion", pos.X, pos.Y, pos.Z, 0.5, 1) + -- Сообщаем главному скрипту + local ev = ReplicatedStorage:FindFirstChild("TargetHit") + if ev then ev:Fire() end + -- Мишень исчезает + part:Destroy() +end)`; + for (const tid of TARGET_IDS) { + overrides['g15_target_' + tid] = targetScript; + } + return overrides; + })(), // ═══════════════════════════════════════════════════════════════ // ИГРА 16 — «Лавовый пол» diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index 72d05a5..71d7f6a 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -436,6 +436,11 @@ function newInstance(className, name) { value.Children.push(proxyRef); try { value.ChildAdded && value.ChildAdded.Fire(proxyRef); } catch (_) {} } + // Спец-регистрация для ClickDetector — чтобы клик по Part + // мог сфейерить MouseClick через fireTargetEvent. + if (t.ClassName === 'ClickDetector' && value) { + try { value._clickDetector = proxyRef; } catch (_) {} + } try { t.AncestryChanged && t.AncestryChanged.Fire(proxyRef, value); } catch (_) {} return true; } @@ -1387,6 +1392,15 @@ export function registerRobloxShim(lua, opts) { inst.Scale = new RbxVector3(1, 1, 1); inst.Offset = new RbxVector3(0, 0, 0); inst.VertexColor = new RbxVector3(1, 1, 1); + } else if (className === 'ClickDetector') { + // ClickDetector — клик по 3D-объекту (нужен Тиру и т.п.). + // Регистрация в part._clickDetector происходит автоматически + // через Proxy.set когда юзер делает clickDet.Parent = part. + inst = newInstance('ClickDetector', 'ClickDetector'); + inst.MouseClick = makeSignal(); + inst.MouseHoverEnter = makeSignal(); + inst.MouseHoverLeave = makeSignal(); + inst.MaxActivationDistance = 32; } else if (className === 'BindableEvent') { inst = newInstance('BindableEvent', 'BindableEvent'); inst.Event = makeSignal(); @@ -2164,6 +2178,18 @@ export function registerRobloxShim(lua, opts) { part.Touched.Fire(hrp); } else if (p.kind === 'untouch' || p.kind === 'untouched') { part.TouchEnded.Fire(hrp); + } else if (p.kind === 'click') { + // ClickDetector создаётся лениво — стрельба по 3D-объектам. + // Юзер делает: part.ClickDetector = Instance.new('ClickDetector', part) + // + clickDet.MouseClick:Connect(fn). Здесь фейерим сигнал. + try { + const cd = part._clickDetector; + if (cd && cd.MouseClick) cd.MouseClick.Fire(localPlayer); + } catch (_) {} + // Также фейерим Part.Clicked если есть (наш расширенный API). + try { + if (part.Clicked) part.Clicked.Fire(); + } catch (_) {} } }, fireGlobalEvent(p) {