I know this type of question has been asked to death for ‘normal’ games, however I am struggling with relating a scrolling background to an entity component system (ECS) approach.
Generic samples here and here and here.
What I struggle with is how to take a fairly procedural view of this problem and abstract it out to an ECS. I’m trying to get a roguelike game up and running in PyGame using Esper. I do have a proof of concept up and running where I generate a large map, however I’m having issues relating how to add a camera here as an entity that other objects would relate to. I’m guessing that I need to refactor my movement code to be based around a camera, and have each separate entity update their relative position as a result.
At present, the background is being directly drawn and needs to be abstracted to an Entity in this system, but I was somewhat holding off on that until I figured out the camera. Here is a snippet of the
Renderable class that each drawn entity has:
class Renderable: def __init__(self, image, posx, posy, depth=0): self.image = image self.depth = depth self.x = posx self.y = posy self.curr_row, self.curr_col = convert_to_cells(posx, posy) self.w = image.get_width() self.h = image.get_height() self.rect = pygame.Rect(self.x,self.y,self.w,self.h)
And here are the Movement processor and Render processors:
class MovementProcessor(esper.Processor): def __init__(self, minx, maxx, miny, maxy, game_map): super().__init__() self.minx = minx self.maxx = maxx self.miny = miny self.maxy = maxy self.game_map = game_map def process(self): # This will iterate over every Entity that has BOTH of these components: for ent, (vel, rend) in self.world.get_components(Velocity, Renderable): # Check if movement is valid: newx = rend.x + int(vel.x * TILE_SIZE) newy = rend.y + int(vel.y * TILE_SIZE) new_row, new_col = convert_to_cells(newx, newy) if self.game_map.data(new_row)(new_col) > 1: # Update the Renderable Component's position by it's Velocity: rend.x = newx rend.y = newy rend.curr_row = new_row rend.curr_col = new_col class RenderProcessor(esper.Processor): def __init__(self, window, clear_color=(0, 0, 0)): super().__init__() self.window = window self.clear_color = clear_color def process(self): # Clear the window: self.window.fill(self.clear_color) # This will iterate over every Entity that has this Component, and blit it: for ent, rend in self.world.get_component(Renderable): self.window.blit(rend.image, (rend.x, rend.y)) # Flip the framebuffers pygame.display.flip()
Later on in my main loop I iterate over my tilemap (
Map class) and simply draw it to a surface, add some debugging overlays, and then assign that surface to the entity associated with the background (
# Blit background directly -- this needs to be abstracted out for row in range(len(game_map.data)): for col in range(len(game_map.data(0))): rect = pygame.Rect(col*TILE_SIZE,row*TILE_SIZE,TILE_SIZE,TILE_SIZE) # Blit a wall sprite if game_map.data(row)(col) in (0,1): bg_surface.blit(walls(game_map.data(row)(col)),rect) # Blit a floor/grass sprite elif game_map.data(row)(col) in (2,3,4,5): bg_surface.blit(floors(game_map.data(row)(col)-2),rect)
Here is the full listing: