Add semi-transparent tunnel cover overlay for internal passages
- Add sdl2.create_overlay_texture() and draw_overlay_texture(alpha) for transparent full-map overlays built from sub-tile surface blits. - Add Graphics.regenerate_tunnel_cover() which builds an overlay of 4 random grass sub-tiles (20x20) for every internal tunnel cell, defined as a tunnel cell surrounded by occupied cells (wall or tunnel) on all four sides. Cells with at least one open side are handled by cave_foreground and skipped here. - Draw the tunnel cover in the game loop after top-layer units/effects but before cave_foreground and points, at 95% opacity (alpha=242) so the unit and effect passing underneath is just barely visible.
This commit is contained in:
@@ -26,6 +26,8 @@ class Graphics:
|
||||
self.blood_layer_sprites = []
|
||||
if not hasattr(self, "cave_foreground_tiles"):
|
||||
self.cave_foreground_tiles = []
|
||||
if not hasattr(self, "tunnel_cover_texture"):
|
||||
self.tunnel_cover_texture = None
|
||||
|
||||
if not getattr(self, "common_assets_loaded", False):
|
||||
print("Loading graphics assets...")
|
||||
@@ -194,6 +196,45 @@ class Graphics:
|
||||
# Draw blood layer as sprites (optimized - no background regeneration)
|
||||
self.draw_blood_layer()
|
||||
|
||||
def regenerate_tunnel_cover(self):
|
||||
"""Generate an overlay texture that covers internal tunnel passages with grass sub-tiles.
|
||||
|
||||
A tunnel cell is considered an internal passage only when it is surrounded
|
||||
by occupied cells (walls or tunnels) on all four sides. In that case there is
|
||||
no cave entrance drawn by cave_foreground, so we cover it here.
|
||||
"""
|
||||
tunnel_tiles = []
|
||||
half_cell = self.game.cell_size // 2
|
||||
|
||||
def occupied(x, y):
|
||||
return self.game.map.in_bounds(x, y) and self.game.map.get_cell(x, y) != maze.MAP_EMPTY
|
||||
|
||||
for y, row in enumerate(self.game.map.tiles):
|
||||
for x, cell in enumerate(row):
|
||||
if cell != maze.MAP_TUNNEL:
|
||||
continue
|
||||
|
||||
above = occupied(x, y - 1)
|
||||
below = occupied(x, y + 1)
|
||||
left = occupied(x - 1, y)
|
||||
right = occupied(x + 1, y)
|
||||
|
||||
if not (above and below and left and right):
|
||||
continue
|
||||
|
||||
px = x * self.game.cell_size
|
||||
py = y * self.game.cell_size
|
||||
for qx, qy, sx, sy in [
|
||||
(0, 0, 0, 0),
|
||||
(half_cell, 0, half_cell, 0),
|
||||
(0, half_cell, 0, half_cell),
|
||||
(half_cell, half_cell, half_cell, half_cell),
|
||||
]:
|
||||
grass = random.choice(self.grasses)
|
||||
tunnel_tiles.append((grass, sx, sy, half_cell, half_cell, px + qx, py + qy))
|
||||
|
||||
self.tunnel_cover_texture = self.game.render_engine.create_overlay_texture(tunnel_tiles)
|
||||
|
||||
def draw_cave_foreground(self):
|
||||
active_cave_explosions = {}
|
||||
for unit in self.game.units.values():
|
||||
@@ -218,6 +259,7 @@ class Graphics:
|
||||
"""Generate or regenerate the background texture (static - no blood stains)"""
|
||||
texture_tiles = []
|
||||
self.cave_foreground_tiles = []
|
||||
self.regenerate_tunnel_cover()
|
||||
half_cell = self.game.cell_size // 2
|
||||
|
||||
def draw(surface, x, y):
|
||||
|
||||
@@ -236,6 +236,61 @@ class GameWindow:
|
||||
sdl2.SDL_FreeSurface(bg_surface)
|
||||
return bg_texture
|
||||
|
||||
def create_overlay_texture(self, tiles: list):
|
||||
"""Create a transparent RGBA texture from sub-tile surface blits.
|
||||
|
||||
Each tile is a tuple (surface, src_x, src_y, width, height, dst_x, dst_y).
|
||||
Source surfaces are converted to RGBA so the resulting texture has an
|
||||
alpha channel and supports global alpha modulation.
|
||||
"""
|
||||
bg_surface = sdl2.SDL_CreateRGBSurface(
|
||||
0, self.width, self.height, 32,
|
||||
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000
|
||||
)
|
||||
sdl2.SDL_FillRect(bg_surface, None, 0)
|
||||
sdl2.SDL_SetSurfaceBlendMode(bg_surface, sdl2.SDL_BLENDMODE_BLEND)
|
||||
rgba_format = sdl2.SDL_PIXELFORMAT_RGBA8888
|
||||
for surface, src_x, src_y, width, height, dst_x, dst_y in tiles:
|
||||
src_fmt = sdl2.SDL_PIXELFORMAT_UNKNOWN
|
||||
try:
|
||||
src_fmt = surface.format.format
|
||||
except AttributeError:
|
||||
src_fmt = sdl2.SDL_PIXELFORMAT_UNKNOWN
|
||||
srcrect = sdl2.SDL_Rect(int(src_x), int(src_y), int(width), int(height))
|
||||
dstrect = sdl2.SDL_Rect(int(dst_x), int(dst_y), int(width), int(height))
|
||||
if src_fmt == rgba_format:
|
||||
sdl2.SDL_BlitSurface(surface, srcrect, bg_surface, dstrect)
|
||||
else:
|
||||
converted = sdl2.SDL_ConvertSurfaceFormat(surface, rgba_format, 0)
|
||||
if converted:
|
||||
sdl2.SDL_BlitSurface(converted, srcrect, bg_surface, dstrect)
|
||||
sdl2.SDL_FreeSurface(converted)
|
||||
else:
|
||||
sdl2.SDL_BlitSurface(surface, srcrect, bg_surface, dstrect)
|
||||
bg_texture = self.factory.from_surface(bg_surface)
|
||||
try:
|
||||
sdl2.SDL_SetTextureBlendMode(bg_texture.texture, sdl2.SDL_BLENDMODE_BLEND)
|
||||
except Exception:
|
||||
pass
|
||||
sdl2.SDL_FreeSurface(bg_surface)
|
||||
return bg_texture
|
||||
|
||||
def draw_overlay_texture(self, texture, alpha=255):
|
||||
"""Draw a full-map transparent overlay texture with current view offset."""
|
||||
if texture is None:
|
||||
return
|
||||
raw_texture = getattr(texture, "texture", texture)
|
||||
if alpha < 255:
|
||||
try:
|
||||
sdl2.SDL_SetTextureAlphaMod(raw_texture, int(alpha))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
sdl2.SDL_SetTextureBlendMode(raw_texture, sdl2.SDL_BLENDMODE_BLEND)
|
||||
except Exception:
|
||||
pass
|
||||
self.renderer.copy(texture, dstrect=sdl2.SDL_Rect(self.w_offset, self.h_offset, self.width, self.height))
|
||||
|
||||
def create_color_surface(self, color, width=None, height=None):
|
||||
"""Create a solid color surface matching the current cell size by default."""
|
||||
width = width or self.cell_size
|
||||
|
||||
@@ -62,6 +62,7 @@ class MiceMaze:
|
||||
self.scrolling = False
|
||||
self.sounds = {}
|
||||
self.background_texture = None
|
||||
self.tunnel_cover_texture = None
|
||||
self.combined_scores = None
|
||||
|
||||
def _setup_engine(self):
|
||||
@@ -527,12 +528,17 @@ class MiceMaze:
|
||||
if getattr(unit, "draw_on_top", False) and not getattr(unit, "draw_last", False):
|
||||
unit.draw()
|
||||
|
||||
# Draw tunnel cover overlay above units/effects but below cave foreground and points
|
||||
self.render_engine.draw_overlay_texture(self.graphics.tunnel_cover_texture, alpha=191)
|
||||
|
||||
# Draw cave foreground (tunnel entrances) above the tunnel cover
|
||||
self.graphics.draw_cave_foreground()
|
||||
|
||||
# Draw foreground/last-layer units (points) on top of everything
|
||||
for unit in sorted_units:
|
||||
if getattr(unit, "draw_last", False):
|
||||
unit.draw()
|
||||
|
||||
self.graphics.draw_cave_foreground()
|
||||
self.render_engine.draw_pointer(self.pointer[0] * self.cell_size, self.pointer[1] * self.cell_size)
|
||||
|
||||
self.render_engine.update_status(f"Mice: {self.unit_manager.count_rats()} - Points: {self.points}")
|
||||
|
||||
Reference in New Issue
Block a user