Python Turtle多线程异常与多人游戏角色同步问题求助
Python Turtle多人游戏核心问题解决思路与代码修改
一、线程异常问题(RuntimeError: main thread is not in main loop)
问题原因
Turtle库基于Tkinter,而Tkinter的GUI操作必须在主线程执行。你的receive_messages线程在子线程中直接调用displayCharacter(),触发了跨线程操作GUI的限制,导致报错。
解决思路
使用线程安全的队列实现解耦:子线程只负责接收服务器消息并放入队列,主线程通过turtle.ontimer()定期从队列中取出消息并处理GUI更新,彻底避免跨线程操作。
代码修改要点
- 导入
queue模块,创建全局消息队列:
import queue msg_queue = queue.Queue()
- 修改
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
- 添加主线程队列处理函数,定时检查并处理队列消息:
def process_queue(): while not msg_queue.empty(): user_data = msg_queue.get() loadUsers(user_data) screen.ontimer(process_queue, 50) # 每50ms检查一次队列
- 在启动线程后,启动队列处理任务:
receive_thread.start() process_queue() # 启动主线程消息处理 send_message(client_socket)
二、多人角色移动同步问题
问题原因
- 客户端收到服务器更新消息时,未同步更新已有角色的
x/y坐标属性,直接调用displayCharacter导致位置无变化; - 若服务器未正确收集并广播所有玩家的最新位置,也会导致同步失效(需服务器端配合调整,以下先解决客户端逻辑)。
解决思路
客户端收到更新数据时,先更新对应角色的坐标属性,再调用显示方法;同时跳过自身角色的重复更新,避免本地操作与服务器消息冲突。
代码修改要点
更新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
相关产品推荐
相关产品推荐

