Pygame+Socket多人2D游戏:新玩家位置同步及环境更新问题
2D相机跟随游戏的玩家位置同步问题
我正在开发一款相机跟随玩家的2D游戏,目前遇到以下问题:
- 新玩家加入后,其位置在其他客户端无法正常更新
- 玩家位置已发送至服务器,但由于玩家自身位置固定、仅周围环境移动的设计,导致新玩家在其他客户端显示位置异常
- 需要实现支持无限玩家的位置同步方案,要求使用
FOR循环,同时在循环中处理环境(玩家二)的更新
原服务端代码
import socket, pickle, time, _thread, threading ######################################### data = { "id": [], "coordinates": [] } ######################################### s = socket.socket() s.bind(("0.0.0.0", 25565)) s.listen() ######################################### def new_client(client,addr): global data while True: try: client.send(pickle.dumps(data)) data = pickle.loads(client.recv(1024000)) except: index = data["id"].index(addr[1]) data["coordinates"].remove(data["coordinates"][index]) data["id"].remove(addr[1]) print(f"Player {addr[1]} left.") break time.sleep(0.016) ######################################### def petla_print(): while True: if () not in data["coordinates"]: data["coordinates"].append(()) print(data) _thread.start_new_thread(petla_print, ()) ######################################### print("Server running.") while True: conn, addr = s.accept() data["id"].append(addr[1]) _thread.start_new_thread(new_client,(conn,addr))
原客户端代码
import socket, threading, time, pickle, re import sys, pygame ####################################### player_ids_except_me = [] player_coords_except_me = [] fps = 60 ######################################### pygame.init() clock = pygame.time.Clock() screen = pygame.display.set_mode((1280, 720)) floor_x = 0 floor_y = 600 direction = None floor = pygame.Rect(floor_x, floor_y, 900, 100) player = pygame.Rect(500, 500, 100, 100) ######################################### def draw(): global floor, player, a, cam_poz_y, cam_poz_x screen.fill((0, 0, 0)) floor = pygame.draw.rect(screen, (0, 255, 0), floor) player = pygame.draw.rect(screen, (255, 255, 255), player) floor = pygame.Rect(floor_x, floor_y, 900, 100) player = pygame.Rect(500, 500, 100, 100) cam_poz_x = ((floor_x)*(-1))+500 cam_poz_y = (floor_y-1090)*(-1) for i in range(len(player_ids_except_me)): a_pos = pygame.Rect(player_coords_except_me[i][0], player_coords_except_me[i][1], 100, 100) a = pygame.draw.rect(screen, (255, 0, 0), a_pos) pygame.display.update() def hit(obj1, obj2): x_obj1 = obj1.x y_obj1 = obj1.y height_obj1 = obj1.height width_obj1 = obj1.width x_obj2 = obj2.x y_obj2 = obj2.y height_obj2 = obj2.height width_obj2 = obj2.width if y_obj1+height_obj1 > y_obj2 and x_obj1 > x_obj2-width_obj1 and x_obj1 < x_obj2+width_obj2 and y_obj1 < y_obj2+height_obj1: touching = True return touching else: touching = False return touching ####################################### client_socket = socket.socket() client_socket.connect(('192.168.0.62', 25565)) client_id = str(client_socket).split(" ") client_id = int(client_id[6].replace(")", "").replace(",", "")) ######################################### while True: data = pickle.loads(client_socket.recv(1024000)) clock.tick(fps) for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() keys = pygame.key.get_pressed() if keys[pygame.K_w]: floor_y += 20 if keys[pygame.K_a]: floor_x += 10 if keys[pygame.K_d]: floor_x -= 10 floor_y -= 10 if hit(player, floor) == True: floor_y += 10 draw() try: index = data["id"].index(client_id) data["coordinates"][index] = (cam_poz_x, cam_poz_y) except: pass client_socket.send(pickle.dumps(data)) try: player_ids_except_me = data["id"] player_ids_except_me.remove(client_id) player_coords_except_me = data["coordinates"] player_coords_except_me.remove((cam_poz_x, cam_poz_y)).remove(()) except: pass
修复后的解决方案
核心问题分析
- 原代码直接将其他玩家的世界坐标作为屏幕坐标绘制,未进行坐标系转换,导致位置偏移
- 服务端用列表存储玩家数据,索引易混乱,不支持无限玩家扩展
- 客户端通过字符串切割获取
client_id的方式不可靠,易出错
修复后服务端代码
import socket import pickle import time import _thread # 用字典存储所有玩家的世界坐标,key为玩家id,支持无限玩家 players = {} # 若需同步环境状态,可在此维护权威环境数据 environment = {"floor_x": 0, "floor_y": 600} s = socket.socket() s.bind(("0.0.0.0", 25565)) s.listen() def handle_client(client, addr): player_id = addr[1] players[player_id] = (0, 0) # 初始化玩家位置 print(f"玩家 {player_id} 加入") try: while True: # 发送所有玩家数据和环境数据 send_data = { "players": players, "environment": environment } client.send(pickle.dumps(send_data)) # 仅更新当前玩家的坐标,避免覆盖整个数据 recv_data = pickle.loads(client.recv(1024000)) if player_id in recv_data: players[player_id] = recv_data[player_id] time.sleep(1/60) # 保持60帧同步频率 except: # 玩家断开连接,移除数据 del players[player_id] print(f"玩家 {player_id} 离开") client.close() def main(): print("服务器启动") while True: conn, addr = s.accept() _thread.start_new_thread(handle_client, (conn, addr)) if __name__ == "__main__": main()
修复后客户端代码
import socket import pickle import sys import pygame # 初始化Pygame pygame.init() fps = 60 clock = pygame.time.Clock() screen_width, screen_height = 1280, 720 screen = pygame.display.set_mode((screen_width, screen_height)) # 本地玩家在屏幕固定位置 local_player_pos = (500, 500) player_size = (100, 100) # 环境状态 floor_size = (900, 100) floor_x = 0 floor_y = 600 # 存储其他玩家的世界坐标 other_players = {} # 连接服务器 client_socket = socket.socket() server_addr = ('192.168.0.62', 25565) client_socket.connect(server_addr) # 用本地端口作为唯一玩家标识,稳定可靠 player_id = client_socket.getsockname()[1] def hit(obj1, obj2): """简化碰撞检测,使用Pygame内置方法""" return obj1.colliderect(obj2) def draw(): screen.fill((0, 0, 0)) # 绘制环境 floor_rect = pygame.Rect(floor_x, floor_y, *floor_size) pygame.draw.rect(screen, (0, 255, 0), floor_rect) # 绘制本地玩家 local_rect = pygame.Rect(local_player_pos[0], local_player_pos[1], *player_size) pygame.draw.rect(screen, (255, 255, 255), local_rect) # 计算自身世界坐标 local_world_x = -floor_x + local_player_pos[0] local_world_y = -floor_y + local_player_pos[1] # 使用FOR循环遍历并绘制所有其他玩家 for pid, (world_x, world_y) in other_players.items(): # 将其他玩家的世界坐标转换为当前屏幕坐标 screen_x = world_x - local_world_x + local_player_pos[0] screen_y = world_y - local_world_y + local_player_pos[1] other_rect = pygame.Rect(screen_x, screen_y, *player_size) pygame.draw.rect(screen, (255, 0, 0), other_rect) pygame.display.update() def update_environment(): """更新环境(玩家二)状态,包含移动、重力和碰撞逻辑""" global floor_x, floor_y keys = pygame.key.get_pressed() # 环境移动控制 if keys[pygame.K_w]: floor_y += 20 if keys[pygame.K_a]: floor_x += 10 if keys[pygame.K_d]: floor_x -= 10 # 重力效果 floor_y -= 10 # 碰撞检测,防止玩家穿过地面 local_rect = pygame.Rect(local_player_pos[0], local_player_pos[1], *player_size) floor_rect = pygame.Rect(floor_x, floor_y, *floor_size) if hit(local_rect, floor_rect): floor_y += 10 def main_loop(): global other_players while True: clock.tick(fps) # 处理退出事件 for event in pygame.event.get(): if event.type == pygame.QUIT: client_socket.close() sys.exit() # 更新环境状态 update_environment() # 接收服务器数据 try: server_data = pickle.loads(client.recv(1024000)) other_players = server_data["players"].copy() del other_players[player_id] # 移除自身数据,避免重复绘制 # 若需同步环境,可取消下面注释: # global floor_x, floor_y # floor_x = server_data["environment"]["floor_x"] # floor_y = server_data["environment"]["floor_y"] except: pass # 发送自身世界坐标到服务器 local_world_x = -floor_x + local_player_pos[0] local_world_y = -floor_y + local_player_pos[1] client_socket.send(pickle.dumps({player_id: (local_world_x, local_world_y)})) # 绘制所有元素 draw() if __name__ == "__main__": main_loop()
关键改进点
- 坐标系转换:其他玩家位置从世界坐标转换为当前屏幕坐标,解决显示异常问题
- 无限玩家支持:服务端用字典存储玩家数据,无需维护索引,可无限扩展
- FOR循环应用:在绘制阶段用FOR循环遍历所有其他玩家,环境更新整合到主循环流程中
- 数据同步可靠性:服务端仅更新单个玩家的坐标,避免客户端覆盖全局数据导致的冲突
- 玩家标识优化:改用本地端口作为玩家ID,避免原方法的不可靠性
内容的提问来源于stack exchange,提问作者PawełeK
相关产品推荐
相关产品推荐

