Roblox合并ServerScriptService两主脚本实现投票后启动对战逻辑
Roblox 服务端双脚本时序冲突整合方案
问题梳理
两个独立部署在ServerScriptService下的脚本生命周期完全独立,无统一状态调度,是时序冲突的核心原因:
- 地图投票脚本存在重复变量声明、双层嵌套死循环问题,易出现逻辑重复执行、内存泄漏
- 对战模式脚本使用旧版Roblox API,独立运行时无法感知投票、地图加载、玩家传送的进度,易出现投票未结束就启动对局、玩家未传送就触发死亡判定的异常
整合后严格时序规则
全流程由单顶层循环统一调度,无并行逻辑抢占执行权,执行顺序固定为:
- 脚本启动完成基础服务初始化、玩家加入监听配置
- 单轮对局启动,优先执行地图投票全流程:从
Lighting.Maps随机抽取3张候选地图、开启投票会话、统计票数选出获胜地图 - 克隆获胜地图至
Workspace,等待地图所有实例加载完成后,将所有在线玩家传送至地图出生点,校验所有玩家传送到位 - 正式启动对战逻辑:清空玩家背包、开启死亡判定、击杀计数、存活玩家检测
- 实时监听对局结束触发条件:对战时长耗尽 / 场上存活玩家仅剩1名,满足任意条件立即触发结算
- 完成奖励发放、对局结果公示、玩家状态重置后,将所有玩家传送回大厅出生点,销毁临时克隆的对战地图
- 回到投票流程起点,开启下一轮对局循环
核心实现代码
-- 服务引用 local Players = game:GetService("Players") local Lighting = game:GetService("Lighting") local Workspace = game:GetService("Workspace") local ReplicatedStorage = game:GetService("ReplicatedStorage") local Teams = game:GetService("Teams") -- 全局配置(收敛所有原脚本重复声明的变量) local CONFIG = { VoteDuration = 20, -- 投票时长(秒) BattleDuration = 300, -- 对战时长(秒) LobbySpawn = Vector3.new(0, 5, 0), -- 大厅出生点坐标 KillReward = 50, -- 单次击杀奖励 WinReward = 200, -- 对局胜利奖励 MinPlayersToStart = 2 -- 最低开局人数 } -- 全局状态标记 local gameState = { InVote = false, InBattle = false, CurrentMap = nil, AlivePlayers = {} } -- 玩家加入初始化leaderstats Players.PlayerAdded:Connect(function(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local KOs = Instance.new("IntValue") KOs.Name = "KOs" KOs.Value = 0 KOs.Parent = leaderstats local Wipeouts = Instance.new("IntValue") Wipeouts.Name = "Wipeouts" Wipeouts.Value = 0 Wipeouts.Parent = leaderstats local Coins = Instance.new("IntValue") Coins.Name = "Coins" Coins.Value = 0 Coins.Parent = leaderstats -- 对战阶段死亡判定 player.CharacterAdded:Connect(function(char) local humanoid = char:WaitForChild("Humanoid") humanoid.Died:Connect(function() if not gameState.InBattle then return end Wipeouts.Value += 1 -- 击杀者计数逻辑 local creatorTag = humanoid:FindFirstChild("creator") if creatorTag and creatorTag.Value and creatorTag.Value:IsA("Player") then local killerStats = creatorTag.Value:FindFirstChild("leaderstats") if killerStats then killerStats.KOs.Value += 1 killerStats.Coins.Value += CONFIG.KillReward end end -- 从存活列表移除 gameState.AlivePlayers[player.UserId] = nil end) end) end) -- 投票流程函数 local function startMapVote() gameState.InVote = true gameState.InBattle = false local allMaps = Lighting.Maps:GetChildren() -- 随机抽3张不重复的候选地图 local candidateMaps = {} local pickedIds = {} while #candidateMaps < math.min(3, #allMaps) do local randomIdx = math.random(1, #allMaps) if not pickedIds[randomIdx] then pickedIds[randomIdx] = true table.insert(candidateMaps, allMaps[randomIdx]) end task.wait() end -- 此处可插入投票UI推送逻辑 task.wait(CONFIG.VoteDuration) -- 简化版计票逻辑,可替换为实际票选统计 local winningMap = candidateMaps[math.random(1, #candidateMaps)] gameState.InVote = false return winningMap end -- 地图加载与玩家传送 local function loadMapAndTeleport(mapTemplate) -- 清理旧地图 if gameState.CurrentMap then gameState.CurrentMap:Destroy() end local newMap = mapTemplate:Clone() newMap.Parent = Workspace gameState.CurrentMap = newMap -- 等待地图出生点加载 local mapSpawn = newMap:WaitForChild("SpawnPoint", 10) if not mapSpawn then error("地图缺少出生点配置") end -- 传送所有在线玩家 for _, player in Players:GetPlayers() do player:LoadCharacter() local char = player.Character or player.CharacterAdded:Wait() char:PivotTo(CFrame.new(mapSpawn.Position + Vector3.new(math.random(-5,5), 3, math.random(-5,5)))) end -- 等待所有玩家传送完成 task.wait(3) return true end -- 对战流程 local function startBattle() gameState.InBattle = true -- 清空所有玩家背包 for _, player in Players:GetPlayers() do player.Backpack:ClearAllChildren() if player.Character then for _, tool in player.Character:GetChildren() do if tool:IsA("Tool") then tool:Destroy() end end end -- 初始化存活列表 gameState.AlivePlayers[player.UserId] = player end -- 对局计时+结束条件检测 local battleTimer = 0 local battleEnd = false while gameState.InBattle and not battleEnd do task.wait(1) battleTimer += 1 -- 检测存活人数 local aliveCount = 0 for _ in pairs(gameState.AlivePlayers) do aliveCount +=1 end -- 结束条件触发 if battleTimer >= CONFIG.BattleDuration or aliveCount <= 1 then battleEnd = true -- 结算奖励 for userId, player in pairs(gameState.AlivePlayers) do local stats = player:FindFirstChild("leaderstats") if stats then stats.Coins.Value += CONFIG.WinReward end end -- 此处插入结果公示逻辑 end end end -- 对局重置回大厅 local function resetToLobby() gameState.InBattle = false -- 传送所有玩家回大厅 for _, player in Players:GetPlayers() do player:LoadCharacter() local char = player.Character or player.CharacterAdded:Wait() char:PivotTo(CFrame.new(CONFIG.LobbySpawn + Vector3.new(math.random(-5,5), 3, math.random(-5,5)))) -- 重置玩家状态 player:SetAttribute("Ready", false) end -- 销毁对战地图 if gameState.CurrentMap then gameState.CurrentMap:Destroy() gameState.CurrentMap = nil end task.wait(5) end -- 顶层单循环(替换原双脚本的双层死循环,从根源控制时序) while true do -- 等待满足最低开局人数 while #Players:GetPlayers() < CONFIG.MinPlayersToStart do task.wait(1) end local winMap = startMapVote() loadMapAndTeleport(winMap) startBattle() resetToLobby() end
关键修复点说明
- 移除原脚本双层嵌套死循环,所有流程由顶层单
while true循环串行调度,不存在并行逻辑抢占,彻底解决时序冲突 - 所有变量统一收敛到
CONFIG和gameState表中,修复原脚本重复变量声明的问题 - 全量替换旧版API:废弃的全局
wait()全部替换为task.wait(),角色加载、道具清理逻辑全部使用当前版本支持的官方API,避免兼容性问题 - 每个流程节点添加完成校验:地图加载后等待出生点、玩家传送后预留加载时间、对局阶段实时检测存活人数和计时,不会出现跳阶段执行的问题
- 所有事件回调增加状态位判断,比如玩家死亡逻辑仅在对战阶段生效,避免投票、大厅阶段的误触发
内容的提问来源于stack exchange,提问作者JameAdame
相关产品推荐
相关产品推荐

