Pygame性能故障排除
我一直在用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也参与进来给出建议。
- Delta Time的计算是在我的主循环之外进行的,这样是不对的。我把它移回到while循环内,确保在每一帧都能计算到。
- 我的update_cars函数确实是在事件循环内计算的,这也意味着它们的计算不正确。这是问题的另一部分。我把这个函数解耦,现在它们在绘制前每帧计算一次。
- 我学会了背景缓存的原理,现在所有地图瓷砖在启动时就渲染到一个单一的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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。