Ursina引擎多人游戏报错Assertion failed: !is_empty()求助
Ursina多人联机游戏断言错误问题排查
在开发多客户端联机游戏时,当接入超过2个客户端或频繁断开重连,会触发错误:
Assertion failed: !is_empty() at line 961 of panda/src/pgraph/nodePath.cxx
问题根源分析
服务端问题
- 重复定义
update函数:
代码中存在两个update函数,后定义的会覆盖前一个,导致原本的旋转信息更新逻辑失效,同时可能打乱网络事件处理的执行时机。 - 客户端断开处理逻辑错误:
- 自定义的
clientdisconnect事件中,del clients_pos[0]会无条件删除列表第一个元素,不管断开的是哪个客户端,直接导致客户端ID与clients_pos、clientplayers的索引完全不匹配,后续操作会访问错误的元素或已销毁的实体。 - 递减
last_id会导致ID重复分配,多个客户端使用同一ID操作同一个实体,销毁后再次访问就会触发断言错误。 - 注释的
onClientDisconnected事件中,clients_pos.pop(clientplayers[ID])是错误的,应该按索引ID删除,而非传入实体对象。
- 自定义的
- 玩家位置更新逻辑隐患:
xycoords事件中直接使用客户端传来的Id作为列表索引,当ID与列表索引不匹配时(比如断开后列表元素被删除),会访问越界或已销毁的实体。
客户端问题
- 错误修改服务端传来的位置列表:
xandyotherplayers事件中使用Content.pop(Id)会直接修改原列表,导致后续位置信息错乱,应该改为通过索引访问自身位置,而非删除元素。 - 玩家实体管理混乱:
- 玩家数量减少时仅销毁
otherplayers[0],无法对应到实际断开的玩家,导致实体与位置信息不匹配。 counter变量的使用逻辑不严谨,当otherplayers长度与位置列表长度不一致时,会出现索引越界。
- 玩家数量减少时仅销毁
- 变量名错误:
全局声明的other_players未被使用,实际代码中用的是otherplayers,可能引发未定义错误。 - 高频网络请求:
每一帧都发送位置信息,会导致网络拥堵,加重服务端负担。
修正后的服务端代码
from ursinanetworking import * from ursina import * server = UrsinaNetworkingServer('192.168.1.34', 22625) App = Ursina() clientplayers = [] clients_pos = [] client_names = [] last_id = 0 nb_clients = 0 ground = Entity(model='ground.obj', scale=(10,1,10), color=color.white, texture='white_cube',texture_scale=(10,10)) ec = EditorCamera(rotation_smoothing=10, enabled=1, rotation=(30,30,0)) rotation_info = Text(position=window.top_left) def update(): rotation_info.text = str(int(ec.rotation_y)) + '\n' + str(int(ec.rotation_x)) server.process_net_events() # 合并到一个update函数 @server.event def onClientConnected(Client): global last_id, nb_clients print(f'{Client} joined the game!') # 分配唯一ID,不重复使用 client_id = last_id Client.send_message("ID", client_id) # 初始化玩家实体和位置 clientplayers.append(Entity(model="person.obj", position=(0,5,0), color=color.red, collider='sphere')) clients_pos.append((0,5,0)) client_names.append(str(Client)) nb_clients += 1 last_id += 1 @server.event def onClientDisconnected(Client): global nb_clients # 从Client对象中提取ID client_id_str = str(Client).replace("Client ", "") if not client_id_str.isdigit(): print(f"Invalid client ID: {Client}") return client_id = int(client_id_str) # 检查ID是否在有效范围内 if 0 <= client_id < len(clientplayers): destroy(clientplayers[client_id]) # 将该位置标记为无效,避免索引错乱 clients_pos[client_id] = None print(f"Client {client_id} left the game") nb_clients -= 1 @server.event def xycoords(Client, Content): x, y, z, client_id = Content # 验证ID有效性 if 0 <= client_id < len(clientplayers) and clients_pos[client_id] is not None: clientplayers[client_id].position = (x, y, z) clients_pos[client_id] = (x, y, z) # 过滤掉无效位置,发送给客户端 valid_positions = [pos for pos in clients_pos if pos is not None] Client.send_message("xandyotherplayers", (client_id, valid_positions)) App.run()
修正后的客户端代码
from ursinanetworking import * from ursina.prefabs.first_person_controller import FirstPersonController client = UrsinaNetworkingClient('192.168.1.34', 22625) App = Ursina() player_id = 0 other_players = [] last_valid_positions = [] ground = Entity(model="ground.obj", scale=(10,1,10), color=color.white, texture="white_cube", texture_scale=(10,10), collider="mesh") player = FirstPersonController(origin_x=0, origin_z=0, origin_y=5, mouse_sensivity=Vec2(40,40), jump_height=2, collider='cube') # 控制位置发送频率,避免高频请求 position_send_timer = 0 SEND_INTERVAL = 0.05 # 每0.05秒发送一次 @client.event def onConnectionEtablished(): print('Client successfully connected!') @client.event def onConnectionError(Reason): print('Client failed to connect, the reason is : ' + Reason) @client.event def ID(Content): global player_id player_id = int(Content) print(f"Assigned ID: {player_id}") @client.event def xandyotherplayers(Content): global other_players, last_valid_positions own_id, valid_positions = Content # 同步其他玩家数量:创建或销毁实体 while len(other_players) < len(valid_positions) - 1: other_player = Entity(model="person.obj", color=color.white, texture="white_cube", texture_scale=(10,2), collider='sphere') other_players.append(other_player) while len(other_players) > len(valid_positions) - 1: destroy(other_players.pop()) # 更新其他玩家位置 idx = 0 for i, pos in enumerate(valid_positions): if i != own_id and pos is not None: if idx < len(other_players): other_players[idx].position = pos idx += 1 last_valid_positions = valid_positions def update(): global position_send_timer position_send_timer += time.dt # 达到间隔再发送位置 if position_send_timer >= SEND_INTERVAL: client.send_message("xycoords", (player.x, player.y, player.z, player_id)) position_send_timer = 0 client.process_net_events() # 死亡重置 if player.y <= -20: player.position = (0, 5, 0) # 退出逻辑 if held_keys["q"]: client.disconnect() application.quit() App.run()
内容的提问来源于stack exchange,提问作者Proplayer2020
相关产品推荐
相关产品推荐

