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

Godot 4.1 C#多人游戏客户端连接时服务器崩溃且玩家不生成

Godot 4.1 C# 多人游戏崩溃与玩家生成问题修复

问题概述

服务器启动正常,但客户端连接后玩家无法生成,服务器直接崩溃,最后日志仅输出“client connected”,运行环境为Arch Linux本地双实例,使用回环IP连接。

核心问题定位

  • PeerConnected信号绑定错误:level类中绑定信号时错误传递了服务器自身ID,而非新连接客户端的ID,导致玩家生成逻辑混乱。
  • Player类属性无限递归:playerId的getter直接返回自身,触发栈溢出导致崩溃。
  • 输入同步未用RPC:PlayerInput中跳跃输入未通过RPC同步到服务器,且玩家生成/销毁未同步到客户端。
  • 空引用风险:销毁玩家时未检查节点是否存在,可能触发空指针异常。

修正后的代码

Multiplayer 类

using Godot;
using System;

namespace Multiplayer
{
    public partial class Multiplayer : Node
    {
        const int MAXPLAYER = 2;
        const int PORT = 5555; 
        [Export]
        private PackedScene _levelScene;
        private Control _ui;


        public override void _Ready() => _ui = GetNode<Control>("UI");

        private void OnHostButtonPressed()
        {
            var peer = new ENetMultiplayerPeer();
            var error = peer.CreateServer(PORT, MAXPLAYER);
            if(error != Error.Ok){
                GD.Print("Failed to start server");
                return;
            }
            GD.Print("Server established");
            Multiplayer.MultiplayerPeer = peer;
            StartGame();
        }

        private void OnConnectButtonPressed()
        {
            var peer = new ENetMultiplayerPeer();
            var ip = RetrieveAddress();
            if(string.IsNullOrEmpty(ip)){
                    GD.Print("Ip can't be empty");
                    return;
            }
            var error = peer.CreateClient(ip, PORT);
            if(error != Error.Ok){
                GD.Print("Failed to connect to server.");
                return;
            }
            GD.Print("Client connected");
            Multiplayer.MultiplayerPeer = peer;
            StartGame();
        }

        private void StartGame(){
            if(!Multiplayer.IsServer())
                _ui.Hide(); 
        }

        private void OnStartButtonPressed()
        {
            if(Multiplayer.IsServer()){
                CallDeferred(nameof(ChangeLevel), _levelScene);
                _ui.Hide(); 
            }
        }

        private void ChangeLevel(PackedScene scene){
            var level = GetNode<Node>("Level");
            foreach (var child in level.GetChildren()){
                level.RemoveChild(child);
                child.QueueFree();
            }
            level.AddChild(scene.Instantiate());
        }   

        private string RetrieveAddress(){
            return GetNode<LineEdit>("UI/VBoxContainer/HBoxContainer/Remote").Text;
        }
    }
}

修正点:改用CreateServer/CreateClient返回的错误码判断连接状态,原GetConnectionStatus无法立即获取有效结果

level 类

using Godot;
using System;

public partial class level : Node2D
{
    [Export]
    private PackedScene playerScene;
    private Node2D spawnPoint;

    public override void _Ready()
    {   
        spawnPoint = GetNode<Node2D>("Player");

        if(!Multiplayer.IsServer())
            return;

        Multiplayer.PeerConnected += ctx => AddPlayer((int)ctx);
        Multiplayer.PeerDisconnected += RemovePlayer;

        foreach(long peerId in Multiplayer.GetPeers()){
            AddPlayer((int)peerId);
        }

        AddPlayer(1);
    }

    private void AddPlayer(int id){
        GD.Print($"Adding player {id}");
        var player = (Player)playerScene.Instantiate();
        player.Name = id.ToString();
        spawnPoint.AddChild(player, true);
        player.playerId = id;
        Rpc(nameof(SpawnPlayerOnClients), id);
    }

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    private void SpawnPlayerOnClients(int id){
        var player = (Player)playerScene.Instantiate();
        player.Name = id.ToString();
        spawnPoint.AddChild(player, true);
        player.playerId = id;
    }

    private void RemovePlayer(long id){
        GD.Print($"Despawn player {id}");    
        var player = spawnPoint.GetNodeOrNull<CharacterBody2D>(id.ToString());
        if(player != null){
            player.QueueFree();
            Rpc(nameof(DespawnPlayerOnClients), id);
        }
    }

    [Rpc(MultiplayerApi.RpcMode.AnyPeer)]
    private void DespawnPlayerOnClients(long id){
        var player = spawnPoint.GetNodeOrNull<CharacterBody2D>(id.ToString());
        if(player != null) player.QueueFree();
    }
}

修正点:修复PeerConnected的ID传递错误,添加RPC同步玩家生成/销毁到所有客户端,用GetNodeOrNull避免空引用崩溃

PlayerInput 类

using Godot;

namespace Multiplayer
{
    public partial class PlayerInput : MultiplayerSynchronizer
    {
        public bool isJumping = false;
        [Export]
        Vector2 motion;

        public override void _Ready()
        {
            SetProcess(GetMultiplayerAuthority() == Multiplayer.GetUniqueId());
        }

        [Rpc(MultiplayerApi.RpcMode.Authority)]
        public void Jump(){
            GD.Print($"Jump from player {GetMultiplayerAuthority()}");
            isJumping = true;
        }

        public override void _Process(double delta)
        {
            if(Input.IsActionJustPressed("Jump")){
                GD.Print("Local jump input detected");
                Rpc(nameof(Jump));
            }
        }
    }
}

修正点:添加RPC标记确保跳跃指令从客户端同步到服务器,仅允许本地玩家处理输入逻辑

Player 类

using Godot;
using Multiplayer;

public partial class Player : CharacterBody2D
{
    private float Gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
    [Export]
    private int spd = 200;
    [Export]
    private int jumpForce = 500;
    [Export]
    private float gravityScale = 1.0f;
    private PlayerInput _input;
    private int _playerId;

    public int playerId{
        get{return _playerId;}
        set{
            _playerId = value;
            GD.Print($"PlayerId set to: {value}");
            _input = GetNode<PlayerInput>("InputSynchronizer");
            _input.SetMultiplayerAuthority(value);
            SetPhysicsProcess(Multiplayer.IsServer() || value == Multiplayer.GetUniqueId());
        }
    }

    public override void _PhysicsProcess(double delta){
        if(_input == null) return;
        Movement(delta);
        MoveAndSlide();
    }

    private void Movement(double delta)
    {
        var motion = Velocity;
        motion.Y += Gravity * (float)delta * gravityScale;

        if(_input.isJumping && IsOnFloor()){
            motion.Y = -jumpForce; 
            _input.isJumping = false;
        }

        if(GetMultiplayerAuthority() == Multiplayer.GetUniqueId()){
            var dir = Input.GetAxis("Left", "Right");
            motion.X = dir * spd;
        }

        Velocity = motion;
    }
}

修正点:修复playerId属性的无限递归问题,添加物理进程启用逻辑,确保仅本地玩家或服务器处理移动


内容的提问来源于stack exchange,提问作者Δημήτρης Δημητριαδης

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 05:04:52