627 lines
39 KiB
HTML
627 lines
39 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" style="color-scheme: dark;"><head>
|
||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||
<title>
|
||
Part 4 - Field of View · Roguelike Tutorials
|
||
</title>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light dark">
|
||
|
||
|
||
|
||
|
||
<meta name="description" content="We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?
|
||
Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, tcod makes that easy!">
|
||
<meta name="keywords" content="">
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="Part 4 - Field of View">
|
||
<meta name="twitter:description" content="We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?
|
||
Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, tcod makes that easy!">
|
||
|
||
<meta property="og:title" content="Part 4 - Field of View">
|
||
<meta property="og:description" content="We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?
|
||
Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, tcod makes that easy!">
|
||
<meta property="og:type" content="article">
|
||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/"><meta property="article:section" content="tutorials">
|
||
<meta property="article:published_time" content="2020-06-29T00:00:00+00:00">
|
||
<meta property="article:modified_time" content="2020-06-29T00:00:00+00:00">
|
||
|
||
|
||
|
||
|
||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/">
|
||
|
||
|
||
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
|
||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
|
||
|
||
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||
|
||
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
|
||
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
|
||
|
||
|
||
|
||
|
||
<meta name="generator" content="Hugo 0.110.0">
|
||
|
||
|
||
|
||
|
||
|
||
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<body class="colorscheme-dark vsc-initialized">
|
||
|
||
<div class="float-container">
|
||
<a id="dark-mode-toggle" class="colorscheme-toggle">
|
||
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
|
||
</a>
|
||
</div>
|
||
|
||
|
||
<main class="wrapper">
|
||
<nav class="navigation">
|
||
<section class="container">
|
||
<a class="navigation-title" href="https://rogueliketutorials.com/">
|
||
Roguelike Tutorials
|
||
</a>
|
||
|
||
<input type="checkbox" id="menu-toggle">
|
||
<label class="menu-button float-right" for="menu-toggle">
|
||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
||
</label>
|
||
<ul class="navigation-list">
|
||
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
|
||
</li>
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
|
||
</li>
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
|
||
</li>
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
|
||
</li>
|
||
|
||
|
||
|
||
</ul>
|
||
|
||
</section>
|
||
</nav>
|
||
|
||
|
||
<div class="content">
|
||
|
||
<section class="container page">
|
||
<article>
|
||
<header>
|
||
<h1 class="title">
|
||
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/">
|
||
Part 4 - Field of View
|
||
</a>
|
||
</h1>
|
||
</header>
|
||
|
||
<p>We have a dungeon now, and we can move about it freely. But are we
|
||
really <em>exploring</em> the dungeon if we can just see it all from the
|
||
beginning?</p>
|
||
<p>Most roguelikes (not all!) only let you see within a certain range of
|
||
your character, and ours will be no different. We need to implement a way
|
||
to calculate the “Field of View” for our adventurer, and fortunately,
|
||
tcod makes that easy!</p>
|
||
<p>When walking around the dungeon, there will essentially be three “states” a tile can be in, relating to our field of view.</p>
|
||
<ol>
|
||
<li>Visible</li>
|
||
<li>Not visible</li>
|
||
<li>Not visible, but previously seen</li>
|
||
</ol>
|
||
<p>What this means is that we should draw the “visible” tiles as well as
|
||
the “not visible, but previously seen” ones to the screen, but
|
||
differentiate them somehow. The “not visible” tiles can simply be drawn
|
||
as an empty tile, with the color black, gray, or whatever you want to
|
||
use.</p>
|
||
<p>In order to differentiate between these tiles, we’ll need two new
|
||
Numpy arrays: One to keep track of the tiles that are currently visible,
|
||
and another to keep track of all the tiles that our character has seen
|
||
before. Add the two arrays to <code>GameMap</code> like this:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
|
||
</span></span><span style="display:flex;"><span> def __init__(self, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self.width, self.height = width, height
|
||
</span></span><span style="display:flex;"><span> self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.visible = np.full((width, height), fill_value=False, order="F") # Tiles the player can currently see
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.explored = np.full((width, height), fill_value=False, order="F") # Tiles the player has seen before
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>class GameMap:
|
||
def __init__(self, width: int, height: int):
|
||
self.width, self.height = width, height
|
||
self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
|
||
|
||
<span class="new-text">self.visible = np.full((width, height), fill_value=False, order="F") # Tiles the player can currently see
|
||
self.explored = np.full((width, height), fill_value=False, order="F") # Tiles the player has seen before</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We create two arrays, <code>visible</code> and <code>explored</code>, and fill them with the value <code>False</code>. In a moment, we’ll create a function that will update these arrays based on what’s in the field of view.</p>
|
||
<p>Let’s turn our attention back to the tile types. Remember when we
|
||
specified the “walkable”, “transparent”, and “dark” attributes? We
|
||
called it “dark” because it’s what the tile will look like when its not
|
||
in the field of view, but what about when it <em>is</em>?</p>
|
||
<p>For that, we’ll want a new <code>graphic_dt</code> in the <code>tile_dt</code> type, called <code>light</code>. We can add that by modifying <code>tile_types.py</code> like this:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>tile_dt = np.dtype(
|
||
</span></span><span style="display:flex;"><span> [
|
||
</span></span><span style="display:flex;"><span> ("walkable", np.bool), # True if this tile can be walked over.
|
||
</span></span><span style="display:flex;"><span> ("transparent", np.bool), # True if this tile doesn't block FOV.
|
||
</span></span><span style="display:flex;"><span> ("dark", graphic_dt), # Graphics for when this tile is not in FOV.
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ ("light", graphic_dt), # Graphics for when the tile is in FOV.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> ]
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>def new_tile(
|
||
</span></span><span style="display:flex;"><span> *, # Enforce the use of keywords, so that parameter order doesn't matter.
|
||
</span></span><span style="display:flex;"><span> walkable: int,
|
||
</span></span><span style="display:flex;"><span> transparent: int,
|
||
</span></span><span style="display:flex;"><span> dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>) -> np.ndarray:
|
||
</span></span><span style="display:flex;"><span> """Helper function for defining individual tile types """
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- return np.array((walkable, transparent, dark), dtype=tile_dt)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ return np.array((walkable, transparent, dark, light), dtype=tile_dt)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+# SHROUD represents unexplored, unseen tiles
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+SHROUD = np.array((ord(" "), (255, 255, 255), (0, 0, 0)), dtype=graphic_dt)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>floor = new_tile(
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- walkable=True, transparent=True, dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ walkable=True,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ transparent=True,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord(" "), (255, 255, 255), (200, 180, 50)),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||
</span></span><span style="display:flex;"><span>wall = new_tile(
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- walkable=False, transparent=False, dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ walkable=False,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ transparent=False,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord(" "), (255, 255, 255), (130, 110, 50)),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>tile_dt = np.dtype(
|
||
[
|
||
("walkable", np.bool), # True if this tile can be walked over.
|
||
("transparent", np.bool), # True if this tile doesn't block FOV.
|
||
("dark", graphic_dt), # Graphics for when this tile is not in FOV.
|
||
<span class="new-text">("light", graphic_dt), # Graphics for when the tile is in FOV.</span>
|
||
]
|
||
)
|
||
|
||
|
||
def new_tile(
|
||
*, # Enforce the use of keywords, so that parameter order doesn't matter.
|
||
walkable: int,
|
||
transparent: int,
|
||
dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
<span class="new-text">light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],</span>
|
||
) -> np.ndarray:
|
||
"""Helper function for defining individual tile types """
|
||
<span class="crossed-out-text">return np.array((walkable, transparent, dark), dtype=tile_dt)</span>
|
||
<span class="new-text">return np.array((walkable, transparent, dark, light), dtype=tile_dt)</span>
|
||
|
||
|
||
<span class="new-text"># SHROUD represents unexplored, unseen tiles
|
||
SHROUD = np.array((ord(" "), (255, 255, 255), (0, 0, 0)), dtype=graphic_dt)</span>
|
||
|
||
floor = new_tile(
|
||
<span class="crossed-out-text">walkable=True, transparent=True, dark=(ord(" "), (255, 255, 255), (50, 50, 150)),</span>
|
||
<span class="new-text">walkable=True,
|
||
transparent=True,
|
||
dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||
light=(ord(" "), (255, 255, 255), (200, 180, 50)),</span>
|
||
)
|
||
wall = new_tile(
|
||
<span class="crossed-out-text">walkable=False, transparent=False, dark=(ord(" "), (255, 255, 255), (0, 0, 100)),</span>
|
||
<span class="new-text">walkable=False,
|
||
transparent=False,
|
||
dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||
light=(ord(" "), (255, 255, 255), (130, 110, 50)),</span>
|
||
)</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Let’s go through the new additions.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span>tile_dt <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>dtype(
|
||
</span></span><span style="display:flex;"><span> [
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"walkable"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile can be walked over.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"transparent"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile doesn't block FOV.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"dark"</span>, graphic_dt), <span style="color:#75715e"># Graphics for when this tile is not in FOV.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"light"</span>, graphic_dt), <span style="color:#75715e"># Graphics for when the tile is in FOV.</span>
|
||
</span></span><span style="display:flex;"><span> ]
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span></code></pre></div><p>We’re adding a new <code>graphic_dt</code> to the <code>tile_dt</code> that we use to define our tiles. <code>light</code> will hold the information about what our tile looks like when it’s in the field of view.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">new_tile</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span>, <span style="color:#75715e"># Enforce the use of keywords, so that parameter order doesn't matter.</span>
|
||
</span></span><span style="display:flex;"><span> walkable: int,
|
||
</span></span><span style="display:flex;"><span> transparent: int,
|
||
</span></span><span style="display:flex;"><span> dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
</span></span><span style="display:flex;"><span> light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
</span></span><span style="display:flex;"><span>) <span style="color:#f92672">-></span> np<span style="color:#f92672">.</span>ndarray:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Helper function for defining individual tile types """</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> np<span style="color:#f92672">.</span>array((walkable, transparent, dark, light), dtype<span style="color:#f92672">=</span>tile_dt)
|
||
</span></span></code></pre></div><p>We’ve modified the <code>new_tile</code> function to account for the new <code>light</code> attribute. <code>light</code> works the same as <code>dark</code>.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#75715e"># SHROUD represents unexplored, unseen tiles</span>
|
||
</span></span><span style="display:flex;"><span>SHROUD <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>array((ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>)), dtype<span style="color:#f92672">=</span>graphic_dt)
|
||
</span></span></code></pre></div><p><code>SHROUD</code> is what we’ll use for when a tile is neither in view nor has been “explored”. It’s set to just draw a black tile.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span>floor <span style="color:#f92672">=</span> new_tile(
|
||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
|
||
</span></span><span style="display:flex;"><span> transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
|
||
</span></span><span style="display:flex;"><span> dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">150</span>)),
|
||
</span></span><span style="display:flex;"><span> light<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">180</span>, <span style="color:#ae81ff">50</span>)),
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span><span style="display:flex;"><span>wall <span style="color:#f92672">=</span> new_tile(
|
||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>,
|
||
</span></span><span style="display:flex;"><span> transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>,
|
||
</span></span><span style="display:flex;"><span> dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">100</span>)),
|
||
</span></span><span style="display:flex;"><span> light<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">130</span>, <span style="color:#ae81ff">110</span>, <span style="color:#ae81ff">50</span>)),
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span></code></pre></div><p>Finally, we add <code>light</code> to both the <code>floor</code> and <code>wall</code>
|
||
tiles. We also modify the functions to fit a bit better on the screen,
|
||
adding new lines after each argument. This is just for the sake of
|
||
readability.</p>
|
||
<p><code>light</code> in both cases is set to a brighter color, so that
|
||
when we draw the field of view to the screen, the player can easily
|
||
differentiate between what’s in view and what’s not. As usual, feel free
|
||
to play with the color schemes to match whatever you might have in
|
||
mind.</p>
|
||
<p>With all that in place, we need to modify the way <code>GameMap</code> draws itself to the screen.</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def render(self, console: Console) -> None:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- console.tiles_rgb[0:self.width, 0:self.height] = self.tiles["dark"]
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ """
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Renders the map.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ If a tile is in the "visible" array, then draw it with the "light" colors.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ If it isn't, but it's in the "explored" array, then draw it with the "dark" colors.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Otherwise, the default is "SHROUD".
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.tiles_rgb[0:self.width, 0:self.height] = np.select(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ condlist=[self.visible, self.explored],
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ choicelist=[self.tiles["light"], self.tiles["dark"]],
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ default=tile_types.SHROUD
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>class GameMap:
|
||
...
|
||
|
||
def render(self, console: Console) -> None:
|
||
<span class="crossed-out-text">console.tiles_rgb[0:self.width, 0:self.height] = self.tiles["dark"]</span>
|
||
<span class="new-text">"""
|
||
Renders the map.
|
||
|
||
If a tile is in the "visible" array, then draw it with the "light" colors.
|
||
If it isn't, but it's in the "explored" array, then draw it with the "dark" colors.
|
||
Otherwise, the default is "SHROUD".
|
||
"""
|
||
console.tiles_rgb[0:self.width, 0:self.height] = np.select(
|
||
condlist=[self.visible, self.explored],
|
||
choicelist=[self.tiles["light"], self.tiles["dark"]],
|
||
default=tile_types.SHROUD
|
||
)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>The first part of the statement, <code>console.tiles_rgb[0:self.width, 0:self.height]</code>, hasn’t changed. But instead of just setting it to <code>self.tiles["dark"]</code>, we’re using <code>np.select</code>.</p>
|
||
<p><code>np.select</code> allows us to conditionally draw the tiles we want, based on what’s specified in <code>condlist</code>. Since we’re passing <code>[self.visible, self.explored]</code>, it will check if the tile being drawn is either visible, then explored. If it’s visible, it uses the first value in <code>choicelist</code>, in this case, <code>self.tiles["light"]</code>. If it’s not visible, but explored, then we draw <code>self.tiles["dark"]</code>. If neither is true, we use the <code>default</code> argument, which is just the <code>SHROUD</code> we defined earlier.</p>
|
||
<p>If you run the project now, none of the tiles will be drawn to the screen. This is because we need a way to actually modify the <code>visible</code> and <code>explored</code> tiles. Let’s modify <code>Engine</code> to do just that:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span>from tcod.context import Context
|
||
</span></span><span style="display:flex;"><span>from tcod.console import Console
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from tcod.map import compute_fov
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>from entity import Entity
|
||
</span></span><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>class Engine:
|
||
</span></span><span style="display:flex;"><span> def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
|
||
</span></span><span style="display:flex;"><span> self.entities = entities
|
||
</span></span><span style="display:flex;"><span> self.event_handler = event_handler
|
||
</span></span><span style="display:flex;"><span> self.game_map = game_map
|
||
</span></span><span style="display:flex;"><span> self.player = player
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.update_fov()
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> def handle_events(self, events: Iterable[Any]) -> None:
|
||
</span></span><span style="display:flex;"><span> for event in events:
|
||
</span></span><span style="display:flex;"><span> action = self.event_handler.dispatch(event)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> if action is None:
|
||
</span></span><span style="display:flex;"><span> continue
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> action.perform(self, self.player)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.update_fov() # Update the FOV before the players next action.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def update_fov(self) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Recompute the visible area based on the players point of view."""
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.visible[:] = compute_fov(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.tiles["transparent"],
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ (self.player.x, self.player.y),
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ radius=8,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # If a tile is "visible" it should be added to "explored".
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.explored |= self.game_map.visible
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> def render(self, console: Console, context: Context) -> None:
|
||
</span></span><span style="display:flex;"><span> self.game_map.render(console)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> for entity in self.entities:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- console.print(entity.x, entity.y, entity.char, fg=entity.color)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ # Only print entities that are in the FOV
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.game_map.visible[entity.x, entity.y]:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(entity.x, entity.y, entity.char, fg=entity.color)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> context.present(console)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console.clear()
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>...
|
||
from tcod.context import Context
|
||
from tcod.console import Console
|
||
<span class="new-text">from tcod.map import compute_fov</span>
|
||
|
||
from entity import Entity
|
||
...
|
||
|
||
class Engine:
|
||
def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
|
||
self.entities = entities
|
||
self.event_handler = event_handler
|
||
self.game_map = game_map
|
||
self.player = player
|
||
<span class="new-text">self.update_fov()</span>
|
||
|
||
def handle_events(self, events: Iterable[Any]) -> None:
|
||
for event in events:
|
||
action = self.event_handler.dispatch(event)
|
||
|
||
if action is None:
|
||
continue
|
||
|
||
action.perform(self, self.player)
|
||
|
||
<span class="new-text">self.update_fov() # Update the FOV before the players next action.</span>
|
||
|
||
<span class="new-text">def update_fov(self) -> None:
|
||
"""Recompute the visible area based on the players point of view."""
|
||
self.game_map.visible[:] = compute_fov(
|
||
self.game_map.tiles["transparent"],
|
||
(self.player.x, self.player.y),
|
||
radius=8,
|
||
)
|
||
# If a tile is "visible" it should be added to "explored".
|
||
self.game_map.explored |= self.game_map.visible</span>
|
||
|
||
def render(self, console: Console, context: Context) -> None:
|
||
self.game_map.render(console)
|
||
|
||
for entity in self.entities:
|
||
<span class="crossed-out-text">console.print(entity.x, entity.y, entity.char, fg=entity.color)</span>
|
||
<span class="new-text"># Only print entities that are in the FOV
|
||
if self.game_map.visible[entity.x, entity.y]:
|
||
console.print(entity.x, entity.y, entity.char, fg=entity.color)</span>
|
||
|
||
context.present(console)
|
||
|
||
console.clear()</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>The most important part of our additions is the <code>update_fov</code> function.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">update_fov</span>(self) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Recompute the visible area based on the players point of view."""</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>visible[:] <span style="color:#f92672">=</span> compute_fov(
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>tiles[<span style="color:#e6db74">"transparent"</span>],
|
||
</span></span><span style="display:flex;"><span> (self<span style="color:#f92672">.</span>player<span style="color:#f92672">.</span>x, self<span style="color:#f92672">.</span>player<span style="color:#f92672">.</span>y),
|
||
</span></span><span style="display:flex;"><span> radius<span style="color:#f92672">=</span><span style="color:#ae81ff">8</span>,
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># If a tile is "visible" it should be added to "explored".</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>explored <span style="color:#f92672">|=</span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>visible
|
||
</span></span></code></pre></div><p>We’re setting the <code>game_map</code>’s <code>visible</code> tiles to equal the result of the <code>compute_fov</code>. We’re giving <code>compute_fov</code> three arguments, which it uses to compute our field of view.</p>
|
||
<ul>
|
||
<li><code>transparency</code>: This is the first argument, which we’re passing <code>self.game_map.tiles["transparent"]</code>. <code>transparency</code>
|
||
takes a 2D numpy array, and considers any non-zero values to be
|
||
transparent. This is the array it uses to calculate the field of view.</li>
|
||
<li><code>pov</code>: The origin point for the field of view, which is a 2D index. We use the player’s x and y position here.</li>
|
||
<li><code>radius</code>: How far the FOV extends.</li>
|
||
</ul>
|
||
<p>There’s more that this function can do, including not lighting up
|
||
walls, and using different algorithms to calculate the FOV. If you’re
|
||
interested, you can find the documentation <a href="https://python-tcod.readthedocs.io/en/latest/tcod/map.html#tcod.map.compute_fov">here</a>.</p>
|
||
<p>The line <code>self.game_map.explored |= self.game_map.visible</code> sets the <code>explored</code> array to include everything in the <code>visible</code> array, plus whatever it already had. This means that any tile the player can see, the player has also “explored.”</p>
|
||
<p>That’s all we need to do to update our field of view. Notice that we call the function when we initialize the <code>Engine</code>
|
||
class, so that the field of view is created before the player can move,
|
||
and after handling an action, so that whenever the player does move,
|
||
the field of view will be updated.</p>
|
||
<p>Lastly, we modify the part that draws the entities, so that only entities in the field of view are drawn.</p>
|
||
<p>Run the project now, and you’ll see something like this:</p>
|
||
<p><img src="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/part-4-fov.png" alt="Part 4 - FOV" title="Field of View"></p>
|
||
<p>It’s hard to believe, but that’s all we need to do for a functioning field of view!</p>
|
||
<p>This chapter was a shorter one, but we’ve accomplished quite a lot.
|
||
Our dungeon feels a lot more mysterious, and in coming chapters, it will
|
||
get a lot more dangerous.</p>
|
||
<p>If you want to see the code so far in its entirety, <a href="https://github.com/TStand90/tcod_tutorial_v2/tree/2020/part-4">click
|
||
here</a>.</p>
|
||
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-5">Click here to move on to the next part of this
|
||
tutorial.</a></p>
|
||
|
||
</article>
|
||
</section>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
<section class="container">
|
||
©
|
||
|
||
2023
|
||
|
||
·
|
||
|
||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||
|
||
</section>
|
||
</footer>
|
||
|
||
</main>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</body></html> |