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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 15:27:37