feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39

Merged
min merged 215 commits from feat/lua-50-games-bundle into main 2026-06-09 21:59:25 +00:00
2 changed files with 120 additions and 5 deletions
Showing only changes of commit 1c5e5fe5bb - Show all commits

View File

@ -4228,7 +4228,122 @@ end)`;
})(), })(),
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// ИГРЫ 45-50: явных Lua-версий пока нет. // ИГРА 45 — «Стрелялка-арена»
// ═══════════════════════════════════════════════════════════════
'arena-shooter': {
g45_main: `-- === ИГРА «СТРЕЛЯЛКА-АРЕНА» — главный скрипт (Lua) ===
${SNIPPET_BROADCAST}
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local GOAL = 15
local score = 0
local over = false
-- Враги: { ref { ref, alive, lastDmg } }
local enemies = {}
__rbxl_score_set(0)
__rbxl_show_text("Перебей 15 врагов! Кликай по ним", 3)
local hitSound = Instance.new("Sound", workspace)
hitSound.SoundId = "hit"; hitSound.Volume = 0.6
local winSound = Instance.new("Sound", workspace)
winSound.SoundId = "win"; winSound.Volume = 1
-- Подписка на смерть игрока
task.delay(0.5, function()
local char = player.Character or player.CharacterAdded:Wait()
local h = char:WaitForChild("Humanoid", 2)
if h then
h.Died:Connect(function()
if over then return end
over = true
__rbxl_show_text("Поражение! Тебя одолели враги.", 5)
end)
end
end)
-- Клик по врагу: EnemyClicked:Fire(ref)
local clickEvent = getEvent("EnemyClicked")
clickEvent.Event:Connect(function(localRef)
if over then return end
local e = enemies[localRef]
if not e or not e.alive then return end
local px = __rbxl_player_x()
local pz = __rbxl_player_z()
local ex = __rbxl_npc_x(localRef)
local ez = __rbxl_npc_z(localRef)
if ex == 0 and ez == 0 then return end
local dx = px - ex
local dz = pz - ez
local dist = math.sqrt(dx*dx + dz*dz)
if dist < 6 then
e.alive = false
__rbxl_npc_remove(localRef)
__rbxl_spawn_particles("explosion", ex, 2, ez, 0.4, 1)
hitSound:Play()
score = score + 1
__rbxl_score_set(score)
if score >= GOAL and not over then
over = true
__rbxl_show_text("Победа! Арена зачищена!", 5)
winSound:Play()
__rbxl_spawn_particles("confetti", px, 3, pz, 3, 3)
end
end
end)
-- Спавн каждые 1.8с
local spawnTimer = 0
RunService.Heartbeat:Connect(function(dt)
if over or score >= GOAL then return end
spawnTimer = spawnTimer + dt
if spawnTimer < 1.8 then return end
spawnTimer = 0
local angle = math.random() * math.pi * 2
local ex = math.cos(angle) * 11
local ez = math.sin(angle) * 11
local ref = __rbxl_spawn_npc("character-b", ex, 1, ez, "Враг", 30, 2.2)
enemies[ref] = { ref = ref, alive = true, lastDmg = 0 }
task.delay(0.3, function()
__rbxl_npc_follow(ref, "player")
end)
__rbxl_npc_on_click(ref, function()
local ev = game:GetService("ReplicatedStorage"):FindFirstChild("EnemyClicked")
if ev then ev:Fire(ref) end
end)
end)
-- Враги бьют игрока вблизи (каждые 0.7с)
RunService.Heartbeat:Connect(function()
if over then return end
local px = __rbxl_player_x()
local pz = __rbxl_player_z()
local now = tick()
for _, e in pairs(enemies) do
if e.alive then
local ex = __rbxl_npc_x(e.ref)
local ez = __rbxl_npc_z(e.ref)
if not (ex == 0 and ez == 0) then
local dx = px - ex
local dz = pz - ez
local dist = math.sqrt(dx*dx + dz*dz)
if dist < 1.8 and now - e.lastDmg > 0.7 then
e.lastDmg = now
__rbxl_damage_player(10)
hitSound:Play()
end
end
end
end
end)`,
},
// ═══════════════════════════════════════════════════════════════
// ИГРЫ 46-50: явных Lua-версий пока нет.
// buildGameProject в docsGamesBuilders.js использует generateFallbackLua // buildGameProject в docsGamesBuilders.js использует generateFallbackLua
// (главный скрипт → показ подсказки + слушает FinishReached → // (главный скрипт → показ подсказки + слушает FinishReached →
// победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached; // победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached;

View File

@ -6291,7 +6291,7 @@ game.self.onTouch(() => {
стрелять и проверяет, кто победил. стрелять и проверяет, кто победил.
</p> </p>
<ScriptKind kind="global" /> <ScriptKind kind="global" />
<Code>{`// === ИГРА «TOWER DEFENSE» — главный скрипт === <CodeBoth game="tower-defense" script="g44_main">{`// === ИГРА «TOWER DEFENSE» — главный скрипт ===
let leaked = 0; // врагов прошло до базы let leaked = 0; // врагов прошло до базы
const MAX_LEAK = 8; const MAX_LEAK = 8;
@ -6374,7 +6374,7 @@ game.every(0.5, () => {
} }
} }
} }
});`}</Code> });`}</CodeBoth>
<p>Разберём:</p> <p>Разберём:</p>
<ul> <ul>
<li><code>towers</code> и <code>enemies</code> два списка: <li><code>towers</code> и <code>enemies</code> два списка:
@ -6395,7 +6395,7 @@ game.every(0.5, () => {
<h3 className="lessonH">Шаг 3. Скрипт площадки под башню</h3> <h3 className="lessonH">Шаг 3. Скрипт площадки под башню</h3>
<ScriptKind kind="object" on="каждую площадку" /> <ScriptKind kind="object" on="каждую площадку" />
<Code>{`// === Скрипт площадки под башню === <CodeBoth game="tower-defense" script="g44_slot_1">{`// === Скрипт площадки под башню ===
let built = false; let built = false;
game.self.onInteract(() => { game.self.onInteract(() => {
if (built) return; if (built) return;
@ -6408,7 +6408,7 @@ game.self.onInteract(() => {
color: '#ffcc33', color: '#ffcc33',
}); });
game.broadcast('addTower', { x: pos.x, z: pos.z }); game.broadcast('addTower', { x: pos.x, z: pos.z });
}, { text: 'Построить башню', distance: 4 });`}</Code> }, { text: 'Построить башню', distance: 4 });`}</CodeBoth>
<p> <p>
При нажатии <kbd className="kbd">E</kbd> скрипт создаёт При нажатии <kbd className="kbd">E</kbd> скрипт создаёт
жёлтый цилиндр-башню над площадкой и шлёт сообщение жёлтый цилиндр-башню над площадкой и шлёт сообщение