Part 3 - The generic Entity, the Engine and the render function ================================================================== action.py ^^^^^^^^^ The ``action.py`` file remains the same:: class Action: pass class EscapeAction(Action): pass class MovementAction(Action): def __init__(self, dx: int, dy: int): super().__init__() self.dx = dx self.dy = dy input_handlers.py ^^^^^^^^^^^^^^^^^^ Event handling is moved to ``input_handlers.py``:: import sdl2 from actions import Action, EscapeAction, MovementAction def event_handler(event) -> None: action = None if event.type == sdl2.SDL_QUIT: action = EscapeAction() elif event.type == sdl2.SDL_KEYDOWN: if event.key.keysym.sym == sdl2.SDLK_UP: action = MovementAction(dx=0, dy=-1) elif event.key.keysym.sym == sdl2.SDLK_DOWN: action = MovementAction(dx=0, dy=1) elif event.key.keysym.sym == sdl2.SDLK_LEFT: action = MovementAction(dx=-1, dy=0) elif event.key.keysym.sym == sdl2.SDLK_RIGHT: action = MovementAction(dx=1, dy=0) elif event.key.keysym.sym == sdl2.SDLK_ESCAPE: action = EscapeAction() return action entity.py ^^^^^^^^^^ A new file is created for players, npcs, enemies, items - ``entity.py``:: from typing import Tuple import sdl2.ext class Entity: """ A generic object to represent players, enemies, items, etc. """ def __init__( self, x: int, y: int, char: str, color: Tuple[int, int, int], renderer, font_size=20, tile_size=20, ): self.x = x self.y = y self.char = char self.color = sdl2.ext.Color(*color) self.font_size = font_size self.bg_color = sdl2.ext.Color(0, 0, 0, 255) self.tile_size = tile_size self.renderer = renderer font_manager = sdl2.ext.FontManager( font_path="C:\\Windows\\Fonts\\arial.ttf", size=font_size, color=self.color, bg_color=self.bg_color, ) factory = sdl2.ext.SpriteFactory(renderer=self.renderer) self.text = factory.from_text(self.char, fontmanager=font_manager) self.x_offset = -self.text.size[0] // 2 self.y_offset = -self.text.size[1] // 2 self.text_width = self.text.size[0] self.text_height = self.text.size[1] self.pixel_x = self.x * self.tile_size + self.x_offset self.pixel_y = self.y * self.tile_size + self.y_offset def move(self, dx: int, dy: int) -> None: self.x += dx self.y += dy self.pixel_x = self.x * self.tile_size + self.x_offset self.pixel_y = self.y * self.tile_size + self.y_offset In more detail: Tile coordinates of the entity:: self.x = x self.y = y Character and color for the character:: self.char = char self.color = sdl2.ext.Color(*color) Creation of the text sprite:: font_manager = sdl2.ext.FontManager( font_path="C:\\Windows\\Fonts\\arial.ttf", size=font_size, color=self.color, bg_color=self.bg_color, ) factory = sdl2.ext.SpriteFactory(renderer=self.renderer) self.text = factory.from_text(self.char, fontmanager=font_manager) Calculation of the pixel values: position, sprite offset, pixel text width and height:: self.x_offset = -self.text.size[0] // 2 self.y_offset = -self.text.size[1] // 2 self.text_width = self.text.size[0] self.text_height = self.text.size[1] self.pixel_x = self.x * self.tile_size + self.x_offset self.pixel_y = self.y * self.tile_size + self.y_offset In the move function:: def move(self, dx: int, dy: int) -> None: first the tile coordinates are updated:: self.x += dx self.y += dy then the pixel coordinates are updated:: self.pixel_x = self.x * self.tile_size + self.x_offset self.pixel_y = self.y * self.tile_size + self.y_offset engine.py ^^^^^^^^^^ The logic for handling events and rendering is moved to ``engine.py``:: from typing import Set, Iterable, Any, List import sdl2.ext from actions import EscapeAction, MovementAction from entity import Entity from input_handlers import event_handler BLACK = sdl2.ext.Color(0, 0, 0) class Engine: def __init__(self, entities: List[Entity], player: Entity): self.entities = entities self.event_handler = event_handler self.player = player def handle_events(self, events: Iterable[Any]) -> None: for event in events: action = event_handler(event) if action is None: continue if isinstance(action, MovementAction): self.player.move(dx=action.dx, dy=action.dy) elif isinstance(action, EscapeAction): sdl2.ext.quit() raise SystemExit() def render(self, renderer, screen_width: int, screen_height: int) -> None: for entity in self.entities: renderer.copy( entity.text, dstrect=( entity.pixel_x + screen_width // 2, entity.pixel_y + screen_height // 2, entity.text_width, entity.text_height, ), ) renderer.present() renderer.clear(BLACK) main.py ^^^^^^^^ And the ``main.py`` file now is very small:: import sdl2 import sdl2.ext from entity import Entity from engine import Engine def run() -> None: sdl2.ext.init() screen_width = 640 screen_height = 480 window = sdl2.ext.Window( "PySDL2 Roguelike Tutorial", size=(screen_width, screen_height) ) window.show() renderer = sdl2.ext.Renderer(window) player = Entity(0, 0, "@", (255, 255, 255), renderer) npc = Entity(0 - 5, 0, "@", (255, 255, 0), renderer) entities = [npc, player] engine = Engine(entities=entities, player=player) while True: engine.render(renderer, screen_width, screen_height) events = sdl2.ext.get_events() engine.handle_events(events) if __name__ == "__main__": run()