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: case tcod.event.KeySym.ESCAPE:
action = ExitAction() action = ExitAction()
case tcod.event.KeySym.SPACE: case tcod.event.KeySym.SPACE:
action = RegenerateRoomsAction(hero) action = RegenerateRoomsAction()
case tcod.event.KeySym.PERIOD: case tcod.event.KeySym.PERIOD:
if not is_shift_pressed: if not is_shift_pressed:
action = WaitAction(hero) 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) 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]): 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.turn_count = turn_count
self.info_window.update_hero(hero) self.info_window.update_hero(hero)
self.map_window.update_drawable_map_bounds(hero)
self.map_window.entities = entities self.map_window.entities = entities
def draw(self, console: Console): def draw(self, console: Console):
'''Draw the UI to the console'''
self.map_window.draw(console) self.map_window.draw(console)
self.info_window.draw(console) self.info_window.draw(console)
self.message_window.draw(console) self.message_window.draw(console)
@ -48,6 +52,7 @@ class InfoWindow(Window):
colors=list(HealthBar.bar_colors())) colors=list(HealthBar.bar_colors()))
def update_hero(self, hero: Hero): def update_hero(self, hero: Hero):
'''Update internal state for the hero'''
assert hero.fighter assert hero.fighter
fighter = hero.fighter fighter = hero.fighter

View file

@ -5,9 +5,10 @@ from typing import List
import numpy as np import numpy as np
from tcod.console import Console from tcod.console import Console
from ..object import Entity from .. import log
from ..geometry import Rect, Vector from ..geometry import Point, Rect, Vector
from ..map import Map from ..map import Map
from ..object import Entity, Hero
class Window: class Window:
@ -19,11 +20,13 @@ class Window:
@property @property
def drawable_bounds(self) -> Rect: def drawable_bounds(self) -> Rect:
'''The bounds of the window that is drawable, inset by any frame'''
if self.is_framed: if self.is_framed:
return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds.inset_rect(1, 1, 1, 1)
return self.bounds return self.bounds
def draw(self, console: Console): def draw(self, console: Console):
'''Draw the window to the conole'''
if self.is_framed: if self.is_framed:
console.draw_frame( console.draw_frame(
self.bounds.origin.x, self.bounds.origin.x,
@ -35,41 +38,67 @@ class Window:
class MapWindow(Window): class MapWindow(Window):
'''A Window that displays a game map''' '''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) super().__init__(bounds, **kwargs)
self.map = map_ self.map = map
self.drawable_map_bounds = map.bounds
self.offset = Vector()
self.entities: List[Entity] = [] 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): def draw(self, console: Console):
super().draw(console) super().draw(console)
self._draw_map(console) self._draw_map(console)
self._draw_entities(console) self._draw_entities(console)
def _draw_map(self, console: Console): def _draw_map(self, console: Console):
map_ = self.map drawable_map_bounds = self.drawable_map_bounds
map_size = map_.size
drawable_bounds = self.drawable_bounds drawable_bounds = self.drawable_bounds
width = min(map_size.width, drawable_bounds.width) log.UI.info('Drawing map')
height = min(map_size.height, drawable_bounds.height) 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_[
map_slice = np.s_[0:width, 0:height] 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 console_slice = np.s_[
max_x = min_x + width drawable_bounds.min_x:drawable_bounds.max_x + 1,
min_y = drawable_bounds.min_y drawable_bounds.min_y:drawable_bounds.max_y + 1]
max_y = min_y + height
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): 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) drawable_bounds_vector = Vector.from_point(self.drawable_bounds.origin)
log.UI.info('Drawing entities')
for ent in self.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)]: if not self.map.visible[tuple(ent.position)]:
continue continue
@ -78,16 +107,15 @@ class MapWindow(Window):
entity_position = ent.position entity_position = ent.position
map_tile_at_entity_position = self.map.composited_tiles[entity_position.x, entity_position.y] 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( console.print(
x=position.x, x=position.x,
y=position.y, y=position.y,
string=ent.symbol, string=ent.symbol,
fg=ent.foreground, fg=ent.foreground,
bg=tuple(map_tile_at_entity_position['bg'][:3])) 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))