pygbag打包后点击主菜单Play按钮,游戏在本地/线上冻结的原因?
问题
原本游戏运行完全正常,添加包含Play按钮的主菜单后,本地环境下所有代码运行符合预期,Play按钮可正常启动主游戏;但使用pygbag打包后,在本地主机(localhost)或GitHub Pages线上环境中,点击Play按钮后游戏立即冻结,调试页面也无法进行操作。此前无主菜单时GitHub Pages可正常运行。
相关素材:
- 篮球素材:basketball.png
- 玩家素材:bird.png(因复用旧移动代码,故命名为bird)
- 背景素材:background.png
原代码
# Import Modules for the Game import pygame import asyncio ''' # Changes what the score says depending on the player(if alive then shows score, if got eaten then says final score) if player_alive: score_text = score_font.render("Score: " + str(score), 1, (0, 0, 0)) else: score_text = score_font.render("Final Score: " + str(score), 1, (0, 0, 0)) ''' # Start the game pygame.init() game_width = 960 game_height = 540 screen = pygame.display.set_mode((game_width, game_height)) clock = pygame.time.Clock() running = True color = (255,255,255) # light shade of the button color_light = (170,170,170) # dark shade of the button color_dark = (100,100,100) # stores the width of the # screen into a variable width = screen.get_width() # stores the height of the # screen into a variable height = screen.get_height() # defining a font smallfont = pygame.font.SysFont('Corbel',35) # rendering a text written in # this font text = smallfont.render('PLAY' , True , color) class Basketball: def __init__(self, screen, running, basketball, player_x, player_y, player_facing_left): self.screen = screen self.running = running self.basketball = basketball self.player_x = player_x self.player_y = player_y self.player_facing_left = player_facing_left def main(self): if self.player_facing_left: self.screen.blit(self.basketball, (self.player_x-7, self.player_y+75)) else: self.screen.blit(self.basketball, (self.player_x+80, self.player_y+75)) class Player: def __init__(self, screen, running, background, player, player_x, player_y, player_speed, player_size, player_facing_left, player_hitbox, player_alive, isjump, jumping, Y_GRAVITY, JUMP_HEIGHT, Y_VELOCITY): self.screen = screen self.running = running self.background = background self.player = player self.player_x = player_x self.player_y = player_y self.player_speed = player_speed self.player_size = player_size self.player_facing_left = player_facing_left self.player_hitbox = player_hitbox self.player_alive = player_alive self.isjump = isjump self.jumping = jumping self.Y_GRAVITY = Y_GRAVITY self.JUMP_HEIGHT = JUMP_HEIGHT self.Y_VELOCITY = Y_VELOCITY def main(self): # Everything under 'while running' will be repeated over and over again keys = pygame.key.get_pressed() # Makes the game stop if the player clicks the X or presses esc for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: self.running = False pygame.quit() if event.type == pygame.KEYDOWN and event.key == pygame.K_w: self.jumping = True if keys[pygame.K_a]: self.player_x -= self.player_speed self.player_facing_left = True #player_y += player_speed if keys[pygame.K_LSHIFT]: if keys[pygame.K_d]: self.player_x += 3.5 #player_facing_left = False if keys[pygame.K_a]: self.player_x -= 3.5 #player_facing_left = True if keys[pygame.K_d]: self.player_x += self.player_speed self.player_facing_left = False self.screen.blit(self.background, (0, 0)) if self.jumping: self.player_y -= self.Y_VELOCITY self.Y_VELOCITY -= self.Y_GRAVITY if self.Y_VELOCITY < -self.JUMP_HEIGHT: self.jumping = False self.Y_VELOCITY = self.JUMP_HEIGHT # Spawn a new Enemy whenever enemy_timer hits 0 # Draw Player player_small = pygame.transform.scale(self.player, (int(self.player_size*.7), int(self.player_size*.7))) if self.player_facing_left: player_small = pygame.transform.flip(player_small, True, False) screen.blit(player_small, (self.player_x, self.player_y)) basketball = Basketball(self.screen, self.running, pygame.image.load('basketball.png').convert_alpha(), self.player_x, self.player_y, self.player_facing_left) basketball.main() #merged = self.player.copy() #screen.blit(score_text, (1600, 30)) # Update Screen pygame.display.update() clock.tick(50) #pygame.display.set_caption("FPS: " + str(clock.get_fps())) ''' def main_menu(running): while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: running = False screen.fill((0,0,255)) main_menu(True) ''' jordan = Player(pygame.display.set_mode((game_width, game_height)), True, pygame.image.load('background.png').convert_alpha(), pygame.image.load('bird.png').convert_alpha(), 200, 370, 3, 160, False, pygame.Rect(0, 0, int(160*1.25), 160), True, False, False, 1, 16, 16) async def main(): while True: mouse = pygame.mouse.get_pos() for ev in pygame.event.get(): if ev.type == pygame.QUIT: pygame.quit() if ev.type == pygame.KEYDOWN and ev.key == pygame.K_ESCAPE: pygame.quit() #checks if a mouse is clicked if ev.type == pygame.MOUSEBUTTONDOWN: #if the mouse is clicked on the # button the game is terminated if width/2.5 <= mouse[0] <= width/2+45 and height/2.3 <= mouse[1] <= height/2+10: while True: jordan.main() # fills the screen with a color screen.fill((190,0,50)) # stores the (x,y) coordinates into # the variable as a tuple # if mouse is hovered on a button it # changes to lighter shade if width/2.5 <= mouse[0] <= width/2+45 and height/2.3 <= mouse[1] <= height/2+10: pygame.draw.rect(screen,color_light,[width/2.5,height/2.3,140,40]) else: pygame.draw.rect(screen,color_dark,[width/2.5,height/2.3,140,40]) # superimposing the text onto our button screen.blit(text, (width/2.325,height/2.27)) # updates the frames of the game pygame.display.update() await asyncio.sleep(0) # Very important, and keep it 0 asyncio.run(main()) #jordan = Player(pygame.display.set_mode((game_width, game_height)), True, pygame.image.load('background.png').convert_alpha(), pygame.image.load('bird.png').convert_alpha(), 200, 370, 3, 160, False, pygame.Rect(0, 0, int(160*1.25), 160), True, False, False, 1, 16, 16) #asyncio.run(jordan.main())
解决方案
核心问题分析
- 同步无限循环阻塞事件循环:在异步的
main函数中,点击按钮后执行while True: jordan.main(),这是完全阻塞的同步循环,会直接卡死Web环境下的asyncio事件循环——pygbag的WebAssembly版本依赖asyncio调度浏览器事件和渲染流程,阻塞循环会导致整个程序无响应。 - 资源重复加载:每次调用
jordan.main()都重新加载basketball.png,会造成资源泄漏和性能损耗。 - 事件处理冲突:菜单和游戏的事件逻辑分离,容易导致事件队列处理不及时。
修复后的代码
# Import Modules for the Game import pygame import asyncio # Start the game pygame.init() game_width = 960 game_height = 540 screen = pygame.display.set_mode((game_width, game_height)) clock = pygame.time.Clock() color = (255,255,255) # Button colors color_light = (170,170,170) color_dark = (100,100,100) # Screen dimensions width = screen.get_width() height = screen.get_height() # Menu font and text smallfont = pygame.font.SysFont('Corbel',35) text = smallfont.render('PLAY' , True , color) class Basketball: def __init__(self, screen, basketball_img): self.screen = screen self.basketball = basketball_img self.player_x = 0 self.player_y = 0 self.player_facing_left = False def update_and_draw(self): if self.player_facing_left: self.screen.blit(self.basketball, (self.player_x-7, self.player_y+75)) else: self.screen.blit(self.basketball, (self.player_x+80, self.player_y+75)) class Player: def __init__(self, screen, background_img, player_img): self.screen = screen self.background = background_img self.player = player_img self.player_x = 200 self.player_y = 370 self.player_speed = 3 self.player_size = 160 self.player_facing_left = False self.player_hitbox = pygame.Rect(0, 0, int(160*1.25), 160) self.player_alive = True self.jumping = False self.Y_GRAVITY = 1 self.JUMP_HEIGHT = 16 self.Y_VELOCITY = self.JUMP_HEIGHT # Preload basketball resource self.basketball = Basketball(screen, pygame.image.load('basketball.png').convert_alpha()) def update(self): keys = pygame.key.get_pressed() # Movement logic if keys[pygame.K_a]: self.player_x -= self.player_speed self.player_facing_left = True if keys[pygame.K_LSHIFT]: if keys[pygame.K_d]: self.player_x += 3.5 if keys[pygame.K_a]: self.player_x -= 3.5 if keys[pygame.K_d]: self.player_x += self.player_speed self.player_facing_left = False # Jump logic if self.jumping: self.player_y -= self.Y_VELOCITY self.Y_VELOCITY -= self.Y_GRAVITY if self.Y_VELOCITY < -self.JUMP_HEIGHT: self.jumping = False self.Y_VELOCITY = self.JUMP_HEIGHT # Draw background self.screen.blit(self.background, (0, 0)) # Draw player player_small = pygame.transform.scale(self.player, (int(self.player_size*.7), int(self.player_size*.7))) if self.player_facing_left: player_small = pygame.transform.flip(player_small, True, False) self.screen.blit(player_small, (self.player_x, self.player_y)) # Update and draw basketball self.basketball.player_x = self.player_x self.basketball.player_y = self.player_y self.basketball.player_facing_left = self.player_facing_left self.basketball.update_and_draw() # Update display pygame.display.update() clock.tick(50) # Initialize game objects jordan = Player(screen, pygame.image.load('background.png').convert_alpha(), pygame.image.load('bird.png').convert_alpha()) async def main(): game_state = "menu" # menu / playing while True: mouse = pygame.mouse.get_pos() # Handle all events in one place for ev in pygame.event.get(): if ev.type == pygame.QUIT: pygame.quit() return if ev.type == pygame.KEYDOWN and ev.key == pygame.K_ESCAPE: pygame.quit() return if game_state == "menu": if ev.type == pygame.MOUSEBUTTONDOWN: # Check if play button is clicked if width/2.5 <= mouse[0] <= width/2+45 and height/2.3 <= mouse[1] <= height/2+10: game_state = "playing" elif game_state == "playing": if ev.type == pygame.KEYDOWN and ev.key == pygame.K_w: jordan.jumping = True # Render based on game state if game_state == "menu": screen.fill((190,0,50)) # Button hover effect if width/2.5 <= mouse[0] <= width/2+45 and height/2.3 <= mouse[1] <= height/2+10: pygame.draw.rect(screen, color_light, [width/2.5, height/2.3, 140, 40]) else: pygame.draw.rect(screen, color_dark, [width/2.5, height/2.3, 140, 40]) screen.blit(text, (width/2.325, height/2.27)) pygame.display.update() elif game_state == "playing": jordan.update() await asyncio.sleep(0) # Critical for async event loop scheduling asyncio.run(main())
修复要点
- 状态机切换逻辑:用
game_state变量管理菜单/游戏状态,避免嵌套阻塞循环,适配asyncio异步模型。 - 统一事件处理:所有事件在主async循环中集中处理,避免菜单与游戏的事件逻辑冲突。
- 预加载资源:将篮球图片的加载移至Player初始化阶段,避免重复加载造成的资源浪费。
- 拆分游戏循环:把Player的
main方法拆分为update,仅负责单帧逻辑更新,由主async循环驱动执行。
修改后,pygbag打包的Web版本将不再出现冻结问题,所有逻辑都在asyncio事件循环中调度,不会阻塞主线程。
内容的提问来源于stack exchange,提问作者Adri M
相关产品推荐
相关产品推荐

