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:
parent
402e910915
commit
356e205f2c
3 changed files with 58 additions and 25 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue