Implement viewport tracking for the MapWindow

As the player moves the hero, the MapWindow will try to keep the
hero in the middle of the view.
This commit is contained in:
Eryn Wells 2023-02-12 19:47:27 -08:00
parent 402e910915
commit 356e205f2c
3 changed files with 58 additions and 25 deletions

View file

@ -87,7 +87,7 @@ class MainGameEventHandler(EventHandler):
case tcod.event.KeySym.ESCAPE:
action = ExitAction()
case tcod.event.KeySym.SPACE:
action = RegenerateRoomsAction(hero)
action = RegenerateRoomsAction()
case tcod.event.KeySym.PERIOD:
if not is_shift_pressed:
action = WaitAction(hero)

View file

@ -23,11 +23,15 @@ class Interface:
self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log)
def update(self, turn_count: int, hero: Hero, entities: List[Entity]):
'''Update game state that the interface needs to render'''
self.info_window.turn_count = turn_count
self.info_window.update_hero(hero)
self.map_window.update_drawable_map_bounds(hero)
self.map_window.entities = entities
def draw(self, console: Console):
'''Draw the UI to the console'''
self.map_window.draw(console)
self.info_window.draw(console)
self.message_window.draw(console)
@ -48,6 +52,7 @@ class InfoWindow(Window):
colors=list(HealthBar.bar_colors()))
def update_hero(self, hero: Hero):
'''Update internal state for the hero'''
assert hero.fighter
fighter = hero.fighter

View file

@ -5,9 +5,10 @@ from typing import List
import numpy as np
from tcod.console import Console
from ..object import Entity
from ..geometry import Rect, Vector
from .. import log
from ..geometry import Point, Rect, Vector
from ..map import Map
from ..object import Entity, Hero
class Window:
@ -19,11 +20,13 @@ class Window:
@property
def drawable_bounds(self) -> Rect:
'''The bounds of the window that is drawable, inset by any frame'''
if self.is_framed:
return self.bounds.inset_rect(1, 1, 1, 1)
return self.bounds
def draw(self, console: Console):
'''Draw the window to the conole'''
if self.is_framed:
console.draw_frame(
self.bounds.origin.x,
@ -35,41 +38,67 @@ class Window:
class MapWindow(Window):
'''A Window that displays a game map'''
def __init__(self, bounds: Rect, map_: Map, **kwargs):
# pylint: disable=redefined-builtin
def __init__(self, bounds: Rect, map: Map, **kwargs):
super().__init__(bounds, **kwargs)
self.map = map_
self.map = map
self.drawable_map_bounds = map.bounds
self.offset = Vector()
self.entities: List[Entity] = []
def update_drawable_map_bounds(self, hero: Hero):
bounds = self.drawable_bounds
map_bounds = self.map.bounds
if map_bounds.width < bounds.width and map_bounds.height < bounds.height:
# We can draw the whole map in the drawable bounds
self.drawable_map_bounds = map_bounds
# Attempt to keep the player centered in the viewport.
hero_point = hero.position
x = min(max(0, hero_point.x - bounds.mid_x), map_bounds.max_x - bounds.width)
y = min(max(0, hero_point.y - bounds.mid_y), map_bounds.max_y - bounds.height)
origin = Point(x, y)
self.drawable_map_bounds = Rect(origin, bounds.size)
def draw(self, console: Console):
super().draw(console)
self._draw_map(console)
self._draw_entities(console)
def _draw_map(self, console: Console):
map_ = self.map
map_size = map_.size
drawable_map_bounds = self.drawable_map_bounds
drawable_bounds = self.drawable_bounds
width = min(map_size.width, drawable_bounds.width)
height = min(map_size.height, drawable_bounds.height)
log.UI.info('Drawing map')
log.UI.info('|- map bounds: %s', drawable_map_bounds)
log.UI.info('|- window bounds: %s', drawable_bounds)
# TODO: Adjust the slice according to where the hero is.
map_slice = np.s_[0:width, 0:height]
map_slice = np.s_[
drawable_map_bounds.min_x:drawable_map_bounds.max_x + 1,
drawable_map_bounds.min_y:drawable_map_bounds.max_y + 1]
min_x = drawable_bounds.min_x
max_x = min_x + width
min_y = drawable_bounds.min_y
max_y = min_y + height
console_slice = np.s_[
drawable_bounds.min_x:drawable_bounds.max_x + 1,
drawable_bounds.min_y:drawable_bounds.max_y + 1]
console.tiles_rgb[min_x:max_x, min_y:max_y] = self.map.composited_tiles[map_slice]
log.UI.info('|- map slice: %s', map_slice)
log.UI.info('`- console slice: %s', console_slice)
console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice]
def _draw_entities(self, console):
map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin)
drawable_bounds_vector = Vector.from_point(self.drawable_bounds.origin)
log.UI.info('Drawing entities')
for ent in self.entities:
# Only process entities that are in the field of view
# Only draw entities that are in the field of view
if not self.map.visible[tuple(ent.position)]:
continue
@ -78,16 +107,15 @@ class MapWindow(Window):
entity_position = ent.position
map_tile_at_entity_position = self.map.composited_tiles[entity_position.x, entity_position.y]
position = ent.position + drawable_bounds_vector
position = ent.position - map_bounds_vector + drawable_bounds_vector
if isinstance(ent, Hero):
log.UI.info('|- hero position on map %s', entity_position)
log.UI.info('`- position in window %s', position)
console.print(
x=position.x,
y=position.y,
string=ent.symbol,
fg=ent.foreground,
bg=tuple(map_tile_at_entity_position['bg'][:3]))
# if ent.position == self.__current_mouse_point:
# entities_at_mouse_position.append(ent)
# if len(entities_at_mouse_position) > 0:
# console.print(x=1, y=43, string=', '.join(e.name for e in entities_at_mouse_position))