GMOD Lua调用SpawnGhoulInitial后玩家死亡致游戏崩溃求助
Garry's Mod FFA模式:生成Nextbot后玩家死亡触发崩溃问题
我正在开发Garry's Mod的自由对战(FFA)游戏模式,玩家需要在互相厮杀和Nextbot NPC的追杀下存活。重写的GM:PlayerDeath函数能正常判断回合结束时机,但调用SpawnGhoulInitial()生成第一个Nextbot后,任何玩家死亡都会导致游戏崩溃。
回合逻辑文件 roundsystem_2.lua
-- GLOBAL VALUES roundActive = false roundCountdownStartTime = 0 roundStartTime = 0 phase = 1 countDownLength = 5 initialGhoulDelay = 10 spawnWepDelay = 15 ghoulMadnessDelay = 20 roundLength = 30 DEFINE_BASECLASS( "gamemode_base" ) -- vector list dictionary of nexbot spawn locations per map, starting with initial spawn location nextbot_spawns = {gm_backrooms_classic = {Vector(-7413, 318, 93), Vector(-4270, 4079, 93), Vector(-3279, -4620, 93), Vector(-11092, -4632, 93), Vector(-11086, 4316, 93)}} function GM:UpdateTimer(time) net.Start("round_timer") net.WriteInt(time, 10) net.Broadcast() end -- function used to spawn the first ghoul of the round function SpawnGhoulInitial() local name = RandomizeNextbot() local Ent = ents.Create(name) if( !IsValid(Ent)) then print("entity is not valid.") return end Ent:SetPos(nextbot_spawns.gm_backrooms_classic[1]) Ent:Spawn() print("Spawned ghoul ".. name) PrintMessage(4, "The Ghoul has spawned. SURVIVE!") end -- function called to spawn the next four ghouls to sweep the map and kill off straggling players function SpawnGhoulMadness() local name = RandomizeNextbot() local Ent = ents.Create(name) if( !IsValid(Ent)) then print("entity is not valid.") return end for i=2, 5 do Ent:SetPos(nextbot_spawns.gm_backrooms_classic[i]) Ent:Spawn() print("Spawned ghoul madness ghoul number ".. tostring(i)) end PrintMessage(4, "The Ghoul has summoned some friends. RUN!") end -- function called to spawn in weapons for the players function SpawnWeps() for k, v in pairs(player.GetAll()) do if (v:Alive()) then v:Give("weapon_crowbar", false) v:Give("weapon_pistol", false) end end PrintMessage(4, "You've found supplies. Kill other survivors!") end function CountAlivePlayers() local players = player.GetAll() local alive = 0 for i = 1, #players do local player = players[i] if (player:Alive()) then alive = alive + 1 end end return alive end -- function called to give us a random nextbot to spawn in function RandomizeNextbot() local names = {"table of nextbot names here, not posting them for stackoverflow because its embarrassing"} local random_nextbot = math.random(#names) return names[random_nextbot] end -- function called under the conditions that the round has ended, will start the next round. function GM:EndRound(winner) -- clear the map, clear sounds, print winner on screen, allow the respawning if (winner == nil) then PrintMessage(4, "Nobody Wins!") else PrintMessage(4, winner:Nick().." Wins!") end game.CleanUpMap(false, {}) RunConsoleCommand("stopsound") timer.Remove("RoundTimer") timer.Create("roundEndTimer", 5, 1, function() for k, v in pairs(player.GetAll()) do if (v:Alive()) then v:StripWeapons() v:KillSilent() end end net.Start("round_active") net.WriteBool(false) net.Broadcast() roundActive = false end) end function GM:StartCountDownTimer(repetitions) self:UpdateTimer(repetitions) timer.Create("CountdownTimer", 1, repetitions, function() repetitions = repetitions - 1 self:UpdateTimer(repetitions) end) end function GM:StartRoundTimer(repetitions) self:UpdateTimer(repetitions) timer.Create("RoundTimer", 1, repetitions, function() repetitions = repetitions - 1 self:UpdateTimer(repetitions) if (repetitions <= 0) then self:EndRound(nil) end end) end --overriding playerdeath function for best death results function GM:PlayerDeath(victim, inflictor, attacker) BaseClass.PlayerDeath(self, victim, inflictor, attacker) local players = player.GetAll() local lastAlive = NULL for i = 1, #players do local p = players[i] if (p:Alive()) then lastAlive = p end end if (CountAlivePlayers() <= 1 && roundActive) then self:EndRound(lastAlive) end end -- hook for starting the round on player spawn if there are enough players. -- countdown for round start will restart if a new player spawns in during the countdown. function GM:DoRoundSpawn() --every time player joins reset the countdown if (CountAlivePlayers() > 1) then roundCountdownStartTime = CurTime() self:StartCountDownTimer(countDownLength) end end -- The main timing of the round. Checks against curtime to find when -- specific events in the round need to happen. function GM:Think() -- if there is more than one player, and the time elapsed is a full 30 sec, start round if (CountAlivePlayers() > 1 && roundActive == false) then if ((roundCountdownStartTime + countDownLength) < CurTime()) then roundStartTime = CurTime() roundActive = true phase = 1 net.Start("round_active") net.WriteBool(true) net.Broadcast() timer.Remove("CountdownTimer") self:StartRoundTimer(roundLength) PrintMessage(4, "The Round has begun. Last player to survive wins!") end end -- if the round is active, do this stuff if (roundActive == true) then -- if its been 10 sec since round start, spawn the ghoul if ((roundStartTime + initialGhoulDelay) < CurTime() && phase == 1) then SpawnGhoulInitial() phase = 2 end -- if its been 30 sec since round start, spawn in weapons for players if((roundStartTime + spawnWepDelay) < CurTime() && phase == 2) then SpawnWeps() phase = 3 end -- if its been 120 sec (2 minutes) since round start, begin the ghoul madness if((roundStartTime + ghoulMadnessDelay) < CurTime() && phase == 3) then SpawnGhoulMadness() phase = 4 end end end
初始化文件 init.lua
AddCSLuaFile("cl_init.lua") AddCSLuaFile("shared.lua") include("shared.lua") include("roundsystem_2.lua") util.AddNetworkString("round_timer") util.AddNetworkString("round_active") roundActive = false playerSpawns = {--table of spawnpoints, too much to paste for stackoverflow} function GM:PlayerSpawn(ply) ply:SetGravity(.85) ply:SetMaxHealth(100) ply:SetupHands() ply:SetWalkSpeed(250) ply:SetRunSpeed(450) ply:SetModel("models/player/Kleiner.mdl") local random_spawn = math.random(#playerSpawns) ply:SetPos(playerSpawns[random_spawn]) --this section basically will make it so if someone spawns --in while the round is active, they will stay dead until --the round is no longer active print("Player: " .. ply:GetName() .. " has spawned!") if (roundActive == true) then ply:KillSilent() return end self:DoRoundSpawn() end function GM:PlayerDeathThink(ply) if (roundActive == false) then ply:Spawn() return true else return false end end
崩溃原因排查与修复方案
Nextbot重复生成逻辑错误
SpawnGhoulMadness()中仅创建一个Nextbot实体,却在循环内重复调用Spawn(),导致同一实体被多次初始化,引发实体状态异常。修改为循环内创建实体:function SpawnGhoulMadness() for i=2, 5 do local name = RandomizeNextbot() local Ent = ents.Create(name) if not IsValid(Ent) then print("entity is not valid for spawn point "..tostring(i)) continue end Ent:SetPos(nextbot_spawns.gm_backrooms_classic[i]) Ent:Spawn() print("Spawned ghoul madness ghoul number ".. tostring(i)) end PrintMessage(4, "The Ghoul has summoned some friends. RUN!") end空实体调用方法崩溃
当所有玩家死亡时,lastAlive为NULL,EndRound()中调用winner:Nick()会触发空指针错误。添加有效性判断:function GM:EndRound(winner) if (winner == nil or not IsValid(winner)) then PrintMessage(4, "Nobody Wins!") else PrintMessage(4, winner:Nick().." Wins!") end -- 剩余逻辑保持不变 endNextbot未被正确清理
game.CleanUpMap(false, {})可能未覆盖Nextbot实体的清理规则,导致回合结束后残留无效实体引用。在EndRound()中手动清理Nextbot:function GM:EndRound(winner) -- 在game.CleanUpMap前添加 for _, ent in ipairs(ents.FindByClass("nextbot_base")) do -- 替换为你的Nextbot实际基类 if IsValid(ent) then ent:Remove() end end -- 剩余逻辑保持不变 end全局变量冲突
roundActive在两个文件中都被定义为全局变量,可能导致赋值冲突。改为GM表成员变量,比如self.roundActive,避免全局命名空间污染。
内容的提问来源于stack exchange,提问作者TheBudderBomb
相关产品推荐
相关产品推荐

