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 (bg_entity):
# 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:
RenderProcessoriterates over all entities and blits them. Could this be as simple as tracking the current camera viewport and checking to see if each entity'sxandyare within that view? \$\endgroup\$