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)预渲染背景优化绘制性能:当前每帧都重新绘制所有瓦片,开销较大。可预先将整个背景渲染到离屏表面,后续仅需绘制该表面:
- 在
main函数加载资源后创建背景表面:# 加载资源后添加 background_surface = pygame.Surface(SCREEN_SIZE) draw_background(background_surface, assets) - 修改
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
相关产品推荐
相关产品推荐

