v1.1: Performance optimizations and bug fixes

Major improvements:
- NumPy-based collision system supporting 200+ units (~3ms/frame)
- Spatial hashing with vectorized distance calculations
- 4-pass game loop ensuring correct collision timing
- Blood overlay system with pre-generated stain pool
- Cached render positions and viewport bounds
- Spawn protection preventing rats spawning on weapons

Bug fixes:
- Fixed bombs not killing rats (collision system timing)
- Fixed gas not affecting rats (collision system timing)
- Fixed rats spawning on weapons (added has_weapon_at check)
- Fixed AttributeError with Gas collisions (added isinstance check)
- Fixed blood stain transparency (RGBA + SDL_BLENDMODE_BLEND)
- Reduced point lifetime from 200 to 90 frames (~1.5s)
- Blood layer now clears on game restart

Technical changes:
- Added engine/collision_system.py with CollisionLayer enum
- Updated all units to use collision layers
- Pre-allocate NumPy arrays with capacity management
- Hybrid collision approach (<10 simple, ≥10 vectorized)
- Python 3.13 compatibility
This commit is contained in:
John Doe
2025-10-24 20:04:26 +02:00
parent 12836dd2d2
commit b4224ed3a1
12 changed files with 619 additions and 135 deletions
+55 -29
View File
@@ -31,6 +31,9 @@ class GameWindow:
self.max_h_offset = self.target_size[1] - self.height
self.scale = self.target_size[1] // self.cell_size
# Cached viewport bounds for fast visibility checks
self._update_viewport_bounds()
print(f"Screen size: {self.width}x{self.height}")
# SDL2 initialization
@@ -305,6 +308,13 @@ class GameWindow:
# VIEW & NAVIGATION
# ======================
def _update_viewport_bounds(self):
"""Update cached viewport bounds for fast visibility checks"""
self.visible_x_min = -self.w_offset - self.cell_size
self.visible_x_max = self.width - self.w_offset
self.visible_y_min = -self.h_offset - self.cell_size
self.visible_y_max = self.height - self.h_offset
def scroll_view(self, pointer):
"""Adjust the view offset based on pointer coordinates"""
x, y = pointer
@@ -323,11 +333,14 @@ class GameWindow:
self.w_offset = x
self.h_offset = y
# Update cached bounds when viewport changes
self._update_viewport_bounds()
def is_in_visible_area(self, x, y):
"""Check if coordinates are within the visible area"""
return (-self.w_offset - self.cell_size <= x <= self.width - self.w_offset and
-self.h_offset - self.cell_size <= y <= self.height - self.h_offset)
"""Check if coordinates are within the visible area (optimized with cached bounds)"""
return (self.visible_x_min <= x <= self.visible_x_max and
self.visible_y_min <= y <= self.visible_y_max)
def get_view_center(self):
"""Get the center coordinates of the current view"""
@@ -531,10 +544,10 @@ class GameWindow:
# ======================
def generate_blood_surface(self):
"""Generate a dynamic blood splatter surface using SDL2"""
"""Generate a dynamic blood splatter surface using SDL2 with transparency"""
size = self.cell_size
# Create RGBA surface for blood splatter
# Create RGBA surface for blood splatter with proper alpha channel
blood_surface = sdl2.SDL_CreateRGBSurface(
0, size, size, 32,
0x000000FF, # R mask
@@ -545,6 +558,13 @@ class GameWindow:
if not blood_surface:
return None
# Enable alpha blending for the surface
sdl2.SDL_SetSurfaceBlendMode(blood_surface, sdl2.SDL_BLENDMODE_BLEND)
# Fill with transparent color first
sdl2.SDL_FillRect(blood_surface, None,
sdl2.SDL_MapRGBA(blood_surface.contents.format, 0, 0, 0, 0))
# Lock surface for pixel manipulation
sdl2.SDL_LockSurface(blood_surface)
@@ -553,13 +573,13 @@ class GameWindow:
pixels = cast(blood_surface.contents.pixels, POINTER(c_uint32))
pitch = blood_surface.contents.pitch // 4 # Convert pitch to pixels (32-bit)
# Blood color variations (ABGR format)
# Blood color variations (RGBA format for proper alpha)
blood_colors = [
0xFF00008B, # Dark red
0xFF002222, # Brick red
0xFF003C14, # Crimson
0xFF0000FF, # Pure red
0xFF000080, # Reddish brown
(139, 0, 0), # Dark red
(178, 34, 34), # Firebrick
(160, 0, 0), # Dark red
(200, 0, 0), # Red
(128, 0, 0), # Maroon
]
# Generate splatter with diffusion algorithm
@@ -581,13 +601,14 @@ class GameWindow:
if random.random() < probability * noise:
# Choose random blood color
color = random.choice(blood_colors)
r, g, b = random.choice(blood_colors)
# Add alpha variation for transparency
alpha = int(255 * probability * random.uniform(0.6, 1.0))
color = (color & 0x00FFFFFF) | (alpha << 24)
pixels[y * pitch + x] = color
# Pack RGBA into uint32 (ABGR format for SDL)
pixel_color = (alpha << 24) | (b << 16) | (g << 8) | r
pixels[y * pitch + x] = pixel_color
else:
# Transparent pixel
pixels[y * pitch + x] = 0x00000000
@@ -607,10 +628,12 @@ class GameWindow:
nx, ny = drop_x + dx, drop_y + dy
if 0 <= nx < size and 0 <= ny < size:
if random.random() < 0.6:
color = random.choice(blood_colors[:3]) # Darker colors for drops
r, g, b = random.choice(blood_colors[:3]) # Darker colors for drops
alpha = random.randint(100, 200)
color = (color & 0x00FFFFFF) | (alpha << 24)
pixels[ny * pitch + nx] = color
# Pack RGBA into uint32 (ABGR format for SDL)
pixel_color = (alpha << 24) | (b << 16) | (g << 8) | r
pixels[ny * pitch + nx] = pixel_color
# Unlock surface
sdl2.SDL_UnlockSurface(blood_surface)
@@ -618,21 +641,24 @@ class GameWindow:
return blood_surface
def draw_blood_surface(self, blood_surface, position):
"""Convert blood surface to texture and return it"""
# Create temporary surface for blood texture
temp_surface = sdl2.SDL_CreateRGBSurface(0, self.cell_size, self.cell_size, 32, 0, 0, 0, 0)
if temp_surface is None:
"""Convert blood surface to texture with proper alpha blending"""
# Create texture directly from renderer
texture_ptr = sdl2.SDL_CreateTextureFromSurface(self.renderer.renderer, blood_surface)
if texture_ptr:
# Enable alpha blending
sdl2.SDL_SetTextureBlendMode(texture_ptr, sdl2.SDL_BLENDMODE_BLEND)
# Wrap in sprite for compatibility
sprite = sdl2.ext.TextureSprite(texture_ptr)
# Free the surface
sdl2.SDL_FreeSurface(blood_surface)
return None
return sprite
# Copy blood surface to temporary surface
sdl2.SDL_BlitSurface(blood_surface, None, temp_surface, None)
sdl2.SDL_FreeSurface(blood_surface)
# Create texture from temporary surface
texture = self.factory.from_surface(temp_surface)
sdl2.SDL_FreeSurface(temp_surface)
return texture
return None
def combine_blood_surfaces(self, existing_surface, new_surface):
"""Combine two blood surfaces by blending them together"""