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

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

修复后的解决方案

核心问题分析

  1. 原代码直接将其他玩家的世界坐标作为屏幕坐标绘制,未进行坐标系转换,导致位置偏移
  2. 服务端用列表存储玩家数据,索引易混乱,不支持无限玩家扩展
  3. 客户端通过字符串切割获取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()

关键改进点

  1. 坐标系转换:其他玩家位置从世界坐标转换为当前屏幕坐标,解决显示异常问题
  2. 无限玩家支持:服务端用字典存储玩家数据,无需维护索引,可无限扩展
  3. FOR循环应用:在绘制阶段用FOR循环遍历所有其他玩家,环境更新整合到主循环流程中
  4. 数据同步可靠性:服务端仅更新单个玩家的坐标,避免客户端覆盖全局数据导致的冲突
  5. 玩家标识优化:改用本地端口作为玩家ID,避免原方法的不可靠性

内容的提问来源于stack exchange,提问作者PawełeK

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:15:24