docs(29) + feat(g31): «Защита базы»
g30 docs CodeBoth 4 скрипта. g31: - killed counter (GOAL=12) + leaked (MAX_LEAK=5) - Heartbeat spawn враг каждые 2с в (random(-8,8), 1, 38), spawn 'character-b' speed 2.5 - task.delay 0.3 npc.moveTo(0, 2) — к базе - __rbxl_npc_on_click(ref, fn) → шлёт ref в общий BindableEvent - При клике главный скрипт проверяет dist<5, наносит урон - Каждые 0.4с проверка прорыва (z<4) → leaked++ + lose sound - 12 убитых → 'Победа!' + confetti - 5 прорывов → 'База разрушена!' Shim: __rbxl_npc_moveto/__rbxl_npc_remove.
This commit is contained in:
parent
f69df55e3b
commit
901c249c29
@ -2734,7 +2734,129 @@ end)`,
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
// ИГРЫ 31-50: явных Lua-версий пока нет.
|
// ИГРА 31 — «Защита базы»
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
'base-defense': {
|
||||||
|
g31_main: `-- === ИГРА «ЗАЩИТА БАЗЫ» — главный скрипт (Lua) ===
|
||||||
|
${SNIPPET_BROADCAST}
|
||||||
|
|
||||||
|
local RunService = game:GetService("RunService")
|
||||||
|
local UserInputService = game:GetService("UserInputService")
|
||||||
|
|
||||||
|
local killed = 0
|
||||||
|
local leaked = 0
|
||||||
|
local total = 0
|
||||||
|
local over = false
|
||||||
|
local GOAL = 12
|
||||||
|
local MAX_LEAK = 5
|
||||||
|
|
||||||
|
__rbxl_score_set(0)
|
||||||
|
__rbxl_show_text("Защити базу! Кликай по врагам", 3)
|
||||||
|
|
||||||
|
local hitSound = Instance.new("Sound", workspace)
|
||||||
|
hitSound.SoundId = "hit"; hitSound.Volume = 0.6
|
||||||
|
local loseSound = Instance.new("Sound", workspace)
|
||||||
|
loseSound.SoundId = "lose"; loseSound.Volume = 0.7
|
||||||
|
local winSound = Instance.new("Sound", workspace)
|
||||||
|
winSound.SoundId = "win"; winSound.Volume = 1
|
||||||
|
|
||||||
|
-- Все живые враги: { ref, dead }
|
||||||
|
local enemies = {}
|
||||||
|
|
||||||
|
-- Клик по NPC (target.kind=npc) — наносим урон ближайшему в радиусе 5
|
||||||
|
local clickEvent = getEvent("EnemyClicked")
|
||||||
|
clickEvent.Event:Connect(function(localRef)
|
||||||
|
if over then return end
|
||||||
|
for _, e in ipairs(enemies) do
|
||||||
|
if not e.dead and e.ref == localRef then
|
||||||
|
-- Проверка расстояния (в радиусе 5)
|
||||||
|
local px = __rbxl_player_x()
|
||||||
|
local pz = __rbxl_player_z()
|
||||||
|
local ex = __rbxl_npc_x(e.ref)
|
||||||
|
local ez = __rbxl_npc_z(e.ref)
|
||||||
|
local dx = px - ex
|
||||||
|
local dz = pz - ez
|
||||||
|
local dist = math.sqrt(dx*dx + dz*dz)
|
||||||
|
if dist < 5 then
|
||||||
|
e.dead = true
|
||||||
|
__rbxl_spawn_particles("explosion", ex, 2, ez, 0.4, 1)
|
||||||
|
__rbxl_npc_remove(e.ref)
|
||||||
|
hitSound:Play()
|
||||||
|
killed = killed + 1
|
||||||
|
__rbxl_score_set(killed)
|
||||||
|
if killed >= GOAL and not over then
|
||||||
|
over = true
|
||||||
|
winSound:Play()
|
||||||
|
__rbxl_show_text("Победа! База защищена!", 5)
|
||||||
|
local px2 = __rbxl_player_x()
|
||||||
|
local py2 = __rbxl_player_y()
|
||||||
|
local pz2 = __rbxl_player_z()
|
||||||
|
__rbxl_spawn_particles("confetti", px2, py2 + 3, pz2, 3, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Регистрируем общий callback на клик по NPC — он шлёт ref в общий event
|
||||||
|
-- (фейерим один раз — при появлении каждого врага зовём __rbxl_npc_on_click)
|
||||||
|
|
||||||
|
-- Спавн врага каждые 2 секунды
|
||||||
|
local spawnTimer = 0
|
||||||
|
RunService.Heartbeat:Connect(function(dt)
|
||||||
|
if over then return end
|
||||||
|
if total >= GOAL + MAX_LEAK then return end
|
||||||
|
spawnTimer = spawnTimer + dt
|
||||||
|
if spawnTimer >= 2 then
|
||||||
|
spawnTimer = 0
|
||||||
|
total = total + 1
|
||||||
|
local x = math.random(-8, 8)
|
||||||
|
local ref = __rbxl_spawn_npc("character-b", x, 1, 38, "Враг", 30, 2.5)
|
||||||
|
local e = { ref = ref, dead = false }
|
||||||
|
table.insert(enemies, e)
|
||||||
|
-- Отложим moveTo пока NPC создастся
|
||||||
|
task.delay(0.3, function()
|
||||||
|
__rbxl_npc_moveto(ref, 0, 2)
|
||||||
|
end)
|
||||||
|
-- Клик по этому NPC → шлём в общий event
|
||||||
|
__rbxl_npc_on_click(ref, function()
|
||||||
|
local ev = game:GetService("ReplicatedStorage"):FindFirstChild("EnemyClicked")
|
||||||
|
if ev then ev:Fire(ref) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Проверка прорыва каждые 0.4с
|
||||||
|
local leakTimer = 0
|
||||||
|
RunService.Heartbeat:Connect(function(dt)
|
||||||
|
if over then return end
|
||||||
|
leakTimer = leakTimer + dt
|
||||||
|
if leakTimer < 0.4 then return end
|
||||||
|
leakTimer = 0
|
||||||
|
for _, e in ipairs(enemies) do
|
||||||
|
if not e.dead then
|
||||||
|
local ez = __rbxl_npc_z(e.ref)
|
||||||
|
local ex = __rbxl_npc_x(e.ref)
|
||||||
|
-- ez=0 ex=0 пока NPC не зарезолвлен — пропускаем
|
||||||
|
if not (ex == 0 and ez == 0) and ez < 4 then
|
||||||
|
e.dead = true
|
||||||
|
__rbxl_npc_remove(e.ref)
|
||||||
|
leaked = leaked + 1
|
||||||
|
loseSound:Play()
|
||||||
|
__rbxl_show_text("Враг прорвался! (" .. leaked .. "/" .. MAX_LEAK .. ")", 2)
|
||||||
|
if leaked >= MAX_LEAK and not over then
|
||||||
|
over = true
|
||||||
|
__rbxl_show_text("База разрушена! Поражение.", 5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ИГРЫ 32-50: явных Lua-версий пока нет.
|
||||||
// buildGameProject в docsGamesBuilders.js использует generateFallbackLua
|
// buildGameProject в docsGamesBuilders.js использует generateFallbackLua
|
||||||
// (главный скрипт → показ подсказки + слушает FinishReached →
|
// (главный скрипт → показ подсказки + слушает FinishReached →
|
||||||
// победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached;
|
// победа+конфетти; скрипт на финиш-примитиве → шлёт FinishReached;
|
||||||
|
|||||||
@ -4197,7 +4197,7 @@ game.self.onTouch(() => {
|
|||||||
по заданиям.
|
по заданиям.
|
||||||
</p>
|
</p>
|
||||||
<ScriptKind kind="global" />
|
<ScriptKind kind="global" />
|
||||||
<Code>{`// === ИГРА «КВЕСТ С ЗАДАНИЯМИ» — главный скрипт ===
|
<CodeBoth game="quest-tasks" script="g30_main">{`// === ИГРА «КВЕСТ С ЗАДАНИЯМИ» — главный скрипт ===
|
||||||
|
|
||||||
// этап квеста: 0=не начат, 1=собрать монетку, 2=дойти до флага,
|
// этап квеста: 0=не начат, 1=собрать монетку, 2=дойти до флага,
|
||||||
// 3=вернуться к NPC, 4=готово
|
// 3=вернуться к NPC, 4=готово
|
||||||
@ -4245,7 +4245,7 @@ game.onMessage('flag-done', () => {
|
|||||||
stage = 3;
|
stage = 3;
|
||||||
game.sound.play('pickup');
|
game.sound.play('pickup');
|
||||||
game.ui.showText('Квест: вернись к квестодателю', 3);
|
game.ui.showText('Квест: вернись к квестодателю', 3);
|
||||||
});`}</Code>
|
});`}</CodeBoth>
|
||||||
<p>Главное здесь — переменная <code>stage</code>:</p>
|
<p>Главное здесь — переменная <code>stage</code>:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>stage</code> хранит, на каком шаге квеста
|
<li><code>stage</code> хранит, на каком шаге квеста
|
||||||
@ -4275,23 +4275,23 @@ game.onMessage('flag-done', () => {
|
|||||||
|
|
||||||
<h3 className="lessonH">Шаг 3. Скрипт квестодателя</h3>
|
<h3 className="lessonH">Шаг 3. Скрипт квестодателя</h3>
|
||||||
<ScriptKind kind="object" on="тумбу квестодателя" />
|
<ScriptKind kind="object" on="тумбу квестодателя" />
|
||||||
<Code>{`// === Скрипт квестодателя ===
|
<CodeBoth game="quest-tasks" script="g30_npc">{`// === Скрипт квестодателя ===
|
||||||
game.self.onInteract(() => {
|
game.self.onInteract(() => {
|
||||||
game.broadcast('talk');
|
game.broadcast('talk');
|
||||||
}, { text: 'Поговорить', distance: 4 });`}</Code>
|
}, { text: 'Поговорить', distance: 4 });`}</CodeBoth>
|
||||||
|
|
||||||
<h3 className="lessonH">Шаг 4. Скрипты монетки и флага</h3>
|
<h3 className="lessonH">Шаг 4. Скрипты монетки и флага</h3>
|
||||||
<ScriptKind kind="object" on="квест-монетку" />
|
<ScriptKind kind="object" on="квест-монетку" />
|
||||||
<Code>{`// === Скрипт квест-монетки ===
|
<CodeBoth game="quest-tasks" script="g30_coin">{`// === Скрипт квест-монетки ===
|
||||||
game.self.onTouch(() => {
|
game.self.onTouch(() => {
|
||||||
game.broadcast('coin-done');
|
game.broadcast('coin-done');
|
||||||
game.self.delete();
|
game.self.delete();
|
||||||
});`}</Code>
|
});`}</CodeBoth>
|
||||||
<ScriptKind kind="object" on="квест-флаг" />
|
<ScriptKind kind="object" on="квест-флаг" />
|
||||||
<Code>{`// === Скрипт квест-флага ===
|
<CodeBoth game="quest-tasks" script="g30_flag">{`// === Скрипт квест-флага ===
|
||||||
game.self.onTouch(() => {
|
game.self.onTouch(() => {
|
||||||
game.broadcast('flag-done');
|
game.broadcast('flag-done');
|
||||||
});`}</Code>
|
});`}</CodeBoth>
|
||||||
|
|
||||||
<h3 className="lessonH">Шаг 5. Проверка</h3>
|
<h3 className="lessonH">Шаг 5. Проверка</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@ -1878,6 +1878,15 @@ export function registerRobloxShim(lua, opts) {
|
|||||||
global.set('__rbxl_npc_stop', (ref) => {
|
global.set('__rbxl_npc_stop', (ref) => {
|
||||||
send('npc.stop', { ref: String(ref || '') });
|
send('npc.stop', { ref: String(ref || '') });
|
||||||
});
|
});
|
||||||
|
global.set('__rbxl_npc_moveto', (ref, x, z) => {
|
||||||
|
send('npc.moveTo', {
|
||||||
|
ref: String(ref || ''),
|
||||||
|
x: +x || 0, z: +z || 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global.set('__rbxl_npc_remove', (ref) => {
|
||||||
|
send('npc.remove', { ref: String(ref || '') });
|
||||||
|
});
|
||||||
// Позиция NPC — резолвится через GameRuntime по локальному ref.
|
// Позиция NPC — резолвится через GameRuntime по локальному ref.
|
||||||
// GameRuntime в tick шлёт api.updateNpcPos(localRef, x, y, z).
|
// GameRuntime в tick шлёт api.updateNpcPos(localRef, x, y, z).
|
||||||
const _npcPositions = new Map(); // localRef → {x,y,z}
|
const _npcPositions = new Map(); // localRef → {x,y,z}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user