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

Python Turtle多线程异常与多人游戏角色同步问题求助

Python Turtle多人游戏核心问题解决思路与代码修改

一、线程异常问题(RuntimeError: main thread is not in main loop)

问题原因

Turtle库基于Tkinter,而Tkinter的GUI操作必须在主线程执行。你的receive_messages线程在子线程中直接调用displayCharacter(),触发了跨线程操作GUI的限制,导致报错。

解决思路

使用线程安全的队列实现解耦:子线程只负责接收服务器消息并放入队列,主线程通过turtle.ontimer()定期从队列中取出消息并处理GUI更新,彻底避免跨线程操作。

代码修改要点

  1. 导入queue模块,创建全局消息队列:
import queue
msg_queue = queue.Queue()
  1. 修改receive_messages函数,仅将消息放入队列,不直接处理GUI:
def receive_messages(client_socket):
    global userObjectDict, username
    print('receive')
    while True:
        try:
            message = json.loads(client_socket.recv(1024).decode())
            if not message:
                break
            if message[0] == 'update':
                msg_queue.put(message[1])  # 把更新数据放入队列
        except ConnectionResetError:
            print("Connection to the server was reset.")
            break
  1. 添加主线程队列处理函数,定时检查并处理队列消息:
def process_queue():
    while not msg_queue.empty():
        user_data = msg_queue.get()
        loadUsers(user_data)
    screen.ontimer(process_queue, 50)  # 每50ms检查一次队列
  1. 在启动线程后,启动队列处理任务:
receive_thread.start()
process_queue()  # 启动主线程消息处理
send_message(client_socket)

二、多人角色移动同步问题

问题原因

  1. 客户端收到服务器更新消息时,未同步更新已有角色的x/y坐标属性,直接调用displayCharacter导致位置无变化;
  2. 若服务器未正确收集并广播所有玩家的最新位置,也会导致同步失效(需服务器端配合调整,以下先解决客户端逻辑)。

解决思路

客户端收到更新数据时,先更新对应角色的坐标属性,再调用显示方法;同时跳过自身角色的重复更新,避免本地操作与服务器消息冲突。

代码修改要点

更新loadUsers函数,增加坐标同步逻辑:

def loadUsers(inpDict):
    global userObjectDict
    for c in list(inpDict.keys()):
        if c not in list(userObjectDict.keys()):
            if c != username:
                corr = inpDict[c]
                userObjectDict[c] = Character(inpDict[c][1],inpDict[c][0][0],inpDict[c][0][1],c)
                userObjectDict[c].createTurtle()
        else:
            # 同步已有角色的最新坐标
            userObjectDict[c].x = inpDict[c][0][0]
            userObjectDict[c].y = inpDict[c][0][1]
    for n in list(userObjectDict.keys()):
        if n != username:  # 跳过自身角色,避免重复更新
            userObjectDict[n].displayCharacter()

完整修改后的代码

from turtle import *
import time
import threading
import socket
import json
import queue

# 全局线程安全消息队列
msg_queue = queue.Queue()

# Define the Character class for managing user characters
class Character:
    def __init__(self, color, x, y, username):
        self.color = color
        self.x = x
        self.y = y
        self.username = username
        self.userTurtle = None
        self.turtle = None

    def createTurtle(self):
        # Create Turtle objects for the character and username label
        self.userTurtle = Turtle()
        self.userTurtle.pencolor('white')
        self.userTurtle.hideturtle()
        self.turtle = Turtle()
        self.turtle.pencolor(self.color)
        self.turtle.fillcolor(self.color)
        self.turtle.shape("square")
    def displayCharacter(self):
        # Update the character's position and username label
        self.turtle.clear()
        self.userTurtle.clear()
        self.turtle.penup()
        self.userTurtle.penup()
        self.turtle.goto(self.x,self.y)
        self.userTurtle.goto(self.x,self.y-25)
            
        self.userTurtle.write(self.username, move=False, align="center", font=("Cooper Black", 11))

        update()

# Define keyboard functions for character movement     
def rightKey():
    if main.x == 290:
        main.x = -310
    else:
        main.x += 20
    message = json.dumps(["charMov",username,[main.x,main.y]])
    client_socket.sendall(message.encode())
    main.displayCharacter()
def leftKey():
    if main.x == -310:
        main.x = 290
    else:
        main.x -= 20
    message = json.dumps(["charMov",username,[main.x,main.y]])
    client_socket.sendall(message.encode())
    main.displayCharacter()
def upKey():
    if main.y != 150:
        main.y += 20
    message = json.dumps(["charMov",username,[main.x,main.y]])
    client_socket.sendall(message.encode())
    main.displayCharacter()
def downKey():
    if main.y != -130:
        main.y -= 20
    message = json.dumps(["charMov",username,[main.x,main.y]])
    client_socket.sendall(message.encode())
    main.displayCharacter()

# Function for configuring user settings
def configUser():
    global main, username
    askConfig = input("Do you have a configuration file (file named config.txt) (y/n)? ").lower()
    if askConfig == 'y':
        getConfig = open("config.txt","r").readlines()
        for i in range(len(getConfig)):
            getConfig[i] = getConfig[i].replace('\n','')
    colors = ['red','orange','yellow','green','blue','purple','pink','white','brown','gray','maroon','cyan','antiquewhite','turquoise','mediumpurple']
    colors.sort()
    if askConfig == 'y':
        back = getConfig[0]
    else:
        back = input("Choose a background colour: ").lower()
    while True:
        if back in colors:
            break
        else:
            print("Unavailable background colour!")
            print("Here are all background colours available:")
            for c in colors:
                print(c)
            back = input("Choose a background colour: ").lower()
    if askConfig == 'y':
        username = getConfig[1]
    else:
        username = input("Pick a username: ")
        
    while True:
        if len(username) < 15 and len(username) > 5:
            client_socket.sendall(json.dumps(['usrRqst',username,back]).encode())
            response = client_socket.recv(1024).decode()
            if response == 't':
                break
            else:
                print("Username already exists! Pick another username!")
                username = input("Pick a username: ")

        else:
            print("Invalid Username! Username must be less than 15 characters, and greater than 5 characters.")
            username = input("Pick a username: ")
        
    if askConfig != 'y':
        askSave = input("Would you like to save these values in config.txt (y/n)? ").lower()
        if askSave == 'y':
            save = open("config.txt","w")
            save.write(back+'\n'+username)
            save.close()

    client_socket.sendall(json.dumps(['usrListRqst']).encode())
    loadUsers(json.loads(client_socket.recv(1024).decode()))
    main = Character(back,10,10, username)
    main.createTurtle()
    
# Function for loading other users
def loadUsers(inpDict):
    global userObjectDict
    for c in list(inpDict.keys()):
        if c not in list(userObjectDict.keys()):
            if c != username:
                corr = inpDict[c]
                userObjectDict[c] = Character(inpDict[c][1],inpDict[c][0][0],inpDict[c][0][1],c)
                userObjectDict[c].createTurtle()
        else:
            # 同步已有角色的最新坐标
            userObjectDict[c].x = inpDict[c][0][0]
            userObjectDict[c].y = inpDict[c][0][1]
    for n in list(userObjectDict.keys()):
        if n != username:  # 跳过自身角色,避免重复更新
            userObjectDict[n].displayCharacter()

# 主线程处理队列消息
def process_queue():
    while not msg_queue.empty():
        user_data = msg_queue.get()
        loadUsers(user_data)
    screen.ontimer(process_queue, 50)  # 每50ms检查一次队列

def send_message(client_socket):
    while True:
        msg = input()
        message = json.dumps(["msg",msg])
        client_socket.sendall(message.encode())

def receive_messages(client_socket):
    global userObjectDict, username
    print('receive')
    while True:
        try:
            message = json.loads(client_socket.recv(1024).decode())
            if not message:
                break
            if message[0] == 'update':
                msg_queue.put(message[1])  # 将更新数据放入队列
        except ConnectionResetError:
            print("Connection to the server was reset.")
            break

userObjectDict = dict()

title('Home')
setup(width=650, height=350)
bgcolor("black")
screen = Screen()

width, height = screen.window_width(), screen.window_height()

tracer(0)

onkey(rightKey,'Right')
onkey(leftKey,'Left')
onkey(upKey,'Up')
onkey(downKey,'Down')
listen()

host = socket.gethostname()

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host,5087))

configUser()

main.displayCharacter()

receive_thread = threading.Thread(target=receive_messages, args=(client_socket,))
receive_thread.daemon = True

receive_thread.start()
process_queue()  # 启动主线程消息处理
send_message(client_socket)

screen.mainloop()

内容的提问来源于stack exchange,提问作者kyan tran

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:05:57