Pygame性能故障排除

编程语言 2026-07-10

我一直在用PyGame做这个Frogger克隆游戏。

到现在为止,我已经做了自己的像素艺术,实现了一个瓷砖地图以及几个精灵。问题在于性能。当我的汽车精灵在屏幕上移动时,画面非常抖动、卡顿,整体上也经常出现滞后!

如果你想查看资源,可以在这里找到我的Github链接: https://github.com/rf19rr/OpenFrogie

通常我会使用面向对象的概念,但这次是为了给11年级学生演示过程式/函数式编程实践,符合他们的课程要求。用这种方式挑战自己也挺有趣的。

我已经尝试实现delta time,确保所有内容在alpha等方面的处理都是正确的。
还不确定还有哪些原因会导致变慢。我是不是把背景绘制得太多,导致处理队列超载?

请告诉我你的想法,先行感谢!

代码在这里:

import pygame
import sys
import json

# --- Configuration ---
TILE_SIZE = 32
GRID_WIDTH = 12
GRID_HEIGHT = 16
SCREEN_SIZE = (GRID_WIDTH * TILE_SIZE, GRID_HEIGHT * TILE_SIZE)
FPS = 60
BACKGROUND_COLOR = (238, 187, 162)

# --- Resource Management ---
def load_assets():
    """Modified: Maps IDs to specific filenames and loads them."""
    # Define your mapping here
    # ID : "filename.png"
    tile_manifest = {
        0: "dirt-center.png",
        1: "grass-dirt-top.png",
        2: "grass-mid-center.png",
        3: "grass-dirt-bottom.png",
        4: "grass-water-top.png",
        5: "water-transition-bottom.png",
        6: "water-center.png",
        7: "water-transition-top.png",
        8: "grass-water-bottom.png",
        9: "alcove-bottom-left.png",
        10: "alcove-bottom-right.png",
        11: "alcove-top-left.png",
        12: "alcove-top-right.png",
    }

    try:
        # 1. Load the JSON layout
        with open("level.json", "r") as f:
            map_data = json.load(f)

        # 2. Load and scale images based on the manifest
        tiles = {}
        for tile_id, filename in tile_manifest.items():
            path = f"assets/{filename}"
            img = pygame.image.load(path).convert_alpha()
            tiles[tile_id] = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE))

        # load in our frog sprite
        frog_img = pygame.image.load("assets/frog.png").convert_alpha()

        # load in our car sprite
        car_img = pygame.image.load("assets/car.png").convert_alpha()

        return {
            "tiles": tiles,
            "map_layout": map_data["map"],
            "frog": pygame.transform.scale(frog_img, (TILE_SIZE, TILE_SIZE)),
            "car_sprite": pygame.transform.scale(car_img, (TILE_SIZE, TILE_SIZE)),
        }
    except (pygame.error, FileNotFoundError, json.JSONDecodeError) as e:
        print(f"Resource Error: {e}")
        sys.exit()


# --- State Management ---
def get_initial_state():
    """Returns the initial state of the game as a dictionary."""
    return {
        "frog_pos": (GRID_WIDTH // 2, GRID_HEIGHT - 1),
        "is_running": True,
        "cars": [
            {"x": -1.0, "y": 12.0, "speed": 10},
            # {"x": -1.0, "y": 5.0, "speed": 0.20},
            # {"x": -1.0, "y": 5.0, "speed": 0.20}
        ]
    }

def update_cars(cars, dt):
    """Processes all cars and returns a new list of car states."""
    updated_cars = []
    for car in cars:
        new_car = car.copy()
        new_car["x"] += new_car["speed"] * dt

        # Reset if it exits the screen (looping)
        if new_car["x"] > GRID_WIDTH:
            new_car["x"] = -1.0

        updated_cars.append(new_car)
    return updated_cars

def update_game_state(state, event, dt):
    """Pure function: calculates a new state based on inputs."""
    new_state = state.copy()
    new_state["cars"] = update_cars(new_state["cars"], dt)

    if event.type == pygame.QUIT:
        new_state["is_running"] = False

    elif event.type == pygame.KEYDOWN:
        x, y = new_state["frog_pos"]
        if event.key == pygame.K_UP and y > 0:
            new_state["frog_pos"] = (x, y - 1)
        elif event.key == pygame.K_DOWN and y < GRID_HEIGHT - 1:
            new_state["frog_pos"] = (x, y + 1)
        elif event.key == pygame.K_LEFT and x > 0:
            new_state["frog_pos"] = (x - 1, y)
        elif event.key == pygame.K_RIGHT and x < GRID_WIDTH - 1:
            new_state["frog_pos"] = (x + 1, y)

    return new_state

def draw_background(screen, assets):
    """Refactored: Renders tiles with a fallback for missing IDs."""
    tile_images = assets["tiles"]
    layout = assets["map_layout"]

    for row_idx, row in enumerate(layout):
        for col_idx, tile_id in enumerate(row):
            # Use .get() to avoid crashing if an ID is missing
            tile_surface = tile_images.get(tile_id)

            if tile_surface:
                screen.blit(tile_surface, (col_idx * TILE_SIZE, row_idx * TILE_SIZE))
            else:
                # Optional: Draw a bright magenta rectangle for missing assets
                pygame.draw.rect(screen, (255, 0, 255),
                                 (col_idx * TILE_SIZE, row_idx * TILE_SIZE, TILE_SIZE, TILE_SIZE))


def draw_game(screen, assets, state):
    """
    1. Clear the screen (optional, but good practice).
    2. Draw the background (this effectively 'erases' the previous frame's player).
    3. Draw the sprites.
    4. Update the display.
    """
    # 1 Clear the screen surface (Fill with black)
    screen.fill(BACKGROUND_COLOR)

    # 2 Redraw the entire background from the map data
    draw_background(screen, assets)

    # Draw all cars
    for car in state["cars"]:
        screen.blit(assets["car_sprite"], (car["x"] * TILE_SIZE, car["y"] * TILE_SIZE))

    # Frog is next
    frog_surface = assets["frog"]

    # calculate the frog cordinates based on state, screen size, and tile size.
    frog_x, frog_y = state["frog_pos"]
    screen_x = frog_x * TILE_SIZE
    screen_y = frog_y * TILE_SIZE

    # Draw the frog
    screen.blit(frog_surface, (screen_x, screen_y))

    pygame.display.flip()

# --- Main Driver ---
def main():
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE)
    clock = pygame.time.Clock()
    dt = clock.tick(FPS) / 1000 # seconds since last frame

    assets = load_assets()
    state = get_initial_state()

    while state["is_running"]:
        for event in pygame.event.get():
            state = update_game_state(state, event, dt)

        draw_game(screen, assets, state)
        clock.tick(FPS)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()

解决方案

目前你调用的是:

        for event in pygame.event.get():
            state = update_game_state(state, event, dt)

因此,如果没有事件(例如移动鼠标)就不会更新游戏状态。然而,update_game_state() 处理的是所有状态,而不仅仅是处理用户输入。所以没有事件时,你就不会调用:

    new_state["cars"] = update_cars(new_state["cars"], dt)

因此汽车的状态会根据你移动鼠标的程度而出现冻结或卡顿。

备选方案

啊哈!有好朋友对我提出了很多指出的地方,他在PyGame的经验比我多。

JosnSG也参与进来给出建议。

  1. Delta Time的计算是在我的主循环之外进行的,这样是不对的。我把它移回到while循环内,确保在每一帧都能计算到。
  2. 我的update_cars函数确实是在事件循环内计算的,这也意味着它们的计算不正确。这是问题的另一部分。我把这个函数解耦,现在它们在绘制前每帧计算一次。
  3. 我学会了背景缓存的原理,现在所有地图瓷砖在启动时就渲染到一个单一的world_surface上,这样减少了每帧大约190次以上的blit操作。因此在性能上节省了很多,特别是在Frogger游戏中背景瓷砖是静态的情况下。

全部改动完成后,我的新代码文件看起来是这样的:

import pygame
import sys
import json

# --- Configuration ---
TILE_SIZE = 32
GRID_WIDTH = 12
GRID_HEIGHT = 16
SCREEN_SIZE = (GRID_WIDTH * TILE_SIZE, GRID_HEIGHT * TILE_SIZE)
FPS = 60
BACKGROUND_COLOR = (238, 187, 162)

def load_assets():
    tile_manifest = {
        0: "dirt-center.png",
        1: "grass-dirt-top.png",
        2: "grass-mid-center.png",
        3: "grass-dirt-bottom.png",
        4: "grass-water-top.png",
        5: "water-transition-bottom.png",
        6: "water-center.png",
        7: "water-transition-top.png",
        8: "grass-water-bottom.png",
        9: "alcove-bottom-left.png",
        10: "alcove-bottom-right.png",
        11: "alcove-top-left.png",
        12: "alcove-top-right.png",
    }
    try:
        with open("level.json", "r") as f:
            map_data = json.load(f)
        tiles = {}
        for tile_id, filename in tile_manifest.items():
            path = f"assets/{filename}"
            img = pygame.image.load(path).convert_alpha()
            tiles[tile_id] = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE))

        frog_img = pygame.image.load("assets/frog.png").convert_alpha()
        car_img = pygame.image.load("assets/car.png").convert_alpha()

        return {
            "tiles": tiles,
            "map_layout": map_data["map"],
            "frog": pygame.transform.scale(frog_img, (TILE_SIZE, TILE_SIZE)),
            "car_sprite": pygame.transform.scale(car_img, (TILE_SIZE, TILE_SIZE)),
        }
    except Exception as e:
        print(f"Resource Error: {e}")
        sys.exit()

def get_initial_state():
    return {
        "frog_pos": (GRID_WIDTH // 2, GRID_HEIGHT - 1),
        "is_running": True,
        "cars": [{"x": -1.0, "y": 12.0, "speed": 4.0}]
    }

def update_cars(cars, dt):
    for car in cars:
        car["x"] += car["speed"] * dt
        if car["x"] > GRID_WIDTH:
            car["x"] = -1.0
    return cars

def handle_input(state, event):
    if event.type == pygame.QUIT:
        state["is_running"] = False
    elif event.type == pygame.KEYDOWN:
        x, y = state["frog_pos"]
        if event.key == pygame.K_UP and y > 0:
            state["frog_pos"] = (x, y - 1)
        elif event.key == pygame.K_DOWN and y < GRID_HEIGHT - 1:
            state["frog_pos"] = (x, y + 1)
        elif event.key == pygame.K_LEFT and x > 0:
            state["frog_pos"] = (x - 1, y)
        elif event.key == pygame.K_RIGHT and x < GRID_WIDTH - 1:
            state["frog_pos"] = (x + 1, y)
    return state

def draw_game(screen, assets, state, world_surface):
    screen.blit(world_surface, (0, 0))
    for car in state["cars"]:
        draw_x = int(car["x"] * TILE_SIZE + 0.5)
        draw_y = int(car["y"] * TILE_SIZE + 0.5)
        screen.blit(assets["car_sprite"], (draw_x, draw_y))

    fx, fy = state["frog_pos"]
    screen.blit(assets["frog"], (fx * TILE_SIZE, fy * TILE_SIZE))
    pygame.display.flip()

def main():
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE)
    clock = pygame.time.Clock()
    assets = load_assets()
    state = get_initial_state()

    world_surface = pygame.Surface(SCREEN_SIZE)
    world_surface.fill((238, 187, 162))
    for row_idx, row in enumerate(assets["map_layout"]):
        for col_idx, tile_id in enumerate(row):
            tile_surface = assets["tiles"].get(tile_id)
            if tile_surface: world_surface.blit(tile_surface, (col_idx * TILE_SIZE, row_idx * TILE_SIZE))

    last_time = pygame.time.get_ticks()
    while state["is_running"]:
        current_time = pygame.time.get_ticks()
        dt = (current_time - last_time) / 1000.0
        last_time = current_time

        for event in pygame.event.get():
            state = handle_input(state, event)

        state["cars"] = update_cars(state["cars"], dt)
        draw_game(screen, assets, state, world_surface)
        clock.tick(FPS)

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章