Pygame通过Socket服务器同步位置后角色绘制异常问题
问题描述
我正在开发一款基于Pygame的双人联机程序,支持两名玩家连接至Socket服务器并发送自身位置。服务器需要完成以下操作:
- 识别发送位置的玩家身份
- 等待两名玩家均发送位置数据
- 向对应玩家转发对方位置(如将player_1位置发送给player2等)
目前单客户端测试时,将player1的位置发送至服务器,服务器再回传给同一客户端并绘制为player2,但出现异常:客户端可正常发送位置数据,但Pygame绘制的player2占据整个屏幕,尽管已指定其宽高。
服务器代码
import NKSERVER import time id_1_occupied = True id_2_occupied = False print(id_1_occupied, id_2_occupied) addr_p1 = '' addr_p2 = '' p1_pos = (0, 0) p2_pos = (0, 0) clients_ready = False queue = 0 ip = '192.168.158.117' port = 5555 serv = NKSERVER.server(ip, port) print('Server Started') serv.listen(2) while True: conn, addr = serv.server.accept() recv = conn.recv(2048) recv_data = recv.decode('utf-8') print('Data received as:', recv_data) if clients_ready == False: # 检查客户端是否请求ID if recv_data == 'ClientReqId': # 检查可用ID if id_1_occupied == False: # 为玩家分配ID addr_p1 = addr # 创建新客户端对象并分配给玩家 player_1 = NKSERVER.new_client(addr_p1) # 将ID数据发送回玩家 serv.send_to_client('id1', player_1.addr, conn) time.sleep(1) print('已向玩家发送ID') id_1_occupied = True if id_2_occupied == False: if id_1_occupied == True: # 避免重复分配ID if addr == addr_p1: print('该玩家已分配ID') else: # 为玩家分配ID addr_p2 = addr # 创建新客户端对象并分配给玩家 player_2 = NKSERVER.new_client(addr_p2) # 将ID数据发送回玩家 serv.send_to_client('id2', player_2.addr, conn) time.sleep(1) print('已向玩家发送ID') id_2_occupied = True print('初始化:开始位置更新') serv.send_to_client('StartPosUpdate', player_2.addr, conn) print('已发送:StartPosUpdate') else: pass # 检查玩家是否准备就绪 if id_1_occupied == True and id_2_occupied == True: clients_ready = True while clients_ready: recv = conn.recv(2048) recv_data = recv.decode('utf-8') print('Data received as:', recv_data) # 处理客户端未收到StartPosUpdate的情况 if recv_data == 'CheckStat': print('初始化:检查状态') print('初始化:开始位置更新') serv.send_to_client('StartPosUpdate', player_2.addr, conn) print('已发送:StartPosUpdate') # 玩家就绪后处理位置更新 if recv_data != 'CheckStat' and recv_data != 'ClientReqId' and clients_ready == True: if queue < 1: if addr == addr_p1: p1_pos = recv_data queue += 1 if addr == addr_p2: p2_pos = recv_data queue += 1 if queue > 0: serv.send_to_client(p2_pos, player_2.addr, conn) queue = 0
客户端代码
# 导入模块 import pygame as p import NKNET p.init() # 玩家变量 p_width = 100 p_height = 100 p_velocity = 2 # 位置变量 p2_pos = None p2_x = 0 p2_y = 0 # 服务器变量 ip = '192.168.158.117' port = 5555 # ID变量 local_id = '' # 定时发送位置的事件 send_pos = p.time.set_timer(p.USEREVENT, 1000) # 设置屏幕 width = 1000 height = 700 sc = p.display.set_mode((width, height)) p.display.set_caption('Barebones: By Nathan Keidel') # 启动客户端 client = NKNET.network(ip, port, local_id) # 向服务器请求ID client.get_id() print(client.id) # 玩家类 class player: def __init__(self, x, y, player_width, player_height, vel, color): self.x = x self.y = y self.width = player_width self.height = player_height self.vel = vel self.color = color # 错误点:误用了全局屏幕宽高,导致矩形占满屏幕 self.rect = (x, y, width, height) def draw(self, window): p.draw.rect(window, self.color, self.rect) def move_player(self): keys = p.key.get_pressed() if keys[p.K_a]: self.x -= self.vel if keys[p.K_d]: self.x += self.vel if keys[p.K_w]: self.y -= self.vel if keys[p.K_s]: self.y += self.vel self.rect = (self.x, self.y, self.width, self.height) def set_boundires_to_window(self): if self.y <= 0: self.y = 1 if self.y >= height - p_height: self.y -= 3 if self.x <= 0: self.x = 1 if self.x >= width - p_width: self.x -= 3 else: pass # 更新窗口函数 def update_window(player_1, window): sc.fill('black') player_1.draw(window) def main(): run = False player1 = player(100, 100, p_width, p_height, p_velocity, (0,255,0)) player2 = player(p2_x, p2_y, p_width, p_height, p_velocity, (0, 0, 255)) # 确保收到服务器的StartPosUpdate消息 while True: client.recv_with_backup('CheckStat') print('收到数据:', client.recv_data) if client.recv_data == None: print('初始化:检查状态') client.send('CheckStat') print('已发送:CheckStat') if client.recv_data == 'StartPosUpdate': print('初始化:开始位置更新') run = True break while run: for event in p.event.get(): if event.type == p.QUIT: run = False p.quit() # 整理player1位置并发送给服务器 player1_pos = (player1.x, player1.y) print('player1位置:', player1_pos) client.send(str(player1_pos)) client.recv() p2_pos = client.recv_data p2_pos = (tuple(map(int, p2_pos.strip("()").split(",")))) (player2.x, player2.y) = p2_pos print('player2位置:', p2_pos) player1.set_boundires_to_window() player1.move_player() update_window(player1, sc) update_window(player2, sc) p.display.update() main()
自定义模块代码
NKNET.py
import socket class network: def __init__(self, ip, port, id): self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_ip = str(ip) self.port = int(port) self.addr = (self.server_ip, self.port) self.id = id self.conn = self.connect() self.recv_data = None def connect(self): try: # 连接服务器 self.client.connect(self.addr) except socket.error as e: print(e) def send(self, data): try: self.client.send(str.encode(data)) except socket.error as e: print(e) def recv(self): try: self.recv_data = self.client.recv(2048).decode('utf-8') except socket.error as e: print(e) def recv_with_backup(self, backup_data): try: self.recv_data = self.client.recv(2048).decode('utf-8') if self.recv_data == None: self.send(backup_data) else: pass except socket.error as e: print(e) def get_id(self): try: self.client.send(str.encode('ClientReqId')) self.id = self.client.recv(2048).decode('utf-8') if self.id == None: self.get_id() else: pass except socket.error as e: print(e)
NKSERVER.py
# 导入socket import socket # 服务器类 class server: def __init__(self, ip, port): self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ip = ip self.port = port try: self.server.bind((self.ip, self.port)) except socket.error as e: str(e) def listen(self, num_of_conns): self.server.listen(num_of_conns) def accept(self): self.server.accept() def send_to_client(self, data, addr, conn): conn.sendto(str.encode(data), addr) def send_to_all_clients(self, data, conn): conn.sendall(str.encode(data)) # 存储新客户端数据的类 class new_client: def __init__(self, addr): self.addr = addr
问题修复方案
1. 核心错误:Player类矩形尺寸错误
客户端player类的__init__方法中,初始化self.rect时误用了全局屏幕宽高width和height,导致绘制的矩形占满整个屏幕。修改为:
def __init__(self, x, y, player_width, player_height, vel, color): self.x = x self.y = y self.width = player_width self.height = player_height self.vel = vel self.color = color # 修正为使用玩家自身的宽高 self.rect = (x, y, self.width, self.height)
2. 修复窗口更新逻辑
当前update_window函数每次调用都会填充黑色背景,导致第二次调用时覆盖之前绘制的玩家。修改为:
# 更新窗口函数 def update_window(window, *players): sc.fill('black') for player in players: player.draw(window)
在main函数中调用时改为:
update_window(sc, player1, player2)
3. 服务器逻辑优化
- 服务器当前每次
accept仅处理一个连接,后续无法响应其他客户端,建议使用多线程处理每个客户端连接。 - 位置转发逻辑需补充:将player1的位置发送给player2,同时将player2的位置发送给player1,当前代码仅实现了单向转发。
内容的提问来源于stack exchange,提问作者Nathan
相关产品推荐
相关产品推荐

