HW

a blog about code.

about

projects

contact

The Python Roguelike Tutorial Revised with a Scrolling Map

After I completed the Revised Python Tutorial and started playing with different ways to procedurally generate street-like maps, I realised that I wanted to change the way the map was rendered — I wanted a scrolling map.

Below, I have detailed the process to get there beginning from a clone of this repo, which is a fork of TStand90's repository roguelike-tutorial-2019.

  • Start by creating a file called 'camera.py' the following code:

    class Camera:
        def __init__(self, x: int, y: int, width: int, height: int, map_width: int, map_height: int):
            self.x = x
            self.y = y
            self.width = width
            self.height = height
            self.map_width = map_width
            self.map_height = map_height
    
        def apply(self, x, y):
            x = x + self.x
            y = y + self.y
            return (x, y)
    
        def update(self, entity):
            x = -entity.x + int(self.width / 2)
            y = -entity.y + int(self.height / 2)
    
            # TODO add limits to stop camera near map edges
    
            self.x, self.y = (x, y)
    
  • In 'engine.py' import our new camera class:

    from camera import Camera
    
  • Now create a new camera object and update it with the player's map coordinates. This code should be added after the game map has been created:

    camera = Camera(
        x=0,
        y=0,
        width=screen_width,
        height=screen_height,
        map_width=map_width,
        map_height=map_height,
    )
    camera.update(player)
    
  • We also want to update the camera after the player moves: ... player.move(dx, dy)

    camera.update(player)
    
    fov_recompute = True
    ...
    
  • We need to pass the camera to the render_all() function:

    render_all(entities=entities, game_map=game_map, colors=colors, camera=camera)
    
  • Import the camera class into 'render_functions.py'

  • Change 'render_functions.py' to pass the camera to the draw functions:

    def render_all(entities: List[Entity], game_map: GameMap, colors, camera: Camera):
        # Draw the map
        game_map.render(colors=colors, camera=camera)
    
        # Draw all entities in the list
        for entity in entities:
            if game_map.fov[entity.x, entity.y]:
                entity.draw(camera)
    
  • In 'entity.py' shift the coordinates at which entities are being drawn.

    def draw(self, camera):
        """
        Draw the entity to the terminal
        """
        x, y = camera.apply(self.x, self.y)
        terminal.printf(x=x, y=y, s=f'[color={self.color}]{self.char}[/color]')
    
  • Import the camera class into 'game_map.py'

    from camera import Camera
    
  • In 'game_map.py', pass the camera to the render method and shift the coordinates for each x and y on the map before drawing:

    def render(self, colors, camera: Camera):
        for y in range(self.height):
            for x in range(self.width):
                wall = self.is_blocked(x, y)
                visible = self.fov[x, y]
    
                x_in_camera, y_in_camera = camera.apply(x, y)
    
                if visible:
                    if wall:
                        terminal.printf(
                            x=x_in_camera,
                            y=y_in_camera,
                            s=f'[color={colors.get("light_wall")}]#[/color]',
                        )
                    else:
                        terminal.printf(
                            x=x_in_camera,
                            y=y_in_camera,
                            s=f'[color={colors.get("light_ground")}].[/color]',
                        )
    
                elif self.explored[x, y]:
                    if wall:
                        terminal.printf(
                            x=x_in_camera,
                            y=y_in_camera,
                            s=f'[color={colors.get("dark_wall")}]#[/color]',
                        )
                    else:
                        terminal.printf(
                            x=x_in_camera,
                            y=y_in_camera,
                            s=f'[color={colors.get("dark_ground")}].[/color]',
                        )
    
        self.explored |= self.fov
    

And that's it! Now when you execute 'python engine.py' the player should be in the centre of the game window and the map should scroll as the player moves.