I'm making my first isometric game in Python using Pygame. It's a 2D tile-based dungeon crawler, but I want to create a nice lighting system. I've made a random level generator that creates the rooms and joins them with corridors. My current lighting method uses a tile-based shadow-casting algorithm to determine line-of-sight from the player. Each tile has an alpha mask with the alpha value determined by the algorithm.
I want something that looks a bit more natural, and instead of the lighting moving 1 tile at a time as the player crosses the tile boundary, something that can move per-pixel if that's indeed possible? The player walks smoothly around, but my lighting "jumps" by tile.
Here is some of my code to show how the shadow-casting works:
# Set multipliers for transforming coords to octants. MULTI = [[1, 0, 0, -1, -1, 0, 0, 1], [0, 1, -1, 0, 0, -1, 1, 0], [0, 1, 1, 0, 0, -1, -1, 0], [1, 0, 0, 1, -1, 0, 0, -1]] OCTANTS = [[1, 0, 0, 1], [-1, 0, 0, 1], [0, -1, 1, 0], [0, -1, -1, 0], [-1, 0, 0, -1], [1, 0, 0, -1], [0, 1, -1, 0], [0, 1, 1, 0]] def render(self): screen.surface.fill(BLACK) # Calculate the radius around the player. x, y, r = player.x, player.y, player.light_radius x1 = 1 if x - r < 1 else x - r y1 = 1 if y - r < 1 else y - r x2 = self.width - 1 if x + r > self.width - 1 else x + r y2 = self.height - 1 if y + r > self.height - 1 else y + r # Set the alpha level of the tiles in range. for row in range(y1, y2 + 1): for col in range(x1, x2 + 1): if self.map[row][col].is_seen: self.map[row][col].alpha_layer.set_alpha(ALPHA_SEEN) else: self.map[row][col].alpha_layer.set_alpha(ALPHA_UNSEEN) self.map[player.y][player.x].alpha_layer.set_alpha(0) # Apply shadow casting to the area. for octant in range(8): self.shadow_cast(1, 1.0, 0.0, self.MULTI[octant], self.MULTI[octant], self.MULTI[octant], self.MULTI[octant]) # Draw all the tiles in the area. for row in range(y1, y2 + 1): for col in range(x1, x2 + 1): tile = self.map[row][col] if tile.name != 'void': x, y = convert_to_iso(tile.x, tile.y) if tile.name == 'wall_tile': y -= TILE_H screen.surface.blit(tile.image, (x, y)) screen.surface.blit(tile.alpha_layer, (x, y)) def shadow_cast(self, row, start, end, xx, xy, yx, yy): if start < end: return new_start = 0 radius2 = (player.light_radius + 1) ** 2 for depth in range(row, player.light_radius + 1): dx, dy = -depth - 1, -depth blocked = False while dx <= 0: dx += 1 cell_x = player.x + dx * xx + dy * xy cell_y = player.y + dx * yx + dy * yy l_slope = (dx - 0.5) / (dy + 0.5) r_slope = (dx + 0.5) / (dy - 0.5) if start < r_slope: continue elif end > l_slope: break else: if dx * dx + dy * dy < radius2: self.set_visible(cell_x, cell_y, depth) if blocked: if self.blocked(cell_x, cell_y): new_start = r_slope continue else: blocked = False start = new_start else: if (self.blocked(cell_x, cell_y) and depth < player.light_radius): blocked = True self.shadow_cast( depth + 1, start, l_slope, xx, xy, yx, yy) new_start = r_slope if blocked: break def set_visible(self, x, y, d): if 0 <= x < self.width and 0 <= y < self.height: self.map[y][x].is_seen = True self.map[y][x].alpha_layer.set_alpha( int(ALPHA_SEEN / (player.light_radius + 1)) * d) def blocked(self, x, y): return (x < 0 or y < 0 or x >= self.width or y >= self.height or self.map[y][x].is_solid)
I haven't added the player in yet, but he is currently the source of the light at the centre. I really want to do something with the lighting that looks a lot better. I don't really want to get into OpenGL or anything like that, I'm currently using Python 3.10, Pygame 2.1.2 and Pycharm Community Edition as my IDE.