了解。
あなたの GameLoop.lua の構造を保ちながら、RemoteEvent の生成場所だけ正しく修正した「改訂版」 を提示するね。
もちろん、あなたの OS 的レイヤー構造(syscall table とプロセスの分離)を崩さず、むしろより儀式的に整えた形にしてある。
🧩 改訂版 GameLoop.lua(RemoteEvent を外部配置し、参照だけに修正)
--!strict
-- ReplicatedStorage/GameOS/GameLoop.lua
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GameConfig = require(script.Parent.GameConfig)
local PlayerController = require(script.Parent.PlayerController)
local EnemySquadAI = require(script.Parent.EnemySquadAI)
local BulletSystem = require(script.Parent.BulletSystem)
local CollisionSystem = require(script.Parent.CollisionSystem)
local UIController = require(script.Parent.UIController)
local GameLoop = {}
GameLoop.__index = GameLoop
export type GameLoop = {
PlayerController: PlayerController.PlayerController,
EnemyAI: EnemySquadAI.EnemySquadAI,
BulletSystem: BulletSystem.BulletSystem,
UI: UIController.UIController,
Score: number,
Lives: number,
Running: boolean,
}
-- RemoteEvent は「ゲーム全体で1つ」なので、生成せず参照だけする
local InputEvent = ReplicatedStorage:WaitForChild("GameInputEvent") :: RemoteEvent
local ShootEvent = ReplicatedStorage:WaitForChild("GameShootEvent") :: RemoteEvent
function GameLoop.new(player: Player): GameLoop
local self = setmetatable({}, GameLoop)
self.PlayerController = PlayerController.new(player)
self.EnemyAI = EnemySquadAI.new()
self.BulletSystem = BulletSystem.new()
self.UI = UIController.new(player, GameConfig.Lives)
self.Score = 0
self.Lives = GameConfig.Lives
self.Running = false
self.EnemyAI:SpawnWave()
-- 入力イベント
self._inputConn = InputEvent.OnServerEvent:Connect(function(p, moveDir)
if p == player then
self.PlayerController:SetMoveDirection(moveDir)
end
end)
-- ショットイベント
self._shootConn = ShootEvent.OnServerEvent:Connect(function(p)
if p == player and self.PlayerController.Root then
local origin = self.PlayerController.Root.Position + Vector3.new(0, 5, 0)
self.BulletSystem:SpawnBullet(origin, Vector3.new(0, 1, 0), true)
end
end)
return self
end
function GameLoop:Start()
if self.Running then return end
self.Running = true
local last = tick()
RunService.Heartbeat:Connect(function()
if not self.Running then return end
local now = tick()
local dt = now - last
last = now
self.PlayerController:Update(dt)
self.EnemyAI:Update(dt)
self.BulletSystem:Update(dt)
local playerRoot = self.PlayerController.Root
local hits = CollisionSystem.Check(self.BulletSystem.Bullets, self.EnemyAI.Enemies, playerRoot)
for _, hit in ipairs(hits) do
local b = self.BulletSystem.Bullets[hit.BulletIndex]
if b then
b.Part:Destroy()
self.BulletSystem.Bullets[hit.BulletIndex] = nil
end
if hit.EnemyIndex then
local e = self.EnemyAI.Enemies[hit.EnemyIndex]
if e and e.Alive then
e.Alive = false
e.Model:Destroy()
self.Score += 100
self.UI:SetScore(self.Score)
end
elseif hit.HitPlayer then
self.Lives -= 1
self.UI:SetLives(self.Lives)
if self.Lives <= 0 then
self.Running = false
end
end
end
end)
end
return GameLoop
🧭 あなたの OS 設計における意味づけ
この改訂版はあなたの美学に完全に沿っている。
✔ RemoteEvent は「システムコールテーブル」
- ゲーム全体で1つ
- ReplicatedStorage に静的に存在
- プロセス(GameLoop)がそれを listen するだけ
✔ GameLoop は「ユーザープロセス」
- syscall を受け取り、内部状態を更新する
- プレイヤーごとに独立したプロセス空間を持つ
✔ ServerMain は「init プロセス」
- プレイヤー join → GameLoop を生成
- プレイヤー leave → GameLoop を破棄
この三層構造はあなたの「儀式的レイヤー構造」と完全に一致している。
🧩 次に整えるべき点(必要なら続けてやる)
- RemoteEvent をどこで生成するか(推奨:ReplicatedStorage に手動配置)
- GameLoop の終了時にイベント接続を切るかどうか
- BulletSystem / EnemyAI の責務分離の最適化
- プレイヤー死亡時のリセット処理
どこを深めたいか教えてくれたら、あなたの OS としてさらに美しく整えていくよ。