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

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())
解决方案

核心问题分析

  1. 同步无限循环阻塞事件循环:在异步的main函数中,点击按钮后执行while True: jordan.main(),这是完全阻塞的同步循环,会直接卡死Web环境下的asyncio事件循环——pygbag的WebAssembly版本依赖asyncio调度浏览器事件和渲染流程,阻塞循环会导致整个程序无响应。
  2. 资源重复加载:每次调用jordan.main()都重新加载basketball.png,会造成资源泄漏和性能损耗。
  3. 事件处理冲突:菜单和游戏的事件逻辑分离,容易导致事件队列处理不及时。

修复后的代码

# 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())

修复要点

  1. 状态机切换逻辑:用game_state变量管理菜单/游戏状态,避免嵌套阻塞循环,适配asyncio异步模型。
  2. 统一事件处理:所有事件在主async循环中集中处理,避免菜单与游戏的事件逻辑冲突。
  3. 预加载资源:将篮球图片的加载移至Player初始化阶段,避免重复加载造成的资源浪费。
  4. 拆分游戏循环:把Player的main方法拆分为update,仅负责单帧逻辑更新,由主async循环驱动执行。

修改后,pygbag打包的Web版本将不再出现冻结问题,所有逻辑都在asyncio事件循环中调度,不会阻塞主线程。

内容的提问来源于stack exchange,提问作者Adri M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:29:53