You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

崩溃原因排查与修复方案

  1. 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
    
  2. 空实体调用方法崩溃
    当所有玩家死亡时,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
        -- 剩余逻辑保持不变
    end
    
  3. Nextbot未被正确清理
    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
    
  4. 全局变量冲突
    roundActive在两个文件中都被定义为全局变量,可能导致赋值冲突。改为GM表成员变量,比如self.roundActive,避免全局命名空间污染。

内容的提问来源于stack exchange,提问作者TheBudderBomb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 12:18:19