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,提问作者Δημήτρης Δημητριαδης
相关产品推荐
相关产品推荐

