同时运行2个Python窗口

3
我正在创建一个游戏应用程序,它有一个菜单窗口和一个独立的游戏窗口。我试图让Tkinter窗口充当菜单,而pygame窗口则用于游戏。但是,如果游戏正在运行,我无法使用菜单(甚至无法关闭它)。如果我尝试关闭,就会收到致命错误消息:
Fatal Python error: PyEval_RestoreThread: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL)
Python runtime state: initialized

Current thread 0x00002afc (most recent call first):
  File "c:\Users\James\Documents\6th form\comp sci\NEA- A Level\code\gametst.py", line 12 in main
  File "c:\Users\James\Documents\6th form\comp sci\NEA- A Level\code\Menu.py", line 15 in chess
  File "C:\Users\James\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1884 in __call__
  File "C:\Users\James\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1421 in mainloop
  File "c:\Users\James\Documents\6th form\comp sci\NEA- A Level\code\Menu.py", line 22 in <module>

这是最小的可重现代码:

菜单:

from tkinter import *
import game

WIN = Tk()
WIN.geometry("200x200")

def f():
    game.main()

button = Button(WIN, text="button", command=f)
button.pack()

WIN.mainloop()

游戏:

def main():
    import pygame
    pygame.init()

    WIN = pygame.display.set_mode((200, 200))

    run = 1
    while run:
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = 0
                pygame.quit()

有没有方法可以让我同时使用菜单和游戏?谢谢任何帮助。

如果您知道更好的将游戏导入菜单的方法,那就太好了,但这并不是特别困扰我。


2
我认为如果你尝试在Pygame中构建整个UI,你会得到更好的结果。 - Karl Knechtel
或者在单独的线程中运行pygame部分也应该足够。 - Matiiss
你编译代码了吗?我认为如果你将它们都构建为.exe文件,然后运行,就没问题了。 - Sırrı Kırımlıoğlu
如果他只运行main(),就不会有菜单冻结的问题。在我看来,main()while循环正在阻塞WIN.mainloop()。实际上,这可能不太明显,但他可以尝试使用after()循环来运行pygame,对吧? - Matiiss
@Matiiss 即使 while 循环阻塞了 tkinter,这也无法解释错误信息。这就是为什么我问是否在没有 tkinter 的情况下运行 main() 会产生相同的错误。此外,从回溯中可以看出,在一个 c 模块(很可能是 pygame)中出现了问题,并且它是从 main() 调用的。 - TheLizzard
显示剩余2条评论
2个回答

4
你可以在一个线程中运行pygame的部分:

game.py

import pygame

def main():
    pygame.init()

    WIN = pygame.display.set_mode((200, 200))
    run = 1
    while run:
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = 0
                pygame.quit()
    print("pygame done")

def terminate():
    if pygame.get_init():
        pygame.event.post(pygame.event.Event(pygame.QUIT))

请注意,我已经添加了terminate()函数来停止pygame循环。

menu.py

from tkinter import *
import game

WIN = Tk()
WIN.geometry("200x200")

def f():
    import threading
    # execute the pygame stuff in a thread
    threading.Thread(target=game.main).start()

button = Button(WIN, text="button", command=f)
button.pack()

WIN.mainloop()
game.terminate()  # terminate pygame loop

这太棒了,谢谢!但是你知道是否有一种方法可以检查游戏窗口是否从菜单运行吗?这是因为当你在游戏运行时按下按钮它会停止响应。我尝试使用终止函数,但它仍然停止响应。 - James.L
没事了,我使用了 is_alive() 函数。感谢你的帮助。 - James.L

2

您可以尝试在另一个进程中启动它。这样,pygame 就不会知道 tkinter 已经启动了它。您可以尝试以下代码:

# menu.py
from tkinter import *
from subprocess import Popen
from sys import stdin, stdout, stderr

window = Tk()
window.geometry("200x200")

def f():
    proc = Popen("python game.py", stdin=stdin, stdout=stdout, stderr=stderr, shell=True)

button = Button(window, text="button", command=f)
button.pack()

window.mainloop()

并且:

# game.py
# Please note this isn't in a function.
import pygame
pygame.init()

WIN = pygame.display.set_mode((200, 200))

run = True
while run:
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
            pygame.quit()

问题在于如果您在IDLE中启动它,它将无法正常工作。为解决此问题,请移除 stdin=stdin, stdout=stdout, stderr=stderr 并尝试再次运行。

使用这种方法,您将无法在 menu.pygame.py 之间发送任何数据。您唯一能做的是调用proc_still_alive = (proc.poll() == None) 来查看进程是否仍在运行。为此,您需要将 proc 变量设置为全局变量。


请注意,当进程仍在运行时,.poll() 返回 None。因此应使用 has_proc_ended = proc.poll() is not None - acw1668
@acw1668 你说得对。谢谢建议。下次随时可以编辑它 :D。我想我的大脑死了。 - TheLizzard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接