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

Pygame版青蛙过河克隆游戏卡顿问题排查求助

问题

我用Pygame制作青蛙过河(Frogger)克隆游戏,已完成自制像素美术、瓦片地图及若干精灵实现,但汽车精灵移动时出现严重抖动、卡顿。项目是为11年级学生做的过程式/函数式编程示例,未使用面向对象编程(OOP),也是自我挑战的尝试。

已尝试实现增量时间(delta time)、确保所有资源用convert_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()

解决方案

  • 修复delta time计算逻辑:当前dt仅在初始化时计算一次,未在主循环中实时更新,导致汽车移动的时间步长固定,无法适配实际帧率波动。需将dt = clock.tick(FPS) / 1000移至主循环内部,每次循环计算最新增量时间:

    def main():
        pygame.init()
        screen = pygame.display.set_mode(SCREEN_SIZE)
        clock = pygame.time.Clock()
    
        assets = load_assets()
        state = get_initial_state()
    
        while state["is_running"]:
            dt = clock.tick(FPS) / 1000  # 移至循环内,实时更新dt
            for event in pygame.event.get():
                state = update_game_state(state, event, dt)
    
            draw_game(screen, assets, state)
    
  • 预渲染背景优化绘制性能:当前每帧都重新绘制所有瓦片,开销较大。可预先将整个背景渲染到离屏表面,后续仅需绘制该表面:

    1. 在main函数加载资源后创建背景表面:
      # 加载资源后添加
      background_surface = pygame.Surface(SCREEN_SIZE)
      draw_background(background_surface, assets)
      
    2. 修改draw_game函数,替换原背景绘制逻辑:
      def draw_game(screen, assets, state, background_surface):
          screen.fill(BACKGROUND_COLOR)
          screen.blit(background_surface, (0, 0))  # 绘制预渲染背景
          # 汽车、青蛙绘制代码保持不变
      
      同时在主循环调用draw_game时传入background_surface。
  • 验证帧率稳定性:在主循环中添加帧率打印,确认实际帧率是否稳定在目标值:

    print(f"FPS: {clock.get_fps():.2f}")  # 添加在draw_game调用后
    
  • 调整汽车速度参数:当前汽车速度设为10,结合dt(约0.016秒)每帧移动0.16个格子,数值精度问题可能导致抖动。可适当降低速度(如设为2),或确保移动像素数为整数(Pygame支持浮点坐标,但整数坐标可减少渲染抖动)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 14:32:26