Performance Tips¶
This guide provides detailed strategies for optimizing your game's performance with the Retro Game Engine.
Profiling and Monitoring¶
Built-in Tools¶
- Use
GameLoop.performance_metricsto monitor: - Frame rate (FPS)
- Frame time
- Update time
- Render time
- Physics time
External Tools¶
- Python profilers (cProfile, line_profiler)
- Memory profilers (memory_profiler)
- System monitoring tools
Rendering Optimization¶
Sprite Management¶
# Bad: Creating sprites individually
for i in range(100):
sprite = Sprite(sheet, SpriteConfig(x=i*32, y=100))
renderer.add_sprite(sprite)
# Good: Using sprite batching
sprite_batch = []
for i in range(100):
sprite = Sprite(sheet, SpriteConfig(x=i*32, y=100))
sprite_batch.append(sprite)
renderer.add_sprites(sprite_batch)
Z-Index Organization¶
# Define clear z-index ranges
Z_BACKGROUND = 0
Z_TERRAIN = 100
Z_OBJECTS = 200
Z_CHARACTERS = 300
Z_PARTICLES = 400
Z_UI = 500
View Culling¶
def is_visible(sprite: Sprite, camera: Rect) -> bool:
sprite_rect = sprite.get_bounds()
return camera.colliderect(sprite_rect)
# Only render visible sprites
visible_sprites = [s for s in sprites if is_visible(s, camera_rect)]
renderer.render(visible_sprites)
Memory Management¶
Object Pooling¶
class BulletPool:
def __init__(self, size: int):
self.bullets = [Bullet() for _ in range(size)]
self.active = set()
def get_bullet(self) -> Optional[Bullet]:
for bullet in self.bullets:
if bullet not in self.active:
self.active.add(bullet)
return bullet
return None
def return_bullet(self, bullet: Bullet):
self.active.remove(bullet)
bullet.reset()
Asset Management¶
class AssetManager:
def __init__(self):
self.cache = {}
self.reference_counts = {}
def load_asset(self, path: str) -> Any:
if path not in self.cache:
self.cache[path] = load_file(path)
self.reference_counts[path] = 0
self.reference_counts[path] += 1
return self.cache[path]
def unload_asset(self, path: str):
self.reference_counts[path] -= 1
if self.reference_counts[path] <= 0:
del self.cache[path]
del self.reference_counts[path]
Physics Optimization¶
Spatial Partitioning¶
class Grid:
def __init__(self, cell_size: int):
self.cell_size = cell_size
self.cells = {}
def add_object(self, obj: GameObject):
cell_x = obj.x // self.cell_size
cell_y = obj.y // self.cell_size
if (cell_x, cell_y) not in self.cells:
self.cells[(cell_x, cell_y)] = set()
self.cells[(cell_x, cell_y)].add(obj)
def get_nearby(self, obj: GameObject) -> Set[GameObject]:
cell_x = obj.x // self.cell_size
cell_y = obj.y // self.cell_size
nearby = set()
for dx in (-1, 0, 1):
for dy in (-1, 0, 1):
cell = (cell_x + dx, cell_y + dy)
if cell in self.cells:
nearby.update(self.cells[cell])
return nearby
Collision Optimization¶
def check_collisions(objects: List[GameObject], grid: Grid):
for obj in objects:
# Only check nearby objects
nearby = grid.get_nearby(obj)
for other in nearby:
if obj != other and obj.collides_with(other):
handle_collision(obj, other)
Input Handling¶
Event Buffering¶
class InputBuffer:
def __init__(self, buffer_time: float = 0.1):
self.buffer_time = buffer_time
self.buffer = []
def add_input(self, action: str, timestamp: float):
self.buffer.append((action, timestamp))
self.cleanup_old_inputs(timestamp)
def cleanup_old_inputs(self, current_time: float):
self.buffer = [(a, t) for a, t in self.buffer
if current_time - t <= self.buffer_time]
def has_input(self, action: str, current_time: float) -> bool:
return any(a == action and current_time - t <= self.buffer_time
for a, t in self.buffer)
Audio Optimization¶
Sound Pooling¶
class SoundPool:
def __init__(self, sound_path: str, pool_size: int = 4):
self.sounds = [load_sound(sound_path) for _ in range(pool_size)]
self.current = 0
def play(self):
sound = self.sounds[self.current]
self.current = (self.current + 1) % len(self.sounds)
sound.play()
Scene Management¶
Loading Optimization¶
class Scene:
def __init__(self):
self.loaded = False
self.assets = set()
async def load(self):
if not self.loaded:
await self.preload_assets()
self.setup_scene()
self.loaded = True
def unload(self):
if self.loaded:
self.cleanup_assets()
self.loaded = False
Debugging Performance¶
Performance Logging¶
class PerformanceLogger:
def __init__(self):
self.timings = {}
self.samples = 100
@contextmanager
def measure(self, name: str):
start = time.perf_counter()
yield
end = time.perf_counter()
if name not in self.timings:
self.timings[name] = []
self.timings[name].append(end - start)
if len(self.timings[name]) > self.samples:
self.timings[name].pop(0)
def get_average(self, name: str) -> float:
return sum(self.timings[name]) / len(self.timings[name])
Common Performance Issues¶
Memory Leaks¶
- Event listeners not being removed
- Circular references between objects
- Cached assets not being cleared
- Texture memory not being freed
CPU Bottlenecks¶
- Too many active physics objects
- Inefficient collision detection
- Complex particle systems
- Unoptimized rendering loops
GPU Bottlenecks¶
- Too many draw calls
- Large textures
- Complex shaders
- Excessive screen effects