feat(g21): полный паритет «Преследователь» + npc.follow/stop/pos в shim
JS:
- spawnNpc 'Охотник' speed=4 follow('player')
- onTick: dist(player,enemy) < 1.6 → respawn + 'Пойман!' + lose
- onMessage 'win' → enemy.stop() + 'Победа!' + win + confetti
- g21_finish: onTouch → broadcast 'win'
Lua (паритет):
- __rbxl_show_text + Sounds
- __rbxl_spawn_npc('character-b', ..., 'Охотник', 100, 4)
- __rbxl_npc_follow(ref, 'player') — велим NPC следовать за игроком
- Heartbeat: __rbxl_npc_x/z для расстояния, при <1.6 → LoadCharacter
+ 'Пойман!' + lose Sound (с throttle 2с)
- BindableEvent WinReached + g21_finish.Touched → ev:Fire
- При победе: npc_stop + showText + win + confetti
Shim хелперы:
- __rbxl_npc_follow(ref, target='player')
- __rbxl_npc_stop(ref)
- __rbxl_npc_x/y/z(ref) — позиция NPC
- api.updateNpcPos(localRef, x, y, z) — GameRuntime синкает каждый кадр
GameRuntime.tick собирает позиции всех NPC из npcManager.npcs через
_localToReal и шлёт sb.api.updateNpcPos.
This commit is contained in:
parent
d7478fe311
commit
ea1308d539
@ -1784,28 +1784,76 @@ end`,
|
|||||||
// ИГРА 21 — «Догонялки»
|
// ИГРА 21 — «Догонялки»
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
'chaser': {
|
'chaser': {
|
||||||
g21_main: `-- === ИГРА «ДОГОНЯЛКИ» (Lua) ===
|
g21_main: `-- === ИГРА «ПРЕСЛЕДОВАТЕЛЬ» — главный скрипт (Lua) ===
|
||||||
|
${SNIPPET_BROADCAST}
|
||||||
|
|
||||||
local Players = game:GetService("Players")
|
local Players = game:GetService("Players")
|
||||||
local RunService = game:GetService("RunService")
|
local RunService = game:GetService("RunService")
|
||||||
|
local player = Players.LocalPlayer
|
||||||
|
local won = false
|
||||||
|
|
||||||
local enemy = workspace:WaitForChild("Догонщик")
|
__rbxl_show_text("Убегай от врага! Добеги до укрытия!", 3)
|
||||||
|
|
||||||
RunService.Heartbeat:Connect(function(dt)
|
local loseSound = Instance.new("Sound", workspace)
|
||||||
local player = Players:GetPlayers()[1]
|
loseSound.SoundId = "lose"; loseSound.Volume = 0.7
|
||||||
if not player or not player.Character then return end
|
local winSound = Instance.new("Sound", workspace)
|
||||||
local target = player.Character:FindFirstChild("HumanoidRootPart")
|
winSound.SoundId = "win"; winSound.Volume = 1
|
||||||
if not target then return end
|
|
||||||
-- Двигаемся в сторону игрока со скоростью 5
|
-- Спавним NPC-преследователя (speed=4, follow за игроком)
|
||||||
local dir = (target.Position - enemy.Position)
|
local enemyRef = __rbxl_spawn_npc("character-b", 0, 1, -3, "Охотник", 100, 4)
|
||||||
if dir.Magnitude > 1 then
|
-- Велим NPC следовать за игроком
|
||||||
enemy.Position = enemy.Position + dir.Unit * 5 * dt
|
__rbxl_npc_follow(enemyRef, "player")
|
||||||
else
|
|
||||||
-- Поймал
|
-- Каждый кадр проверяем — не догнал ли враг
|
||||||
local h = player.Character:FindFirstChild("Humanoid")
|
local lastCaughtTime = 0
|
||||||
if h then h:TakeDamage(10) end
|
RunService.Heartbeat:Connect(function()
|
||||||
|
if won then return end
|
||||||
|
local px = __rbxl_player_x()
|
||||||
|
local pz = __rbxl_player_z()
|
||||||
|
local ex = __rbxl_npc_x(enemyRef)
|
||||||
|
local ez = __rbxl_npc_z(enemyRef)
|
||||||
|
-- Если позиции NPC ещё не пришли (ex=0,ez=0 = до спавна) — пропускаем
|
||||||
|
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 < 1.6 then
|
||||||
|
local now = tick()
|
||||||
|
if now - lastCaughtTime > 2 then
|
||||||
|
lastCaughtTime = now
|
||||||
|
player:LoadCharacter()
|
||||||
|
__rbxl_show_text("Пойман! Беги снова!", 2)
|
||||||
|
loseSound:Play()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
print("Убегай от догонщика!")`,
|
|
||||||
|
-- Финиш сообщает о победе
|
||||||
|
local winEvent = getEvent("WinReached")
|
||||||
|
winEvent.Event:Connect(function()
|
||||||
|
if won then return end
|
||||||
|
won = true
|
||||||
|
__rbxl_npc_stop(enemyRef)
|
||||||
|
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)`,
|
||||||
|
g21_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)`,
|
||||||
},
|
},
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@ -986,6 +986,20 @@ export class GameRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
// Собираем позиции NPC для Lua-shim
|
||||||
|
const npcPositions = [];
|
||||||
|
try {
|
||||||
|
const nm = this.scene3d?.npcManager;
|
||||||
|
if (nm && nm.npcs && this._localToReal) {
|
||||||
|
// localRef ('npc_lua_N') → реальный 'npc:<id>' → npc
|
||||||
|
for (const [localRef, realRef] of this._localToReal.entries()) {
|
||||||
|
if (typeof realRef !== 'string' || !realRef.startsWith('npc:')) continue;
|
||||||
|
const npcId = Number(realRef.slice(4));
|
||||||
|
const npc = nm.npcs.get(npcId);
|
||||||
|
if (npc) npcPositions.push([localRef, npc.x, npc.y, npc.z]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
for (const sb of this.sandboxes) {
|
for (const sb of this.sandboxes) {
|
||||||
// Обновляем реальную позицию игрока для Lua-shim
|
// Обновляем реальную позицию игрока для Lua-shim
|
||||||
if (realPos && sb.api?.updatePlayerPos) {
|
if (realPos && sb.api?.updatePlayerPos) {
|
||||||
@ -997,6 +1011,12 @@ export class GameRuntime {
|
|||||||
try { sb.api.updateSpawnedPos(id, x, y, z); } catch (_) {}
|
try { sb.api.updateSpawnedPos(id, x, y, z); } catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Синк позиций NPC
|
||||||
|
if (npcPositions.length > 0 && sb.api?.updateNpcPos) {
|
||||||
|
for (const [ref, x, y, z] of npcPositions) {
|
||||||
|
try { sb.api.updateNpcPos(ref, x, y, z); } catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Для скриптов с target — добавляем актуальную позицию self
|
// Для скриптов с target — добавляем актуальную позицию self
|
||||||
const stateForSb = sb.target
|
const stateForSb = sb.target
|
||||||
? { ...state, selfPosition: this._collectSelfPosition(sb.target) }
|
? { ...state, selfPosition: this._collectSelfPosition(sb.target) }
|
||||||
|
|||||||
@ -1869,6 +1869,27 @@ export function registerRobloxShim(lua, opts) {
|
|||||||
duration: +duration || 3,
|
duration: +duration || 3,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
global.set('__rbxl_npc_follow', (ref, targetRef) => {
|
||||||
|
send('npc.follow', {
|
||||||
|
ref: String(ref || ''),
|
||||||
|
target: String(targetRef || 'player'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global.set('__rbxl_npc_stop', (ref) => {
|
||||||
|
send('npc.stop', { ref: String(ref || '') });
|
||||||
|
});
|
||||||
|
// Позиция NPC — резолвится через GameRuntime по локальному ref.
|
||||||
|
// GameRuntime в tick шлёт api.updateNpcPos(localRef, x, y, z).
|
||||||
|
const _npcPositions = new Map(); // localRef → {x,y,z}
|
||||||
|
global.set('__rbxl_npc_pos', (ref) => {
|
||||||
|
const p = _npcPositions.get(String(ref || ''));
|
||||||
|
if (!p) return { x: 0, y: 0, z: 0, ok: false };
|
||||||
|
return { x: p.x, y: p.y, z: p.z, ok: true };
|
||||||
|
});
|
||||||
|
// Отдельные x/y/z — обходим wasmoon userdata-proxy.
|
||||||
|
global.set('__rbxl_npc_x', (ref) => (_npcPositions.get(String(ref || ''))?.x ?? 0));
|
||||||
|
global.set('__rbxl_npc_y', (ref) => (_npcPositions.get(String(ref || ''))?.y ?? 0));
|
||||||
|
global.set('__rbxl_npc_z', (ref) => (_npcPositions.get(String(ref || ''))?.z ?? 0));
|
||||||
global.set('__rbxl_npc_damage', (ref, amount) => {
|
global.set('__rbxl_npc_damage', (ref, amount) => {
|
||||||
send('npc.damage', {
|
send('npc.damage', {
|
||||||
ref: String(ref || ''),
|
ref: String(ref || ''),
|
||||||
@ -2034,6 +2055,10 @@ export function registerRobloxShim(lua, opts) {
|
|||||||
setNpcLocalRef(localRef, realRef) {
|
setNpcLocalRef(localRef, realRef) {
|
||||||
_localToRealNpc.set(String(localRef), String(realRef));
|
_localToRealNpc.set(String(localRef), String(realRef));
|
||||||
},
|
},
|
||||||
|
// GameRuntime каждый кадр обновляет позиции NPC для Lua-скриптов.
|
||||||
|
updateNpcPos(localRef, x, y, z) {
|
||||||
|
_npcPositions.set(String(localRef), { x: +x, y: +y, z: +z });
|
||||||
|
},
|
||||||
onSceneSnapshot(snap) {
|
onSceneSnapshot(snap) {
|
||||||
try {
|
try {
|
||||||
const prims = snap?.primitives || [];
|
const prims = snap?.primitives || [];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user