<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://aibodh.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://aibodh.com/" rel="alternate" type="text/html" /><updated>2026-03-02T10:50:48+00:00</updated><id>https://aibodh.com/feed.xml</id><title type="html">AIBodh</title><subtitle>I built AIBodh to solve the problems I faced: navigating technology noise, balancing deep learning with management deadlines, and finding content that goes beyond toy projects. I&apos;m exploring better teaching methods, foundational systems knowledge, and developer tools that reduce cognitive overload while building intelligent systems.</subtitle><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 7 - Let There Be Enemies</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-7/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 7 - Let There Be Enemies" /><published>2026-02-02T00:00:00+00:00</published><updated>2026-02-02T00:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-7</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-7/"><![CDATA[<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>

<p>By the end of this chapter, you’ll have enemies that follow and attack your player.</p>

<blockquote>
  <p><strong>Prerequisites</strong>: This is Chapter 7 of our Bevy tutorial series. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> for updates on new releases. Before starting, complete <a href="/posts/bevy-rust-game-development-chapter-1/">Chapter 1: Let There Be a Player</a>, <a href="/posts/bevy-rust-game-development-chapter-2/">Chapter 2: Let There Be a World</a>, <a href="/posts/bevy-rust-game-development-chapter-3/">Chapter 3: Let The Data Flow</a>, <a href="/posts/bevy-rust-game-development-chapter-4/">Chapter 4: Let There Be Collisions</a>, <a href="/posts/bevy-rust-game-development-chapter-5/">Chapter 5: Let There Be Pickups</a>, and <a href="/posts/bevy-rust-game-development-chapter-6/">Chapter 6: Let There Be Particles</a>, or clone the Chapter 6 code from <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">this repository</a> to follow along.</p>
</blockquote>

<p><img src="/assets/book_assets/chapter7/ch7.gif" alt="Enemy System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<strong>Before We Begin:</strong> <em style="font-size: 14px;">I'm constantly working to improve this tutorial and make your learning journey enjoyable. Your feedback matters - share your frustrations, questions, or suggestions on <a href="https://www.reddit.com/r/bevy/" target="_blank">Reddit</a>/<a target="_blank" href="https://discord.com/invite/cD9qEsSjUH">Discord</a>/<a href="https://www.linkedin.com/in/febinjohnjames" target="_blank">LinkedIn</a>. Loved it? Let me know what worked well for you! Together, we'll make game development with Rust and Bevy more accessible for everyone.</em>
</div>

<h2 id="what-makes-an-enemy-different-from-a-player">What Makes an Enemy Different from a Player?</h2>

<p>Your player character moves, animates, collides with objects, and attacks. Now you want enemies do almost the same things expect for decision making.</p>

<p>Players make decisions through keyboard input. You press ↑, the character walks up. You press Ctrl, they attack. The input system reads your keypresses and sets components like <code class="language-plaintext highlighter-rouge">Velocity</code> and <code class="language-plaintext highlighter-rouge">CharacterState</code>.</p>

<p>Enemies need to make the same decisions, but automatically. Instead of reading a keyboard, they read the game world. “Where is the player? Am I close enough to attack? Which direction should I face?”</p>

<p>Both players and enemies need to:</p>
<ul>
  <li>Move around the world</li>
  <li>Animate based on state</li>
  <li>Collide with walls and objects</li>
  <li>Attack with magical powers</li>
</ul>

<p>The only difference is <strong>who decides what to do</strong>. For players, you decide. For enemies, AI decides. But once the decision is made, the rest is identical. Hence can we re-use the code we have written for player for enemy? Let’s find out.</p>

<h2 id="thinking-in-components">Thinking in Components</h2>

<p>In Bevy’s ECS architecture, entities are just IDs. Components are the data. Systems are the logic. This separation lets us build systems that work on <strong>any</strong> entity with the right components.</p>

<p>Your player entity has these components:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Player</code> (marker)</li>
  <li><code class="language-plaintext highlighter-rouge">Transform</code> (position)</li>
  <li><code class="language-plaintext highlighter-rouge">Velocity</code> (movement)</li>
  <li><code class="language-plaintext highlighter-rouge">CharacterState</code> (Idle, Walking, etc.)</li>
  <li><code class="language-plaintext highlighter-rouge">Facing</code> (direction)</li>
  <li><code class="language-plaintext highlighter-rouge">AnimationController</code> (sprite animation)</li>
  <li><code class="language-plaintext highlighter-rouge">Collider</code> (physics)</li>
</ul>

<p>An enemy entity needs most of the same components. The key insight: systems like <code class="language-plaintext highlighter-rouge">apply_velocity</code>, <code class="language-plaintext highlighter-rouge">validate_movement</code>, and <code class="language-plaintext highlighter-rouge">animate_characters</code> don’t care whether an entity is a player or enemy. They just process components.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_9cc1d8cc84cdddff66849948227c9ef3.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="9cc1d8cc84cdddff66849948227c9ef3" data-comic-settings="d2-diagram" />
</div>

<h2 id="creating-the-enemy-module">Creating the Enemy Module</h2>

<p>Create a new folder <code class="language-plaintext highlighter-rouge">src/enemy</code> with the following structure:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>src/
├── enemy/
│   ├── mod.rs
│   ├── components.rs
│   ├── ai.rs
│   ├── combat.rs
│   └── spawn.rs
</code></pre></div></div>

<h3 id="enemy-components">Enemy Components</h3>

<p>We need three components to define enemy behavior. The <code class="language-plaintext highlighter-rouge">Enemy</code> marker identifies which entities are enemies. The <code class="language-plaintext highlighter-rouge">EnemyCombat</code> component gives them attack capabilities. The <code class="language-plaintext highlighter-rouge">AIBehavior</code> component controls their decision-making.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/enemy/components.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/enemy/components.rs</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">combat</span><span class="p">::</span><span class="n">PowerType</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Marker component for enemy entities</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Enemy</span><span class="p">;</span>

<span class="cd">/// Combat capabilities for enemies</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">EnemyCombat</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">cooldown</span><span class="p">:</span> <span class="n">Timer</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">EnemyCombat</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">power_type</span><span class="p">:</span> <span class="nn">PowerType</span><span class="p">::</span><span class="n">Shadow</span><span class="p">,</span> <span class="c1">// Graveyard reaper uses shadow magic</span>
            <span class="n">cooldown</span><span class="p">:</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Once</span><span class="p">),</span> <span class="c1">// Slower than player</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">EnemyCombat</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">,</span> <span class="n">cooldown_seconds</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">power_type</span><span class="p">,</span>
            <span class="n">cooldown</span><span class="p">:</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">cooldown_seconds</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Once</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cd">/// AI behavior state for enemies</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AIBehavior</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">attack_range</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">detection_range</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">AIBehavior</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">attack_range</span><span class="p">:</span> <span class="mf">150.0</span><span class="p">,</span>    <span class="c1">// Stop and attack within this range</span>
            <span class="n">detection_range</span><span class="p">:</span> <span class="mf">500.0</span><span class="p">,</span> <span class="c1">// Start following player within this range</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">AIBehavior</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">attack_range</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">detection_range</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">attack_range</span><span class="p">,</span>
            <span class="n">detection_range</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s happening here?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">Enemy</code> component is just a marker. It has no data, it just tags entities as enemies so systems can query for them.</p>

<p><code class="language-plaintext highlighter-rouge">EnemyCombat</code> stores the enemy’s attack capabilities. The <code class="language-plaintext highlighter-rouge">power_type</code> determines what kind of projectile they fire (Shadow magic for our graveyard reaper). The <code class="language-plaintext highlighter-rouge">cooldown</code> timer prevents them from attacking every frame.</p>

<p><code class="language-plaintext highlighter-rouge">AIBehavior</code> defines two ranges. The <code class="language-plaintext highlighter-rouge">detection_range</code> is how far away the enemy can “see” the player. The <code class="language-plaintext highlighter-rouge">attack_range</code> is how close they need to be before they stop moving and start attacking.</p>

<p><strong>Why use Default?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">Default</code> trait lets us spawn enemies quickly without specifying every field. We can call <code class="language-plaintext highlighter-rouge">EnemyCombat::default()</code> to get sensible starting values, then customize specific enemies with <code class="language-plaintext highlighter-rouge">EnemyCombat::new()</code> if needed.</p>

<h3 id="making-enemies-follow-you">Making Enemies Follow You</h3>

<p>Now for the interesting part. We need a system that makes enemies track and follow the player.</p>

<p>Simple, move enemies directly toward the player? But what if there’s a tree in the way? The enemy walks into the obstacle and gets stuck.</p>

<p>We need <strong>pathfinding</strong>, the ability to find a route around obstacles. The industry-standard solution is the A* (“A-star”) algorithm.</p>

<h3 id="understanding-a-pathfinding">Understanding A* Pathfinding</h3>

<p>A* is a pathfinding algorithm that finds the shortest path between two points on a grid. It’s used everywhere in games, robotics, GPS navigation because it’s both efficient and guaranteed to find the shortest path.</p>

<p>A* explores the grid by calculating a score for each cell. A cell is a single square in the grid (like a tile in your game world). Cost here refers to how expensive it is to travel somewhere.</p>
<ul>
  <li><strong>g-cost</strong>: Actual distance traveled from start to this cell</li>
  <li><strong>h-cost</strong>: Heuristic - Estimated distance from this cell to goal</li>
  <li><strong>f-cost</strong>: Total cost - g + h (total estimated cost if we take this path)</li>
</ul>

<p>The algorithm always explores the cell with the lowest f-cost first. This makes it “greedy” (always picking what looks best) but informed (using the heuristic to guide the search).</p>

<div id="astar-viz" data-d3-print-frames="800,2000,3200" style="margin: 30px auto; max-width: 700px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <h4 style="margin-top: 0; color: #333 !important;">A* Pathfinding</h4>
  <svg id="astar-grid" width="100%" viewBox="0 0 600 520" style="max-width: 600px;"></svg>
  <div id="astar-info" style="margin-top: 15px; font-size: 14px; color: #555;"></div>
</div>

<script>
(function() {
  const gridSize = 10;
  const cellSize = 40;
  const svgWidth = 600;
  const gridWidth = gridSize * cellSize;
  const margin = (svgWidth - gridWidth) / 2; // Center the grid
  
  const svg = d3.select("#astar-grid");
  const infoDiv = d3.select("#astar-info");
  
  let start = {x: 1, y: 1};
  let goal = {x: 8, y: 8};
  let walls = new Set();
  let openSet = [];
  let closedSet = new Set();
  let path = [];
  let current = null;
  let stepInterval = null;
  
  const obstaclePositions = [[3,2], [3,3], [3,4], [3,5], [6,4], [6,5], [6,6], [6,7]];
  obstaclePositions.forEach(([x, y]) => walls.add(`${x},${y}`));
  
  function heuristic(a, b) {
    return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
  }
  
  function getNeighbors(node) {
    const neighbors = [];
    const dirs = [[0,1], [0,-1], [1,0], [-1,0]];
    for (const [dx, dy] of dirs) {
      const nx = node.x + dx, ny = node.y + dy;
      if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize && !walls.has(`${nx},${ny}`)) {
        neighbors.push({x: nx, y: ny});
      }
    }
    return neighbors;
  }
  
  function reset() {
    openSet = [{...start, g: 0, h: heuristic(start, goal), f: heuristic(start, goal), parent: null}];
    closedSet = new Set();
    path = [];
    current = null;
    if (stepInterval) clearInterval(stepInterval);
    stepInterval = null;
    render();
    infoDiv.html("Searching for path...");
    // Auto-start the animation
    setTimeout(() => {
      stepInterval = setInterval(() => {
        if (!step()) {
          setTimeout(reset, 2000); // Reset after 2 seconds
        }
      }, 150);
    }, 500);
  }
  
  function step() {
    if (openSet.length === 0) {
      infoDiv.html("<strong>No path found!</strong>");
      return false;
    }
    
    openSet.sort((a, b) => a.f - b.f);
    current = openSet.shift();
    
    if (current.x === goal.x && current.y === goal.y) {
      path = [];
      let temp = current;
      while (temp) {
        path.push({x: temp.x, y: temp.y});
        temp = temp.parent;
      }
      path.reverse();
      render();
      infoDiv.html(`<strong>Path found!</strong> Length: ${path.length - 1} steps`);
      if (stepInterval) clearInterval(stepInterval);
      return false;
    }
    
    closedSet.add(`${current.x},${current.y}`);
    
    const neighbors = getNeighbors(current);
    for (const neighbor of neighbors) {
      const key = `${neighbor.x},${neighbor.y}`;
      if (closedSet.has(key)) continue;
      
      const g = current.g + 1;
      const h = heuristic(neighbor, goal);
      const f = g + h;
      
      const existing = openSet.find(n => n.x === neighbor.x && n.y === neighbor.y);
      if (!existing) {
        openSet.push({...neighbor, g, h, f, parent: current});
      } else if (g < existing.g) {
        existing.g = g;
        existing.f = f;
        existing.parent = current;
      }
    }
    
    render();
    infoDiv.html(`Exploring: (${current.x}, ${current.y}) | Open set: ${openSet.length} | Closed: ${closedSet.size}`);
    return true;
  }
  
  function render() {
    svg.selectAll("*").remove();
    
    const topMargin = 20; // Vertical spacing
    
    for (let y = 0; y < gridSize; y++) {
      for (let x = 0; x < gridSize; x++) {
        const key = `${x},${y}`;
        let fill = "#fff";
        
        if (walls.has(key)) fill = "#333";
        else if (x === start.x && y === start.y) fill = "#4CAF50";
        else if (x === goal.x && y === goal.y) fill = "#FF5722";
        else if (path.some(p => p.x === x && p.y === y)) fill = "#FFC107";
        else if (current && current.x === x && current.y === y) fill = "#2196F3";
        else if (closedSet.has(key)) fill = "#FFEBEE";
        else if (openSet.some(n => n.x === x && n.y === y)) fill = "#E3F2FD";
        
        svg.append("rect")
          .attr("x", x * cellSize + margin)
          .attr("y", y * cellSize + topMargin)
          .attr("width", cellSize - 2)
          .attr("height", cellSize - 2)
          .attr("fill", fill)
          .attr("stroke", "#ddd");
      }
    }
    
    // Legend - two rows to prevent cutoff
    const legend = [
      {label: "Start", color: "#4CAF50"},
      {label: "Goal", color: "#FF5722"},
      {label: "Obstacle", color: "#333"},
      {label: "Path", color: "#FFC107"},
      {label: "Current", color: "#2196F3"},
      {label: "Checked", color: "#FFEBEE"},
      {label: "To Check", color: "#E3F2FD"}
    ];
    
    const legendY = gridSize * cellSize + topMargin + 30; // More space between grid and legend
    const itemWidth = 90; // Reduced for better mobile fit
    const itemsPerRow = 4;
    
    legend.forEach((item, i) => {
      const row = Math.floor(i / itemsPerRow);
      const col = i % itemsPerRow;
      const x = margin + col * itemWidth;
      const y = legendY + row * 18;
      
      svg.append("rect")
        .attr("x", x)
        .attr("y", y)
        .attr("width", 12)
        .attr("height", 12)
        .attr("fill", item.color);
      
      svg.append("text")
        .attr("x", x + 16)
        .attr("y", y + 10)
        .attr("font-size", "11")
        .attr("fill", "#333")
        .text(item.label);
    });
  }
  
  reset();
})();
</script>

<p>The <strong>green</strong> cell is where the enemy starts, and the <strong>red</strong> cell is the player’s position. <strong>Black obstacles</strong> block the way, forcing the algorithm to navigate around them.</p>

<p>As A* explores, it marks cells in <strong>light blue</strong> (candidates to check next) and <strong>pink</strong> (already checked and ruled out). The <strong>dark blue</strong> cell shows what A* is currently examining, and once the path is found, it lights up in <strong>yellow</strong>.</p>

<p>Notice how the algorithm doesn’t waste time exploring cells that are obviously far from the goal, it intelligently prioritizes the most promising directions!</p>

<h4 id="adding-the-pathfinding-crate">Adding the Pathfinding Crate</h4>

<p>We’ll use the <code class="language-plaintext highlighter-rouge">pathfinding</code> crate for the A* algorithm.</p>

<p>Update your <code class="language-plaintext highlighter-rouge">Cargo.toml</code>:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[dependencies]</span>
<span class="py">bevy</span> <span class="p">=</span> <span class="s">"0.18"</span>
<span class="py">bevy_procedural_tilemaps</span> <span class="p">=</span> <span class="s">"0.2.0"</span>
<span class="nn">bevy_common_assets</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.15.0-rc.1"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["ron"]</span> <span class="p">}</span>
<span class="nn">serde</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"1.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["derive"]</span> <span class="p">}</span>
<span class="py">rand</span> <span class="p">=</span> <span class="s">"0.8"</span>
<span class="py">pathfinding</span> <span class="p">=</span> <span class="s">"4.9"</span>  <span class="c"># Add this line</span>
</code></pre></div></div>

<p>Now that we have the pathfinding algorithm, we need to build the <strong>infrastructure</strong> to use it. This involves three pieces:</p>

<ol>
  <li><strong>EnemyPath Component</strong>: Stores the calculated path and tracks which waypoint the enemy is currently moving toward</li>
  <li><strong>CollisionMap Methods</strong>: Extends our collision system with pathfinding capabilities</li>
  <li><strong>AI System</strong>: Uses both to make enemies intelligently navigate to the player</li>
</ol>

<p>Let’s build each piece step by step.</p>

<p><strong>What are waypoints?</strong></p>

<p>Waypoints are like breadcrumbs along a path. Instead of storing “move 3 units north, then 2 units east,” we store actual positions like <code class="language-plaintext highlighter-rouge">[(100, 50), (150, 50), (150, 100)]</code>. The enemy moves toward the first waypoint, and when it gets close enough (16 units), it advances to the next one. This makes pathfinding flexible—waypoints can adapt to any terrain shape.</p>

<div id="waypoint-viz" data-d3-print-frames="800,2000,3200" style="margin: 30px auto; max-width: 500px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <h4 style="margin-top: 0; color: #333 !important;">How Waypoints Work</h4>
  <svg id="waypoint-svg" width="100%" viewBox="0 0 400 320" style="max-width: 400px;"></svg>
  <div id="waypoint-info" style="margin-top: 10px; font-size: 13px; color: #555;"></div>
</div>

<script>
(function() {
  const svg = d3.select("#waypoint-svg");
  const infoDiv = d3.select("#waypoint-info");
  
  // Path waypoints (world coordinates)
  const waypoints = [
    {x: 80, y: 80},
    {x: 180, y: 80},
    {x: 180, y: 150},
    {x: 280, y: 150},
    {x: 280, y: 220}
  ];
  
  let currentIndex = 0;
  let enemy = {x: waypoints[0].x, y: waypoints[0].y};
  const player = waypoints[waypoints.length - 1];
  const speed = 1.5;
  const threshold = 16;
  
  function animate() {
    if (currentIndex >= waypoints.length - 1) {
      // Reached player, reset
      setTimeout(() => {
        currentIndex = 0;
        enemy = {x: waypoints[0].x, y: waypoints[0].y};
      }, 1500);
    } else {
      const target = waypoints[currentIndex + 1];
      const dx = target.x - enemy.x;
      const dy = target.y - enemy.y;
      const distance = Math.sqrt(dx * dx + dy * dy);
      
      if (distance < threshold) {
        currentIndex++;
        infoDiv.html(`Enemy reached waypoint ${currentIndex}! Moving to next...`);
      } else {
        enemy.x += (dx / distance) * speed;
        enemy.y += (dy / distance) * speed;
        infoDiv.html(`Following waypoint ${currentIndex + 1} (${Math.round(distance)}px away)`);
      }
    }
    render();
  }
  
  function render() {
    svg.selectAll("*").remove();
    
    // Draw path lines
    for (let i = 0; i < waypoints.length - 1; i++) {
      svg.append("line")
        .attr("x1", waypoints[i].x)
        .attr("y1", waypoints[i].y)
        .attr("x2", waypoints[i + 1].x)
        .attr("y2", waypoints[i + 1].y)
        .attr("stroke", "#bbb")
        .attr("stroke-width", 2)
        .attr("stroke-dasharray", "5,5");
    }
    
    // Draw waypoints
    waypoints.forEach((wp, i) => {
      const isPassed = i <= currentIndex;
      const isCurrent = i === currentIndex + 1;
      
      svg.append("circle")
        .attr("cx", wp.x)
        .attr("cy", wp.y)
        .attr("r", 6)
        .attr("fill", isPassed ? "#3498db" : (isCurrent ? "#f39c12" : "#ddd"))
        .attr("stroke", isCurrent ? "#e67e22" : "none")
        .attr("stroke-width", 2);
      
      svg.append("text")
        .attr("x", wp.x)
        .attr("y", wp.y - 15)
        .attr("text-anchor", "middle")
        .attr("font-size", "10")
        .attr("fill", "#555")
        .text(`W${i + 1}`);
    });
    
    // Draw enemy (green circle)
    svg.append("circle")
      .attr("cx", enemy.x)
      .attr("cy", enemy.y)
      .attr("r", 10)
      .attr("fill", "#4CAF50")
      .attr("stroke", "white")
      .attr("stroke-width", 2);
    
    svg.append("text")
      .attr("x", enemy.x)
      .attr("y", enemy.y + 4)
      .attr("text-anchor", "middle")
      .attr("font-size", "12")
      .attr("fill", "white")
      .attr("font-weight", "bold")
      .text("E");
    
    // Draw player (red circle)
    svg.append("circle")
      .attr("cx", player.x)
      .attr("cy", player.y)
      .attr("r", 10)
      .attr("fill", "#FF5722")
      .attr("stroke", "white")
      .attr("stroke-width", 2);
    
    svg.append("text")
      .attr("x", player.x)
      .attr("y", player.y + 4)
      .attr("text-anchor", "middle")
      .attr("font-size", "12")
      .attr("fill", "white")
      .attr("font-weight", "bold")
      .text("P");
    
    // Legend
    const legend = [
      {label: "E = Enemy", x: 20, color: "#4CAF50"},
      {label: "P = Player", x: 120, color: "#FF5722"},
      {label: "Waypoint", x: 230, color: "#ddd"}
    ];
    
    legend.forEach(item => {
      svg.append("circle")
        .attr("cx", item.x)
        .attr("cy", 290)
        .attr("r", 5)
        .attr("fill", item.color);
      
      svg.append("text")
        .attr("x", item.x + 10)
        .attr("y", 294)
        .attr("font-size", "11")
        .attr("fill", "#333")
        .text(item.label);
    });
  }
  
  setInterval(animate, 30);
  render();
})();
</script>

<p>The enemy (green) follows each waypoint in sequence. When it gets within 16 pixels of the current waypoint, it moves to the next one. Eventually it navigates the path to reach the player (red)!</p>

<h3 id="the-enemypath-component">The EnemyPath Component</h3>

<p>So let’s create an <code class="language-plaintext highlighter-rouge">EnemyPath</code> component that caches the waypoints, tracks the current position along the path, and manages a recalculation timer.</p>

<p>Update <code class="language-plaintext highlighter-rouge">src/enemy/components.rs</code> to add this at the end:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/enemy/components.rs</span>
<span class="c1">// Add this after the AIBehavior implementation</span>

<span class="cd">/// Cached path for enemy navigation</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">EnemyPath</span> <span class="p">{</span>
    <span class="cd">/// Waypoints in world coordinates</span>
    <span class="k">pub</span> <span class="n">waypoints</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Vec2</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="cd">/// Current waypoint index</span>
    <span class="k">pub</span> <span class="n">current_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="cd">/// Timer for path recalculation</span>
    <span class="k">pub</span> <span class="n">recalc_timer</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">EnemyPath</span> <span class="p">{</span>
    <span class="cd">/// Distance threshold to consider a waypoint reached</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">WAYPOINT_THRESHOLD</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">16.0</span><span class="p">;</span>
    <span class="cd">/// How often to recalculate path (seconds)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">RECALC_INTERVAL</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.5</span><span class="p">;</span>
    
    <span class="cd">/// Get current waypoint position</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">current_waypoint</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Vec2</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.waypoints</span><span class="nf">.get</span><span class="p">(</span><span class="k">self</span><span class="py">.current_index</span><span class="p">)</span><span class="nf">.copied</span><span class="p">()</span>
    <span class="p">}</span>
    
    <span class="cd">/// Advance to next waypoint, returns true if path completed</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">advance</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.current_index</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="k">self</span><span class="py">.current_index</span> <span class="o">&gt;=</span> <span class="k">self</span><span class="py">.waypoints</span><span class="nf">.len</span><span class="p">()</span>
    <span class="p">}</span>
    
    <span class="cd">/// Set a new path (skips first waypoint since it's the starting position)</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">set_path</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">waypoints</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Vec2</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Skip waypoint 0 - it's the enemy's current position</span>
        <span class="c1">// This prevents jitter from briefly facing backwards</span>
        <span class="k">let</span> <span class="n">new_waypoints</span> <span class="o">=</span> <span class="k">if</span> <span class="n">waypoints</span><span class="nf">.len</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="p">{</span>
            <span class="n">waypoints</span><span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">]</span><span class="nf">.to_vec</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">waypoints</span>
        <span class="p">};</span>
        
        <span class="c1">// If we have an existing path, check if new path is similar</span>
        <span class="c1">// This prevents flickering when paths are recalculated</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">current_target</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.current_waypoint</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">new_first</span><span class="p">)</span> <span class="o">=</span> <span class="n">new_waypoints</span><span class="nf">.first</span><span class="p">()</span> <span class="p">{</span>
                <span class="c1">// If new first waypoint is close to our current target,</span>
                <span class="c1">// keep the current path - we're already heading the right way</span>
                <span class="k">if</span> <span class="n">current_target</span><span class="nf">.distance</span><span class="p">(</span><span class="o">*</span><span class="n">new_first</span><span class="p">)</span> <span class="o">&lt;</span> <span class="k">Self</span><span class="p">::</span><span class="n">WAYPOINT_THRESHOLD</span> <span class="o">*</span> <span class="mf">1.5</span> <span class="p">{</span>
                    <span class="k">return</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        
        <span class="k">self</span><span class="py">.waypoints</span> <span class="o">=</span> <span class="n">new_waypoints</span><span class="p">;</span>
        <span class="k">self</span><span class="py">.current_index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="cd">/// Check if we have a valid path</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">has_path</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="o">!</span><span class="k">self</span><span class="py">.waypoints</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="k">self</span><span class="py">.current_index</span> <span class="o">&lt;</span> <span class="k">self</span><span class="py">.waypoints</span><span class="nf">.len</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Let’s break down each piece:</strong></p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">waypoints</code></strong>: Stores the path as a vector of world positions. Once we calculate a path, we cache it here.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">current_index</code></strong>: Tracks which waypoint we’re heading toward. When we reach waypoint 0, we increment to waypoint 1, etc.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">recalc_timer</code></strong>: Counts down from 0.5 seconds. When it hits zero, we recalculate the path (in case the player moved).</li>
  <li><strong><code class="language-plaintext highlighter-rouge">current_waypoint()</code></strong>: Returns the waypoint we’re currently heading toward (or None if we’ve completed the path).</li>
  <li><strong><code class="language-plaintext highlighter-rouge">advance()</code></strong>: Moves to the next waypoint, returns true if we’ve completed the entire path.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">set_path()</code></strong>: Stores a new path with smart optimizations:
    <ul>
      <li>Skips waypoint 0 (the enemy’s current position) to avoid jitter</li>
      <li>Checks if the new path is similar to the current one - if so, keeps the existing path to reduce flickering</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">has_path()</code></strong>: Returns true if we have a valid, incomplete path to follow.</li>
</ul>

<p><strong>Why cache paths?</strong></p>

<p>Running A* every frame would slow down the game. By recalculating every 0.5 seconds, we get smooth movement with minimal performance cost.</p>

<p><strong>Why have a waypoint threshold?</strong></p>

<p>Due to movement speed and frame timing, an enemy might never land exactly on a waypoint. The 16-unit threshold lets us consider it “reached” when close enough.</p>

<hr />

<p>Now that we can store paths, we need to <strong>calculate</strong> them. But who does the pathfinding? The <code class="language-plaintext highlighter-rouge">CollisionMap</code>!</p>

<p><strong>Why does <code class="language-plaintext highlighter-rouge">CollisionMap</code> do pathfinding? Shouldn’t the enemy figure out its own path?</strong></p>

<p>Good question! The <code class="language-plaintext highlighter-rouge">CollisionMap</code> already knows which tiles are walkable and which aren’t, it has all the terrain data. Rather than duplicating this knowledge in the enemy AI, we extend <code class="language-plaintext highlighter-rouge">CollisionMap</code> with pathfinding methods. This way:</p>
<ul>
  <li><strong>Enemies</strong> just ask: “Give me a path to position X”</li>
  <li><strong>CollisionMap</strong> responds: “Here’s the list of waypoints to get there”</li>
</ul>

<p>This keeps our code clean and follows the principle: <em>the component with the data provides the operations on that data</em>.</p>

<h3 id="extending-collisionmap-for-pathfinding">Extending CollisionMap for Pathfinding</h3>

<p>Our collision map knows about walkable tiles, but it doesn’t know how to <em>find a path</em> through them. We need to:</p>
<ol>
  <li>Get valid neighboring cells (ensuring diagonal moves don’t cut corners through walls)</li>
  <li>Find the nearest walkable tile if the target is blocked</li>
  <li>Run A* pathfinding to calculate the optimal path</li>
</ol>

<p><strong>What does “don’t cut corners” mean?</strong></p>

<p>Imagine two walls meeting at a corner diagonally. Without proper checks, an enemy could move diagonally through that gap, which looks like walking through walls! We prevent this by only allowing diagonal movement when both adjacent cardinal cells are also walkable.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  ┌───┬───┐
  │   │ W │  W = Wall
  ├───┼───┤  
  │ W │   │  Can't cut diagonally!
  └───┴───┘
</code></pre></div></div>

<p>So let’s add these three methods to <code class="language-plaintext highlighter-rouge">CollisionMap</code> that work together to provide intelligent pathfinding.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/collision/map.rs</code> and add these methods at the end of the <code class="language-plaintext highlighter-rouge">impl CollisionMap</code> block:</p>

<p>First, add the import at the top of <code class="language-plaintext highlighter-rouge">src/collision/map.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/map.rs  </span>
<span class="k">use</span> <span class="nn">pathfinding</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="n">astar</span><span class="p">;</span>
</code></pre></div></div>

<p>Now add these methods at the end of the <code class="language-plaintext highlighter-rouge">impl CollisionMap</code> block:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/map.rs</span>
<span class="c1">// Add at the end of impl CollisionMap, before the closing brace</span>

    <span class="cd">/// Get walkable neighboring grid cells (8 directions)</span>
    <span class="cd">/// Diagonal movement only allowed if both adjacent cardinals are clear</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">get_neighbors</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">pos</span><span class="p">:</span> <span class="n">IVec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">IVec2</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">neighbors</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        
        <span class="c1">// Cardinal directions (always allowed if walkable)</span>
        <span class="k">let</span> <span class="n">cardinals</span> <span class="o">=</span> <span class="p">[</span>
            <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
        <span class="p">];</span>
        
        <span class="k">for</span> <span class="n">dir</span> <span class="k">in</span> <span class="n">cardinals</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">neighbor</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="n">dir</span><span class="p">;</span>
            <span class="k">if</span> <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">neighbor</span><span class="py">.x</span><span class="p">,</span> <span class="n">neighbor</span><span class="py">.y</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">neighbors</span><span class="nf">.push</span><span class="p">(</span><span class="n">neighbor</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
        
        <span class="c1">// Diagonal directions - only if both adjacent cardinals are clear</span>
        <span class="c1">// This prevents corner-cutting through diagonal walls</span>
        <span class="k">let</span> <span class="n">diagonals</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">(</span><span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)),</span>   <span class="c1">// Up-Left</span>
            <span class="p">(</span><span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)),</span>     <span class="c1">// Up-Right</span>
            <span class="p">(</span><span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)),</span> <span class="c1">// Down-Left</span>
            <span class="p">(</span><span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)),</span>   <span class="c1">// Down-Right</span>
        <span class="p">];</span>
        
        <span class="k">for</span> <span class="p">(</span><span class="n">diagonal</span><span class="p">,</span> <span class="n">adj1</span><span class="p">,</span> <span class="n">adj2</span><span class="p">)</span> <span class="k">in</span> <span class="n">diagonals</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">diag_pos</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="n">diagonal</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">adj1_pos</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="n">adj1</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">adj2_pos</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="n">adj2</span><span class="p">;</span>
            
            <span class="c1">// Only allow diagonal if destination AND both adjacent cells are walkable</span>
            <span class="k">if</span> <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">diag_pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">diag_pos</span><span class="py">.y</span><span class="p">)</span>
                <span class="o">&amp;&amp;</span> <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">adj1_pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">adj1_pos</span><span class="py">.y</span><span class="p">)</span>
                <span class="o">&amp;&amp;</span> <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">adj2_pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">adj2_pos</span><span class="py">.y</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">neighbors</span><span class="nf">.push</span><span class="p">(</span><span class="n">diag_pos</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
        
        <span class="n">neighbors</span>
    <span class="p">}</span>
    
    <span class="cd">/// Find path using A* algorithm</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">find_path</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">start</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">goal</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Vec2</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
        <span class="k">use</span> <span class="nn">pathfinding</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="n">astar</span><span class="p">;</span>
        
        <span class="k">let</span> <span class="n">start_grid</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.world_to_grid</span><span class="p">(</span><span class="n">start</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">goal_grid</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.world_to_grid</span><span class="p">(</span><span class="n">goal</span><span class="p">);</span>
        
        <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">start_grid</span><span class="py">.x</span><span class="p">,</span> <span class="n">start_grid</span><span class="py">.y</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nb">None</span><span class="p">;</span>
        <span class="p">}</span>
        
        <span class="k">let</span> <span class="n">actual_goal</span> <span class="o">=</span> <span class="k">if</span> <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">goal_grid</span><span class="py">.x</span><span class="p">,</span> <span class="n">goal_grid</span><span class="py">.y</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">goal_grid</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">self</span><span class="nf">.find_nearest_walkable</span><span class="p">(</span><span class="n">goal_grid</span><span class="p">)</span><span class="o">?</span>
        <span class="p">};</span>
        
        <span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="nf">astar</span><span class="p">(</span>
            <span class="o">&amp;</span><span class="n">start_grid</span><span class="p">,</span>
            <span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="o">*</span><span class="n">pos</span><span class="p">;</span>
                <span class="k">self</span><span class="nf">.get_neighbors</span><span class="p">(</span><span class="n">pos</span><span class="p">)</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.map</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">n</span><span class="p">|</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">cost</span> <span class="o">=</span> <span class="k">if</span> <span class="p">(</span><span class="n">n</span><span class="py">.x</span> <span class="o">-</span> <span class="n">pos</span><span class="py">.x</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">+</span> <span class="p">(</span><span class="n">n</span><span class="py">.y</span> <span class="o">-</span> <span class="n">pos</span><span class="py">.y</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">==</span> <span class="mi">2</span> <span class="p">{</span>
                        <span class="mi">14u32</span> <span class="c1">// Diagonal</span>
                    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                        <span class="mi">10u32</span> <span class="c1">// Cardinal</span>
                    <span class="p">};</span>
                    <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">cost</span><span class="p">)</span>
                <span class="p">})</span>
            <span class="p">},</span>
            <span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">dx</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="py">.x</span> <span class="o">-</span> <span class="n">actual_goal</span><span class="py">.x</span><span class="p">)</span><span class="nf">.abs</span><span class="p">();</span>
                <span class="k">let</span> <span class="n">dy</span> <span class="o">=</span> <span class="p">(</span><span class="n">pos</span><span class="py">.y</span> <span class="o">-</span> <span class="n">actual_goal</span><span class="py">.y</span><span class="p">)</span><span class="nf">.abs</span><span class="p">();</span>
                <span class="p">((</span><span class="n">dx</span> <span class="o">+</span> <span class="n">dy</span><span class="p">)</span> <span class="o">*</span> <span class="mi">10</span><span class="p">)</span> <span class="k">as</span> <span class="nb">u32</span>
            <span class="p">},</span>
            <span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="o">*</span><span class="n">pos</span> <span class="o">==</span> <span class="n">actual_goal</span><span class="p">,</span>
        <span class="p">);</span>
        
        <span class="n">result</span><span class="nf">.map</span><span class="p">(|(</span><span class="n">path</span><span class="p">,</span> <span class="n">_cost</span><span class="p">)|</span> <span class="p">{</span>
            <span class="n">path</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.map</span><span class="p">(|</span><span class="n">p</span><span class="p">|</span> <span class="k">self</span><span class="nf">.grid_to_world</span><span class="p">(</span><span class="n">p</span><span class="py">.x</span><span class="p">,</span> <span class="n">p</span><span class="py">.y</span><span class="p">))</span><span class="nf">.collect</span><span class="p">()</span>
        <span class="p">})</span>
    <span class="p">}</span>
    
    <span class="cd">/// Find nearest walkable cell</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">find_nearest_walkable</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">pos</span><span class="p">:</span> <span class="n">IVec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">IVec2</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">radius</span> <span class="k">in</span> <span class="mi">1i32</span><span class="o">..</span><span class="mi">10</span> <span class="p">{</span>
            <span class="k">for</span> <span class="n">dx</span> <span class="k">in</span> <span class="o">-</span><span class="n">radius</span><span class="o">..=</span><span class="n">radius</span> <span class="p">{</span>
                <span class="k">for</span> <span class="n">dy</span> <span class="k">in</span> <span class="o">-</span><span class="n">radius</span><span class="o">..=</span><span class="n">radius</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="n">dx</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">==</span> <span class="n">radius</span> <span class="p">||</span> <span class="n">dy</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">==</span> <span class="n">radius</span> <span class="p">{</span>
                        <span class="k">let</span> <span class="n">check</span> <span class="o">=</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">pos</span><span class="py">.x</span> <span class="o">+</span> <span class="n">dx</span><span class="p">,</span> <span class="n">pos</span><span class="py">.y</span> <span class="o">+</span> <span class="n">dy</span><span class="p">);</span>
                        <span class="k">if</span> <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">check</span><span class="py">.x</span><span class="p">,</span> <span class="n">check</span><span class="py">.y</span><span class="p">)</span> <span class="p">{</span>
                            <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">check</span><span class="p">);</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nb">None</span>
    <span class="p">}</span>

    <span class="cd">/// Find nearest position where a circle of the given radius is fully clear.</span>
    <span class="cd">/// Searches expanding rings up to 20 tiles from the given world position.</span>
    <span class="cd">/// Returns a world-space position (tile center) or None if nothing found.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">find_nearest_clear_position</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">world_pos</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Vec2</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">grid_pos</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.world_to_grid</span><span class="p">(</span><span class="n">world_pos</span><span class="p">);</span>

        <span class="k">for</span> <span class="n">ring</span> <span class="k">in</span> <span class="mi">0i32</span><span class="o">..</span><span class="mi">20</span> <span class="p">{</span>
            <span class="k">for</span> <span class="n">dx</span> <span class="k">in</span> <span class="o">-</span><span class="n">ring</span><span class="o">..=</span><span class="n">ring</span> <span class="p">{</span>
                <span class="k">for</span> <span class="n">dy</span> <span class="k">in</span> <span class="o">-</span><span class="n">ring</span><span class="o">..=</span><span class="n">ring</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="n">ring</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">dx</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">!=</span> <span class="n">ring</span> <span class="o">&amp;&amp;</span> <span class="n">dy</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">!=</span> <span class="n">ring</span> <span class="p">{</span>
                        <span class="k">continue</span><span class="p">;</span> <span class="c1">// Only check the ring perimeter</span>
                    <span class="p">}</span>
                    <span class="k">let</span> <span class="n">candidate_grid</span> <span class="o">=</span> <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">grid_pos</span><span class="py">.x</span> <span class="o">+</span> <span class="n">dx</span><span class="p">,</span> <span class="n">grid_pos</span><span class="py">.y</span> <span class="o">+</span> <span class="n">dy</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">candidate_world</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.grid_to_world</span><span class="p">(</span><span class="n">candidate_grid</span><span class="py">.x</span><span class="p">,</span> <span class="n">candidate_grid</span><span class="py">.y</span><span class="p">);</span>
                    <span class="k">if</span> <span class="k">self</span><span class="nf">.is_circle_clear</span><span class="p">(</span><span class="n">candidate_world</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">candidate_world</span><span class="p">);</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nb">None</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><strong>Let’s break down the pathfinding code:</strong></p>

<p><strong>1. Neighbor Detection (<code class="language-plaintext highlighter-rouge">get_neighbors</code>):</strong></p>
<ul>
  <li>First checks all 4 cardinal directions (up/down/left/right)</li>
  <li>Then checks all 4 diagonal directions <strong>only if</strong> both adjacent cardinals are walkable</li>
  <li>This prevents enemies from “cutting corners” through diagonal walls</li>
</ul>

<p><strong>2. Nearest Walkable Search (<code class="language-plaintext highlighter-rouge">find_nearest_walkable</code>):</strong></p>
<ul>
  <li>Uses a spiral search pattern starting from the target</li>
  <li>Checks increasingly larger squares until it finds a walkable tile</li>
  <li>Returns None if nothing found within 10 tiles (player might be unreachable)</li>
</ul>

<p><strong>2b. Nearest Clear Position (<code class="language-plaintext highlighter-rouge">find_nearest_clear_position</code>):</strong></p>
<ul>
  <li>Like <code class="language-plaintext highlighter-rouge">find_nearest_walkable</code>, but validates the <strong>full collider circle</strong> at each candidate, not just the tile type</li>
  <li>Used for spawning, where we need to guarantee the entity won’t overlap any obstacle after placement</li>
  <li>Searches up to 20 tiles and returns a world-space position ready to use</li>
</ul>

<p><strong>3. A* Pathfinding (<code class="language-plaintext highlighter-rouge">find_path</code>):</strong></p>
<ul>
  <li>Converts world positions to grid coordinates</li>
  <li>Validates start position; if goal is blocked, finds nearest walkable alternative</li>
  <li>Runs A* algorithm using:
    <ul>
      <li><strong>Successors</strong>: Get neighbors with movement cost (10 for cardinal, 14 for diagonal)</li>
      <li><strong>Heuristic</strong>: Manhattan distance estimate to goal</li>
      <li><strong>Success</strong>: Checks if the current position is the goal</li>
    </ul>
  </li>
  <li>Converts resulting grid path back to world coordinates</li>
</ul>

<h3 id="understanding-the-astar-function">Understanding the <code class="language-plaintext highlighter-rouge">astar</code> Function</h3>

<p>The A* function doesn’t care if you’re navigating a dungeon, flying a spaceship, or routing a delivery truck, it just needs to know “what positions exist” (nodes) and “how expensive is it to move between them” (costs).</p>

<p>Hence it’s just a generic function that works with any node type <code class="language-plaintext highlighter-rouge">N</code> and cost type <code class="language-plaintext highlighter-rouge">C</code>. Node in our situation, is a position on the board where the enemy can stand (in our case, grid coordinates like <code class="language-plaintext highlighter-rouge">(5, 3)</code>)</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use </span>
<span class="k">pub</span> <span class="k">fn</span> <span class="n">astar</span><span class="o">&lt;...&gt;</span><span class="p">(</span>
    <span class="n">start</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">N</span><span class="p">,</span>
    <span class="n">successors</span><span class="p">:</span> <span class="n">FN</span><span class="p">,</span>
    <span class="n">heuristic</span><span class="p">:</span> <span class="n">FH</span><span class="p">,</span>
    <span class="n">success</span><span class="p">:</span> <span class="n">FS</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="p">(</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">N</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span><span class="o">&gt;</span>
</code></pre></div></div>
<p>What matters are the <strong>four parameters</strong>:</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">start: &amp;N</code></strong> - Enemy’s starting position</li>
  <li><strong><code class="language-plaintext highlighter-rouge">successors: FN</code></strong> - A function that answers “where can I move from here?” It helps us know from the enemy’s current tile, which adjacent tiles can it walk to and how much does each move cost?</li>
  <li><strong><code class="language-plaintext highlighter-rouge">heuristic: FH</code></strong> - A function that guesses distance to target or “how many tiles away is the player from here?”. It helps enemy prioritize which direction to explore first.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">success: FS</code></strong> - A function that checks “are we there yet?”. It returns true when the enemy has reached the player’s position.</li>
</ol>

<p>The function returns <code class="language-plaintext highlighter-rouge">Option&lt;(Vec&lt;N&gt;, C)&gt;</code>:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Some((path, cost))</code> A list of grid positions the enemy should walk through to reach the player, plus the total cost.</li>
  <li><code class="language-plaintext highlighter-rouge">None</code> if no path exists or player is completely blocked off by obstacles, enemy can’t reach them.</li>
</ul>

<p>Now let’s see how we provide these functions using <strong>closures</strong>:</p>

<p><strong>Understanding Closures in the A* Implementation:</strong></p>

<p>You might notice the <code class="language-plaintext highlighter-rouge">astar</code> function looks a bit odd, it takes <strong>closures</strong> (anonymous functions) as arguments. This is a powerful Rust pattern called <strong>higher-order functions</strong>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="nf">astar</span><span class="p">(</span>
    <span class="o">&amp;</span><span class="n">start_grid</span><span class="p">,</span>              <span class="c1">// Starting position</span>
    <span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="p">{</span> <span class="cm">/* closure 1 */</span> <span class="p">},</span>  <span class="c1">// How to get neighbors and costs</span>
    <span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="p">{</span> <span class="cm">/* closure 2 */</span> <span class="p">},</span>  <span class="c1">// How to estimate distance to goal</span>
    <span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="p">{</span> <span class="cm">/* closure 3 */</span> <span class="p">},</span>  <span class="c1">// How to check if we reached the goal</span>
<span class="p">);</span>
</code></pre></div></div>

<p><strong>Why closures?</strong> 
The <code class="language-plaintext highlighter-rouge">pathfinding</code> crate’s <code class="language-plaintext highlighter-rouge">astar</code> function is generic - it works for ANY type of pathfinding problem (chess moves, graph traversal, tile grids, etc.). By accepting closures, it lets YOU define:</p>
<ol>
  <li>What counts as a “neighbor” (straight lines only? diagonals too? teleportation?)</li>
  <li>What the movement costs are (flat terrain? hills?)</li>
  <li>How to estimate distance (Manhattan? Euclidean?)</li>
  <li>What the goal condition is</li>
</ol>

<p><strong>How do closures capture data?</strong></p>

<p>Notice that the closures reference <code class="language-plaintext highlighter-rouge">self</code> and <code class="language-plaintext highlighter-rouge">actual_goal</code> , variables from the outer scope. Rust closures can “capture” these variables:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code don't use</span>
<span class="p">|</span><span class="n">pos</span><span class="p">|</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="o">*</span><span class="n">pos</span><span class="p">;</span>
    <span class="k">self</span><span class="nf">.get_neighbors</span><span class="p">(</span><span class="n">pos</span><span class="p">)</span>  <span class="c1">// Uses 'self' from outer scope!</span>
        <span class="nf">.into_iter</span><span class="p">()</span>
        <span class="nf">.map</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">n</span><span class="p">|</span> <span class="p">{</span>
            <span class="c1">// Calculate cost based on movement type</span>
            <span class="k">let</span> <span class="n">cost</span> <span class="o">=</span> <span class="k">if</span> <span class="p">(</span><span class="n">n</span><span class="py">.x</span> <span class="o">-</span> <span class="n">pos</span><span class="py">.x</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">+</span> <span class="p">(</span><span class="n">n</span><span class="py">.y</span> <span class="o">-</span> <span class="n">pos</span><span class="py">.y</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">==</span> <span class="mi">2</span> <span class="p">{</span>
                <span class="mi">14u32</span>  <span class="c1">// Diagonal movement</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="mi">10u32</span>  <span class="c1">// Cardinal movement</span>
            <span class="p">};</span>
            <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">cost</span><span class="p">)</span>
    <span class="p">})</span>
 <span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s the <code class="language-plaintext highlighter-rouge">move</code> keyword?</strong></p>

<p>In the inner closure (the one with <code class="language-plaintext highlighter-rouge">.map(move |n| ...)</code>), the <code class="language-plaintext highlighter-rouge">move</code> keyword forces the closure to <strong>take ownership</strong> of the captured variable <code class="language-plaintext highlighter-rouge">pos</code>. Without <code class="language-plaintext highlighter-rouge">move</code>, the closure would borrow <code class="language-plaintext highlighter-rouge">pos</code>, but since we’re returning this closure from <code class="language-plaintext highlighter-rouge">.map()</code>, it needs to own its data. Think of it like: “This closure is leaving home, so it needs to pack its own copy of <code class="language-plaintext highlighter-rouge">pos</code>.”</p>

<p><strong>How does borrowing work with <code class="language-plaintext highlighter-rouge">self.get_neighbors</code>?</strong></p>

<p>Great question! When the closure captures <code class="language-plaintext highlighter-rouge">self</code>, it’s actually borrowing it:</p>
<ul>
  <li>The outer closure borrows <code class="language-plaintext highlighter-rouge">self</code> immutably (just reading from it)</li>
  <li><code class="language-plaintext highlighter-rouge">self.get_neighbors(pos)</code> only needs to read the collision map data</li>
  <li>The <code class="language-plaintext highlighter-rouge">astar</code> function promises to only call the closure while we’re in the <code class="language-plaintext highlighter-rouge">find_path</code> method</li>
  <li>No ownership is transferred, so no borrowing rules are broken!</li>
</ul>

<p>The borrowing is temporary and safe because:</p>
<ol>
  <li>We’re not trying to modify <code class="language-plaintext highlighter-rouge">self</code> (immutable borrow is fine)</li>
  <li>The closure only exists during the <code class="language-plaintext highlighter-rouge">astar</code> call, not after</li>
  <li>Rust’s borrow checker verifies this at compile time</li>
</ol>

<p>This closure is essentially a custom function that says: “For any position, here’s how to find its neighbors and the cost to reach them.”</p>

<h4 id="the-ai-system-with-pathfinding">The AI System with Pathfinding</h4>

<p>Now we can implement AI that uses pathfinding.</p>

<p>We need enemies to intelligently pursue the player by:</p>
<ol>
  <li>Detecting when the player is in range</li>
  <li>Using pathfinding to navigate around obstacles</li>
  <li>Stopping to attack when close enough</li>
  <li>Handling edge cases (player too far, pathfinding fails, etc.)</li>
  <li>Preventing jitter and oscillation with smart state transitions</li>
</ol>

<p>Build an AI system that manages three behavior states (idle, following, attacking) with smooth transitions between them.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/enemy/ai.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/enemy/ai.rs</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">components</span><span class="p">::{</span><span class="n">AIBehavior</span><span class="p">,</span> <span class="n">Enemy</span><span class="p">,</span> <span class="n">EnemyPath</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::{</span>
    <span class="nn">config</span><span class="p">::</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="nn">facing</span><span class="p">::</span><span class="n">Facing</span><span class="p">,</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">,</span>
    <span class="nn">physics</span><span class="p">::{</span><span class="n">Velocity</span><span class="p">,</span> <span class="n">calculate_velocity</span><span class="p">},</span>
    <span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMap</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// AI system that makes enemies follow the player using A* pathfinding</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">enemy_follow_player</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">collision_map</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CollisionMap</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">enemy_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span>
        <span class="p">(</span>
            <span class="o">&amp;</span><span class="n">Transform</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">CharacterState</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Velocity</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Facing</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">AIBehavior</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EnemyPath</span><span class="p">,</span>
        <span class="p">),</span>
        <span class="n">With</span><span class="o">&lt;</span><span class="n">Enemy</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="o">&gt;</span><span class="p">,</span>
    <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">player_transform</span><span class="p">)</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.single</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">collision_map</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="n">player_pos</span> <span class="o">=</span> <span class="n">player_transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">enemy_transform</span><span class="p">,</span> <span class="k">mut</span> <span class="n">state</span><span class="p">,</span> <span class="k">mut</span> <span class="n">velocity</span><span class="p">,</span> <span class="k">mut</span> <span class="n">facing</span><span class="p">,</span> <span class="n">character</span><span class="p">,</span> <span class="n">ai</span><span class="p">,</span> <span class="k">mut</span> <span class="n">path</span><span class="p">)</span> <span class="k">in</span>
        <span class="n">enemy_query</span><span class="nf">.iter_mut</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">let</span> <span class="n">enemy_pos</span> <span class="o">=</span> <span class="n">enemy_transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">to_player</span> <span class="o">=</span> <span class="n">player_pos</span> <span class="o">-</span> <span class="n">enemy_pos</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">to_player</span><span class="nf">.length</span><span class="p">();</span>

        <span class="c1">// Outside detection range - go idle</span>
        <span class="k">if</span> <span class="n">distance</span> <span class="o">&gt;</span> <span class="n">ai</span><span class="py">.detection_range</span> <span class="p">{</span>
            <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">!=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="o">*</span><span class="n">velocity</span> <span class="o">=</span> <span class="nn">Velocity</span><span class="p">::</span><span class="n">ZERO</span><span class="p">;</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Within attack range - stop and attack</span>
        <span class="c1">// Use hysteresis: different threshold for staying vs entering attack mode</span>
        <span class="c1">// This prevents oscillation at the boundary</span>
        <span class="k">let</span> <span class="n">attack_threshold</span> <span class="o">=</span> <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">==</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">{</span>
            <span class="n">ai</span><span class="py">.attack_range</span> <span class="o">+</span> <span class="mf">20.0</span> <span class="c1">// Stay in attack mode even if player moves slightly away</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">ai</span><span class="py">.attack_range</span> <span class="c1">// Enter attack mode at normal range</span>
        <span class="p">};</span>
        
        <span class="k">if</span> <span class="n">distance</span> <span class="o">&lt;=</span> <span class="n">attack_threshold</span> <span class="p">{</span>
            <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">!=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="o">*</span><span class="n">velocity</span> <span class="o">=</span> <span class="nn">Velocity</span><span class="p">::</span><span class="n">ZERO</span><span class="p">;</span>
            
            <span class="c1">// Face the player while attacking</span>
            <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="n">to_player</span><span class="nf">.normalize_or_zero</span><span class="p">();</span>
            <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">new_facing</span> <span class="o">=</span> <span class="nn">Facing</span><span class="p">::</span><span class="nf">from_velocity</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>
                <span class="k">if</span> <span class="o">*</span><span class="n">facing</span> <span class="o">!=</span> <span class="n">new_facing</span> <span class="p">{</span>
                    <span class="o">*</span><span class="n">facing</span> <span class="o">=</span> <span class="n">new_facing</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Need to move toward player - use pathfinding</span>
        <span class="n">path</span><span class="py">.recalc_timer</span> <span class="o">-=</span> <span class="n">delta</span><span class="p">;</span>
        
        <span class="c1">// Recalculate path if we don't have one</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">path</span><span class="nf">.has_path</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waypoints</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span><span class="nf">.find_path</span><span class="p">(</span><span class="n">enemy_pos</span><span class="p">,</span> <span class="n">player_pos</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">path</span><span class="nf">.set_path</span><span class="p">(</span><span class="n">waypoints</span><span class="p">);</span>
                <span class="n">path</span><span class="py">.recalc_timer</span> <span class="o">=</span> <span class="nn">EnemyPath</span><span class="p">::</span><span class="n">RECALC_INTERVAL</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">path</span><span class="py">.recalc_timer</span> <span class="o">&lt;=</span> <span class="mf">0.0</span> <span class="p">{</span>
            <span class="c1">// Periodically update existing path  </span>
            <span class="n">path</span><span class="py">.recalc_timer</span> <span class="o">=</span> <span class="nn">EnemyPath</span><span class="p">::</span><span class="n">RECALC_INTERVAL</span><span class="p">;</span>
            
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waypoints</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span><span class="nf">.find_path</span><span class="p">(</span><span class="n">enemy_pos</span><span class="p">,</span> <span class="n">player_pos</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">path</span><span class="nf">.set_path</span><span class="p">(</span><span class="n">waypoints</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// Follow current waypoint</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">waypoint</span><span class="p">)</span> <span class="o">=</span> <span class="n">path</span><span class="nf">.current_waypoint</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">to_waypoint</span> <span class="o">=</span> <span class="n">waypoint</span> <span class="o">-</span> <span class="n">enemy_pos</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">waypoint_distance</span> <span class="o">=</span> <span class="n">to_waypoint</span><span class="nf">.length</span><span class="p">();</span>
            
            <span class="c1">// Check if we reached the waypoint</span>
            <span class="k">if</span> <span class="n">waypoint_distance</span> <span class="o">&lt;</span> <span class="nn">EnemyPath</span><span class="p">::</span><span class="n">WAYPOINT_THRESHOLD</span> <span class="p">{</span>
                <span class="n">path</span><span class="nf">.advance</span><span class="p">();</span>
            <span class="p">}</span>
            
            <span class="c1">// Recalculate direction for current waypoint (might have advanced)</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">current_wp</span><span class="p">)</span> <span class="o">=</span> <span class="n">path</span><span class="nf">.current_waypoint</span><span class="p">()</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">to_waypoint</span> <span class="o">=</span> <span class="n">current_wp</span> <span class="o">-</span> <span class="n">enemy_pos</span><span class="p">;</span>
                <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="n">to_waypoint</span><span class="nf">.normalize_or_zero</span><span class="p">();</span>
            
            <span class="c1">// Update state</span>
            <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">!=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span><span class="p">;</span>
            <span class="p">}</span>
            
            <span class="c1">// Update facing</span>
            <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">new_facing</span> <span class="o">=</span> <span class="nn">Facing</span><span class="p">::</span><span class="nf">from_velocity</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>
                <span class="k">if</span> <span class="o">*</span><span class="n">facing</span> <span class="o">!=</span> <span class="n">new_facing</span> <span class="p">{</span>
                    <span class="o">*</span><span class="n">facing</span> <span class="o">=</span> <span class="n">new_facing</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
                
                <span class="c1">// Calculate velocity toward waypoint</span>
                <span class="o">*</span><span class="n">velocity</span> <span class="o">=</span> <span class="nf">calculate_velocity</span><span class="p">(</span><span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="n">direction</span><span class="p">,</span> <span class="n">character</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// No path available - fallback to direct movement</span>
            <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="n">to_player</span><span class="nf">.normalize_or_zero</span><span class="p">();</span>
            
            <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">!=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="p">{</span>
                <span class="o">*</span><span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span><span class="p">;</span>
            <span class="p">}</span>
            
            <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">new_facing</span> <span class="o">=</span> <span class="nn">Facing</span><span class="p">::</span><span class="nf">from_velocity</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>
                <span class="k">if</span> <span class="o">*</span><span class="n">facing</span> <span class="o">!=</span> <span class="n">new_facing</span> <span class="p">{</span>
                    <span class="o">*</span><span class="n">facing</span> <span class="o">=</span> <span class="n">new_facing</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
            
            <span class="o">*</span><span class="n">velocity</span> <span class="o">=</span> <span class="nf">calculate_velocity</span><span class="p">(</span><span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="n">direction</span><span class="p">,</span> <span class="n">character</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How does this work?</strong></p>

<p>The query fetches all enemies with their path component. We also get the <code class="language-plaintext highlighter-rouge">CollisionMap</code> resource and player’s position.</p>

<p>For each enemy, we check three ranges:</p>
<ul>
  <li><strong>Too far</strong> (beyond detection range): Go idle</li>
  <li><strong>In attack range</strong>: Stop moving, face player</li>
  <li><strong>Between ranges</strong>: Use pathfinding to approach</li>
</ul>

<p><strong>Attack range hysteresis:</strong> We use different thresholds for entering vs staying in attack mode:</p>
<ul>
  <li>Enter attack: <code class="language-plaintext highlighter-rouge">distance &lt;= attack_range</code> (150)</li>
  <li>Stay attacking: <code class="language-plaintext highlighter-rouge">distance &lt;= attack_range + 20</code> (170)</li>
</ul>

<p>This prevents <strong>oscillation</strong>, a buggy behavior where the enemy rapidly flickers between states. Imagine the player is exactly 150 units away (the attack range). The enemy enters attack mode, but as soon as it stops moving, the distance might increase to 151 units, triggering follow mode again. This causes the enemy to jitter back and forth between “attack” and “follow” dozens of times per second! By adding a buffer zone, the enemy must move further away (170 units) before switching back to following.</p>

<p><strong>Path management:</strong> We separate path creation from path updates:</p>
<ol>
  <li><strong>No path</strong> → Create immediately and reset timer</li>
  <li><strong>Have path, timer expired</strong> → Update periodically (every 0.5s)</li>
</ol>

<p>This prevents the path from resetting mid-frame when enemies complete waypoints.</p>

<p><strong>Path similarity check:</strong> Before replacing an existing path, we check if the new path’s first waypoint is close to our current target. If so, we keep the existing path since we’re already heading the right direction. This reduces flickering during periodic updates.</p>

<p><strong>Skipping the first waypoint:</strong> A* includes the starting position as waypoint 0. If we don’t skip it, enemies briefly face backward (toward their own position) before turning to the actual destination. By skipping waypoint 0 in <code class="language-plaintext highlighter-rouge">set_path()</code>, enemies immediately move toward the first real destination.</p>

<p><strong>Waypoint advancement:</strong> When we reach a waypoint, we call <code class="language-plaintext highlighter-rouge">advance()</code> and immediately recalculate the direction for the NEW current waypoint in the same frame. This prevents jitter from skipping frames between waypoints.</p>

<p><strong>Fallback behavior:</strong> If pathfinding fails, we fall back to direct movement. This ensures enemies don’t get completely stuck.</p>

<p><strong>Why check if state changed?</strong></p>

<p>Bevy’s change detection tracks when components are modified. If we set <code class="language-plaintext highlighter-rouge">*state = new_state</code> every frame even when the value doesn’t change, Bevy thinks the state changed. This triggers systems that run on <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code>, like animation updates. By only updating when the value actually changes, we avoid unnecessary work.</p>

<h3 id="making-enemies-attack">Making Enemies Attack</h3>

<p>Enemies can follow the player, but they need to attack. We’ll create a combat system that fires when enemies are in range and their cooldown is ready.</p>

<p>The combat system needs to:</p>
<ol>
  <li>Tick the cooldown timer</li>
  <li>Check if the player is in attack range</li>
  <li>Fire a projectile when ready</li>
  <li>Reset the cooldown</li>
</ol>

<p>Create <code class="language-plaintext highlighter-rouge">src/enemy/combat.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/enemy/combat.rs</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">components</span><span class="p">::{</span><span class="n">AIBehavior</span><span class="p">,</span> <span class="n">Enemy</span><span class="p">,</span> <span class="n">EnemyCombat</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">combat</span><span class="p">::</span><span class="nn">systems</span><span class="p">::</span><span class="n">spawn_projectile</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// System that handles enemy attacks</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">enemy_attack</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">enemy_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">GlobalTransform</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EnemyCombat</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">AIBehavior</span><span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Enemy</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">player_transform</span><span class="p">)</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.single</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">enemy_transform</span><span class="p">,</span> <span class="k">mut</span> <span class="n">combat</span><span class="p">,</span> <span class="n">ai</span><span class="p">)</span> <span class="k">in</span> <span class="n">enemy_query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Tick the cooldown timer</span>
        <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.tick</span><span class="p">(</span><span class="n">time</span><span class="nf">.delta</span><span class="p">());</span>

        <span class="k">let</span> <span class="n">enemy_pos</span> <span class="o">=</span> <span class="n">enemy_transform</span><span class="nf">.translation</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">player_pos</span> <span class="o">=</span> <span class="n">player_transform</span><span class="py">.translation</span><span class="p">;</span>
        
        <span class="c1">// Calculate distance to player</span>
        <span class="k">let</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">enemy_pos</span><span class="nf">.distance</span><span class="p">(</span><span class="n">player_pos</span><span class="p">);</span>

        <span class="c1">// Attack if in range and cooldown is ready</span>
        <span class="k">if</span> <span class="n">distance</span> <span class="o">&lt;=</span> <span class="n">ai</span><span class="py">.attack_range</span> <span class="o">&amp;&amp;</span> <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.elapsed</span><span class="p">()</span> <span class="o">&gt;=</span> <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.duration</span><span class="p">()</span> <span class="p">{</span>
            <span class="c1">// Calculate direction to player</span>
            <span class="k">let</span> <span class="n">to_player</span> <span class="o">=</span> <span class="p">(</span><span class="n">player_pos</span> <span class="o">-</span> <span class="n">enemy_pos</span><span class="p">)</span><span class="nf">.normalize</span><span class="p">();</span>
            <span class="k">let</span> <span class="n">spawn_position</span> <span class="o">=</span> <span class="n">enemy_pos</span> <span class="o">+</span> <span class="n">to_player</span> <span class="o">*</span> <span class="mf">5.0</span><span class="p">;</span>

            <span class="c1">// Get visuals from power type (using actual direction to player)</span>
            <span class="k">let</span> <span class="n">visuals</span> <span class="o">=</span> <span class="n">combat</span><span class="py">.power_type</span><span class="nf">.visuals</span><span class="p">(</span><span class="n">to_player</span><span class="p">);</span>

            <span class="c1">// Spawn projectile (reuse existing function!)</span>
            <span class="nf">spawn_projectile</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">commands</span><span class="p">,</span> <span class="n">spawn_position</span><span class="p">,</span> <span class="n">combat</span><span class="py">.power_type</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">visuals</span><span class="p">);</span>

            <span class="c1">// Reset cooldown for next attack</span>
            <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.reset</span><span class="p">();</span>

            <span class="nd">info!</span><span class="p">(</span><span class="s">"Enemy fired {:?} projectile at player!"</span><span class="p">,</span> <span class="n">combat</span><span class="py">.power_type</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How does the attack system work?</strong></p>

<p>Every frame, we tick each enemy’s cooldown timer. We check if <code class="language-plaintext highlighter-rouge">elapsed() &gt;= duration()</code> to see if the cooldown is ready. This allows enemies to attack immediately when spawned (the timer starts in a finished state) and then every 2 seconds after.</p>

<p>We calculate the actual direction vector from enemy to player using <code class="language-plaintext highlighter-rouge">(player_pos - enemy_pos).normalize()</code>. This ensures projectiles always aim at the player’s current position, not just in a cardinal direction.</p>

<p>We check if the player is within attack range. If both conditions are met (cooldown ready AND player in range), we fire a projectile toward the player and reset the cooldown.</p>

<p>The <code class="language-plaintext highlighter-rouge">spawn_projectile</code> function is the same one the player uses. We made it public in <code class="language-plaintext highlighter-rouge">combat/systems.rs</code> so both players and enemies can call it. This is code reuse at its best.</p>

<h2 id="fixing-player-spawning">Fixing Player Spawning</h2>

<p>Before we spawn enemies, we need to fix a critical bug in how the player spawn. Right now, the player spawns at <code class="language-plaintext highlighter-rouge">(0, 0)</code> during <code class="language-plaintext highlighter-rouge">Startup</code>, before the collision map exists. This means the player might spawn on an obstacle (rock, tree, water) and get stuck!</p>

<p>To prevent this, let’s use a <strong>validate-then-spawn</strong> pattern:</p>
<ol>
  <li>Load character assets → <code class="language-plaintext highlighter-rouge">Startup</code></li>
  <li>Wait for collision map → <code class="language-plaintext highlighter-rouge">Update</code> (run condition)</li>
  <li>Validate position → Check <code class="language-plaintext highlighter-rouge">is_circle_clear</code></li>
  <li>Spawn at valid position</li>
</ol>

<p>This ensures characters <strong>never</strong> spawn on obstacles.</p>

<p>Let’s update <code class="language-plaintext highlighter-rouge">src/characters/spawn.rs</code>. We’ll split the old system into two cleaner functions:</p>

<p><strong>First, add these imports at the top of the file:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMapBuilt</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">player</span><span class="p">::</span><span class="n">COLLIDER_RADIUS</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMap</span><span class="p">;</span> <span class="c1">// Add this line</span>
</code></pre></div></div>

<p><strong>Remove these old functions</strong>:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//DELETE spawn_player</span>
<span class="c1">//DELETE initialize_player_character</span>
</code></pre></div></div>

<p><strong>Add these new functions:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs</span>
<span class="c1">// Update the resource name at the top of the file</span>
<span class="cd">/// Resource to track if player has been spawned (prevents spawning multiple times)</span>
<span class="nd">#[derive(Resource,</span> <span class="nd">Default,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="nf">PlayerSpawned</span><span class="p">(</span><span class="k">pub</span> <span class="nb">bool</span><span class="p">);</span>

<span class="cd">/// Get a valid spawn position, checking collision map and adjusting if needed</span>
<span class="k">fn</span> <span class="nf">get_valid_spawn_position</span><span class="p">(</span><span class="n">collision_map</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CollisionMap</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
    <span class="c1">// Use the same radius as the runtime collision system</span>
    <span class="k">if</span> <span class="n">collision_map</span><span class="nf">.is_circle_clear</span><span class="p">(</span><span class="n">desired_pos</span><span class="p">,</span> <span class="n">COLLIDER_RADIUS</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">desired_pos</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Find nearest position where the full collider circle is clear</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clear_pos</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span><span class="nf">.find_nearest_clear_position</span><span class="p">(</span><span class="n">desired_pos</span><span class="p">,</span> <span class="n">COLLIDER_RADIUS</span><span class="p">)</span> <span class="p">{</span>
        <span class="nd">info!</span><span class="p">(</span>
            <span class="s">"Adjusted player spawn from {:?} to {:?} (was on obstacle)"</span><span class="p">,</span>
            <span class="n">desired_pos</span><span class="p">,</span> <span class="n">clear_pos</span>
        <span class="p">);</span>
        <span class="k">return</span> <span class="n">clear_pos</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Fallback to original</span>
    <span class="nd">warn!</span><span class="p">(</span><span class="s">"Could not find walkable spawn position near {:?}"</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">);</span>
    <span class="n">desired_pos</span>
<span class="p">}</span>

<span class="c1">// Replace spawn_player with this</span>
<span class="cd">/// Load character assets at startup (before collision map is built)</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">load_character_assets</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">character_index</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Load the characters list</span>
    <span class="k">let</span> <span class="n">characters_list_handle</span><span class="p">:</span> <span class="n">Handle</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;</span> <span class="o">=</span>
        <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="s">"characters/characters.ron"</span><span class="p">);</span>

    <span class="c1">// Store the handle in a resource</span>
    <span class="n">commands</span><span class="nf">.insert_resource</span><span class="p">(</span><span class="n">CharactersListResource</span> <span class="p">{</span>
        <span class="n">handle</span><span class="p">:</span> <span class="n">characters_list_handle</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="c1">// Initialize with first character</span>
    <span class="n">character_index</span><span class="py">.index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Character assets loading started"</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Add this new function</span>
<span class="cd">/// Spawn player at a valid position AFTER collision map is built</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_player_at_valid_position</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_lists</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">character_index</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">characters_list_res</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CharactersListResource</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">collision_map</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CollisionMap</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">player_spawned</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">PlayerSpawned</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Wait for collision map</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">collision_map</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="c1">// Wait for character list resource</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list_res</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_list_res</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="c1">// Get the character list asset</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_lists</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">characters_list_res</span><span class="py">.handle</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">if</span> <span class="n">character_index</span><span class="py">.index</span> <span class="o">&gt;=</span> <span class="n">characters_list</span><span class="py">.characters</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
        <span class="nd">warn!</span><span class="p">(</span><span class="s">"Invalid character index: {}"</span><span class="p">,</span> <span class="n">character_index</span><span class="py">.index</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">let</span> <span class="n">character_entry</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">characters_list</span><span class="py">.characters</span><span class="p">[</span><span class="n">character_index</span><span class="py">.index</span><span class="p">];</span>
    
    <span class="c1">// Calculate valid spawn position</span>
    <span class="k">let</span> <span class="n">desired_pos</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">valid_pos</span> <span class="o">=</span> <span class="nf">get_valid_spawn_position</span><span class="p">(</span><span class="o">&amp;</span><span class="n">collision_map</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">);</span>
    
    <span class="c1">// Create sprite</span>
    <span class="k">let</span> <span class="n">texture</span> <span class="o">=</span> <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="o">&amp;</span><span class="n">character_entry</span><span class="py">.texture_path</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nf">create_character_atlas_layout</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">,</span> <span class="n">character_entry</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">sprite</span> <span class="o">=</span> <span class="nn">Sprite</span><span class="p">::</span><span class="nf">from_atlas_image</span><span class="p">(</span><span class="n">texture</span><span class="p">,</span> <span class="n">TextureAtlas</span> <span class="p">{</span> <span class="n">layout</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="mi">0</span> <span class="p">});</span>
    
    <span class="c1">// Spawn player with all components at valid position</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="n">Player</span><span class="p">,</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">valid_pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">valid_pos</span><span class="py">.y</span><span class="p">,</span> <span class="n">PLAYER_Z_POSITION</span><span class="p">))</span>
            <span class="nf">.with_scale</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">PLAYER_SCALE</span><span class="p">)),</span>
        <span class="n">sprite</span><span class="p">,</span>
        <span class="nn">AnimationController</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
        <span class="nn">CharacterState</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
        <span class="nn">Velocity</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
        <span class="nn">Collider</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
        <span class="nn">PlayerCombat</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
        <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span>
            <span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">,</span>
            <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">,</span>
        <span class="p">)),</span>
        <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">(),</span>
    <span class="p">));</span>
    
    <span class="c1">// Mark player as spawned</span>
    <span class="n">player_spawned</span><span class="na">.0</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Player spawned at validated position {:?}"</span><span class="p">,</span> <span class="n">valid_pos</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What changed?</strong></p>

<ol>
  <li><strong>load_character_assets</strong> (Startup) - Only loads the asset, doesn’t spawn anything</li>
  <li><strong>spawn_player_at_valid_position</strong> (Update) - Waits for collision map, validates position, then spawns with ALL components in one step</li>
  <li><strong>get_valid_spawn_position</strong> - Shared helper that checks <code class="language-plaintext highlighter-rouge">is_circle_clear</code> (not just single tile!)</li>
</ol>

<p>Notice we use <code class="language-plaintext highlighter-rouge">COLLIDER_RADIUS</code> — the same constant the runtime collision system uses. This is important: if spawn validation checks a smaller radius than what the movement system enforces, an entity could pass the spawn check but still overlap an obstacle at runtime and get stuck. Using a single source of truth eliminates that mismatch. The fallback <code class="language-plaintext highlighter-rouge">find_nearest_clear_position</code> also validates the full circle, not just the tile type.</p>

<h3 id="updating-the-plugin">Updating the Plugin</h3>

<p>Now update <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>
<span class="c1">// Update imports</span>
<span class="k">use</span> <span class="nn">spawn</span><span class="p">::</span><span class="n">PlayerSpawned</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMapBuilt</span><span class="p">;</span> <span class="c1">// Add this line</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">CharactersPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">RonAssetPlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="s">"characters.ron"</span><span class="p">]))</span>
            <span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">spawn</span><span class="p">::</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PlayerSpawned</span><span class="o">&gt;</span><span class="p">()</span> <span class="c1">// Add this line</span>
            <span class="c1">// Load character assets at startup (before collision map)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="nn">spawn</span><span class="p">::</span><span class="n">load_character_assets</span><span class="p">)</span> <span class="c1">// Change function name</span>
            <span class="c1">// Spawn player at valid position AFTER collision map is built</span>
            <span class="c1">// Add this</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="nn">spawn</span><span class="p">::</span><span class="n">spawn_player_at_valid_position</span> 
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">resource_equals</span><span class="p">(</span><span class="nf">CollisionMapBuilt</span><span class="p">(</span><span class="k">true</span><span class="p">)))</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">resource_equals</span><span class="p">(</span><span class="nf">PlayerSpawned</span><span class="p">(</span><span class="k">false</span><span class="p">)))</span> 
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">)</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="p">(</span>
                    <span class="nn">input</span><span class="p">::</span><span class="n">handle_player_input</span><span class="p">,</span>
                    <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
                    <span class="nn">input</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
                    <span class="nn">animation</span><span class="p">::</span><span class="n">on_state_change_update_animation</span><span class="p">,</span>
                    <span class="nn">collider</span><span class="p">::</span><span class="n">validate_movement</span><span class="p">,</span>
                    <span class="nn">physics</span><span class="p">::</span><span class="n">apply_velocity</span><span class="p">,</span>
                    <span class="nn">rendering</span><span class="p">::</span><span class="n">update_player_depth</span><span class="p">,</span>
                    <span class="nn">animation</span><span class="p">::</span><span class="n">animations_playback</span><span class="p">,</span>
                <span class="p">)</span>
                    <span class="nf">.chain</span><span class="p">()</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And one more fix in <code class="language-plaintext highlighter-rouge">src/state/mod.rs</code> - remove the old initialization from <code class="language-plaintext highlighter-rouge">OnExit(Loading)</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/state/mod.rs</span>
<span class="nf">.add_systems</span><span class="p">(</span><span class="nf">OnExit</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Loading</span><span class="p">),</span> 
    <span class="nn">loading</span><span class="p">::</span><span class="n">despawn_loading_screen</span><span class="p">,</span>
    <span class="c1">// REMOVE: crate::characters::spawn::initialize_player_character,</span>
<span class="p">)</span>
</code></pre></div></div>

<h2 id="adding-enemy-configuration">Adding Enemy Configuration</h2>

<p>Now, let’s add configuration constants for enemies in <code class="language-plaintext highlighter-rouge">src/config.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">player</span> <span class="p">{</span>
    <span class="c1">// ... existing player config ...</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">mod</span> <span class="n">enemy</span> <span class="p">{</span>
    <span class="cd">/// Z-position for enemy rendering (same as player for consistent layering)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">ENEMY_Z_POSITION</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">20.0</span><span class="p">;</span>

    <span class="cd">/// Visual scale of enemy sprites (same as player for consistency)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">ENEMY_SCALE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1.2</span><span class="p">;</span>
<span class="p">}</span> <span class="c1">// Add this</span>

<span class="k">pub</span> <span class="k">mod</span> <span class="n">pickup</span> <span class="p">{</span>
    <span class="c1">// ... existing pickup config ...</span>
<span class="p">}</span>
<span class="c1">// ... rest of config ...</span>
</code></pre></div></div>

<p>We create a separate <code class="language-plaintext highlighter-rouge">enemy</code> module to keep enemy constants organized and avoid confusion with player constants.</p>

<h3 id="spawning-enemies">Spawning Enemies</h3>

<p>Now we need to actually create enemy entities in the world. We’ll write a spawn function that creates an enemy with all the necessary components, then a system that spawns test enemies - using the <strong>exact same pattern</strong> we just used for the player.</p>

<p>The spawn function needs to:</p>
<ol>
  <li>Load the character configuration from characters.ron</li>
  <li>Create the sprite atlas</li>
  <li>Spawn an entity with all required components</li>
</ol>

<p>Create <code class="language-plaintext highlighter-rouge">src/enemy/spawn.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/enemy/spawn.rs</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">components</span><span class="p">::{</span><span class="n">AIBehavior</span><span class="p">,</span> <span class="n">Enemy</span><span class="p">,</span> <span class="n">EnemyCombat</span><span class="p">,</span> <span class="n">EnemyPath</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::{</span>
    <span class="nn">animation</span><span class="p">::{</span><span class="n">AnimationController</span><span class="p">,</span> <span class="n">AnimationTimer</span><span class="p">,</span> <span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">},</span>
    <span class="nn">collider</span><span class="p">::</span><span class="n">Collider</span><span class="p">,</span>
    <span class="nn">config</span><span class="p">::{</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">CharactersList</span><span class="p">},</span>
    <span class="nn">facing</span><span class="p">::</span><span class="n">Facing</span><span class="p">,</span>
    <span class="nn">physics</span><span class="p">::</span><span class="n">Velocity</span><span class="p">,</span>
    <span class="nn">spawn</span><span class="p">::</span><span class="n">CharactersListResource</span><span class="p">,</span> <span class="c1">// Add this line</span>
    <span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMap</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">enemy</span><span class="p">::{</span><span class="n">ENEMY_SCALE</span><span class="p">,</span> <span class="n">ENEMY_Z_POSITION</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">player</span><span class="p">::</span><span class="n">COLLIDER_RADIUS</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Spawn an enemy at the given position</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_enemy</span><span class="p">(</span>
    <span class="n">commands</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">AssetServer</span><span class="p">,</span>
    <span class="n">atlas_layouts</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_list</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CharactersList</span><span class="p">,</span>
    <span class="n">position</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="n">character_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Entity</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// Find the character entry by name</span>
    <span class="k">let</span> <span class="n">character_entry</span> <span class="o">=</span> <span class="n">characters_list</span>
        <span class="py">.characters</span>
        <span class="nf">.iter</span><span class="p">()</span>
        <span class="nf">.find</span><span class="p">(|</span><span class="n">c</span><span class="p">|</span> <span class="n">c</span><span class="py">.name</span> <span class="o">==</span> <span class="n">character_name</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// Create atlas layout</span>
    <span class="k">let</span> <span class="n">max_row</span> <span class="o">=</span> <span class="n">character_entry</span><span class="nf">.calculate_max_animation_row</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="n">atlas_layouts</span><span class="nf">.add</span><span class="p">(</span><span class="nn">TextureAtlasLayout</span><span class="p">::</span><span class="nf">from_grid</span><span class="p">(</span>
        <span class="nn">UVec2</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">character_entry</span><span class="py">.tile_size</span><span class="p">),</span>
        <span class="n">character_entry</span><span class="py">.atlas_columns</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
        <span class="p">(</span><span class="n">max_row</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
        <span class="nb">None</span><span class="p">,</span>
        <span class="nb">None</span><span class="p">,</span>
    <span class="p">));</span>

    <span class="c1">// Load texture</span>
    <span class="k">let</span> <span class="n">texture</span> <span class="o">=</span> <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="o">&amp;</span><span class="n">character_entry</span><span class="py">.texture_path</span><span class="p">);</span>

    <span class="c1">// Create sprite</span>
    <span class="k">let</span> <span class="n">sprite</span> <span class="o">=</span> <span class="nn">Sprite</span><span class="p">::</span><span class="nf">from_atlas_image</span><span class="p">(</span><span class="n">texture</span><span class="p">,</span> <span class="n">TextureAtlas</span> <span class="p">{</span> <span class="n">layout</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="mi">0</span> <span class="p">});</span>

    <span class="c1">// Spawn enemy entity with all necessary components</span>
    <span class="k">let</span> <span class="n">entity</span> <span class="o">=</span> <span class="n">commands</span>
        <span class="nf">.spawn</span><span class="p">((</span>
            <span class="n">Enemy</span><span class="p">,</span>
            <span class="n">sprite</span><span class="p">,</span>
            <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">position</span><span class="p">)</span><span class="nf">.with_scale</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">ENEMY_SCALE</span><span class="p">)),</span>
            <span class="nn">GlobalTransform</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">AnimationController</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">CharacterState</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">Velocity</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">Collider</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">EnemyCombat</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">AIBehavior</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">EnemyPath</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>  <span class="c1">// Add this line</span>
            <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span>
                <span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">,</span>
                <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">,</span>
            <span class="p">)),</span>
            <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="p">))</span>
        <span class="nf">.id</span><span class="p">();</span>

    <span class="nd">info!</span><span class="p">(</span><span class="s">"Spawned enemy '{}' at {:?}"</span><span class="p">,</span> <span class="n">character_name</span><span class="p">,</span> <span class="n">position</span><span class="p">);</span>

     <span class="nf">Some</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span>
<span class="p">}</span>

<span class="cd">/// Resource to track if enemies have been spawned</span>
<span class="nd">#[derive(Resource,</span> <span class="nd">Default,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="nf">EnemiesSpawned</span><span class="p">(</span><span class="k">pub</span> <span class="nb">bool</span><span class="p">);</span>

<span class="cd">/// Validate and adjust spawn position to ensure it's on a walkable tile</span>
<span class="k">fn</span> <span class="nf">get_valid_spawn_position</span><span class="p">(</span><span class="n">collision_map</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CollisionMap</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
    <span class="c1">// Use the same radius as the runtime collision system</span>
    <span class="k">if</span> <span class="n">collision_map</span><span class="nf">.is_circle_clear</span><span class="p">(</span><span class="n">desired_pos</span><span class="p">,</span> <span class="n">COLLIDER_RADIUS</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">desired_pos</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Find nearest position where the full collider circle is clear</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clear_pos</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span><span class="nf">.find_nearest_clear_position</span><span class="p">(</span><span class="n">desired_pos</span><span class="p">,</span> <span class="n">COLLIDER_RADIUS</span><span class="p">)</span> <span class="p">{</span>
        <span class="nd">info!</span><span class="p">(</span>
            <span class="s">"Adjusted spawn from {:?} to {:?} (was on obstacle)"</span><span class="p">,</span>
            <span class="n">desired_pos</span><span class="p">,</span> <span class="n">clear_pos</span>
        <span class="p">);</span>
        <span class="k">return</span> <span class="n">clear_pos</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Fallback to original (shouldn't happen in a valid map)</span>
    <span class="nd">warn!</span><span class="p">(</span><span class="s">"Could not find walkable spawn position near {:?}"</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">);</span>
    <span class="n">desired_pos</span>
<span class="p">}</span>

<span class="cd">/// System to spawn test enemies when collision map is ready</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_test_enemies</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_lists</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_list_res</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CharactersListResource</span><span class="o">&gt;&gt;</span><span class="p">,</span> <span class="c1">// Add this line</span>
    <span class="n">collision_map</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CollisionMap</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">enemies_spawned</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">EnemiesSpawned</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Wait for collision map</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">collision_map</span><span class="p">)</span> <span class="o">=</span> <span class="n">collision_map</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="c1">// Wait for character list resource</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list_res</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_list_res</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="c1">// Get the character list asset</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_lists</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">characters_list_res</span><span class="py">.handle</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="c1">// Define desired spawn positions</span>
    <span class="k">let</span> <span class="n">spawn_positions</span> <span class="o">=</span> <span class="p">[</span><span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">200.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="mf">200.0</span><span class="p">,</span> <span class="mf">100.0</span><span class="p">)];</span>

    <span class="k">for</span> <span class="n">desired_pos</span> <span class="k">in</span> <span class="n">spawn_positions</span> <span class="p">{</span>
        <span class="c1">// Validate position against collision map</span>
        <span class="k">let</span> <span class="n">valid_pos</span> <span class="o">=</span> <span class="nf">get_valid_spawn_position</span><span class="p">(</span><span class="o">&amp;</span><span class="n">collision_map</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">);</span>

        <span class="nf">spawn_enemy</span><span class="p">(</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">commands</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="n">asset_server</span><span class="p">,</span>
            <span class="o">&amp;</span><span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">,</span>
            <span class="n">characters_list</span><span class="p">,</span>
            <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">valid_pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">valid_pos</span><span class="py">.y</span><span class="p">,</span> <span class="n">ENEMY_Z_POSITION</span><span class="p">),</span>
            <span class="s">"graveyard_reaper"</span><span class="p">,</span>
        <span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Mark enemies as spawned so this system doesn't run again</span>
    <span class="n">enemies_spawned</span><span class="na">.0</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Enemies spawned with validated positions"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s happening in spawn_enemy?</strong></p>

<p>This function follows the same pattern as the player spawn system. Both players and enemies need the same components for movement, animation, and collision.</p>

<p>Both player and enemy spawning now follow the same “validate-then-spawn” approach:</p>

<ol>
  <li><strong>Wait for collision map</strong> (run condition: <code class="language-plaintext highlighter-rouge">CollisionMapBuilt(true)</code>)</li>
  <li><strong>Check spawn position</strong> (using <code class="language-plaintext highlighter-rouge">is_circle_clear</code> with <code class="language-plaintext highlighter-rouge">COLLIDER_RADIUS</code>)</li>
  <li><strong>Find nearest clear position if blocked</strong> (using <code class="language-plaintext highlighter-rouge">find_nearest_clear_position</code>, which validates the full collider circle)</li>
  <li><strong>Spawn with all components</strong> (once valid position is determined)</li>
</ol>

<p>The key differences between player and enemy entities are:</p>
<ul>
  <li><strong>Marker component</strong>: <code class="language-plaintext highlighter-rouge">Player</code> vs <code class="language-plaintext highlighter-rouge">Enemy</code></li>
  <li><strong>Behavior components</strong>: <code class="language-plaintext highlighter-rouge">PlayerCombat</code> vs <code class="language-plaintext highlighter-rouge">EnemyCombat</code> + <code class="language-plaintext highlighter-rouge">AIBehavior</code></li>
  <li><strong>Context-specific</strong>: Player gets inventory, enemies get pathfinding</li>
</ul>

<p>Notice how we reuse <code class="language-plaintext highlighter-rouge">CharacterEntry</code> from characters.ron. The “graveyard_reaper” character we defined in Chapter 3 works perfectly for enemies. Same sprite system, same animation definitions, zero duplication.</p>

<p><strong>Why return <code class="language-plaintext highlighter-rouge">Option&lt;Entity&gt;</code>?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">find()</code> call might fail if the character name doesn’t exist in characters.ron. Returning <code class="language-plaintext highlighter-rouge">Option&lt;Entity&gt;</code> lets the caller handle this gracefully. If the character is found, we return <code class="language-plaintext highlighter-rouge">Some(entity_id)</code>. If not, we return <code class="language-plaintext highlighter-rouge">None</code>.</p>

<h2 id="the-enemy-plugin">The Enemy Plugin</h2>

<p>Now we need to wire everything together into a plugin. The plugin will register all our enemy systems and schedule them to run at the right time.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/enemy/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/enemy/mod.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">ai</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">combat</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">components</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">spawn</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMapBuilt</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">spawn</span><span class="p">::</span><span class="n">EnemiesSpawned</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">use</span> <span class="nn">components</span><span class="p">::{</span><span class="n">AIBehavior</span><span class="p">,</span> <span class="n">Enemy</span><span class="p">,</span> <span class="n">EnemyCombat</span><span class="p">};</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">spawn</span><span class="p">::</span><span class="n">spawn_enemy</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">EnemyPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">EnemyPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span>
            <span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="n">EnemiesSpawned</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="c1">// Spawn enemies AFTER collision map is ready (prevents spawning on obstacles)</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="nn">spawn</span><span class="p">::</span><span class="n">spawn_test_enemies</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">resource_equals</span><span class="p">(</span><span class="nf">CollisionMapBuilt</span><span class="p">(</span><span class="k">true</span><span class="p">)))</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">resource_equals</span><span class="p">(</span><span class="nf">EnemiesSpawned</span><span class="p">(</span><span class="k">false</span><span class="p">)))</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">)</span>
            <span class="c1">// Enemy AI and combat systems</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="p">(</span><span class="nn">ai</span><span class="p">::</span><span class="n">enemy_follow_player</span><span class="p">,</span> <span class="nn">combat</span><span class="p">::</span><span class="n">enemy_attack</span><span class="p">)</span>
                    <span class="nf">.chain</span><span class="p">()</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How does the plugin work?</strong></p>

<p>We use <strong>run conditions</strong> to control when <code class="language-plaintext highlighter-rouge">spawn_test_enemies</code> executes:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">CollisionMapBuilt(true)</code> - collision map must exist before validating spawn positions</li>
  <li><code class="language-plaintext highlighter-rouge">EnemiesSpawned(false)</code> - spawns only once (resource tracks spawn state)</li>
  <li><code class="language-plaintext highlighter-rouge">in_state(GameState::Playing)</code> - only spawns during actual gameplay</li>
</ul>

<p>This ensures enemies spawn at validated positions AFTER the collision map is ready, preventing them from spawning on obstacles.</p>

<p>The AI and combat systems run every frame during <code class="language-plaintext highlighter-rouge">Playing</code> state. We chain them together so AI runs first (updating positions), then combat runs (firing projectiles).</p>

<p>Also, our enemy combat system needs to call <code class="language-plaintext highlighter-rouge">spawn_projectile</code>, but it’s currently private to the combat module. We need to make two changes: make the systems module public, and export the <code class="language-plaintext highlighter-rouge">spawn_projectile</code> function.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/combat/mod.rs</code> and update it:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/combat/mod.rs</span>
<span class="k">mod</span> <span class="n">player_combat</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">power_type</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">systems</span><span class="p">;</span> <span class="c1">// Line update alert: Make module public</span>

<span class="k">pub</span> <span class="k">use</span> <span class="nn">player_combat</span><span class="p">::</span><span class="n">PlayerCombat</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">power_type</span><span class="p">::{</span><span class="n">PowerType</span><span class="p">,</span> <span class="n">PowerVisuals</span><span class="p">};</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">systems</span><span class="p">::{</span><span class="n">debug_switch_power</span><span class="p">,</span> <span class="n">handle_power_input</span><span class="p">,</span> <span class="n">spawn_projectile</span><span class="p">};</span> <span class="c1">// Line update alert: Export spawn_projectile</span>
</code></pre></div></div>

<p>Then open <code class="language-plaintext highlighter-rouge">src/combat/systems.rs</code> and make the <code class="language-plaintext highlighter-rouge">spawn_projectile</code> function public:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/combat/systems.rs</span>
<span class="c1">// Find this function and add 'pub' keyword</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_projectile</span><span class="p">(</span> <span class="c1">// Line update alert: Add 'pub' keyword</span>
    <span class="n">commands</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">position</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">,</span>
    <span class="n">visuals</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PowerVisuals</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// ... rest of the function</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now both players and enemies can call <code class="language-plaintext highlighter-rouge">spawn_projectile</code> from outside the combat module.</p>

<h3 id="fixing-enemy-depth-sorting">Fixing Enemy Depth Sorting</h3>

<p>There’s one more issue to address. Remember in Chapter 5 when we implemented depth sorting for the player? The player’s Z position updates based on Y position, so they render correctly behind or in front of objects.</p>

<p>Enemies need the same treatment. Right now, they spawn with a static Z position and never update. This means an enemy standing at the bottom of the screen (low Y) might render behind a tree at the top of the screen (high Y), which looks wrong.</p>

<p>The good news? We already have the depth sorting logic. We just need to make it work for all characters, not just the player.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/characters/rendering.rs</code> and update it:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/rendering.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">;</span> <span class="c1">// Line update alert: Change from Player to CharacterState</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">map</span><span class="p">::{</span><span class="n">GRID_Y</span><span class="p">,</span> <span class="n">TILE_SIZE</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">player</span><span class="p">::</span><span class="n">PLAYER_SCALE</span><span class="p">;</span>

<span class="cd">/// Z-depth constants for proper layering.</span>
<span class="cd">/// The tilemap uses `with_z_offset_from_y(true)` which assigns Z based on Y position.</span>
<span class="cd">/// We need to match this formula for all characters (player and enemies). // Line update alert</span>
<span class="k">const</span> <span class="n">NODE_SIZE_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span>  <span class="c1">// Same as tilemap generator</span>
<span class="k">const</span> <span class="n">CHARACTER_BASE_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">4.0</span><span class="p">;</span>  <span class="c1">// Match props layer Z range // Line update alert</span>
<span class="k">const</span> <span class="n">CHARACTER_Z_OFFSET</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.5</span><span class="p">;</span>  <span class="c1">// Small offset to stay above ground props // Line update alert</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_character_depth</span><span class="p">(</span> <span class="c1">// Line update alert: Renamed from update_player_depth</span>
    <span class="c1">//Updated from PlayerState to CharacterState</span>
    <span class="k">mut</span> <span class="n">character_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span> <span class="p">(</span><span class="n">With</span><span class="o">&lt;</span><span class="n">CharacterState</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">Changed</span><span class="o">&lt;</span><span class="n">Transform</span><span class="o">&gt;</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span> 
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Map dimensions for normalization</span>
    <span class="k">let</span> <span class="n">map_height</span> <span class="o">=</span> <span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_Y</span> <span class="k">as</span> <span class="nb">f32</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">map_y0</span> <span class="o">=</span> <span class="o">-</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_Y</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">;</span>  <span class="c1">// Map origin Y (centered)</span>
    
    <span class="c1">// Character sprite height for feet position calculation // Line update alert</span>
    <span class="k">let</span> <span class="n">character_sprite_height</span> <span class="o">=</span> <span class="mf">64.0</span> <span class="o">*</span> <span class="n">PLAYER_SCALE</span><span class="p">;</span> <span class="c1">// Line update alert</span>

    <span class="k">for</span> <span class="k">mut</span> <span class="n">transform</span> <span class="k">in</span> <span class="n">character_query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// Line update alert</span>
        <span class="k">let</span> <span class="n">character_center_y</span> <span class="o">=</span> <span class="n">transform</span><span class="py">.translation.y</span><span class="p">;</span> <span class="c1">// Line update alert</span>

        <span class="c1">// Use character's FEET position for depth sorting (not center) // Line update alert</span>
        <span class="k">let</span> <span class="n">character_feet_y</span> <span class="o">=</span> <span class="n">character_center_y</span> <span class="o">-</span> <span class="p">(</span><span class="n">character_sprite_height</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">);</span> <span class="c1">// Line update alert</span>

        <span class="c1">// Normalize feet Y to [0, 1] across the grid height</span>
        <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="p">((</span><span class="n">character_feet_y</span> <span class="o">-</span> <span class="n">map_y0</span><span class="p">)</span> <span class="o">/</span> <span class="n">map_height</span><span class="p">)</span><span class="nf">.clamp</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span> <span class="c1">// Line update alert</span>

        <span class="c1">// Y-to-Z formula:</span>
        <span class="c1">// Lower Y (bottom of screen) = higher t = lower Z offset = rendered in front</span>
        <span class="c1">// Higher Y (top of screen) = lower t = higher Z offset = rendered behind</span>
        <span class="k">let</span> <span class="n">character_z</span> <span class="o">=</span> <span class="n">CHARACTER_BASE_Z</span> <span class="o">+</span> <span class="n">NODE_SIZE_Z</span> <span class="o">*</span> <span class="p">(</span><span class="mf">1.0</span> <span class="o">-</span> <span class="n">t</span><span class="p">)</span> <span class="o">+</span> <span class="n">CHARACTER_Z_OFFSET</span><span class="p">;</span> <span class="c1">// Line update alert</span>

        <span class="n">transform</span><span class="py">.translation.z</span> <span class="o">=</span> <span class="n">character_z</span><span class="p">;</span> <span class="c1">// Line update alert</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What changed?</strong></p>

<p>We changed the query from <code class="language-plaintext highlighter-rouge">With&lt;Player&gt;</code> to <code class="language-plaintext highlighter-rouge">With&lt;CharacterState&gt;</code>. Since both players and enemies have the <code class="language-plaintext highlighter-rouge">CharacterState</code> component, this system now runs for all characters.</p>

<p>We also renamed the function and variables to reflect that it’s no longer player-specific.</p>

<p>Now update <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code> to use the new function name:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>
<span class="c1">// Find the Update systems and update this line:</span>
<span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">handle_player_input</span><span class="p">,</span>
    <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">on_state_change_update_animation</span><span class="p">,</span>
    <span class="nn">collider</span><span class="p">::</span><span class="n">validate_movement</span><span class="p">,</span>
    <span class="nn">physics</span><span class="p">::</span><span class="n">apply_velocity</span><span class="p">,</span>
    <span class="nn">rendering</span><span class="p">::</span><span class="n">update_character_depth</span><span class="p">,</span> <span class="c1">// Line update alert: Renamed</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">animations_playback</span><span class="p">,</span>
<span class="p">)</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)));</span>
</code></pre></div></div>

<p><strong>Why does this work?</strong></p>

<p>Both players and enemies have the <code class="language-plaintext highlighter-rouge">CharacterState</code> component. By querying for <code class="language-plaintext highlighter-rouge">With&lt;CharacterState&gt;</code> instead of <code class="language-plaintext highlighter-rouge">With&lt;Player&gt;</code>, the depth sorting system automatically applies to all character entities.</p>

<p>The system uses <code class="language-plaintext highlighter-rouge">Changed&lt;Transform&gt;</code> to only recalculate Z-depth when entities move, keeping it efficient.</p>

<h3 id="preventing-entity-stacking">Preventing Entity Stacking</h3>

<p>We have one more collision problem to solve. Right now, the <code class="language-plaintext highlighter-rouge">validate_movement</code> system only checks against the <strong>collision map</strong> (tiles and obstacles). It doesn’t check against other <strong>entities</strong>. This means:</p>

<ol>
  <li>Enemies can stack on top of each other</li>
  <li>Enemies pass right through the player</li>
</ol>

<p>We need <strong>entity-to-entity collision detection</strong>.</p>

<p><strong>The Approach:</strong></p>

<p>After validating against the collision map, we’ll check each entity against all other entities. If two entities overlap (their collision circles intersect), we push them apart.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/characters/collider.rs</code> and add this new system at the end:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/collider.rs</span>
<span class="c1">// Add at the end of the file</span>

<span class="cd">/// Resolve collisions between entities (player and enemies)</span>
<span class="cd">/// Prevents entities from moving into each other</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">resolve_entity_collisions</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="n">Entity</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Velocity</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Collider</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Collect all entity positions first to avoid multiple mutable borrows</span>
    <span class="k">let</span> <span class="n">entities</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">_</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">query</span>
        <span class="nf">.iter</span><span class="p">()</span>
        <span class="nf">.map</span><span class="p">(|(</span><span class="n">e</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">)|</span> <span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">c</span><span class="nf">.world_position</span><span class="p">(</span><span class="n">t</span><span class="p">),</span> <span class="n">c</span><span class="py">.radius</span><span class="p">))</span>
        <span class="nf">.collect</span><span class="p">();</span>

    <span class="c1">// Check each entity against all others</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">entity</span><span class="p">,</span> <span class="n">transform</span><span class="p">,</span> <span class="k">mut</span> <span class="n">velocity</span><span class="p">,</span> <span class="n">collider</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Skip if not moving</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">velocity</span><span class="nf">.is_moving</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">collider</span><span class="nf">.world_position</span><span class="p">(</span><span class="n">transform</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">radius</span> <span class="o">=</span> <span class="n">collider</span><span class="py">.radius</span><span class="p">;</span>

        <span class="k">for</span> <span class="o">&amp;</span><span class="p">(</span><span class="n">other_entity</span><span class="p">,</span> <span class="n">other_pos</span><span class="p">,</span> <span class="n">other_radius</span><span class="p">)</span> <span class="k">in</span> <span class="o">&amp;</span><span class="n">entities</span> <span class="p">{</span>
            <span class="c1">// Skip self</span>
            <span class="k">if</span> <span class="n">entity</span> <span class="o">==</span> <span class="n">other_entity</span> <span class="p">{</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">other_pos</span> <span class="o">-</span> <span class="n">pos</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">delta</span><span class="nf">.length</span><span class="p">();</span>
            <span class="k">let</span> <span class="n">min_distance</span> <span class="o">=</span> <span class="n">radius</span> <span class="o">+</span> <span class="n">other_radius</span><span class="p">;</span>

            <span class="c1">// Check if entities are overlapping or very close</span>
            <span class="k">if</span> <span class="n">distance</span> <span class="o">&lt;</span> <span class="n">min_distance</span> <span class="o">*</span> <span class="mf">1.1</span> <span class="p">{</span>
                <span class="c1">// Calculate the direction toward the other entity</span>
                <span class="k">if</span> <span class="n">distance</span> <span class="o">&gt;</span> <span class="mf">0.01</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="n">delta</span> <span class="o">/</span> <span class="n">distance</span><span class="p">;</span>
                    
                    <span class="c1">// Project velocity onto the direction toward the other entity</span>
                    <span class="k">let</span> <span class="n">velocity_toward</span> <span class="o">=</span> <span class="n">velocity</span><span class="na">.0</span><span class="nf">.dot</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>
                    
                    <span class="c1">// If moving toward the other entity, block that movement</span>
                    <span class="k">if</span> <span class="n">velocity_toward</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span>
                        <span class="c1">// Remove the component of velocity moving toward the other entity</span>
                        <span class="n">velocity</span><span class="na">.0</span> <span class="o">-=</span> <span class="n">direction</span> <span class="o">*</span> <span class="n">velocity_toward</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How does this work?</strong></p>

<p>This uses <strong>vector projection</strong> to block movement toward other entities:</p>

<ol>
  <li><strong>Collect positions first</strong> - Store all entity positions to avoid borrowing issues</li>
  <li><strong>Skip stationary entities</strong> - Only check moving entities</li>
  <li><strong>Check proximity</strong> - If entities are within <code class="language-plaintext highlighter-rouge">1.1 * (radius1 + radius2)</code>, they’re close enough to block</li>
  <li><strong>Project velocity</strong> - Use dot product to find how much velocity points toward the other entity</li>
  <li><strong>Remove that component</strong> - Subtract the “toward” velocity, leaving only tangential movement</li>
</ol>

<p><strong>Why vector projection?</strong></p>

<p>Instead of pushing entities apart (which feels jarring), we let them slide past each other. If an enemy walks toward the player, the component of velocity moving directly toward the player is removed, but sideways velocity remains. This creates smooth “sliding” collisions.</p>

<p><strong>Example:</strong></p>
<ul>
  <li>Enemy moving diagonally toward player: ↗</li>
  <li>After blocking: Enemy slides sideways past player: →</li>
</ul>

<p>This feels natural and prevents the jittery pushback effect.</p>

<p><strong>Why modify velocity instead of directly moving entities?</strong></p>

<p>Modifying velocity keeps physics consistent. The pushback force is applied as acceleration, then <code class="language-plaintext highlighter-rouge">apply_velocity</code> moves the entity. This ensures collision response feels smooth and natural.</p>

<p>Now add this system to the chain in <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>
<span class="nf">.add_systems</span><span class="p">(</span>
    <span class="n">Update</span><span class="p">,</span>
    <span class="p">(</span>
        <span class="nn">input</span><span class="p">::</span><span class="n">handle_player_input</span><span class="p">,</span>
        <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
        <span class="nn">input</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
        <span class="nn">animation</span><span class="p">::</span><span class="n">on_state_change_update_animation</span><span class="p">,</span>
        <span class="nn">collider</span><span class="p">::</span><span class="n">validate_movement</span><span class="p">,</span>
        <span class="nn">collider</span><span class="p">::</span><span class="n">resolve_entity_collisions</span><span class="p">,</span> <span class="c1">// NEW: Prevent entity stacking</span>
        <span class="nn">physics</span><span class="p">::</span><span class="n">apply_velocity</span><span class="p">,</span>
        <span class="nn">rendering</span><span class="p">::</span><span class="n">update_character_depth</span><span class="p">,</span>
        <span class="nn">animation</span><span class="p">::</span><span class="n">animations_playback</span><span class="p">,</span>
    <span class="p">)</span>
        <span class="nf">.chain</span><span class="p">()</span>
        <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
<span class="p">)</span>
</code></pre></div></div>

<p><strong>System order matters:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>validate_movement    → Check vs tiles/obstacles
resolve_entity_collisions  → Check vs other entities  
apply_velocity       → Actually move the character
</code></pre></div></div>

<p>Both collision checks happen BEFORE movement is applied. This prevents entities from getting stuck inside obstacles or each other.</p>

<h3 id="integrating-the-enemy-module">Integrating the Enemy Module</h3>

<p>Finally, let’s add the enemy module to our game. Open <code class="language-plaintext highlighter-rouge">src/main.rs</code> and add the module declaration and plugin:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs</span>
<span class="k">mod</span> <span class="n">camera</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">characters</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">collision</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">combat</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">config</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">enemy</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="k">mod</span> <span class="n">inventory</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">map</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">particles</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">state</span><span class="p">;</span>
</code></pre></div></div>

<p>Then add the plugin to your app:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Inside main function of src/main.rs</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="c1">// ... existing plugins ...</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">combat</span><span class="p">::</span><span class="n">CombatPlugin</span><span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">enemy</span><span class="p">::</span><span class="n">EnemyPlugin</span><span class="p">)</span> <span class="c1">// Add this line</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">inventory</span><span class="p">::</span><span class="n">InventoryPlugin</span><span class="p">)</span>
        <span class="c1">// ... rest of the code ...</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p><img src="/assets/book_assets/chapter7/ch7.gif" alt="Enemy System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
You might notice enemies don't actually deal damage yet. In <strong>Chapter 8</strong>, we'll add health, damage, and death. Enemies will become threatening, and your survival will depend on dodging their attacks.
</div>

<h2 id="optimizing-debug-builds">Optimizing Debug Builds</h2>

<blockquote>
  <p><strong>Community Tip</strong>: This optimization was pointed out by one of our <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust/issues/1">community members</a>, thank you for helping make this tutorial better!</p>
</blockquote>

<p>Bevy’s default debug configuration can lead to performance issues, scenes that should run smoothly might drop to unplayable framerates, or large assets might take minutes to load.</p>

<p>The Bevy team <a href="https://bevy.org/learn/quick-start/getting-started/setup/#compile-with-performance-optimizations">documents this issue</a> and provides a solution.</p>

<p><strong>Add these optimizations to your <code class="language-plaintext highlighter-rouge">Cargo.toml</code>:</strong></p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># At the bottom of your Cargo.toml</span>

<span class="c"># Enable a small amount of optimization in the dev profile</span>
<span class="nn">[profile.dev]</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="mi">1</span>

<span class="c"># Enable a large amount of optimization in the dev profile for dependencies</span>
<span class="nn">[profile.dev.package."*"]</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="mi">3</span>
</code></pre></div></div>

<p><strong>What does this do?</strong></p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">opt-level = 1</code> for your code</strong>: Applies minimal optimization to your own code, keeping compile times fast while improving runtime performance</li>
  <li><strong><code class="language-plaintext highlighter-rouge">opt-level = 3</code> for dependencies</strong>: Heavily optimizes Bevy and other dependencies (which rarely change), dramatically improving framerate</li>
</ul>

<p><strong>The trade-off:</strong></p>

<p>Your first build after adding this will take longer (dependencies need to recompile with optimizations). But subsequent builds remain fast since dependencies are cached. You get much better debug performance without sacrificing development speed!</p>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Build intelligent enemies that hunt and attack the player.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aibodh.com/assets/book_assets/chapter7/ch7.gif" /><media:content medium="image" url="https://aibodh.com/assets/book_assets/chapter7/ch7.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 6 - Let There Be Particles</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-6/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 6 - Let There Be Particles" /><published>2026-01-22T00:00:00+00:00</published><updated>2026-01-22T00:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-6</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-6/"><![CDATA[<style>
.tile-image {
  margin: 0 !important;
  object-fit: none !important;
  cursor: default !important;
  pointer-events: none !important;
}
</style>

<!-- Load D3.js for visualizations -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>

<p>By the end of this chapter, you’ll have built a particle system that brings magical powers to life. You’ll create four unique effects (Fire, Arcane, Shadow, and Poison), each with glowing particles that move, rotate, and fade. You’ll learn how to create particle emitters, write a custom shader for glowing effects, and use additive blending to make particles that feel magical.</p>

<blockquote>
  <p><strong>Prerequisites</strong>: This is Chapter 6 of our Bevy tutorial series. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> for updates on new releases. Before starting, complete <a href="/posts/bevy-rust-game-development-chapter-1/">Chapter 1: Let There Be a Player</a>, <a href="/posts/bevy-rust-game-development-chapter-2/">Chapter 2: Let There Be a World</a>, <a href="/posts/bevy-rust-game-development-chapter-3/">Chapter 3: Let The Data Flow</a>, <a href="/posts/bevy-rust-game-development-chapter-4/">Chapter 4: Let There Be Collisions</a>, and <a href="/posts/bevy-rust-game-development-chapter-5/">Chapter 5: Let There Be Pickups</a>, or clone the Chapter 5 code from <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">this repository</a> to follow along.</p>
</blockquote>

<p><img src="/assets/book_assets/chapter6/ch6.gif" alt="Combat System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<strong>Before We Begin:</strong> <em style="font-size: 14px;">I'm constantly working to improve this tutorial and make your learning journey enjoyable. Your feedback matters - share your frustrations, questions, or suggestions on <a href="https://www.reddit.com/r/bevy/" target="_blank">Reddit</a>/<a target="_blank" href="https://discord.com/invite/cD9qEsSjUH">Discord</a>/<a href="https://www.linkedin.com/in/febinjohnjames" target="_blank">LinkedIn</a>. Loved it? Let me know what worked well for you! Together, we'll make game development with Rust and Bevy more accessible for everyone.</em>
</div>

<h2 id="lets-give-your-players-magic-powers">Let’s Give Your Players Magic Powers</h2>

<p>By the end of this chapter, you’ll learn:</p>
<ul>
  <li>How to spawn and update thousands of particles efficiently</li>
  <li>Add variance for organic, natural looking effects</li>
  <li>Custom shaders with additive blending for that magical glow</li>
  <li>Building a flexible system that’s easy to extend</li>
  <li>Give your player magical powers</li>
</ul>

<h3 id="understanding-particle-systems">Understanding Particle Systems</h3>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_ae9a113ed58522eff1b6265cb90f1f99.svg" alt="Comic Panel" class="comic-image" data-comic-hash="ae9a113ed58522eff1b6265cb90f1f99" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>A particle system spawns many small sprites that each:</p>
<ol>
  <li><strong>Spawn</strong> from an emitter with initial properties (position, velocity, color, size)</li>
  <li><strong>Live</strong> for a short time, moving and changing</li>
  <li><strong>Die</strong> when their lifetime expires</li>
</ol>

<p><strong>The magic is in the numbers</strong>: spawn enough particles with slight variations, and they combine to create complex, beautiful effects.</p>

<h3 id="building-the-particle-system">Building the Particle System</h3>

<p>Each particle needs to be independent—moving, rotating, fading, and shrinking on its own. To achieve this, we need two types of properties: <strong>physics</strong> (how it moves) and <strong>visuals</strong> (how it looks). The physics properties like velocity, acceleration, and angular velocitygive particles realistic motion.</p>

<div id="particle-physics-viz" style="margin: 30px auto; max-width: 700px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <div style="text-align: center; margin-bottom: 20px;">
    <strong style="font-size: 1.1em; color: #333 !important;">Physics + Visual Properties in Action</strong>
    <p style="color: #666 !important; font-size: 0.9em; margin-top: 8px;">Watch particles move (velocity), rotate (angular velocity), shrink (scale curve), and fade (color curve)</p>
  </div>
  <div style="display: flex; justify-content: center;">
    <svg id="physics-svg" viewBox="0 0 700 350" style="width: 100%; max-width: 700px; height: auto;"></svg>
  </div>
</div>

<script>
(function() {
  const svg = d3.select("#physics-svg");
  const width = 700, height = 350;
  
  // Background
  svg.append("rect")
    .attr("width", width)
    .attr("height", height)
    .attr("fill", "#fafafa");
  
  // Emitter
  const emitterX = width / 2;
  const emitterY = height / 2;
  
  svg.append("circle")
    .attr("cx", emitterX)
    .attr("cy", emitterY)
    .attr("r", 10)
    .attr("fill", "#9b59b6")
    .attr("stroke", "white")
    .attr("stroke-width", 3);
  
  svg.append("text")
    .attr("x", emitterX)
    .attr("y", emitterY - 20)
    .attr("text-anchor", "middle")
    .attr("font-size", "12")
    .attr("fill", "#666")
    .text("Emitter");
  
  // Legend
  const legendY = 30;
  const legendItems = [
    { label: "Velocity: moves particle outward", color: "#9b59b6" },
    { label: "Angular velocity: rotates particle", color: "#e74c3c" },
    { label: "Scale curve: shrinks over time", color: "#3498db" },
    { label: "Color curve: fades to black", color: "#2ecc71" }
  ];
  
  legendItems.forEach((item, i) => {
    const y = legendY + i * 20;
    svg.append("circle")
      .attr("cx", 20)
      .attr("cy", y)
      .attr("r", 4)
      .attr("fill", item.color);
    
    svg.append("text")
      .attr("x", 30)
      .attr("y", y + 4)
      .attr("font-size", "11")
      .attr("fill", "#666")
      .text(item.label);
  });
  
  // Particle class with circles
  class AnimatedParticle {
    constructor(delay, angle) {
      this.delay = delay;
      this.angle = angle;
      this.maxLifetime = 2.5;
      this.startScale = 14;
      this.endScale = 3;
      this.speed = 100;
      this.spiralFactor = 30;
      
      // Create main circle
      this.circle = svg.append("circle")
        .attr("fill", "#9b59b6")
        .attr("stroke", "white")
        .attr("stroke-width", 2)
        .style("filter", "drop-shadow(0px 0px 6px rgba(0,0,0,0.3))");
    }
    
    update(globalProgress) {
      const progress = Math.max(0, Math.min(1, (globalProgress - this.delay) / 0.7));
      
      if (progress <= 0) {
        this.circle.attr("opacity", 0);
        return;
      }
      
      const t = progress * this.maxLifetime;
      
      // Spiral motion: move outward while rotating
      const distance = this.speed * t;
      const spiralAngle = this.angle + t * 0.5;
      const x = emitterX + distance * Math.cos(spiralAngle);
      const y = emitterY + distance * Math.sin(spiralAngle);
      
      // Visual: scale shrinks
      const scale = this.startScale + (this.endScale - this.startScale) * progress;
      
      // Visual: color fades (bright purple → dark purple → black)
      const brightness = 1 - progress;
      const r = Math.round(155 * brightness);
      const g = Math.round(89 * brightness);
      const b = Math.round(182 * brightness);
      
      this.circle
        .attr("cx", x)
        .attr("cy", y)
        .attr("r", scale)
        .attr("fill", `rgb(${r}, ${g}, ${b})`)
        .attr("opacity", 1 - progress * 0.5);
    }
  }
  
  // Create particles in a circle pattern
  const particles = [];
  const particleCount = 12;
  for (let i = 0; i < particleCount; i++) {
    const angle = (i / particleCount) * Math.PI * 2;
    particles.push(new AnimatedParticle(i * 0.04, angle));
  }
  
  // Animation loop
  function animate() {
    const duration = 3500;
    const startTime = Date.now();
    
    function update() {
      const elapsed = Date.now() - startTime;
      const progress = (elapsed % duration) / duration;
      
      particles.forEach(p => p.update(progress));
      
      requestAnimationFrame(update);
    }
    update();
  }
  animate();
})();
</script>

<p>Particles shouldn’t live forever. A fire particle needs to burn out, a magic spell needs to fade away. But particles also need smooth animations as they age, they should gradually fade, shrink, and change color over time, not just blink out of existence.</p>

<p>To make this happen, particles need to track two things: when to die and how far along they are in their life. That’s why we use a countdown timer paired with progress tracking.</p>

<p>A countdown timer (<code class="language-plaintext highlighter-rouge">lifetime</code>) tells us when to delete the particle, but not its progress value. A particle with 0.5s left could be 25% done (started at 2.0s) or 99% done (started at 0.51s).</p>

<p>We need both values:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">lifetime</code> - time remaining</li>
  <li><code class="language-plaintext highlighter-rouge">max_lifetime</code> - original duration</li>
</ul>

<p>Then: <code class="language-plaintext highlighter-rouge">progress = 1.0 - (lifetime / max_lifetime)</code></p>

<p>Now we know exactly where the particle is: 0% at birth, 50% at midpoint, 100% at death. This progress value drives all animations like color, size, opacity. Without both values, particles just blink on/off. With both, they transition smoothly.</p>

<div id="lifetime-progress-viz" style="margin: 30px auto; max-width: 700px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <div style="text-align: center; margin-bottom: 20px;">
    <strong style="font-size: 1.1em; color: #333 !important;">Lifetime vs Progress</strong>
    <p style="color: #666 !important; font-size: 0.9em; margin-top: 8px;">Watch two particles with different max_lifetimes die at different times</p>
  </div>
  <div style="display: flex; justify-content: center;">
    <svg id="lifetime-svg" viewBox="0 0 700 400" style="width: 100%; max-width: 700px; height: auto;"></svg>
  </div>
</div>

<script>
(function() {
  const svg = d3.select("#lifetime-svg");
  const width = 700, height = 400;
  
  // Background
  svg.append("rect")
    .attr("width", width)
    .attr("height", height)
    .attr("fill", "#fafafa");
  
  // Starting X position (particles spawn from label boxes)
  const startX = 130;
  
  // Particle configurations - 4s and 2s lifetimes, larger particles
  const particleConfigs = [
    { maxLifetime: 4.0, color: "#ff6b6b", name: "Particle A", yOffset: -80, speed: 100 },
    { maxLifetime: 2.0, color: "#4ecdc4", name: "Particle B", yOffset: 80, speed: 100 }
  ];
  
  // Create particle groups
  const particleGroups = particleConfigs.map((config, i) => {
    const group = svg.append("g");
    
    // Create a background panel for the label
    const labelY = height / 2 + config.yOffset;
    const labelX = 30;
    
    group.append("rect")
      .attr("x", labelX)
      .attr("y", labelY - 20)
      .attr("width", 100)
      .attr("height", 35)
      .attr("fill", "white")
      .attr("stroke", config.color)
      .attr("stroke-width", 2)
      .attr("rx", 6)
      .attr("opacity", 0.9);
    
    // Label - name
    group.append("text")
      .attr("x", labelX + 50)
      .attr("y", labelY - 5)
      .attr("text-anchor", "middle")
      .attr("font-size", "12")
      .attr("fill", config.color)
      .attr("font-weight", "bold")
      .text(config.name);
    
    // Label - lifetime
    group.append("text")
      .attr("x", labelX + 50)
      .attr("y", labelY + 9)
      .attr("text-anchor", "middle")
      .attr("font-size", "10")
      .attr("fill", "#666")
      .text(`${config.maxLifetime}s lifetime`);
    
    return {
      config,
      particles: [],
      group
    };
  });
  
  // Animation
  function animate() {
    const cycleDuration = 5000;
    const startTime = Date.now();
    
    function update() {
      const elapsed = Date.now() - startTime;
      const globalTime = (elapsed % cycleDuration) / 1000;
      
      particleGroups.forEach(pg => {
        // Spawn new particles periodically
        if (globalTime < 0.1 && pg.particles.length === 0) {
          for (let i = 0; i < 8; i++) {
            pg.particles.push({
              spawnTime: globalTime,
              circle: pg.group.append("circle")
                .attr("r", 16) // Even larger particles
                .attr("fill", pg.config.color)
                .attr("stroke", "white")
                .attr("stroke-width", 2)
                .style("filter", "drop-shadow(0px 0px 5px rgba(0,0,0,0.3))")
            });
          }
        }
        
        // Update existing particles
        pg.particles = pg.particles.filter(p => {
          const age = globalTime - p.spawnTime;
          
          // Check if particle is dead
          if (age >= pg.config.maxLifetime) {
            p.circle.remove();
            return false;
          }
          
          // Calculate progress
          const progress = age / pg.config.maxLifetime;
          
          // Position - start from right edge of label box
          const x = startX + pg.config.speed * age;
          const y = height / 2 + pg.config.yOffset;
          
          // Scale shrinks over time
          const scale = 16 * (1 - progress * 0.7);
          
          // Opacity fades
          const opacity = 1 - progress * 0.3;
          
          p.circle
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", scale)
            .attr("opacity", opacity);
          
          return true;
        });
      });
      
      requestAnimationFrame(update);
    }
    update();
  }
  animate();
})();
</script>

<h3 id="particle-components">Particle Components</h3>

<p>Now that we understand what properties particles need, let’s create these variables for our particle system. We’ll bundle them into a <code class="language-plaintext highlighter-rouge">Particle</code> component that tracks everything from physics to visuals.</p>

<p>Create <code class="language-plaintext highlighter-rouge">particles</code> folder inside <code class="language-plaintext highlighter-rouge">src</code> and add <code class="language-plaintext highlighter-rouge">src/particles/components.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/particles/components.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// A single particle in the particle system</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Particle</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">velocity</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>           <span class="c1">// Movement speed and direction (units/sec)</span>
    <span class="k">pub</span> <span class="n">lifetime</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>             <span class="c1">// Remaining time before death (seconds)</span>
    <span class="k">pub</span> <span class="n">max_lifetime</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>         <span class="c1">// Original lifetime for progress calculation</span>
    <span class="k">pub</span> <span class="n">scale</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>                <span class="c1">// Current size multiplier</span>
    <span class="k">pub</span> <span class="n">angular_velocity</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>     <span class="c1">// Rotation speed (radians/sec)</span>
    <span class="k">pub</span> <span class="n">acceleration</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>        <span class="c1">// Forces like gravity (units/sec²)</span>
    <span class="c1">// Color curve support (start → mid → end)</span>
    <span class="k">pub</span> <span class="n">start_color</span><span class="p">:</span> <span class="n">Color</span><span class="p">,</span>        <span class="c1">// Color at birth (0% lifetime)</span>
    <span class="k">pub</span> <span class="n">mid_color</span><span class="p">:</span> <span class="n">Color</span><span class="p">,</span>          <span class="c1">// Color at midpoint (50% lifetime)</span>
    <span class="k">pub</span> <span class="n">end_color</span><span class="p">:</span> <span class="n">Color</span><span class="p">,</span>          <span class="c1">// Color at death (100% lifetime)</span>
    <span class="c1">// Scale curve support</span>
    <span class="k">pub</span> <span class="n">start_scale</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>          <span class="c1">// Size at birth</span>
    <span class="k">pub</span> <span class="n">end_scale</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>            <span class="c1">// Size at death (usually smaller)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why three colors?</strong></p>

<p>We animate color over the particle’s lifetime using a <strong>curve</strong>:</p>
<ul>
  <li>Start (bright) → Mid (dimmer) → End (fade to black)</li>
</ul>

<p>This creates smooth transitions. A simple blend between two colors looks linear and boring. Three control points give us more expressive fading.</p>

<div id="particle-lifetime-viz" style="margin: 30px auto; max-width: 600px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <div style="text-align: center; margin-bottom: 20px;">
    <strong style="font-size: 1.1em; color: #333 !important;">Particle Color Curve</strong>
    <p style="color: #666 !important; font-size: 0.9em; margin-top: 8px;">Watch how a Shadow particle smoothly transitions through three color keyframes</p>
  </div>
  <div style="display: flex; justify-content: center;">
    <svg id="particle-curve-svg" viewBox="0 0 600 300" style="width: 100%; max-width: 600px; height: auto;"></svg>
  </div>
</div>

<script>
(function() {
  const svg = d3.select("#particle-curve-svg");
  const width = 600, height = 300;
  const padding = 60;

  // Shadow particle colors (matching the code example)
  const startColor = { r: 0.6, g: 0.2, b: 1.2 };      // Dark purple (Shadow power)
  const midColor = { r: 0.42, g: 0.14, b: 0.84 };     // Dimmer purple (70% of start)
  const endColor = { r: 0.18, g: 0.06, b: 0.36 };     // Very dark purple (30% of start, fade to black)

  function mixColors(c1, c2, t) {
    return {
      r: c1.r + (c2.r - c1.r) * t,
      g: c1.g + (c2.g - c1.g) * t,
      b: c1.b + (c2.b - c1.b) * t
    };
  }

  function getCurrentColor(progress) {
    if (progress < 0.5) {
      // First half: start → mid
      const t = progress * 2.0;
      return mixColors(startColor, midColor, t);
    } else {
      // Second half: mid → end
      const t = (progress - 0.5) * 2.0;
      return mixColors(midColor, endColor, t);
    }
  }

  function colorToString(color) {
    return `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)})`;
  }

  // Define the 3 keyframe positions
  const startPoint = { x: padding, y: padding + 30 };           // Start: high (bright)
  const midPoint = { x: width / 2, y: height / 2 + 20 };        // Mid: middle (dimmer)
  const endPoint = { x: width - padding, y: height - padding }; // End: low (fade to black)

  // Create smooth curve using quadratic Bezier curves through 3 points
  // Control points are calculated to make curve pass smoothly through midPoint
  const ctrl1 = { x: startPoint.x + 100, y: startPoint.y };     // Control point for first segment
  const ctrl2 = { x: endPoint.x - 100, y: endPoint.y };         // Control point for second segment

  // Build SVG path: Start -> smooth curve through Mid -> End
  const pathD = `M ${startPoint.x} ${startPoint.y}
                 Q ${ctrl1.x} ${ctrl1.y} ${midPoint.x} ${midPoint.y}
                 Q ${ctrl2.x} ${ctrl2.y} ${endPoint.x} ${endPoint.y}`;

  // Draw the path
  const path = svg.append("path")
    .attr("d", pathD)
    .attr("fill", "none")
    .attr("stroke", "#ccc")
    .attr("stroke-width", 4)
    .attr("stroke-linecap", "round");

  const pathLength = path.node().getTotalLength();

  // Draw keyframe markers at the 3 exact points
  const keyframePoints = [startPoint, midPoint, endPoint];
  const keyframeColors = [startColor, midColor, endColor];

  keyframePoints.forEach((point, i) => {
    svg.append("circle")
      .attr("cx", point.x)
      .attr("cy", point.y)
      .attr("r", 8)
      .attr("fill", colorToString(keyframeColors[i]))
      .attr("stroke", "white")
      .attr("stroke-width", 3)
      .attr("opacity", 0.9);
  });

  // Add labels near each keyframe point
  svg.append("text")
    .attr("x", startPoint.x)
    .attr("y", startPoint.y - 15)
    .attr("text-anchor", "middle")
    .attr("font-size", "12")
    .attr("fill", "#666")
    .text("Start");

  svg.append("text")
    .attr("x", midPoint.x)
    .attr("y", midPoint.y - 15)
    .attr("text-anchor", "middle")
    .attr("font-size", "12")
    .attr("fill", "#666")
    .text("Mid");

  svg.append("text")
    .attr("x", endPoint.x)
    .attr("y", endPoint.y - 15)
    .attr("text-anchor", "middle")
    .attr("font-size", "12")
    .attr("fill", "#666")
    .text("End");

  // Create animated particle
  const particle = svg.append("circle")
    .attr("r", 16)
    .attr("stroke", "white")
    .attr("stroke-width", 3)
    .style("filter", "drop-shadow(0px 0px 8px rgba(0,0,0,0.3))");

  // Create trail effect
  const trailGroup = svg.append("g");
  const trailParticles = [];
  const maxTrailLength = 8;

  for (let i = 0; i < maxTrailLength; i++) {
    trailParticles.push(
      trailGroup.append("circle")
        .attr("r", 12 - i * 1.2)
        .attr("opacity", 0)
    );
  }

  // Animation function
  function animate() {
    const duration = 3000; // 3 seconds
    const startTime = Date.now();

    function update() {
      const elapsed = Date.now() - startTime;
      const progress = (elapsed % duration) / duration;

      // Get current position and color
      const point = path.node().getPointAtLength(progress * pathLength);
      const color = getCurrentColor(progress);
      const colorStr = colorToString(color);

      // Update main particle
      particle
        .attr("cx", point.x)
        .attr("cy", point.y)
        .attr("fill", colorStr);

      // Update trail
      trailParticles.forEach((trail, i) => {
        const trailProgress = Math.max(0, progress - (i + 1) * 0.03);
        if (trailProgress > 0) {
          const trailPoint = path.node().getPointAtLength(trailProgress * pathLength);
          const trailColor = getCurrentColor(trailProgress);
          trail
            .attr("cx", trailPoint.x)
            .attr("cy", trailPoint.y)
            .attr("fill", colorToString(trailColor))
            .attr("opacity", 0.6 - i * 0.07);
        } else {
          trail.attr("opacity", 0);
        }
      });

      requestAnimationFrame(update);
    }

    update();
  }

  // Start animation
  animate();
})();
</script>

<p>The <code class="language-plaintext highlighter-rouge">Particle</code> struct holds all the data, but we need methods to:</p>

<ol>
  <li><strong>Create particles easily</strong> - A constructor that sets sensible defaults</li>
  <li><strong>Customize particles</strong> - Builder methods to override specific properties</li>
  <li><strong>Calculate animations</strong> - Methods that compute current color and scale based on lifetime progress</li>
</ol>

<p>Without these methods, every system that renders particles would need to duplicate this logic. By centralizing it here, we ensure consistency and make the code easier to maintain.</p>

<p>Let’s implement the particle methods:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/components.rs</span>

<span class="k">impl</span> <span class="n">Particle</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">velocity</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span> <span class="n">lifetime</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">scale</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">start_color</span><span class="p">:</span> <span class="n">Color</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">velocity</span><span class="p">,</span>
            <span class="n">lifetime</span><span class="p">,</span>
            <span class="n">max_lifetime</span><span class="p">:</span> <span class="n">lifetime</span><span class="p">,</span>
            <span class="n">scale</span><span class="p">,</span>
            <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
            <span class="n">start_color</span><span class="p">,</span>
            <span class="n">mid_color</span><span class="p">:</span> <span class="n">start_color</span><span class="p">,</span>  <span class="c1">// Default to same color</span>
            <span class="n">end_color</span><span class="p">:</span> <span class="n">start_color</span><span class="p">,</span>
            <span class="n">start_scale</span><span class="p">:</span> <span class="n">scale</span><span class="p">,</span>
            <span class="n">end_scale</span><span class="p">:</span> <span class="n">scale</span> <span class="o">*</span> <span class="mf">0.5</span><span class="p">,</span>  <span class="c1">// Default: shrink to half</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_angular_velocity</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">angular_velocity</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.angular_velocity</span> <span class="o">=</span> <span class="n">angular_velocity</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_acceleration</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">acceleration</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.acceleration</span> <span class="o">=</span> <span class="n">acceleration</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>

    <span class="cd">/// Set color curve for smooth color transitions</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_color_curve</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">mid</span><span class="p">:</span> <span class="n">Color</span><span class="p">,</span> <span class="n">end</span><span class="p">:</span> <span class="n">Color</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.mid_color</span> <span class="o">=</span> <span class="n">mid</span><span class="p">;</span>
        <span class="k">self</span><span class="py">.end_color</span> <span class="o">=</span> <span class="n">end</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>

    <span class="cd">/// Set scale curve</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_scale_curve</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">end_scale</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.end_scale</span> <span class="o">=</span> <span class="n">end_scale</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>

    <span class="cd">/// Returns the normalized lifetime progress (0.0 to 1.0)</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">progress</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
        <span class="mf">1.0</span> <span class="o">-</span> <span class="p">(</span><span class="k">self</span><span class="py">.lifetime</span> <span class="o">/</span> <span class="k">self</span><span class="py">.max_lifetime</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="cd">/// Get interpolated color based on lifetime progress</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">current_color</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Color</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">progress</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.progress</span><span class="p">();</span>
        
        <span class="k">if</span> <span class="n">progress</span> <span class="o">&lt;</span> <span class="mf">0.5</span> <span class="p">{</span>
            <span class="c1">// First half: start → mid</span>
            <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="n">progress</span> <span class="o">*</span> <span class="mf">2.0</span><span class="p">;</span>  <span class="c1">// Remap 0.0-0.5 to 0.0-1.0</span>
            <span class="k">self</span><span class="py">.start_color</span><span class="nf">.mix</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.mid_color</span><span class="p">,</span> <span class="n">t</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// Second half: mid → end</span>
            <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="p">(</span><span class="n">progress</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="mf">2.0</span><span class="p">;</span>  <span class="c1">// Remap 0.5-1.0 to 0.0-1.0</span>
            <span class="k">self</span><span class="py">.mid_color</span><span class="nf">.mix</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.end_color</span><span class="p">,</span> <span class="n">t</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="cd">/// Get interpolated scale based on lifetime progress</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">current_scale</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">progress</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.progress</span><span class="p">();</span>
        <span class="k">self</span><span class="py">.start_scale</span><span class="nf">.lerp</span><span class="p">(</span><span class="k">self</span><span class="py">.end_scale</span><span class="p">,</span> <span class="n">progress</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>The Builder Pattern</strong></p>

<p>Methods like <code class="language-plaintext highlighter-rouge">with_angular_velocity()</code> and <code class="language-plaintext highlighter-rouge">with_color_curve()</code> use the <strong>builder pattern</strong>, a Rust idiom for constructing complex objects step-by-step. Each method:</p>
<ul>
  <li>Takes <code class="language-plaintext highlighter-rouge">self</code> (ownership of the particle)</li>
  <li>Modifies one field</li>
  <li>Returns <code class="language-plaintext highlighter-rouge">self</code> back</li>
</ul>

<p>This lets us chain calls together:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="nn">Particle</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">velocity</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="nn">Color</span><span class="p">::</span><span class="n">RED</span><span class="p">)</span>
    <span class="nf">.with_angular_velocity</span><span class="p">(</span><span class="mf">3.14</span><span class="p">)</span>
    <span class="nf">.with_color_curve</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">ORANGE</span><span class="p">,</span> <span class="nn">Color</span><span class="p">::</span><span class="n">BLACK</span><span class="p">)</span>
    <span class="nf">.with_scale_curve</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</code></pre></div></div>

<p>Clean, readable, and flexible. You only specify what you need to customize, everything else uses defaults from <code class="language-plaintext highlighter-rouge">new()</code>.</p>

<p><strong>The Calculation Methods</strong></p>

<p>Methods like <code class="language-plaintext highlighter-rouge">progress()</code>, <code class="language-plaintext highlighter-rouge">current_color()</code>, and <code class="language-plaintext highlighter-rouge">current_scale()</code> are where the magic happens. They <strong>compute</strong> values based on the particle’s current state:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">progress()</code> - Converts remaining lifetime into a 0.0-1.0 percentage, so we know exactly where the particle is in its life cycle (just born at 0%, halfway through at 50%, about to die at 100%)</li>
  <li><code class="language-plaintext highlighter-rouge">current_color()</code> - Blends smoothly between the three colors based on progress, creating that magical fade effect where fire particles glow bright orange then dim to black, or poison clouds shift from sickly green to dark purple</li>
  <li><code class="language-plaintext highlighter-rouge">current_scale()</code> - Gradually shrinks the particle from full size to tiny as it ages, making effects feel more dynamic and preventing particles from just blinking out of existence</li>
</ul>

<p><strong>The <code class="language-plaintext highlighter-rouge">current_color()</code> method:</strong></p>

<ol>
  <li>First, it calls <code class="language-plaintext highlighter-rouge">progress()</code> to get a value between 0.0 (particle just spawned) and 1.0 (particle about to die)</li>
  <li>Then it splits the lifetime into two halves:
    <ul>
      <li><strong>First half (0% to 50%)</strong>: Blends from <code class="language-plaintext highlighter-rouge">start_color</code> to <code class="language-plaintext highlighter-rouge">mid_color</code></li>
      <li><strong>Second half (50% to 100%)</strong>: Blends from <code class="language-plaintext highlighter-rouge">mid_color</code> to <code class="language-plaintext highlighter-rouge">end_color</code></li>
    </ul>
  </li>
</ol>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">.mix()</code>?</strong></p>

<p>Bevy’s color interpolation method. <code class="language-plaintext highlighter-rouge">color1.mix(&amp;color2, 0.5)</code> gives you 50% between the two colors.</p>

<p><strong>Why the <code class="language-plaintext highlighter-rouge">* 2.0</code>?</strong> 
<br />
The <code class="language-plaintext highlighter-rouge">mix()</code> function needs input from 0.0 to 1.0 to do a complete blend. During the first half of life, progress only reaches 0.5. That’s not enough, it would only blend halfway. Multiplying by 2 makes progress reach 1.0 by the halfway point, giving <code class="language-plaintext highlighter-rouge">mix()</code> the full range it needs.</p>

<p><strong>The <code class="language-plaintext highlighter-rouge">current_scale()</code> method:</strong></p>

<p>You know how good particle effects don’t just blink out,they shrink and fade away naturally? That’s what <code class="language-plaintext highlighter-rouge">current_scale()</code> creates. To achieve this, we gradually reduce the particle’s size from its starting size to a tiny ending size based on how much of its life has passed.</p>

<p>For example, imagine a particle that starts at size 2.0 and should end at 0.5:</p>
<ul>
  <li>At 0% progress: size is 2.0 (full size, just spawned)</li>
  <li>At 50% progress: size is 1.25 (halfway between)</li>
  <li>At 100% progress: size is 0.5 (tiny, about to disappear)</li>
</ul>

<p>The particle smoothly shrinks over time, creating that satisfying dissipation effect.</p>

<h3 id="particle-emitter">Particle Emitter</h3>

<p>Now we need something to actually <strong>create</strong> particles. That’s the job of the <code class="language-plaintext highlighter-rouge">ParticleEmitter</code> component. Think of it as a particle factory attached to an entity (like your player character).</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_7fb12aa08640372ac804b3832e3cda8b.svg" alt="Comic Panel" class="comic-image" data-comic-hash="7fb12aa08640372ac804b3832e3cda8b" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>The emitter needs to track:</p>
<ul>
  <li><strong>When to spawn</strong>  - A timer that ticks down and triggers particle creation</li>
  <li><strong>How many to spawn</strong> - Burst size (e.g., 5 particles at once)</li>
  <li><strong>What kind to spawn</strong> - The template for creating particles</li>
  <li><strong>Whether it’s active</strong> - Can be turned on/off</li>
  <li><strong>One-shot vs continuous</strong> - Fire once or keep firing</li>
</ul>

<p>Here’s the emitter component:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/components.rs</span>

<span class="cd">/// Configuration for a particle emitter</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">ParticleEmitter</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">spawn_timer</span><span class="p">:</span> <span class="n">Timer</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">particle_config</span><span class="p">:</span> <span class="n">ParticleConfig</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">active</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">one_shot</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">has_spawned</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">ParticleEmitter</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">spawn_rate</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">particle_config</span><span class="p">:</span> <span class="n">ParticleConfig</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">spawn_timer</span><span class="p">:</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">spawn_rate</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">),</span>
            <span class="n">particles_per_spawn</span><span class="p">,</span>
            <span class="n">particle_config</span><span class="p">,</span>
            <span class="n">active</span><span class="p">:</span> <span class="k">true</span><span class="p">,</span>
            <span class="n">one_shot</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
            <span class="n">has_spawned</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">one_shot</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.one_shot</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How does <code class="language-plaintext highlighter-rouge">one_shot</code> work?</strong></p>

<p>Without <code class="language-plaintext highlighter-rouge">one_shot</code>, the emitter keeps spawning particles forever (or until you manually set <code class="language-plaintext highlighter-rouge">active = false</code>). This is perfect for continuous effects like a torch flame or a magic aura.</p>

<p>With <code class="language-plaintext highlighter-rouge">one_shot = true</code>, the emitter spawns particles <strong>once</strong> and then automatically deactivates. This is ideal for one-time effects like a spell cast or an explosion—you don’t want those repeating every frame!</p>

<h3 id="particle-configuration">Particle Configuration</h3>

<p>The <code class="language-plaintext highlighter-rouge">ParticleConfig</code> struct is the DNA for creating particles. It defines all the properties each particle should have, but with a twist: <strong>variance</strong>.</p>

<div id="variance-viz" style="margin: 30px auto; max-width: 700px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <div style="text-align: center; margin-bottom: 20px;">
    <strong style="font-size: 1.1em; color: #333 !important;">Variance</strong>
    <p style="color: #666 !important; font-size: 0.9em; margin-top: 8px;">Without variance (left) vs with variance (right) - see the difference!</p>
  </div>
  <div style="display: flex; justify-content: center;">
    <svg id="variance-svg" viewBox="0 0 700 300" style="width: 100%; max-width: 700px; height: auto;"></svg>
  </div>
</div>

<script>
(function() {
  const svg = d3.select("#variance-svg");
  const width = 700, height = 300;
  
  // Background
  svg.append("rect")
    .attr("width", width)
    .attr("height", height)
    .attr("fill", "#fafafa");
  
  // Divider line
  svg.append("line")
    .attr("x1", width / 2)
    .attr("y1", 0)
    .attr("x2", width / 2)
    .attr("y2", height)
    .attr("stroke", "#ccc")
    .attr("stroke-width", 2)
    .attr("stroke-dasharray", "5,5");
  
  // Labels
  svg.append("text")
    .attr("x", width / 4)
    .attr("y", 25)
    .attr("text-anchor", "middle")
    .attr("font-size", "14")
    .attr("font-weight", "bold")
    .attr("fill", "#666");

  svg.append("text")
    .attr("x", 3 * width / 4)
    .attr("y", 25)
    .attr("text-anchor", "middle")
    .attr("font-size", "14")
    .attr("font-weight", "bold")
    .attr("fill", "#666");
  
  const emitterY = height / 2;
  const particleCount = 30;
  
  // Left side: No variance
  const leftEmitterX = width / 4;
  svg.append("circle")
    .attr("cx", leftEmitterX)
    .attr("cy", emitterY)
    .attr("r", 8)
    .attr("fill", "#ff6b6b")
    .attr("stroke", "white")
    .attr("stroke-width", 2);
  
  // Right side: With variance
  const rightEmitterX = 3 * width / 4;
  svg.append("circle")
    .attr("cx", rightEmitterX)
    .attr("cy", emitterY)
    .attr("r", 8)
    .attr("fill", "#4ecdc4")
    .attr("stroke", "white")
    .attr("stroke-width", 2);
  
  // Create particle groups
  const leftParticles = [];
  const rightParticles = [];
  
  for (let i = 0; i < particleCount; i++) {
    // Left: identical particles
    leftParticles.push({
      circle: svg.append("circle")
        .attr("r", 4)
        .attr("fill", "#ff6b6b")
        .attr("opacity", 0.7),
      angle: 0, // All same direction
      speed: 80, // All same speed
      size: 4 // All same size
    });
    
    // Right: varied particles
    const variance = 0.3;
    rightParticles.push({
      circle: svg.append("circle")
        .attr("fill", "#4ecdc4")
        .attr("opacity", 0.7),
      angle: (Math.random() - 0.5) * variance * 2, // Random angle
      speed: 80 + (Math.random() - 0.5) * 40, // Random speed
      size: 4 + (Math.random() - 0.5) * 2 // Random size
    });
  }
  
  // Animation
  function animate() {
    const duration = 2000;
    const startTime = Date.now();
    
    function update() {
      const elapsed = Date.now() - startTime;
      const progress = (elapsed % duration) / duration;
      
      // Update left particles (no variance)
      leftParticles.forEach((p, i) => {
        const delay = i / particleCount;
        const particleProgress = Math.max(0, Math.min(1, (progress - delay * 0.3) / 0.7));
        const distance = particleProgress * 120;
        
        p.circle
          .attr("cx", leftEmitterX + distance)
          .attr("cy", emitterY)
          .attr("r", p.size)
          .attr("opacity", particleProgress > 0 ? 0.7 * (1 - particleProgress) : 0);
      });
      
      // Update right particles (with variance)
      rightParticles.forEach((p, i) => {
        const delay = i / particleCount;
        const particleProgress = Math.max(0, Math.min(1, (progress - delay * 0.3) / 0.7));
        const distance = particleProgress * p.speed;
        
        p.circle
          .attr("cx", rightEmitterX + distance * Math.cos(p.angle))
          .attr("cy", emitterY + distance * Math.sin(p.angle))
          .attr("r", p.size)
          .attr("opacity", particleProgress > 0 ? 0.7 * (1 - particleProgress) : 0);
      });
      
      requestAnimationFrame(update);
    }
    update();
  }
  animate();
})();
</script>

<p>Without variance, every particle would be identical (boring!). With variance, each particle gets slightly randomized values, creating organic, natural looking effects. For each property, we store:</p>
<ul>
  <li><strong>Base value</strong> - The target value (e.g., <code class="language-plaintext highlighter-rouge">lifetime: 1.0</code> seconds)</li>
  <li><strong>Variance</strong> - How much to randomize it (e.g., <code class="language-plaintext highlighter-rouge">lifetime_variance: 0.2</code> means ±0.2 seconds)</li>
</ul>

<p>So a particle might live for 0.8 seconds, another for 1.1 seconds, another for 0.95 seconds, all slightly different, making the effect feel alive.</p>

<p>Now let’s create the struct that holds all particle properties. This  <code class="language-plaintext highlighter-rouge">ParticleConfig</code> serves as a template that particle emitters use to spawn new particles. Instead of hardcoding values, we define them once in a config and reuse it.</p>

<p>Here’s the configuration struct:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/components.rs</span>

<span class="cd">/// Configuration for spawning particles</span>
<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">ParticleConfig</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">lifetime</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">lifetime_variance</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">speed</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">speed_variance</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">direction_variance</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>  <span class="c1">// In radians</span>
    <span class="k">pub</span> <span class="n">scale</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">scale_variance</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">color</span><span class="p">:</span> <span class="n">Color</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">angular_velocity</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">acceleration</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">emission_shape</span><span class="p">:</span> <span class="n">EmissionShape</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">ParticleConfig</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">lifetime</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
            <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
            <span class="n">speed</span><span class="p">:</span> <span class="mf">100.0</span><span class="p">,</span>
            <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">10.0</span><span class="p">,</span>
            <span class="n">direction</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">X</span><span class="p">,</span>
            <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
            <span class="n">scale</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
            <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
            <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">,</span>
            <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
            <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
            <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Point</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">EmissionShape</span> <span class="p">{</span>
    <span class="n">Point</span><span class="p">,</span>
    <span class="n">Circle</span> <span class="p">{</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span> <span class="p">},</span>
    <span class="n">Cone</span> <span class="p">{</span> <span class="n">angle</span><span class="p">:</span> <span class="nb">f32</span> <span class="p">},</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Understanding the config attributes:</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">lifetime</code> / <code class="language-plaintext highlighter-rouge">lifetime_variance</code> - How long particles exist (seconds)</li>
  <li><code class="language-plaintext highlighter-rouge">speed</code> / <code class="language-plaintext highlighter-rouge">speed_variance</code> - Initial velocity magnitude</li>
  <li><code class="language-plaintext highlighter-rouge">direction</code> / <code class="language-plaintext highlighter-rouge">direction_variance</code> - Which way particles fly (direction_variance in radians creates spread)</li>
  <li><code class="language-plaintext highlighter-rouge">scale</code> / <code class="language-plaintext highlighter-rouge">scale_variance</code> - Size of particles</li>
  <li><code class="language-plaintext highlighter-rouge">color</code> - Base particle tint (can be HDR values above 1.0)</li>
  <li><code class="language-plaintext highlighter-rouge">angular_velocity</code> / <code class="language-plaintext highlighter-rouge">angular_velocity_variance</code> - How fast particles spin</li>
  <li><code class="language-plaintext highlighter-rouge">acceleration</code> - Constant force applied (like gravity or wind)</li>
  <li><code class="language-plaintext highlighter-rouge">emission_shape</code> - Where particles spawn (Point, Circle, or Cone)</li>
</ul>

<h3 id="particle-system-updates">Particle System Updates</h3>

<p>So far we’ve defined the <strong>data structures</strong>, what particles and emitters <em>are</em>. Now we need the <strong>systems</strong>, the code that actually <em>does</em> things every frame.</p>

<p>We need two key behaviors:</p>
<ol>
  <li><strong>Spawn particles</strong> - Check each emitter’s timer, and when it fires, create new particle entities</li>
  <li><strong>Update particles</strong> - Move them, rotate them, fade their colors, shrink their size, and delete them when they die</li>
</ol>

<p>Without these systems, our components would just sit there doing nothing. Let’s start with the spawning system. Create <code class="language-plaintext highlighter-rouge">src/particles/systems.rs</code>:</p>

<p>To spawn particles, we need two functions working together:</p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">update_emitters</code></strong> - Runs every frame, checks each emitter’s timer, and when the timer fires, triggers particle creation</li>
  <li><strong><code class="language-plaintext highlighter-rouge">spawn_particle</code></strong> - A helper function that creates a single particle entity with randomized properties</li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/particles/systems.rs</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">components</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">material</span><span class="p">::</span><span class="n">ParticleMaterial</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">rand</span><span class="p">::</span><span class="n">Rng</span><span class="p">;</span>

<span class="cd">/// System to update particle emitters and spawn new particles</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_emitters</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">emitters</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="n">Entity</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ParticleEmitter</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">GlobalTransform</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">meshes</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">Mesh</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">materials</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">ParticleMaterial</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">rng</span> <span class="o">=</span> <span class="nn">rand</span><span class="p">::</span><span class="nf">thread_rng</span><span class="p">();</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">entity</span><span class="p">,</span> <span class="k">mut</span> <span class="n">emitter</span><span class="p">,</span> <span class="n">global_transform</span><span class="p">)</span> <span class="k">in</span> <span class="n">emitters</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">emitter</span><span class="py">.active</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Handle one-shot emitters</span>
        <span class="k">if</span> <span class="n">emitter</span><span class="py">.one_shot</span> <span class="o">&amp;&amp;</span> <span class="n">emitter</span><span class="py">.has_spawned</span> <span class="p">{</span>
            <span class="n">emitter</span><span class="py">.active</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">emitter</span><span class="py">.spawn_timer</span><span class="nf">.tick</span><span class="p">(</span><span class="n">time</span><span class="nf">.delta</span><span class="p">());</span>

        <span class="k">if</span> <span class="n">emitter</span><span class="py">.spawn_timer</span><span class="nf">.just_finished</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">emitter</span><span class="py">.has_spawned</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>

            <span class="c1">// Spawn particles</span>
            <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">emitter</span><span class="py">.particles_per_spawn</span> <span class="p">{</span>
                <span class="nf">spawn_particle</span><span class="p">(</span>
                    <span class="o">&amp;</span><span class="k">mut</span> <span class="n">commands</span><span class="p">,</span>
                    <span class="o">&amp;</span><span class="n">emitter</span><span class="py">.particle_config</span><span class="p">,</span>
                    <span class="n">global_transform</span><span class="p">,</span>
                    <span class="o">&amp;</span><span class="k">mut</span> <span class="n">rng</span><span class="p">,</span>
                    <span class="o">&amp;</span><span class="k">mut</span> <span class="n">meshes</span><span class="p">,</span>
                    <span class="o">&amp;</span><span class="k">mut</span> <span class="n">materials</span><span class="p">,</span>
                    <span class="nf">Some</span><span class="p">(</span><span class="n">entity</span><span class="p">),</span>
                    <span class="n">i</span><span class="p">,</span>
                <span class="p">);</span>
            <span class="p">}</span>

            <span class="k">if</span> <span class="n">emitter</span><span class="py">.one_shot</span> <span class="p">{</span>
                <span class="n">emitter</span><span class="py">.active</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How <code class="language-plaintext highlighter-rouge">update_emitters</code> Works</strong></p>

<p>This system runs every frame and manages all particle emitters in the game. Here’s the flow:</p>

<ol>
  <li><strong>Setup</strong> - Create a random number generator (we’ll need it for variance)</li>
  <li><strong>Loop through all emitters</strong> - The <code class="language-plaintext highlighter-rouge">Query</code> gives us every entity with a <code class="language-plaintext highlighter-rouge">ParticleEmitter</code> component</li>
  <li><strong>Skip inactive emitters</strong> - If <code class="language-plaintext highlighter-rouge">active</code> is false, don’t spawn anything</li>
  <li><strong>Handle one-shot logic</strong> - If it’s a one-shot emitter that already spawned, deactivate it</li>
  <li><strong>Tick the timer</strong> - Advance the spawn timer by the frame’s delta time</li>
  <li><strong>Check if timer finished</strong> - When the timer completes, it’s time to spawn!</li>
  <li><strong>Spawn a burst</strong> - Create <code class="language-plaintext highlighter-rouge">particles_per_spawn</code> particles using the <code class="language-plaintext highlighter-rouge">spawn_particle</code> helper</li>
  <li><strong>Deactivate one-shots</strong> - If it’s a one-shot emitter, turn it off after spawning</li>
</ol>

<p>Emitters don’t spawn particles every frame, they use a timer to control the spawn rate. A timer of 0.1 seconds means 10 bursts per second.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">rand::thread_rng()</code>?</strong></p>

<p>Creates a random number generator for this thread. We use it to add variance to particle properties, each particle gets slightly different lifetime, speed, direction, etc.</p>

<p>Now the particle spawning function:</p>

<p><code class="language-plaintext highlighter-rouge">update_emitters</code> handles the emitter’s timer and decides when to spawn particles. But it delegates the actual particle creation to a helper function called <code class="language-plaintext highlighter-rouge">spawn_particle</code>. This function takes the emitter’s configuration and creates a single particle entity with randomized properties (lifetime, speed, direction, etc.), a visual mesh, and all the necessary Bevy components.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/systems.rs</span>

<span class="cd">/// Helper function to spawn a single particle</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_particle</span><span class="p">(</span>
    <span class="n">commands</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">config</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ParticleConfig</span><span class="p">,</span>
    <span class="n">global_transform</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">GlobalTransform</span><span class="p">,</span>
    <span class="n">rng</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">rand</span><span class="p">::</span><span class="nn">rngs</span><span class="p">::</span><span class="n">ThreadRng</span><span class="p">,</span>
    <span class="n">meshes</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">Mesh</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">materials</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">ParticleMaterial</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">owner</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Entity</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">_particle_index</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Calculate randomized values</span>
    <span class="k">let</span> <span class="n">lifetime</span> <span class="o">=</span>
        <span class="n">config</span><span class="py">.lifetime</span> <span class="o">+</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="o">-</span><span class="n">config</span><span class="py">.lifetime_variance</span><span class="o">..</span><span class="n">config</span><span class="py">.lifetime_variance</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">speed</span> <span class="o">=</span> <span class="n">config</span><span class="py">.speed</span> <span class="o">+</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="o">-</span><span class="n">config</span><span class="py">.speed_variance</span><span class="o">..</span><span class="n">config</span><span class="py">.speed_variance</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">scale</span> <span class="o">=</span> <span class="n">config</span><span class="py">.scale</span> <span class="o">+</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="o">-</span><span class="n">config</span><span class="py">.scale_variance</span><span class="o">..</span><span class="n">config</span><span class="py">.scale_variance</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">angular_velocity</span> <span class="o">=</span> <span class="n">config</span><span class="py">.angular_velocity</span>
        <span class="o">+</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="o">-</span><span class="n">config</span><span class="py">.angular_velocity_variance</span><span class="o">..</span><span class="n">config</span><span class="py">.angular_velocity_variance</span><span class="p">);</span>

    <span class="c1">// Calculate direction with variance</span>
    <span class="k">let</span> <span class="n">base_direction</span> <span class="o">=</span> <span class="n">config</span><span class="py">.direction</span><span class="nf">.normalize_or_zero</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="k">if</span> <span class="n">config</span><span class="py">.direction_variance</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span>
        <span class="nf">apply_direction_variance</span><span class="p">(</span><span class="n">base_direction</span><span class="p">,</span> <span class="n">config</span><span class="py">.direction_variance</span><span class="p">,</span> <span class="n">rng</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">base_direction</span>
    <span class="p">};</span>

    <span class="c1">// Calculate emission offset based on shape</span>
    <span class="k">let</span> <span class="n">emission_offset</span> <span class="o">=</span> <span class="k">match</span> <span class="n">config</span><span class="py">.emission_shape</span> <span class="p">{</span>
        <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Point</span> <span class="k">=&gt;</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
        <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Circle</span> <span class="p">{</span> <span class="n">radius</span> <span class="p">}</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">angle</span> <span class="o">=</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="mf">0.0</span><span class="o">..</span><span class="nn">std</span><span class="p">::</span><span class="nn">f32</span><span class="p">::</span><span class="nn">consts</span><span class="p">::</span><span class="n">TAU</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="mf">0.0</span><span class="o">..</span><span class="n">radius</span><span class="p">);</span>
            <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">angle</span><span class="nf">.cos</span><span class="p">()</span> <span class="o">*</span> <span class="n">distance</span><span class="p">,</span> <span class="n">angle</span><span class="nf">.sin</span><span class="p">()</span> <span class="o">*</span> <span class="n">distance</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Cone</span> <span class="p">{</span> <span class="n">angle</span> <span class="p">}</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">cone_angle</span> <span class="o">=</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="o">-</span><span class="n">angle</span><span class="o">..</span><span class="n">angle</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">rotated</span> <span class="o">=</span> <span class="nf">rotate_vector_2d</span><span class="p">(</span><span class="n">base_direction</span><span class="p">,</span> <span class="n">cone_angle</span><span class="p">);</span>
            <span class="n">rotated</span> <span class="o">*</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="mf">0.0</span><span class="o">..</span><span class="mf">1.0</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="n">velocity</span> <span class="o">=</span> <span class="n">direction</span> <span class="o">*</span> <span class="n">speed</span><span class="p">;</span>

    <span class="c1">// Get position directly from GlobalTransform</span>
    <span class="k">let</span> <span class="n">emitter_position</span> <span class="o">=</span> <span class="n">global_transform</span><span class="nf">.translation</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">position</span> <span class="o">=</span> <span class="n">emitter_position</span> <span class="o">+</span> <span class="n">emission_offset</span><span class="p">;</span>

    <span class="c1">// Ensure particles are at a visible Z layer (above player)</span>
    <span class="n">position</span><span class="py">.z</span> <span class="o">=</span> <span class="mf">25.0</span><span class="p">;</span>

    <span class="c1">// Create particle with color curves</span>
    <span class="k">let</span> <span class="n">start_color</span> <span class="o">=</span> <span class="n">config</span><span class="py">.color</span><span class="p">;</span>
    <span class="c1">// Create color curve: bright → slightly dimmer → fade to black</span>
    <span class="k">let</span> <span class="n">mid_color</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">linear</span> <span class="o">=</span> <span class="n">config</span><span class="py">.color</span><span class="nf">.to_linear</span><span class="p">();</span>
        <span class="nn">Color</span><span class="p">::</span><span class="nf">LinearRgba</span><span class="p">(</span><span class="nn">LinearRgba</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
            <span class="n">linear</span><span class="py">.red</span> <span class="o">*</span> <span class="mf">0.7</span><span class="p">,</span>
            <span class="n">linear</span><span class="py">.green</span> <span class="o">*</span> <span class="mf">0.7</span><span class="p">,</span>
            <span class="n">linear</span><span class="py">.blue</span> <span class="o">*</span> <span class="mf">0.7</span><span class="p">,</span>
            <span class="n">linear</span><span class="py">.alpha</span><span class="p">,</span>
        <span class="p">))</span>
    <span class="p">};</span>
    <span class="k">let</span> <span class="n">end_color</span> <span class="o">=</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgba</span><span class="p">(</span>
        <span class="n">config</span><span class="py">.color</span><span class="nf">.to_linear</span><span class="p">()</span><span class="py">.red</span> <span class="o">*</span> <span class="mf">0.3</span><span class="p">,</span>
        <span class="n">config</span><span class="py">.color</span><span class="nf">.to_linear</span><span class="p">()</span><span class="py">.green</span> <span class="o">*</span> <span class="mf">0.3</span><span class="p">,</span>
        <span class="n">config</span><span class="py">.color</span><span class="nf">.to_linear</span><span class="p">()</span><span class="py">.blue</span> <span class="o">*</span> <span class="mf">0.3</span><span class="p">,</span>
        <span class="mf">0.0</span><span class="p">,</span>
    <span class="p">);</span> <span class="c1">// Fading to black</span>

    <span class="k">let</span> <span class="n">particle</span> <span class="o">=</span> <span class="nn">Particle</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">velocity</span><span class="p">,</span> <span class="n">lifetime</span><span class="p">,</span> <span class="n">scale</span><span class="p">,</span> <span class="n">start_color</span><span class="p">)</span>
        <span class="nf">.with_angular_velocity</span><span class="p">(</span><span class="n">angular_velocity</span><span class="p">)</span>
        <span class="nf">.with_acceleration</span><span class="p">(</span><span class="n">config</span><span class="py">.acceleration</span><span class="p">)</span>
        <span class="nf">.with_color_curve</span><span class="p">(</span><span class="n">mid_color</span><span class="p">,</span> <span class="n">end_color</span><span class="p">)</span>
        <span class="nf">.with_scale_curve</span><span class="p">(</span><span class="n">scale</span> <span class="o">*</span> <span class="mf">0.2</span><span class="p">);</span> <span class="c1">// Shrink to 20%</span>

    <span class="c1">// Create a mesh for the particle</span>
    <span class="k">let</span> <span class="n">size</span> <span class="o">=</span> <span class="mf">24.0</span> <span class="o">*</span> <span class="n">scale</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">mesh</span> <span class="o">=</span> <span class="n">meshes</span><span class="nf">.add</span><span class="p">(</span><span class="nn">Rectangle</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">size</span><span class="p">));</span>
    <span class="k">let</span> <span class="n">material</span> <span class="o">=</span> <span class="n">materials</span><span class="nf">.add</span><span class="p">(</span><span class="nn">ParticleMaterial</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">start_color</span><span class="p">));</span>

    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="n">particle</span><span class="p">,</span>
        <span class="nf">Mesh2d</span><span class="p">(</span><span class="n">mesh</span><span class="p">),</span>
        <span class="nf">MeshMaterial2d</span><span class="p">(</span><span class="n">material</span><span class="p">),</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">position</span><span class="p">),</span>
    <span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How <code class="language-plaintext highlighter-rouge">spawn_particle</code> Works</strong></p>

<p>This function creates a single particle entity with randomized properties.</p>

<p><strong>Step 1: Add randomness to basic properties</strong></p>
<ul>
  <li>We don’t want every particle to be identical, it won’t look natural</li>
  <li>Take the base values (how long it lives, how fast it moves, how big it is, how fast it spins)</li>
  <li>Add a random amount within the variance range</li>
  <li>Now each particle is unique!</li>
</ul>

<p><strong>Step 2: Randomize the direction</strong></p>
<ul>
  <li>Particles shouldn’t all fly in exactly the same direction</li>
  <li>Start with the base direction (e.g., “fly to the right”)</li>
  <li>If there’s direction variance, randomly rotate it a bit, otherwise keep it straight</li>
</ul>

<p><strong>Step 3: Pick a spawn position within the emitter’s shape</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Point</code>: all particles spawn at the exact same spot (like a laser beam origin)</li>
  <li><code class="language-plaintext highlighter-rouge">Circle</code>: particles spawn randomly within a circular area (like a campfire)</li>
  <li><code class="language-plaintext highlighter-rouge">Cone</code>: particles spawn in a cone shape (like a flamethrower)</li>
</ul>

<p><strong>Step 4: Figure out where the particle starts</strong></p>
<ul>
  <li>Combine direction and speed to get velocity (how it moves each frame)</li>
  <li>Start at the emitter’s position in the world</li>
  <li>Add the shape offset from Step 3</li>
  <li>Put it at Z = 25.0 so it appears above the player</li>
</ul>

<p><strong>Step 5: Set up the color fade animation</strong></p>
<ul>
  <li>Particles should fade out as they die, not just disappear</li>
  <li>Start: full brightness (the color you configured)</li>
  <li>Middle: 70% brightness (getting dimmer)</li>
  <li>End: 30% brightness and transparent (fading to nothing)</li>
</ul>

<p><strong>Step 6: Create the particle with all its settings</strong></p>
<ul>
  <li>Use the builder pattern to chain all the properties together</li>
  <li>Set velocity, lifetime, scale, rotation speed, color curve, scale curve</li>
  <li>Everything is configured and ready to go</li>
</ul>

<p><strong>Step 7: Make a visual square for the particle</strong></p>
<ul>
  <li>Create a 24-pixel square mesh (scaled by the particle’s size)</li>
  <li>Create a material that uses the particle’s starting color</li>
  <li>This is what you’ll actually see on screen</li>
</ul>

<p><strong>Step 8: Tell Bevy to create the particle entity</strong></p>
<ul>
  <li>Bundle everything together: the particle data, the mesh, the material, the position</li>
  <li>Bevy spawns a new entity with all these components</li>
  <li>The particle is now alive in the game world!</li>
</ul>

<p><strong>Why <code class="language-plaintext highlighter-rouge">std::f32::consts::TAU</code>?</strong></p>

<p>TAU is 2π (approximately 6.28), a full circle in radians. For the <code class="language-plaintext highlighter-rouge">Circle</code> emission shape, we pick a random angle from 0 to TAU to get a point anywhere around the circle.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">.normalize_or_zero()</code>?</strong></p>

<p>Converts a vector to length 1.0, making it a pure direction. If the vector is zero (no direction), it returns (0,0,0) instead of nan (Not a Number). This is safer than <code class="language-plaintext highlighter-rouge">.normalize()</code> which can panic on zero vectors.</p>

<p>Now add the helper functions:</p>

<p>These are small utility functions that help with the math in <code class="language-plaintext highlighter-rouge">spawn_particle</code>. They handle the geometry of spreading particles in different directions, essential for creating cone and spray effects instead of straight lines.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/systems.rs</span>

<span class="cd">/// Apply directional variance to a vector</span>
<span class="k">fn</span> <span class="nf">apply_direction_variance</span><span class="p">(</span>
    <span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="n">variance</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="n">rng</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">rand</span><span class="p">::</span><span class="nn">rngs</span><span class="p">::</span><span class="n">ThreadRng</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec3</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">angle</span> <span class="o">=</span> <span class="n">rng</span><span class="nf">.gen_range</span><span class="p">(</span><span class="o">-</span><span class="n">variance</span><span class="o">..</span><span class="n">variance</span><span class="p">);</span>
    <span class="nf">rotate_vector_2d</span><span class="p">(</span><span class="n">direction</span><span class="p">,</span> <span class="n">angle</span><span class="p">)</span>
<span class="p">}</span>

<span class="cd">/// Rotate a 2D vector by an angle (in radians)</span>
<span class="k">fn</span> <span class="nf">rotate_vector_2d</span><span class="p">(</span><span class="n">vec</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span> <span class="n">angle</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec3</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">cos</span> <span class="o">=</span> <span class="n">angle</span><span class="nf">.cos</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">sin</span> <span class="o">=</span> <span class="n">angle</span><span class="nf">.sin</span><span class="p">();</span>
    <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">vec</span><span class="py">.x</span> <span class="o">*</span> <span class="n">cos</span> <span class="o">-</span> <span class="n">vec</span><span class="py">.y</span> <span class="o">*</span> <span class="n">sin</span><span class="p">,</span> <span class="n">vec</span><span class="py">.x</span> <span class="o">*</span> <span class="n">sin</span> <span class="o">+</span> <span class="n">vec</span><span class="py">.y</span> <span class="o">*</span> <span class="n">cos</span><span class="p">,</span> <span class="n">vec</span><span class="py">.z</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s this rotation math?</strong></p>

<p>This is a 2D rotation matrix. To rotate a vector by an angle:</p>
<ul>
  <li>New X = old X × cos(angle) - old Y × sin(angle)</li>
  <li>New Y = old X × sin(angle) + old Y × cos(angle)</li>
</ul>

<p>Now the particle update system:</p>

<p>We’ve created the spawning system, but now we need to bring particles to life. The <code class="language-plaintext highlighter-rouge">update_particles</code> system runs every frame and handles everything that happens during a particle’s lifetime: moving it, spinning it, fading its color, shrinking its size, and removing it when it dies.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/systems.rs</span>

<span class="cd">/// System to update particle lifetime and properties</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_particles</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">particles</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="n">Entity</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Particle</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">MeshMaterial2d</span><span class="o">&lt;</span><span class="n">ParticleMaterial</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">materials</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">ParticleMaterial</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">entity</span><span class="p">,</span> <span class="k">mut</span> <span class="n">particle</span><span class="p">,</span> <span class="k">mut</span> <span class="n">transform</span><span class="p">,</span> <span class="n">material_handle</span><span class="p">)</span> <span class="k">in</span> <span class="n">particles</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">particle</span><span class="py">.lifetime</span> <span class="o">-=</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>

        <span class="k">if</span> <span class="n">particle</span><span class="py">.lifetime</span> <span class="o">&lt;=</span> <span class="mf">0.0</span> <span class="p">{</span>
            <span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.despawn</span><span class="p">();</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Update position</span>
        <span class="k">let</span> <span class="n">acceleration</span> <span class="o">=</span> <span class="n">particle</span><span class="py">.acceleration</span><span class="p">;</span>
        <span class="n">particle</span><span class="py">.velocity</span> <span class="o">+=</span> <span class="n">acceleration</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
        <span class="n">transform</span><span class="py">.translation</span> <span class="o">+=</span> <span class="n">particle</span><span class="py">.velocity</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>

        <span class="c1">// Update rotation</span>
        <span class="n">transform</span><span class="nf">.rotate_z</span><span class="p">(</span><span class="n">particle</span><span class="py">.angular_velocity</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">());</span>

        <span class="c1">// Apply color curve interpolation</span>
        <span class="k">let</span> <span class="n">current_color</span> <span class="o">=</span> <span class="n">particle</span><span class="nf">.current_color</span><span class="p">();</span>

        <span class="c1">// Apply scale curve interpolation</span>
        <span class="k">let</span> <span class="n">current_scale</span> <span class="o">=</span> <span class="n">particle</span><span class="nf">.current_scale</span><span class="p">();</span>
        <span class="n">transform</span><span class="py">.scale</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">current_scale</span><span class="p">);</span>

        <span class="c1">// Update material color</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">material</span><span class="p">)</span> <span class="o">=</span> <span class="n">materials</span><span class="nf">.get_mut</span><span class="p">(</span><span class="o">&amp;</span><span class="n">material_handle</span><span class="na">.0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">material</span><span class="py">.color</span> <span class="o">=</span> <span class="n">current_color</span><span class="nf">.to_linear</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<ol>
  <li><strong>Countdown lifetime</strong>: Subtract frame time from particle’s remaining life</li>
  <li><strong>Despawn dead particles</strong>: When lifetime hits zero, remove the entity</li>
  <li><strong>Apply acceleration</strong>: Forces modify velocity over time</li>
  <li><strong>Update position</strong>: Move by velocity each frame</li>
  <li><strong>Rotate</strong>: Spin the particle based on angular velocity</li>
  <li><strong>Update color</strong>: Use the curve from <code class="language-plaintext highlighter-rouge">current_color()</code></li>
  <li><strong>Update scale</strong>: Shrink/grow using <code class="language-plaintext highlighter-rouge">current_scale()</code></li>
  <li><strong>Update material</strong>: Push the new color to the shader</li>
</ol>

<p>Finally, add emitter cleanup:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/systems.rs</span>

<span class="cd">/// System to clean up inactive emitters that are one-shot</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">cleanup_finished_emitters</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">emitters</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="n">Entity</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ParticleEmitter</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">entity</span><span class="p">,</span> <span class="n">emitter</span><span class="p">)</span> <span class="k">in</span> <span class="n">emitters</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">emitter</span><span class="py">.one_shot</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">emitter</span><span class="py">.active</span> <span class="p">{</span>
            <span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.despawn</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This removes one-shot emitters after they’ve spawned their particles. Continuous emitters stick around until manually despawned.</p>

<h2 id="the-shader">The Shader</h2>

<p>We now have particles spawning, moving, and dying. But they still look like flat colored squares. To make them glow like magical energy, we need a custom shader that runs on the GPU.</p>

<p><strong>What is a shader?</strong></p>

<p>A shader is a small program that runs on your GPU (graphics card) for every pixel on screen. While our Rust code runs on the CPU and manages game logic, shaders run massively in parallel on the GPU to create visual effects.</p>

<p><strong>What we’re achieving:</strong></p>

<p>We’re creating a radial glow effect where each particle is bright and intense at the center, smoothly fading to transparent at the edges. This makes particles look like glowing orbs of energy instead of flat squares.</p>

<div id="shader-glow-viz" style="margin: 30px auto; max-width: 700px; background: #f5f5f5; padding: 20px; border-radius: 8px;">
  <div style="text-align: center; margin-bottom: 20px;">
    <strong style="font-size: 1.1em; color: #333 !important;">Shader Glow Effect Comparison</strong>
    <p style="color: #666 !important; font-size: 0.9em; margin-top: 8px;">Without shader (left) vs With radial gradient shader (right)</p>
  </div>
  <div style="display: flex; justify-content: center; position: relative;">
    <canvas id="glow-canvas" width="700" height="350" style="width: 100%; max-width: 700px; height: auto; background: #fafafa; border-radius: 4px;"></canvas>
    <canvas id="ui-canvas" width="700" height="350" style="position: absolute; width: 100%; max-width: 700px; height: auto; pointer-events: none;"></canvas>
  </div>
</div>

<script>
(function() {
  const canvas = document.getElementById('glow-canvas');
  const gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false });
  
  if (!gl) {
    console.error('WebGL not supported');
    return;
  }
  
  const width = canvas.width;
  const height = canvas.height;
  gl.viewport(0, 0, width, height);
  
  // Vertex shader - simple passthrough
  const vertexShaderSource = `
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    attribute vec4 a_color;
    attribute float a_size;
    attribute float a_useGlow;
    
    varying vec2 v_texCoord;
    varying vec4 v_color;
    varying float v_useGlow;
    
    void main() {
      gl_Position = vec4(a_position, 0.0, 1.0);
      gl_PointSize = a_size;
      v_texCoord = a_texCoord;
      v_color = a_color;
      v_useGlow = a_useGlow;
    }
  `;
  
  // Fragment shader - improved smooth glow blending
  const fragmentShaderSource = `
    precision mediump float;
    
    varying vec4 v_color;
    varying float v_useGlow;
    
    void main() {
      vec2 center = vec2(0.5, 0.5);
      float dist = distance(gl_PointCoord, center) * 2.0;
      
      if (v_useGlow > 0.5) {
        // WITH GLOW: Ultra-smooth gradient for natural fire-like appearance
        float clampedDist = clamp(dist, 0.0, 1.0);
        
        // Smoother base falloff with gentler edges
        float smoothFalloff = 1.0 - smoothstep(0.0, 1.0, clampedDist);
        
        // Bright center with very gentle falloff curve
        float centerBoost = pow(max(1.0 - clampedDist, 0.0), 2.0);
        
        // Combine with adjusted weights for seamless blend
        float intensity = smoothFalloff * 0.75 + centerBoost * 0.5;
        
        // Slightly brighter center for hot fire core
        float brightness = 1.0 + centerBoost * 0.7;
        
        // Apply to color with premultiplied alpha
        float finalAlpha = v_color.a * intensity;
        vec3 finalRgb = v_color.rgb * brightness * finalAlpha;
        
        gl_FragColor = vec4(finalRgb, finalAlpha);
      } else {
        // WITHOUT GLOW: Flat solid circle
        if (dist > 1.0) discard;
        gl_FragColor = v_color;
      }
    }
  `;
  
  // Compile shaders
  function compileShader(source, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      console.error('Shader compile error:', gl.getShaderInfoLog(shader));
      gl.deleteShader(shader);
      return null;
    }
    return shader;
  }
  
  const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER);
  const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
  
  const program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error('Program link error:', gl.getProgramInfoLog(program));
  }
  
  gl.useProgram(program);
  
  // Get attribute locations
  const positionLoc = gl.getAttribLocation(program, 'a_position');
  const colorLoc = gl.getAttribLocation(program, 'a_color');
  const sizeLoc = gl.getAttribLocation(program, 'a_size');
  const useGlowLoc = gl.getAttribLocation(program, 'a_useGlow');
  
  // Particle class
  class GlowParticle {
    constructor(x, y, useGlow) {
      this.x = x;
      this.y = y;
      this.useGlow = useGlow;
      this.lifetime = 1.5;
      this.maxLifetime = 1.5;
      // Larger particles with more variance for fire effect
      this.size = 50 + Math.random() * 40;
      this.vx = (Math.random() - 0.5) * 50;
      this.vy = -75 + (Math.random() - 0.5) * 25;
      // Fire colors from Rust code: srgb(3.0, 0.5, 0.1) - bright orange-red
      this.color = { r: 1.0, g: 0.35, b: 0.1 };
    }
    
    update(dt) {
      this.lifetime -= dt;
      this.x += this.vx * dt;
      this.y += this.vy * dt;
    }
    
    isDead() {
      return this.lifetime <= 0;
    }
    
    getData() {
      const progress = 1.0 - (this.lifetime / this.maxLifetime);
      // Fade only in the last 40% of lifetime
      const fadeProgress = Math.max(0, (progress - 0.6) / 0.4);
      const brightness = 1.0 - fadeProgress * 0.7;
      const alpha = 1.0 - fadeProgress * 0.5;
      const size = this.size * (1.0 - progress * 0.3);
      
      // Convert to WebGL coordinates (-1 to 1)
      const glX = (this.x / width) * 2 - 1;
      const glY = -((this.y / height) * 2 - 1);
      
      // Fade to dark red instead of black
      const fadeToColor = { r: 0.3, g: 0.05, b: 0.0 };
      const r = this.color.r * brightness + fadeToColor.r * fadeProgress;
      const g = this.color.g * brightness + fadeToColor.g * fadeProgress;
      const b = this.color.b * brightness + fadeToColor.b * fadeProgress;
      
      return {
        position: [glX, glY],
        color: [r, g, b, alpha],
        size: this.useGlow ? size * 2.5 : size, // Glow particles are larger
        useGlow: this.useGlow ? 1.0 : 0.0
      };
    }
  }
  
  const particles = [];
  let lastTime = Date.now();
  let spawnTimer = 0;
  
  const leftEmitterX = width * 0.25;
  const rightEmitterX = width * 0.75;
  const emitterY = height - 60;
  
  function spawnParticle(x, useGlow) {
    particles.push(new GlowParticle(x, emitterY, useGlow));
  }
  
  // Get UI canvas for overlay
  const uiCanvas = document.getElementById('ui-canvas');
  const ctx = uiCanvas.getContext('2d');
  
  function drawUI() {
    ctx.clearRect(0, 0, width, height);
    
    // Draw divider line only
    ctx.strokeStyle = '#ccc';
    ctx.lineWidth = 2;
    ctx.setLineDash([5, 5]);
    ctx.beginPath();
    ctx.moveTo(width / 2, 0);
    ctx.lineTo(width / 2, height);
    ctx.stroke();
    ctx.setLineDash([]);
  }
  
  function animate() {
    const now = Date.now();
    const dt = (now - lastTime) / 1000;
    lastTime = now;
    
    // Clear with light background
    gl.clearColor(0.98, 0.98, 0.98, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    // Enable blending for transparency with premultiplied alpha
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
    
    // Spawn particles - fewer but larger for fire effect
    spawnTimer += dt;
    if (spawnTimer > 0.5) {
      spawnParticle(leftEmitterX, false);
      spawnParticle(rightEmitterX, true);
      spawnTimer = 0;
    }
    
    // Update particles
    for (let i = particles.length - 1; i >= 0; i--) {
      particles[i].update(dt);
      if (particles[i].isDead()) {
        particles.splice(i, 1);
      }
    }
    
    // Prepare particle data for WebGL
    const positions = [];
    const colors = [];
    const sizes = [];
    const useGlows = [];
    
    particles.forEach(p => {
      const data = p.getData();
      positions.push(...data.position);
      colors.push(...data.color);
      sizes.push(data.size);
      useGlows.push(data.useGlow);
    });
    
    if (particles.length > 0) {
      // Create and bind buffers
      const posBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(positionLoc);
      gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
      
      const colorBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(colorLoc);
      gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
      
      const sizeBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sizes), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(sizeLoc);
      gl.vertexAttribPointer(sizeLoc, 1, gl.FLOAT, false, 0, 0);
      
      const glowBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, glowBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(useGlows), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(useGlowLoc);
      gl.vertexAttribPointer(useGlowLoc, 1, gl.FLOAT, false, 0, 0);
      
      // Draw particles
      gl.drawArrays(gl.POINTS, 0, particles.length);
    }
    
    // Draw UI overlay
    drawUI();
    
    requestAnimationFrame(animate);
  }
  
  animate();
})();
</script>

<p><strong>The language:</strong></p>

<p>This shader is written in <strong>WGSL</strong> (WebGPU Shading Language), Bevy’s shader language. It’s similar to Rust in some ways but designed specifically for GPU programming.</p>

<p>Create the folder <code class="language-plaintext highlighter-rouge">shaders</code> in <code class="language-plaintext highlighter-rouge">src/assets</code> and add the shader file <code class="language-plaintext highlighter-rouge">particle_glow.wgsl</code>, the final path should be <code class="language-plaintext highlighter-rouge">src/assets/shaders/particle_glow.wgsl</code>.</p>

<pre><code class="language-wgsl">// src/assets/shaders/particle_glow.wgsl
// Custom shader for particles
// Creates a radial gradient glow effect with additive blending
#import bevy_sprite::mesh2d_vertex_output::VertexOutput

@group(#{MATERIAL_BIND_GROUP}) @binding(0) var&lt;uniform&gt; color: vec4&lt;f32&gt;;

@fragment
fn fragment(mesh: VertexOutput) -&gt; @location(0) vec4&lt;f32&gt; {
    // Calculate distance from center (UV space is 0-1)
    let center = vec2&lt;f32&gt;(0.5, 0.5);
    let dist = distance(mesh.uv, center) * 2.0; // *2 to normalize to 0-1
    
    // Create radial gradient
    // Bright center → fades to edges
    let radial = 1.0 - smoothstep(0.0, 1.0, dist);
    
    // Add extra glow in the center
    let glow = pow(1.0 - dist, 3.0);
    
    // Combine radial gradient with center glow
    let intensity = radial * 0.7 + glow * 0.5;
    
    // Boost brightness near center for hot glow effect
    let brightness = 1.0 + glow * 0.5;
    
    // Apply to color (supports HDR - values &gt; 1.0)
    let final_rgb = color.rgb * brightness;
    let final_alpha = color.a * intensity;
    
    return vec4&lt;f32&gt;(final_rgb, final_alpha);
}
</code></pre>

<p><strong>How the shader creates the glow effect:</strong></p>

<p><strong>Inputs the shader receives:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">color</code> - The particle’s color sent from Rust code via <code class="language-plaintext highlighter-rouge">ParticleMaterial</code> (remember the <code class="language-plaintext highlighter-rouge">#[uniform(0)]</code> binding)</li>
  <li><code class="language-plaintext highlighter-rouge">mesh.uv</code> - The pixel’s position on the particle square. When Bevy renders a sprite with <code class="language-plaintext highlighter-rouge">Material2d</code>, it automatically creates a quad (rectangle) mesh and assigns UV coordinates to each corner: (0,0) at bottom-left, (1,1) at top-right. The GPU interpolates these for each pixel in between.</li>
</ul>

<p><strong>What’s a mesh and what are UV coordinates?</strong></p>

<p>A <strong>mesh</strong> is a 3D shape made of triangles. For 2D sprites, Bevy creates a simple quad (2 triangles forming a rectangle) to display the image.</p>

<p><strong>UV coordinates</strong> are like a map that tells the shader where each pixel is on that rectangle. Think of it like a grid:</p>
<ul>
  <li>U goes left to right (0.0 = left edge, 1.0 = right edge)</li>
  <li>V goes bottom to top (0.0 = bottom edge, 1.0 = top edge)</li>
  <li>So (0.5, 0.5) is the exact center of the particle</li>
</ul>

<p>When the shader runs, every pixel knows its UV position. With these inputs (color and UV coordinates), the shader creates a radial gradient by calculating each pixel’s distance from the particle’s center. Pixels near the center get bright colors, while pixels at the edges fade to transparent.</p>

<p>Here’s the technique:</p>

<ol>
  <li><strong>Calculate distance</strong> - Measure how far this pixel is from the particle center</li>
  <li><strong>Create smooth falloff</strong> - Use <code class="language-plaintext highlighter-rouge">smoothstep</code> to gradually fade from bright (center) to dark (edges)</li>
  <li><strong>Boost center brightness</strong> - Multiply center pixels by values above 1.0 for that “hot core” effect</li>
  <li><strong>Combine</strong> - Mix the smooth fade with the bright center for a natural-looking glow</li>
</ol>

<p>Without the shader, particles are just flat colored squares. With it, they become glowing orbs of energy.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">smoothstep</code>?</strong></p>

<p>A function that creates smooth transitions. <code class="language-plaintext highlighter-rouge">smoothstep(edge0, edge1, x)</code> returns 0 when x is at edge0, 1 when x is at edge1, and smoothly transitions between them. Unlike a straight line transition, it starts slow, speeds up in the middle, then slows down at the end, creating natural looking fades.</p>

<h3 id="custom-shader-material">Custom Shader Material</h3>

<p>Now that we understand shaders, let’s create the Rust code that uses our shader. The <code class="language-plaintext highlighter-rouge">ParticleMaterial</code> struct is our bridge between Rust code and the GPU shader, it holds the particle’s color and tells Bevy which shader file to use for rendering.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/particles/material.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/particles/material.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::{</span>
    <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span>
    <span class="nn">reflect</span><span class="p">::</span><span class="n">TypePath</span><span class="p">,</span>
    <span class="nn">render</span><span class="p">::</span><span class="nn">render_resource</span><span class="p">::{</span>
        <span class="n">AsBindGroup</span><span class="p">,</span> <span class="n">BlendComponent</span><span class="p">,</span> <span class="n">BlendFactor</span><span class="p">,</span> <span class="n">BlendOperation</span><span class="p">,</span> <span class="n">BlendState</span><span class="p">,</span> <span class="n">ColorWrites</span><span class="p">,</span>
        <span class="n">RenderPipelineDescriptor</span><span class="p">,</span> <span class="n">SpecializedMeshPipelineError</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="nn">shader</span><span class="p">::</span><span class="n">ShaderRef</span><span class="p">,</span>
    <span class="nn">sprite_render</span><span class="p">::{</span><span class="n">AlphaMode2d</span><span class="p">,</span> <span class="n">Material2d</span><span class="p">,</span> <span class="n">Material2dKey</span><span class="p">},</span>
    <span class="nn">mesh</span><span class="p">::</span><span class="n">MeshVertexBufferLayoutRef</span><span class="p">,</span>
<span class="p">};</span>

<span class="cd">/// Custom material for particles with radial gradient shader and additive blending</span>
<span class="nd">#[derive(Asset,</span> <span class="nd">TypePath,</span> <span class="nd">AsBindGroup,</span> <span class="nd">Debug,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">ParticleMaterial</span> <span class="p">{</span>
    <span class="nd">#[uniform(</span><span class="mi">0</span><span class="nd">)]</span>
    <span class="k">pub</span> <span class="n">color</span><span class="p">:</span> <span class="n">LinearRgba</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">ParticleMaterial</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">color</span><span class="p">:</span> <span class="n">Color</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">color</span><span class="p">:</span> <span class="n">color</span><span class="nf">.to_linear</span><span class="p">(),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">AsBindGroup</code>?</strong></p>

<p>This macro tells Bevy how to send data from your Rust code to the GPU shader. Think of it like packing a box to ship: the <code class="language-plaintext highlighter-rouge">#[uniform(0)]</code> label says “put the color value in slot 0 so the shader can find it.”</p>

<p><strong>Understanding the terms:</strong></p>

<ul>
  <li>
    <p><strong>Material</strong>: A material defines how a surface looks when rendered. It combines a shader (the rendering program) with properties (like color). Our <code class="language-plaintext highlighter-rouge">ParticleMaterial</code> is a custom material specifically for particles.</p>
  </li>
  <li>
    <p><strong>Fragment shader</strong>: A shader program that runs for each pixel being drawn. It calculates the final color of that pixel. Our fragment shader creates the radial glow effect.</p>
  </li>
  <li>
    <p><strong>Alpha blending</strong>: How transparent objects are combined with what’s behind them. Normal alpha blending makes things see-through. Additive blending (what we use) adds brightness values together for glowing effects.</p>
  </li>
  <li>
    <p><strong>Specialize</strong>: Customizing the rendering pipeline for this specific material. We use it to configure additive blending instead of normal transparency.</p>
  </li>
</ul>

<p>Now implement the Material2d trait:</p>

<p>The <code class="language-plaintext highlighter-rouge">Material2d</code> trait tells Bevy how to render our custom material. We implement three methods:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">fragment_shader()</code> - Returns the path to our shader file</li>
  <li><code class="language-plaintext highlighter-rouge">alpha_mode()</code> - Enables transparency blending</li>
  <li><code class="language-plaintext highlighter-rouge">specialize()</code> - Configures the rendering pipeline for additive blending</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/particles/material.rs</span>

<span class="k">impl</span> <span class="n">Material2d</span> <span class="k">for</span> <span class="n">ParticleMaterial</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">fragment_shader</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">ShaderRef</span> <span class="p">{</span>
        <span class="s">"shaders/particle_glow.wgsl"</span><span class="nf">.into</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">alpha_mode</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">AlphaMode2d</span> <span class="p">{</span>
        <span class="nn">AlphaMode2d</span><span class="p">::</span><span class="n">Blend</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">specialize</span><span class="p">(</span>
        <span class="n">descriptor</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">RenderPipelineDescriptor</span><span class="p">,</span>
        <span class="n">_layout</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">MeshVertexBufferLayoutRef</span><span class="p">,</span>
        <span class="n">_key</span><span class="p">:</span> <span class="n">Material2dKey</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="n">SpecializedMeshPipelineError</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// Set up additive blending for glowing effect</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">fragment</span><span class="p">)</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">descriptor</span><span class="py">.fragment</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">target</span><span class="p">)</span> <span class="o">=</span> <span class="n">fragment</span><span class="py">.targets</span><span class="nf">.first_mut</span><span class="p">()</span> <span class="p">{</span>
                <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">target_state</span><span class="p">)</span> <span class="o">=</span> <span class="n">target</span><span class="nf">.as_mut</span><span class="p">()</span> <span class="p">{</span>
                    <span class="n">target_state</span><span class="py">.blend</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">BlendState</span> <span class="p">{</span>
                        <span class="n">color</span><span class="p">:</span> <span class="n">BlendComponent</span> <span class="p">{</span>
                            <span class="n">src_factor</span><span class="p">:</span> <span class="nn">BlendFactor</span><span class="p">::</span><span class="n">SrcAlpha</span><span class="p">,</span>
                            <span class="n">dst_factor</span><span class="p">:</span> <span class="nn">BlendFactor</span><span class="p">::</span><span class="n">One</span><span class="p">,</span> <span class="c1">// Additive!</span>
                            <span class="n">operation</span><span class="p">:</span> <span class="nn">BlendOperation</span><span class="p">::</span><span class="nb">Add</span><span class="p">,</span>
                        <span class="p">},</span>
                        <span class="n">alpha</span><span class="p">:</span> <span class="n">BlendComponent</span> <span class="p">{</span>
                            <span class="n">src_factor</span><span class="p">:</span> <span class="nn">BlendFactor</span><span class="p">::</span><span class="n">One</span><span class="p">,</span>
                            <span class="n">dst_factor</span><span class="p">:</span> <span class="nn">BlendFactor</span><span class="p">::</span><span class="n">One</span><span class="p">,</span>
                            <span class="n">operation</span><span class="p">:</span> <span class="nn">BlendOperation</span><span class="p">::</span><span class="nb">Add</span><span class="p">,</span>
                        <span class="p">},</span>
                    <span class="p">});</span>
                    <span class="n">target_state</span><span class="py">.write_mask</span> <span class="o">=</span> <span class="nn">ColorWrites</span><span class="p">::</span><span class="n">ALL</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">specialize</code> function configures the GPU’s rendering pipeline for our particle material. It tells the GPU how to blend each rendered particle with what’s already on screen.</p>

<p><strong>What this means for rendered particles:</strong> When a particle is drawn, the GPU needs to know how to combine its color with the background. Normal transparency makes overlapping particles darker. Additive blending makes them brighter and glowing. This is what creates that magical fire/magic look.</p>

<p>Here’s what the function does:</p>

<ol>
  <li><strong>Access the pipeline descriptor</strong> - Gets the configuration for how this material will be rendered</li>
  <li><strong>Find the color target</strong> - Locates where color output is defined</li>
  <li><strong>Set up additive blending</strong> - Configures the blend mode:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">src_factor: SrcAlpha</code> - Multiply particle color by its transparency</li>
      <li><code class="language-plaintext highlighter-rouge">dst_factor: One</code> - Keep the background color at full strength (don’t darken it)</li>
      <li><code class="language-plaintext highlighter-rouge">operation: Add</code> - Add them together</li>
    </ul>
  </li>
</ol>

<p>Result: Overlapping particles add their brightness together, creating intense glows where they overlap. Ten overlapping fire particles create a bright white-hot center!</p>

<p><strong>What’s additive blending?</strong></p>

<p>Normal alpha blending: <code class="language-plaintext highlighter-rouge">new_color = particle_color * alpha + background * (1 - alpha)</code></p>

<p>Additive blending: <code class="language-plaintext highlighter-rouge">new_color = particle_color + background</code></p>

<p>Overlapping particles add their brightness together, creating intense glows. This is how fire, magic, and explosions get that magical glowing look.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_faf4cf041c02f0c904437738059dffae.svg" alt="Comic Panel" class="comic-image" data-comic-hash="faf4cf041c02f0c904437738059dffae" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="particles-module-plugin">Particles Module Plugin</h3>

<p>Create <code class="language-plaintext highlighter-rouge">src/particles/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/particles/mod.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">components</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">material</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">systems</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::{</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span> <span class="nn">sprite_render</span><span class="p">::</span><span class="n">Material2dPlugin</span><span class="p">};</span>

<span class="k">pub</span> <span class="k">use</span> <span class="nn">material</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">systems</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">ParticlesPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">ParticlesPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="nd">info!</span><span class="p">(</span><span class="s">"Initializing ParticlesPlugin"</span><span class="p">);</span>
        <span class="n">app</span><span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">Material2dPlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">ParticleMaterial</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="p">(</span><span class="n">update_emitters</span><span class="p">,</span> <span class="n">update_particles</span><span class="p">,</span> <span class="n">cleanup_finished_emitters</span><span class="p">)</span>
                    <span class="nf">.chain</span><span class="p">()</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">);</span>
        <span class="nd">info!</span><span class="p">(</span><span class="s">"ParticlesPlugin initialized"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why <code class="language-plaintext highlighter-rouge">.chain()</code>?</strong></p>

<p>Systems in a chain run sequentially in the order specified. We want:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">update_emitters</code> - Spawn new particles</li>
  <li><code class="language-plaintext highlighter-rouge">update_particles</code> - Update existing particles</li>
  <li><code class="language-plaintext highlighter-rouge">cleanup_finished_emitters</code> - Remove dead emitters</li>
</ol>

<p>This prevents edge cases where an emitter spawns particles then immediately despawns.</p>

<h2 id="creating-the-combat-module">Creating the Combat Module</h2>

<p>Now that we have a particle system, let’s build the combat system that uses it! Create a new folder <code class="language-plaintext highlighter-rouge">src/combat/</code> for our combat system.</p>

<h3 id="power-types">Power Types</h3>

<p>Different powers need different behaviors and visuals. Let’s start by defining what makes each power unique.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_e789b46fe79e55be0691407bc53cb83a.svg" alt="Comic Panel" class="comic-image" data-comic-hash="e789b46fe79e55be0691407bc53cb83a" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>Create <code class="language-plaintext highlighter-rouge">src/combat/power_type.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/combat/power_type.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">particles</span><span class="p">::</span><span class="nn">components</span><span class="p">::{</span><span class="n">EmissionShape</span><span class="p">,</span> <span class="n">ParticleConfig</span><span class="p">};</span>

<span class="cd">/// The different magical powers available</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">PowerType</span> <span class="p">{</span>
    <span class="nd">#[default]</span>
    <span class="n">Fire</span><span class="p">,</span>
    <span class="n">Arcane</span><span class="p">,</span>
    <span class="n">Shadow</span><span class="p">,</span>
    <span class="n">Poison</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now let’s define the visual configuration for each power. We’ll separate visuals from behavior (future chapters will add damage, collision, etc.):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/combat/power_type.rs</span>

<span class="cd">/// Visual configuration for a power - decoupled from behavior</span>
<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">PowerVisuals</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">primary</span><span class="p">:</span> <span class="n">ParticleConfig</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">core</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">ParticleConfig</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">core_particles_per_spawn</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s the difference between <code class="language-plaintext highlighter-rouge">primary</code> and <code class="language-plaintext highlighter-rouge">core</code>?</strong></p>

<p>Many effects have two layers:</p>
<ul>
  <li><strong>Primary</strong>: The outer glow/trail (lots of particles, less bright)</li>
  <li><strong>Core</strong>: The bright center (fewer particles, very bright)</li>
</ul>

<p>Imagine a fireball, the core is the white-hot center, the primary is the orange flames around it. Not all powers need a core, poison is just a single layer of green particles.</p>

<p>Now we can use the <code class="language-plaintext highlighter-rouge">ParticleConfig</code> and <code class="language-plaintext highlighter-rouge">EmissionShape</code> types we defined earlier! Let’s implement the visual configurations for each power type:</p>

<p>Now we’ll configure how each power looks and behaves using the <code class="language-plaintext highlighter-rouge">ParticleConfig</code> struct. Each power gets unique visual properties (colors, speeds, sizes, variances) that define its character. Fire gets HDR orange colors and wide spread, while Shadow uses tight precision with many fast particles.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/combat/power_type.rs</span>

<span class="k">impl</span> <span class="n">PowerType</span> <span class="p">{</span>
    <span class="cd">/// Get visual configuration for this power</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">visuals</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">PowerVisuals</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="nn">PowerType</span><span class="p">::</span><span class="n">Fire</span> <span class="k">=&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="nf">fire_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">),</span>
            <span class="nn">PowerType</span><span class="p">::</span><span class="n">Arcane</span> <span class="k">=&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="nf">arcane_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">),</span>
            <span class="nn">PowerType</span><span class="p">::</span><span class="n">Shadow</span> <span class="k">=&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="nf">shadow_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">),</span>
            <span class="nn">PowerType</span><span class="p">::</span><span class="n">Poison</span> <span class="k">=&gt;</span> <span class="k">Self</span><span class="p">::</span><span class="nf">poison_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">fire_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">PowerVisuals</span> <span class="p">{</span>
        <span class="n">PowerVisuals</span> <span class="p">{</span>
            <span class="n">primary</span><span class="p">:</span> <span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.2</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">350.0</span><span class="p">,</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">40.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.12</span><span class="p">,</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">1.5</span><span class="p">,</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">3.0</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">),</span>  <span class="c1">// Bright orange-red</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">3.0</span><span class="p">,</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Circle</span> <span class="p">{</span> <span class="n">radius</span><span class="p">:</span> <span class="mf">10.0</span> <span class="p">},</span>
            <span class="p">},</span>
            <span class="n">core</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">0.8</span><span class="p">,</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.2</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">350.0</span><span class="p">,</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">30.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.08</span><span class="p">,</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">4.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">),</span>  <span class="c1">// Very bright yellow-white</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">5.0</span><span class="p">,</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Circle</span> <span class="p">{</span> <span class="n">radius</span><span class="p">:</span> <span class="mf">5.0</span> <span class="p">},</span>
            <span class="p">}),</span>
            <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
            <span class="n">core_particles_per_spawn</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">arcane_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">PowerVisuals</span> <span class="p">{</span>
        <span class="n">PowerVisuals</span> <span class="p">{</span>
            <span class="n">primary</span><span class="p">:</span> <span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">1.2</span><span class="p">,</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.2</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">300.0</span><span class="p">,</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">30.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.05</span><span class="p">,</span>  <span class="c1">// Very precise</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">1.2</span><span class="p">,</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="mf">2.5</span><span class="p">),</span>  <span class="c1">// Blue arcane energy</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Circle</span> <span class="p">{</span> <span class="n">radius</span><span class="p">:</span> <span class="mf">6.0</span> <span class="p">},</span>
            <span class="p">},</span>
            <span class="n">core</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">300.0</span><span class="p">,</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">20.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.03</span><span class="p">,</span>  <span class="c1">// Even more precise</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">0.8</span><span class="p">,</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.2</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">0.9</span><span class="p">,</span> <span class="mf">0.95</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">),</span>  <span class="c1">// Bright white-blue</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">,</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Point</span><span class="p">,</span>
            <span class="p">}),</span>
            <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
            <span class="n">core_particles_per_spawn</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">shadow_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">PowerVisuals</span> <span class="p">{</span>
        <span class="n">PowerVisuals</span> <span class="p">{</span>
            <span class="n">primary</span><span class="p">:</span> <span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">,</span>  <span class="c1">// Short-lived</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.15</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">600.0</span><span class="p">,</span>  <span class="c1">// Very fast</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">100.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.04</span><span class="p">,</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.4</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">0.6</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">,</span> <span class="mf">1.2</span><span class="p">),</span>  <span class="c1">// Dark purple</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span>  <span class="c1">// Spins fast</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">4.0</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Point</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="n">core</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">,</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">650.0</span><span class="p">,</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">80.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.02</span><span class="p">,</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">1.3</span><span class="p">,</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">1.8</span><span class="p">),</span>  <span class="c1">// Brighter purple core</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">12.0</span><span class="p">,</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">5.0</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Point</span><span class="p">,</span>
            <span class="p">}),</span>
            <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>  <span class="c1">// Many particles</span>
            <span class="n">core_particles_per_spawn</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">poison_visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">PowerVisuals</span> <span class="p">{</span>
        <span class="n">PowerVisuals</span> <span class="p">{</span>
            <span class="n">primary</span><span class="p">:</span> <span class="n">ParticleConfig</span> <span class="p">{</span>
                <span class="n">lifetime</span><span class="p">:</span> <span class="mf">1.5</span><span class="p">,</span>  <span class="c1">// Long-lived</span>
                <span class="n">lifetime_variance</span><span class="p">:</span> <span class="mf">0.4</span><span class="p">,</span>
                <span class="n">speed</span><span class="p">:</span> <span class="mf">200.0</span><span class="p">,</span>  <span class="c1">// Slow</span>
                <span class="n">speed_variance</span><span class="p">:</span> <span class="mf">50.0</span><span class="p">,</span>
                <span class="n">direction</span><span class="p">,</span>
                <span class="n">direction_variance</span><span class="p">:</span> <span class="mf">0.25</span><span class="p">,</span>  <span class="c1">// Spreads a lot</span>
                <span class="n">scale</span><span class="p">:</span> <span class="mf">1.8</span><span class="p">,</span>  <span class="c1">// Large particles</span>
                <span class="n">scale_variance</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">,</span>
                <span class="n">color</span><span class="p">:</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">0.3</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">0.3</span><span class="p">),</span>  <span class="c1">// Toxic green</span>
                <span class="n">angular_velocity</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>
                <span class="n">angular_velocity_variance</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">,</span>
                <span class="n">acceleration</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">20.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>  <span class="c1">// Rises slightly</span>
                <span class="n">emission_shape</span><span class="p">:</span> <span class="nn">EmissionShape</span><span class="p">::</span><span class="n">Circle</span> <span class="p">{</span> <span class="n">radius</span><span class="p">:</span> <span class="mf">15.0</span> <span class="p">},</span>
            <span class="p">},</span>
            <span class="n">core</span><span class="p">:</span> <span class="nb">None</span><span class="p">,</span>  <span class="c1">// No core - just a cloud</span>
            <span class="n">particles_per_spawn</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
            <span class="n">core_particles_per_spawn</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The configuration numbers define each power’s unique character. Fire has high speed (350), wide spread (0.12 variance), and HDR orange colors. Shadow has very high speed (500), tight beam (0.05 variance), and dark purple.</p>

<p><strong>Why <code class="language-plaintext highlighter-rouge">Color::srgb(3.0, 0.5, 0.1)</code> with values above 1.0?</strong></p>

<p>Values above 1.0 create <strong>HDR (High Dynamic Range) colors</strong> that glow brighter than normal. When combined with additive blending (from our particle shader), these create the magical glow effect. It’s like cranking the brightness past 100%, perfect for fire and magic.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">direction_variance</code>?</strong></p>

<p>Controls how much particles spread. Low variance (0.03 for Arcane) means particles stay in a tight beam. High variance (0.25 for Poison) creates a wide, spreading cloud. It’s measured in radians.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_e6fcf5b7ada1868cb3542459a92bd67a.svg" alt="Comic Panel" class="comic-image" data-comic-hash="e6fcf5b7ada1868cb3542459a92bd67a" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>Now notice the design pattern here: each power has a distinct <strong>character</strong> expressed through numbers:</p>

<table>
  <thead>
    <tr>
      <th>Power</th>
      <th>Character</th>
      <th>Speed</th>
      <th>Lifetime</th>
      <th>Spread</th>
      <th>Particles</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Fire</td>
      <td>Hot, chaotic</td>
      <td>Medium</td>
      <td>Short</td>
      <td>Medium</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td>Arcane</td>
      <td>Precise, magical</td>
      <td>Medium</td>
      <td>Long</td>
      <td>Tight</td>
      <td>Few</td>
    </tr>
    <tr>
      <td>Shadow</td>
      <td>Fast, deadly</td>
      <td>Fast</td>
      <td>Very short</td>
      <td>Tight</td>
      <td>Many</td>
    </tr>
    <tr>
      <td>Poison</td>
      <td>Spreading, lingering</td>
      <td>Slow</td>
      <td>Long</td>
      <td>Wide</td>
      <td>Medium</td>
    </tr>
  </tbody>
</table>

<p>This data-driven approach means adding a new power is just adding a new function with different numbers, no code logic changes needed.</p>

<h3 id="player-combat-component">Player Combat Component</h3>

<p>Now we need a component to attach to the player that tracks their current power and prevents rapid-fire spam.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_b8d22d48a1be79d2d7c9861074393e20.svg" alt="Comic Panel" class="comic-image" data-comic-hash="b8d22d48a1be79d2d7c9861074393e20" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>We use a countdown <code class="language-plaintext highlighter-rouge">Timer</code> that must finish before the next attack. When the player attacks, we check if the timer is finished. If yes, spawn particles and reset the timer back to 0.5 seconds. If no, ignore the input. This creates a smooth attack rate without complex cooldown tracking.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/combat/player_combat.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/combat/player_combat.rs</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">power_type</span><span class="p">::</span><span class="n">PowerType</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Attach to any entity that can use powers (player, NPCs)</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">PlayerCombat</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">cooldown</span><span class="p">:</span> <span class="n">Timer</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">PlayerCombat</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">power_type</span><span class="p">:</span> <span class="nn">PowerType</span><span class="p">::</span><span class="n">Fire</span><span class="p">,</span>
            <span class="n">cooldown</span><span class="p">:</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Once</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">PlayerCombat</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">power_type</span><span class="p">,</span>
            <span class="n">cooldown</span><span class="p">:</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Once</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_cooldown</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">seconds</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.cooldown</span> <span class="o">=</span> <span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">seconds</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Once</span><span class="p">);</span>
        <span class="k">self</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">TimerMode::Once</code>?</strong></p>

<p>Timers in Bevy can be <code class="language-plaintext highlighter-rouge">Once</code> (stop when finished) or <code class="language-plaintext highlighter-rouge">Repeating</code> (restart automatically). For cooldowns, we want <code class="language-plaintext highlighter-rouge">Once</code>, the timer counts down from 0.5 seconds to 0, then stops. We manually reset it when the player attacks.</p>

<p><strong>Why 0.5 seconds?</strong></p>

<p>This creates a ~2 attacks per second rate. Too fast feels spammy, too slow feels unresponsive. You can tweak this with <code class="language-plaintext highlighter-rouge">.with_cooldown()</code> for different weapons or upgrades.</p>

<h3 id="combat-systems">Combat Systems</h3>

<p>Now for the system that handles player input and spawns projectiles.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/combat/systems.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/combat/systems.rs</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">player_combat</span><span class="p">::</span><span class="n">PlayerCombat</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">power_type</span><span class="p">::{</span><span class="n">PowerType</span><span class="p">,</span> <span class="n">PowerVisuals</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">facing</span><span class="p">::</span><span class="n">Facing</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">particles</span><span class="p">::</span><span class="nn">components</span><span class="p">::</span><span class="n">ParticleEmitter</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Marker for projectile effects</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">ProjectileEffect</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">handle_power_input</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">GlobalTransform</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Facing</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">PlayerCombat</span><span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="n">global_transform</span><span class="p">,</span> <span class="n">facing</span><span class="p">,</span> <span class="k">mut</span> <span class="n">combat</span><span class="p">))</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.tick</span><span class="p">(</span><span class="n">time</span><span class="nf">.delta</span><span class="p">());</span>

    <span class="k">let</span> <span class="n">ctrl_pressed</span> <span class="o">=</span>
        <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ControlLeft</span><span class="p">)</span> <span class="p">||</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ControlRight</span><span class="p">);</span>

    <span class="k">if</span> <span class="o">!</span><span class="n">ctrl_pressed</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Only fire if cooldown has elapsed</span>
    <span class="k">if</span> <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.elapsed_secs</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.duration</span><span class="p">()</span><span class="nf">.as_secs_f32</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">combat</span><span class="py">.cooldown</span><span class="nf">.reset</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">position</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="n">global_transform</span><span class="nf">.translation</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="nf">facing_to_vec3</span><span class="p">(</span><span class="n">facing</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">spawn_position</span> <span class="o">=</span> <span class="n">position</span> <span class="o">+</span> <span class="n">direction</span> <span class="o">*</span> <span class="mf">5.0</span><span class="p">;</span>

    <span class="c1">// Get visuals from power type</span>
    <span class="k">let</span> <span class="n">visuals</span> <span class="o">=</span> <span class="n">combat</span><span class="py">.power_type</span><span class="nf">.visuals</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>

    <span class="nf">spawn_projectile</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">commands</span><span class="p">,</span> <span class="n">spawn_position</span><span class="p">,</span> <span class="n">combat</span><span class="py">.power_type</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">visuals</span><span class="p">);</span>

    <span class="nd">info!</span><span class="p">(</span><span class="s">"{:?} projectile fired!"</span><span class="p">,</span> <span class="n">combat</span><span class="py">.power_type</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<ol>
  <li><strong>Tick the cooldown</strong>: <code class="language-plaintext highlighter-rouge">combat.cooldown.tick(time.delta())</code> counts down by the frame time</li>
  <li><strong>Check input</strong>: Only proceed if Ctrl is pressed</li>
  <li><strong>Check cooldown</strong>: If <code class="language-plaintext highlighter-rouge">elapsed_secs() &lt; duration()</code>, we’re still on cooldown</li>
  <li><strong>Reset cooldown</strong>: <code class="language-plaintext highlighter-rouge">combat.cooldown.reset()</code> starts the timer over</li>
  <li><strong>Calculate spawn position</strong>: Offset slightly from the player in the facing direction</li>
  <li><strong>Get visuals</strong>: Power type knows its own visual configuration</li>
  <li><strong>Spawn projectile</strong>: Create the particle emitters using our particle system!</li>
</ol>

<p>Now let’s implement the projectile spawning system. This is where we convert player input into visible magical effects.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/combat/systems.rs</span>

<span class="k">fn</span> <span class="nf">spawn_projectile</span><span class="p">(</span>
    <span class="n">commands</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">position</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="n">power_type</span><span class="p">:</span> <span class="n">PowerType</span><span class="p">,</span>
    <span class="n">visuals</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PowerVisuals</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Primary particles</span>
    <span class="k">let</span> <span class="n">primary_emitter</span> <span class="o">=</span>
        <span class="nn">ParticleEmitter</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">0.016</span><span class="p">,</span> <span class="n">visuals</span><span class="py">.particles_per_spawn</span><span class="p">,</span> <span class="n">visuals</span><span class="py">.primary</span><span class="nf">.clone</span><span class="p">())</span>
            <span class="nf">.one_shot</span><span class="p">();</span>

    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="n">primary_emitter</span><span class="p">,</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">position</span><span class="p">),</span>
        <span class="nn">GlobalTransform</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">position</span><span class="p">)),</span>
        <span class="n">ProjectileEffect</span> <span class="p">{</span> <span class="n">power_type</span> <span class="p">},</span>
    <span class="p">));</span>

    <span class="c1">// Core particles (if the power has a core)</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="k">ref</span> <span class="n">core_config</span><span class="p">)</span> <span class="o">=</span> <span class="n">visuals</span><span class="py">.core</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">core_emitter</span> <span class="o">=</span>
            <span class="nn">ParticleEmitter</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">0.016</span><span class="p">,</span> <span class="n">visuals</span><span class="py">.core_particles_per_spawn</span><span class="p">,</span> <span class="n">core_config</span><span class="nf">.clone</span><span class="p">())</span>
                <span class="nf">.one_shot</span><span class="p">();</span>

        <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
            <span class="n">core_emitter</span><span class="p">,</span>
            <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">position</span><span class="p">),</span>
            <span class="nn">GlobalTransform</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">position</span><span class="p">)),</span>
            <span class="n">ProjectileEffect</span> <span class="p">{</span> <span class="n">power_type</span> <span class="p">},</span>
        <span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How the spawning function works:</strong></p>

<ol>
  <li><strong>Create primary emitter</strong> - Spawn the main particle layer with configured count and settings</li>
  <li><strong>Mark as one-shot</strong> - Emitter spawns once then deactivates (perfect for projectiles)</li>
  <li><strong>Position it</strong> - Place at the specified position</li>
  <li><strong>Add marker component</strong> - <code class="language-plaintext highlighter-rouge">ProjectileEffect</code> tags this as a projectile for other systems</li>
  <li><strong>Spawn core (if exists)</strong> - Some powers have a bright center layer</li>
</ol>

<p>The function takes the visual configuration and converts it into actual particle emitters. Each emitter is its own entity with the <code class="language-plaintext highlighter-rouge">ParticleEmitter</code> component.</p>

<p>Now add the helper function to convert facing to direction:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/combat/systems.rs</span>

<span class="k">fn</span> <span class="nf">facing_to_vec3</span><span class="p">(</span><span class="n">facing</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Facing</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec3</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">facing</span> <span class="p">{</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="k">=&gt;</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">X</span><span class="p">,</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="k">=&gt;</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">NEG_X</span><span class="p">,</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="k">=&gt;</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">Y</span><span class="p">,</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="k">=&gt;</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">NEG_Y</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, let’s add a debug system to quickly switch between powers for testing:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/combat/systems.rs</span>

<span class="cd">/// Switch powers with number keys (for testing)</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">debug_switch_power</span><span class="p">(</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">PlayerCombat</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="k">mut</span> <span class="n">combat</span><span class="p">)</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="n">new_power</span> <span class="o">=</span> <span class="k">if</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit1</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">PowerType</span><span class="p">::</span><span class="n">Fire</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit2</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">PowerType</span><span class="p">::</span><span class="n">Arcane</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit3</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">PowerType</span><span class="p">::</span><span class="n">Shadow</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit4</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">PowerType</span><span class="p">::</span><span class="n">Poison</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nb">None</span>
    <span class="p">};</span>

    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">power</span><span class="p">)</span> <span class="o">=</span> <span class="n">new_power</span> <span class="p">{</span>
        <span class="n">combat</span><span class="py">.power_type</span> <span class="o">=</span> <span class="n">power</span><span class="p">;</span>
        <span class="nd">info!</span><span class="p">(</span><span class="s">"Switched to {:?}"</span><span class="p">,</span> <span class="n">power</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This lets you press 1-4 to instantly switch powers while playing. Essential for testing visual effects without restarting the game.</p>

<h3 id="combat-module-plugin">Combat Module Plugin</h3>

<p>Create <code class="language-plaintext highlighter-rouge">src/combat/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/combat/mod.rs</span>
<span class="k">mod</span> <span class="n">player_combat</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">power_type</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">systems</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">use</span> <span class="nn">player_combat</span><span class="p">::</span><span class="n">PlayerCombat</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">power_type</span><span class="p">::{</span><span class="n">PowerType</span><span class="p">,</span> <span class="n">PowerVisuals</span><span class="p">};</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">systems</span><span class="p">::{</span><span class="n">ProjectileEffect</span><span class="p">,</span> <span class="n">debug_switch_power</span><span class="p">,</span> <span class="n">handle_power_input</span><span class="p">};</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">CombatPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">CombatPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span><span class="n">handle_power_input</span><span class="p">,</span> <span class="n">debug_switch_power</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Perfect! Now our combat module is ready to use the particle system.</p>

<h2 id="putting-it-all-together">Putting it all together</h2>

<p>Now let’s wire everything together!</p>

<p>First, add <code class="language-plaintext highlighter-rouge">rand</code> to your <code class="language-plaintext highlighter-rouge">Cargo.toml</code>:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[dependencies]</span>
<span class="py">bevy</span> <span class="p">=</span> <span class="s">"0.18"</span>
<span class="py">bevy_procedural_tilemaps</span> <span class="p">=</span> <span class="s">"0.2.0"</span>
<span class="nn">bevy_common_assets</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.15.0-rc.1"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["ron"]</span> <span class="p">}</span>
<span class="nn">serde</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"1.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["derive"]</span> <span class="p">}</span>
<span class="py">rand</span> <span class="p">=</span> <span class="s">"0.8"</span>  <span class="c"># Add this line</span>
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">src/main.rs</code> and add the modules:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Add to module declarations</span>
<span class="k">mod</span> <span class="n">combat</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">particles</span><span class="p">;</span>
</code></pre></div></div>

<p>Then add the plugins:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Add to the plugin chain</span>
<span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">combat</span><span class="p">::</span><span class="n">CombatPlugin</span><span class="p">)</span>
<span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">particles</span><span class="p">::</span><span class="n">ParticlesPlugin</span><span class="p">)</span>
</code></pre></div></div>

<p>The player needs the <code class="language-plaintext highlighter-rouge">PlayerCombat</code> component. Open <code class="language-plaintext highlighter-rouge">src/characters/spawn.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs - Add import</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">combat</span><span class="p">::</span><span class="n">PlayerCombat</span><span class="p">;</span>
</code></pre></div></div>

<p>Find the <code class="language-plaintext highlighter-rouge">initialize_player_character</code> system and add the combat component when inserting the player:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In the initialize_player_character function, add PlayerCombat to the character entity bundle</span>
<span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.insert</span><span class="p">((</span>
            <span class="nn">AnimationController</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">CharacterState</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span> 
            <span class="nn">Velocity</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>       
            <span class="nn">Facing</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>         
            <span class="nn">Collider</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">PlayerCombat</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span> <span class="c1">// Add this line</span>
            <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span>
                <span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">,</span>
                <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">,</span>
            <span class="p">)),</span>
            <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">(),</span>
            <span class="n">sprite</span><span class="p">,</span>
        <span class="p">));</span>
</code></pre></div></div>

<p>Run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p><img src="/assets/book_assets/chapter6/ch6.gif" alt="Combat System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #f8d7da; border-radius: 8px; border-left: 4px solid #dc3545;">
<strong>Spawn Troubleshooting:</strong> There's a small chance the procedural generation places the player on top of a blocking object (tree, rock) at spawn. If you can't move when the game starts, simply restart to generate a new map. This is a quirk of random generation we'll address in future chapters.
</div>

<p><strong>Controls:</strong></p>
<ul>
  <li><strong>Arrow keys</strong>: Move</li>
  <li><strong>Ctrl</strong>: Fire current power</li>
  <li><strong>1-4</strong>: Switch powers (Fire, Arcane, Shadow, Poison)</li>
</ul>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Learn to build a particle system to create stunning visual effects. Implement custom shaders, additive blending, and bring magic into your game.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aibodh.com/assets/book_assets/chapter6/ch6.gif" /><media:content medium="image" url="https://aibodh.com/assets/book_assets/chapter6/ch6.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 5 - Let There Be Pickups</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-5/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 5 - Let There Be Pickups" /><published>2026-01-11T00:00:00+00:00</published><updated>2026-01-11T00:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-5</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-5/"><![CDATA[<style>
.tile-image {
  margin: 0 !important;
  object-fit: none !important;
  cursor: default !important;
  pointer-events: none !important;
}
</style>

<p>By the end of this chapter, you’ll have an inventory system that lets your player collect items from the world, and a smooth camera that follows them around. Walk near a plant or mushroom, and it disappears into your inventory. Move across the map, and the camera keeps you centered with buttery-smooth motion.</p>

<blockquote>
  <p><strong>Prerequisites</strong>: This is Chapter 5 of our Bevy tutorial series. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> for updates on new releases. Before starting, complete <a href="/posts/bevy-rust-game-development-chapter-1/">Chapter 1: Let There Be a Player</a>, <a href="/posts/bevy-rust-game-development-chapter-2/">Chapter 2: Let There Be a World</a>, <a href="/posts/bevy-rust-game-development-chapter-3/">Chapter 3: Let The Data Flow</a>, and <a href="/posts/bevy-rust-game-development-chapter-4/">Chapter 4: Let There Be Collisions</a>, or clone the Chapter 4 code from <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">this repository</a> to follow along.</p>
</blockquote>

<p><img src="/assets/book_assets/chapter5/ch5.gif" alt="Inventory System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<strong>Before We Begin:</strong> <em style="font-size: 14px;">I'm constantly working to improve this tutorial and make your learning journey enjoyable. Your feedback matters - share your frustrations, questions, or suggestions on <a href="https://www.reddit.com/r/bevy/" target="_blank">Reddit</a>/<a target="_blank" href="https://discord.com/invite/cD9qEsSjUH">Discord</a>/<a href="https://www.linkedin.com/in/febinjohnjames" target="_blank">LinkedIn</a>. Loved it? Let me know what worked well for you! Together, we'll make game development with Rust and Bevy more accessible for everyone.</em>
</div>

<h2 id="building-the-pickup-system">Building the Pickup System</h2>

<p>In Chapter 4, we made solid objects block the player. Now let’s do the opposite: make items the player can walk through and collect.</p>

<h3 id="the-pickup-system">The Pickup System</h3>

<p>Think about how pickups work in games you’ve played:</p>

<ol>
  <li><strong>Detection</strong>: “Is the player close enough to this item?”</li>
  <li><strong>Collection</strong>: “Remove the item from the world”</li>
  <li><strong>Storage</strong>: “Remember what was collected”</li>
</ol>

<p>We’ll build this in three parts:</p>
<ul>
  <li>A <code class="language-plaintext highlighter-rouge">Pickable</code> component to mark items as collectible</li>
  <li>A <code class="language-plaintext highlighter-rouge">handle_pickups</code> system to detect when the player is near items</li>
  <li>An <code class="language-plaintext highlighter-rouge">Inventory</code> resource to track what’s been collected</li>
</ul>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_c9b6c1360b08406210fcd225e44a7076.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="c9b6c1360b08406210fcd225e44a7076" data-comic-settings="d2-diagram" />
</div>

<h3 id="configuring-pickup-radius">Configuring Pickup Radius</h3>

<p>Before we build the inventory, let’s add configuration for pickup detection. We already have a <code class="language-plaintext highlighter-rouge">config.rs</code> file from Chapter 4 where we centralized collision settings. Let’s add pickup configuration there.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/config.rs</code> and you’ll see it’s organized into modules: <code class="language-plaintext highlighter-rouge">player</code>, <code class="language-plaintext highlighter-rouge">map</code>, <code class="language-plaintext highlighter-rouge">debug</code>. Let’s add a <code class="language-plaintext highlighter-rouge">pickup</code> module:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config.rs - Add this section after the player module</span>

<span class="cd">/// Pickup/inventory configuration</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">pickup</span> <span class="p">{</span>
    <span class="cd">/// Default radius for item pickup detection (in world units)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">DEFAULT_RADIUS</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">40.0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s a world unit?</strong></p>

<p>In Bevy, positions are measured in world units. Our tiles are 32 units wide (from <code class="language-plaintext highlighter-rouge">map::TILE_SIZE</code>), so a radius of 40 units means the player can pick up items from slightly more than one tile away. Not too far, not too close.</p>

<h2 id="building-the-inventory-system">Building the Inventory System</h2>

<p>Create a new folder <code class="language-plaintext highlighter-rouge">src/inventory/</code> for our inventory module.</p>

<h3 id="defining-item-types">Defining Item Types</h3>

<p>What kinds of items can the player collect? In our game, we have plants, mushrooms(or something that looks like a mushroom) scattered around the map. Each needs a unique identifier.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/inventory/inventory.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/inventory/inventory.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::</span><span class="n">HashMap</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="n">fmt</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">pickup</span><span class="p">::</span><span class="n">DEFAULT_RADIUS</span><span class="p">;</span>

<span class="cd">/// Types of items that can be collected.</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Hash)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">ItemKind</span> <span class="p">{</span>
    <span class="n">Plant1</span><span class="p">,</span>
    <span class="n">Plant2</span><span class="p">,</span>
    <span class="n">Plant3</span><span class="p">,</span>
    <span class="n">Plant4</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="display-names">Display Names</h3>

<p>Players don’t want to see “Plant1” in their inventory. They want “Herb” or “Flower”. Let’s add human-readable names:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/inventory/inventory.rs</span>

<span class="k">impl</span> <span class="n">ItemKind</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">display_name</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant1</span> <span class="k">=&gt;</span> <span class="s">"Herb"</span><span class="p">,</span>
            <span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant2</span> <span class="k">=&gt;</span> <span class="s">"Flower"</span><span class="p">,</span>
            <span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant3</span> <span class="k">=&gt;</span> <span class="s">"Mushroom"</span><span class="p">,</span>
            <span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant4</span> <span class="k">=&gt;</span> <span class="s">"Fern"</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nn">fmt</span><span class="p">::</span><span class="n">Display</span> <span class="k">for</span> <span class="n">ItemKind</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">fmt</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">f</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">fmt</span><span class="p">::</span><span class="n">Formatter</span><span class="o">&lt;</span><span class="nv">'_</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">fmt</span><span class="p">::</span><span class="nb">Result</span> <span class="p">{</span>
        <span class="n">f</span><span class="nf">.write_str</span><span class="p">(</span><span class="k">self</span><span class="nf">.display_name</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">fmt::Display</code> for?</strong></p>

<p><code class="language-plaintext highlighter-rouge">fmt::Display</code>  is like a predefined contract Rust makes with your type. It’s like saying, “If you implement this trait, I’ll let you use <code class="language-plaintext highlighter-rouge">{}</code> in print statements.”</p>

<p>Without it, <code class="language-plaintext highlighter-rouge">println!("Collected: {}", item_kind)</code> would fail to compile. Rust wouldn’t know how to convert your <code class="language-plaintext highlighter-rouge">ItemKind::Plant1</code> into text for display. By implementing <code class="language-plaintext highlighter-rouge">Display</code>, we fulfill the contract, we tell Rust “when you need to print this, call this <code class="language-plaintext highlighter-rouge">fmt</code> method I wrote.”</p>

<h3 id="the-pickable-component">The Pickable Component</h3>

<p>Now we need a component to mark entities as collectible. When we spawn a plant in the world, we’ll attach this component to say “the player can pick this up.”</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/inventory/inventory.rs</span>

<span class="cd">/// Component marking an entity as collectible.</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Pickable</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">kind</span><span class="p">:</span> <span class="n">ItemKind</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Pickable</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">kind</span><span class="p">:</span> <span class="n">ItemKind</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">kind</span><span class="p">,</span>
            <span class="n">radius</span><span class="p">:</span> <span class="n">DEFAULT_RADIUS</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why store <code class="language-plaintext highlighter-rouge">radius</code> per item?</strong></p>

<p>Most items use the default radius (40 units), but maybe you want a treasure chest to have a smaller radius (must be right next to it) or a glowing orb to have a larger one (magnetic pull). Storing it in the component gives you flexibility.</p>

<h3 id="the-inventory-resource">The Inventory Resource</h3>

<p>The inventory persists across the entire game session, it’s not tied to any single entity. That’s what Resources are for: global game state that any system can access.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/inventory/inventory.rs</span>

<span class="cd">/// Resource storing collected items.</span>
<span class="nd">#[derive(Resource,</span> <span class="nd">Default,</span> <span class="nd">Debug)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Inventory</span> <span class="p">{</span>
    <span class="n">items</span><span class="p">:</span> <span class="n">HashMap</span><span class="o">&lt;</span><span class="n">ItemKind</span><span class="p">,</span> <span class="nb">u32</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why <code class="language-plaintext highlighter-rouge">HashMap&lt;ItemKind, u32&gt;</code> instead of <code class="language-plaintext highlighter-rouge">Vec&lt;ItemKind&gt;</code>?</strong></p>

<p>A <code class="language-plaintext highlighter-rouge">Vec</code> would store every individual item: <code class="language-plaintext highlighter-rouge">[Plant1, Plant1, Plant2, Plant1...]</code>. To count how many Plant1s you have, you’d scan the whole list. A <code class="language-plaintext highlighter-rouge">HashMap</code> stores counts directly: <code class="language-plaintext highlighter-rouge">{Plant1: 3, Plant2: 1}</code>. It also makes lookups efficient.</p>

<h3 id="adding-items-to-inventory">Adding Items to Inventory</h3>

<p>Now let’s implement the logic to add items to the inventory:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/inventory/inventory.rs</span>

<span class="k">impl</span> <span class="n">Inventory</span> <span class="p">{</span>
    <span class="cd">/// Add an item to the inventory, returns new count.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">add</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">kind</span><span class="p">:</span> <span class="n">ItemKind</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">entry</span> <span class="o">=</span> <span class="k">self</span><span class="py">.items</span><span class="nf">.entry</span><span class="p">(</span><span class="n">kind</span><span class="p">)</span><span class="nf">.or_insert</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
        <span class="o">*</span><span class="n">entry</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="o">*</span><span class="n">entry</span>
    <span class="p">}</span>

    <span class="cd">/// Get a summary string of inventory contents.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">summary</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="py">.items</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">return</span> <span class="s">"empty"</span><span class="nf">.to_string</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="k">mut</span> <span class="n">parts</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="o">=</span> <span class="k">self</span>
            <span class="py">.items</span>
            <span class="nf">.iter</span><span class="p">()</span>
            <span class="nf">.map</span><span class="p">(|(</span><span class="n">kind</span><span class="p">,</span> <span class="n">count</span><span class="p">)|</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"{}: {}"</span><span class="p">,</span> <span class="n">kind</span><span class="p">,</span> <span class="n">count</span><span class="p">))</span>
            <span class="nf">.collect</span><span class="p">();</span>
        <span class="n">parts</span><span class="nf">.sort</span><span class="p">();</span>
        <span class="n">parts</span><span class="nf">.join</span><span class="p">(</span><span class="s">", "</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">.entry(kind).or_insert(0)</code> doing?</strong></p>

<p>This is HashMap’s “get or create” pattern. If <code class="language-plaintext highlighter-rouge">kind</code> exists in the map, <code class="language-plaintext highlighter-rouge">.entry(kind)</code> gets a mutable reference to its value. If not, <code class="language-plaintext highlighter-rouge">.or_insert(0)</code> creates it with a count of 0. Then we increment and return the new count.</p>

<p><strong>Why return the count from <code class="language-plaintext highlighter-rouge">add</code>?</strong></p>

<p>So the pickup system can log “Picked up Herb (total: 3)”. It’s a small quality-of-life feature that makes debugging easier.</p>

<p><strong>What’s the <code class="language-plaintext highlighter-rouge">summary</code> method for?</strong></p>

<p>It formats the inventory for display: “Herb: 3, Flower: 1, Wood: 2”. We sort the items alphabetically so the order is consistent. Later, you could show this in a UI panel or debug overlay.</p>

<h2 id="pickup-detection-system">Pickup Detection System</h2>

<p>Now for the system that actually detects when the player is near an item. Create <code class="language-plaintext highlighter-rouge">src/inventory/systems.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/inventory/systems.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="nn">inventory</span><span class="p">::{</span><span class="n">Pickable</span><span class="p">,</span> <span class="n">Inventory</span><span class="p">};</span>

<span class="cd">/// System that checks for and processes item pickups.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">handle_pickups</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">inventory</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Inventory</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">pickables</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="n">Entity</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">GlobalTransform</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Pickable</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">player_transform</span><span class="p">)</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.single</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="n">player_pos</span> <span class="o">=</span> <span class="n">player_transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">collected</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Check distance to each pickable</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">entity</span><span class="p">,</span> <span class="n">global_transform</span><span class="p">,</span> <span class="n">pickable</span><span class="p">)</span> <span class="k">in</span> <span class="n">pickables</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">item_pos</span> <span class="o">=</span> <span class="n">global_transform</span><span class="nf">.translation</span><span class="p">()</span><span class="nf">.truncate</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">distance_sq</span> <span class="o">=</span> <span class="n">player_pos</span><span class="nf">.distance_squared</span><span class="p">(</span><span class="n">item_pos</span><span class="p">);</span>
        
        <span class="k">if</span> <span class="n">distance_sq</span> <span class="o">&lt;=</span> <span class="n">pickable</span><span class="py">.radius</span> <span class="o">*</span> <span class="n">pickable</span><span class="py">.radius</span> <span class="p">{</span>
            <span class="n">collected</span><span class="nf">.push</span><span class="p">((</span><span class="n">entity</span><span class="p">,</span> <span class="n">pickable</span><span class="py">.kind</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Process collected items</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">entity</span><span class="p">,</span> <span class="n">kind</span><span class="p">)</span> <span class="k">in</span> <span class="n">collected</span> <span class="p">{</span>
        <span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.despawn</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">count</span> <span class="o">=</span> <span class="n">inventory</span><span class="nf">.add</span><span class="p">(</span><span class="n">kind</span><span class="p">);</span>
        <span class="nd">info!</span><span class="p">(</span>
            <span class="s">" Picked up {} (total: {}) — inventory: {}"</span><span class="p">,</span>
            <span class="n">kind</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">inventory</span><span class="nf">.summary</span><span class="p">()</span>
        <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s break this down step by step.</p>

<h3 id="getting-the-player-position">Getting the Player Position</h3>

<p>We query for the player’s position as we did in the earlier chapters.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">.truncate()</code>?</strong></p>

<p><code class="language-plaintext highlighter-rouge">Transform.translation</code> is a <code class="language-plaintext highlighter-rouge">Vec3</code> (x, y, z). We’re in 2D, so we only care about x and y. <code class="language-plaintext highlighter-rouge">.truncate()</code> converts <code class="language-plaintext highlighter-rouge">Vec3</code> to <code class="language-plaintext highlighter-rouge">Vec2</code>, dropping the z component.</p>

<h3 id="collecting-items-to-process">Collecting Items to Process</h3>

<p>Before we can despawn collected items, we need to identify which ones are within pickup range.</p>

<p>We create an empty <code class="language-plaintext highlighter-rouge">Vec</code> and iterate through all pickable items. For each item within range, we store its entity ID and item kind as a tuple <code class="language-plaintext highlighter-rouge">(entity, pickable.kind)</code>. This gives us a list of everything the player just picked up.</p>

<p><strong>Why <code class="language-plaintext highlighter-rouge">distance_squared</code> instead of <code class="language-plaintext highlighter-rouge">distance</code>?</strong></p>

<p>The actual distance formula is <code class="language-plaintext highlighter-rouge">sqrt((x2-x1)² + (y2-y1)²)</code>. Square root is expensive. But we’re just comparing distances, so we can compare the <em>squared</em> distances instead:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">distance &lt; radius</code> is the same as <code class="language-plaintext highlighter-rouge">distance² &lt; radius²</code></li>
  <li>No square root needed, just multiplication</li>
</ul>

<p>This is a common game dev optimization. When checking hundreds of items every frame, it adds up.</p>

<h3 id="processing-collected-items">Processing Collected Items</h3>

<p>Later we go through the collected items and do the following:</p>
<ol>
  <li><strong>Despawn the entity</strong>: Removes it from the world (no more rendering, no more queries)</li>
  <li><strong>Add to inventory</strong>: Updates the count in the HashMap</li>
  <li><strong>Log the pickup</strong>: Prints to console for debugging</li>
</ol>

<p><strong>Why collect items into a <code class="language-plaintext highlighter-rouge">Vec</code> first before despawning them?</strong></p>

<p>You might notice we use a two-step pattern: first collect items into a list, then process them. In our Bevy code, we could actually skip this and despawn directly in the loop, but we follow this pattern because it’s a Rust best practice that makes code safer and clearer.</p>

<p>Let’s learn why this pattern exists:</p>

<p><strong>The Borrow Checker</strong></p>

<p>Remember in Chapter 1, where we learned about how Rust gets mad when we don’t put <code class="language-plaintext highlighter-rouge">mut</code> when declaring a variable we want to change? That was just the beginning. Rust has more rules about how you can access and modify data, enforced by something called the <strong>borrow checker</strong>.</p>

<p><strong>What’s a borrow checker?</strong></p>

<p>Let’s start with a fundamental problem: when should we free memory? When you create a variable in most languages, it allocates memory. When you’re done with it, that memory should be freed:</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_24c98bf8cab04c28beaa712b7cf88fa9.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="24c98bf8cab04c28beaa712b7cf88fa9" data-comic-settings="d2-diagram" />
</div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_2e841e450d89b96d8a0b2f7b258969b5.svg" alt="Comic Panel" class="comic-image" data-comic-hash="2e841e450d89b96d8a0b2f7b258969b5" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>Languages like Python and JavaScript: Garbage Collection</strong></p>

<p>In Python or JavaScript, you never explicitly free variables. A <em>garbage collector</em> runs periodically, checking which variables are still being used:</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_d45f7145a636b9effad65025419b099f.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="d45f7145a636b9effad65025419b099f" data-comic-settings="d2-diagram" />
</div>

<p>This is convenient, you don’t think about memory. But it has costs:</p>
<ul>
  <li><strong>Pause times</strong>: The garbage collector must pause your program to scan memory</li>
  <li><strong>Overhead</strong>: It tracks every variable at runtime, using extra memory</li>
  <li><strong>Unpredictability</strong>: You don’t know when pauses happen</li>
</ul>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_c4ae0b5b6824da9d0599f2fd548de146.svg" alt="Comic Panel" class="comic-image" data-comic-hash="c4ae0b5b6824da9d0599f2fd548de146" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>Rust’s Approach: Compile-Time Checks</strong></p>

<p>Rust takes a different approach. It checks at compile time that your code follows strict rules. When the compiler sees these rules followed, it knows <em>exactly</em> when each variable’s memory can be freed, no runtime tracking needed:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">fn</span> <span class="nf">example</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">items</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">];</span>  <span class="c1">// items owns the vector</span>
    
    <span class="nf">process</span><span class="p">(</span><span class="n">items</span><span class="p">);</span>              <span class="c1">// ownership transferred to process()</span>
                                 <span class="c1">// items can't be used anymore</span>
<span class="p">}</span>  <span class="c1">// Compiler inserts cleanup here - no garbage collector!</span>
</code></pre></div></div>

<p>The borrow checker enforces rules that let the compiler figure out memory lifetimes. Let’s learn these rules.</p>

<p><strong>The Borrowing Rules</strong></p>

<p><strong>Many readers OR one writer, never both</strong></p>

<p>You can have many immutable references OR one mutable reference, but not both at the same time.</p>

<p><em>Why?</em> Imagine you’re reading a map while someone is erasing and redrawing it. You might read half-old, half-new data—corruption! This rule prevents that:</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_99981ebbe3493b930ba67a6e7d0aa1d9.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="99981ebbe3493b930ba67a6e7d0aa1d9" data-comic-settings="d2-diagram" />
</div>

<p>Example code:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">inventory</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="s">"Herb"</span><span class="p">,</span> <span class="s">"Flower"</span><span class="p">];</span>

<span class="k">let</span> <span class="n">reader1</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">inventory</span><span class="p">;</span>  <span class="c1">// ✓ Immutable borrow</span>
<span class="k">let</span> <span class="n">reader2</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">inventory</span><span class="p">;</span>  <span class="c1">// ✓ Another immutable borrow is fine</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">reader1</span><span class="p">);</span> <span class="c1">// Both can read</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">reader2</span><span class="p">);</span>

<span class="c1">// After the immutable borrows are done being used, we can create a mutable borrow:</span>
<span class="k">let</span> <span class="n">writer</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">inventory</span><span class="p">;</span>  <span class="c1">// ✓ Now this works</span>
<span class="n">writer</span><span class="nf">.push</span><span class="p">(</span><span class="s">"Mushroom"</span><span class="p">);</span>
</code></pre></div></div>

<p>The key insight: borrows end when they’re last used, not when they go out of scope. Once <code class="language-plaintext highlighter-rouge">reader1</code> and <code class="language-plaintext highlighter-rouge">reader2</code> are done (after the <code class="language-plaintext highlighter-rouge">println!</code> calls), the mutable borrow is allowed.</p>

<p>This won’t compile:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">inventory</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="s">"Herb"</span><span class="p">,</span> <span class="s">"Flower"</span><span class="p">];</span>

<span class="k">let</span> <span class="n">reader</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">inventory</span><span class="p">;</span>
<span class="k">let</span> <span class="n">writer</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">inventory</span><span class="p">;</span>  <span class="c1">// ✗ ERROR: Can't borrow as mutable while immutably borrowed</span>

<span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">reader</span><span class="p">);</span>  <span class="c1">// reader still used here</span>
</code></pre></div></div>

<p><strong>References must always be valid (no dangling pointers)</strong></p>

<p>A reference can’t outlive the data it points to.</p>

<p><em>Why?</em> In C++, you can create a pointer to memory that gets freed, leaving a “dangling pointer.” Reading it accesses random memory—crash or worse, subtle corruption.</p>

<p>Here’s a C++ example that compiles but crashes at runtime:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span><span class="o">*</span> <span class="nf">create_dangling_pointer</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>           <span class="c1">// x is allocated on the stack</span>
    <span class="kt">int</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">;</span>       <span class="c1">// ptr points to x</span>
    <span class="k">return</span> <span class="n">ptr</span><span class="p">;</span>          <span class="c1">// Return pointer to x</span>
<span class="p">}</span>  <span class="c1">// x goes out of scope here - memory freed!</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">int</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="n">create_dangling_pointer</span><span class="p">();</span>
    
    <span class="c1">// ptr now points to freed memory (dangling pointer)</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="o">*</span><span class="n">ptr</span><span class="p">;</span>   <span class="c1">// CRASH! Reading freed memory</span>
    
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What happens:</strong></p>
<ol>
  <li>Function creates local variable <code class="language-plaintext highlighter-rouge">x</code> on the stack</li>
  <li>Function returns a pointer to <code class="language-plaintext highlighter-rouge">x</code></li>
  <li>When function exits, <code class="language-plaintext highlighter-rouge">x</code> is destroyed, but <code class="language-plaintext highlighter-rouge">ptr</code> still holds its address</li>
  <li>Back in <code class="language-plaintext highlighter-rouge">main</code>, using <code class="language-plaintext highlighter-rouge">ptr</code> reads memory that’s been freed</li>
  <li>Result: undefined behavior (crash, garbage data, or worse)</li>
</ol>

<p>Rust prevents this at compile time:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">fn</span> <span class="nf">dangling</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="nb">String</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">s</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="s">"hello"</span><span class="p">);</span>
    <span class="o">&amp;</span><span class="n">s</span>  <span class="c1">// ✗ ERROR: `s` doesn't live long enough</span>
<span class="p">}</span>  <span class="c1">// s is dropped here, but we're trying to return a reference to it!</span>
</code></pre></div></div>

<p>The compiler rejects this. You must either return the owned value or ensure the reference outlives the data.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_5ff5abbdb4014ce91d35c4240bd35386.svg" alt="Comic Panel" class="comic-image" data-comic-hash="5ff5abbdb4014ce91d35c4240bd35386" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>How this helps with memory management:</strong></p>

<p>When Rust sees code following these rules, it can prove at compile time that:</p>
<ul>
  <li>No variable is used after being freed</li>
  <li>No variable is modified while being read</li>
  <li>Memory can be safely freed when the owner goes out of scope</li>
</ul>

<p>The compiler inserts cleanup code automatically, knowing it’s safe. No garbage collector needed!</p>

<p><strong>Why This Pattern Exists in General Rust</strong></p>

<p>In most Rust code (unlike Bevy’s special Commands), you can’t modify a collection while iterating over it. Here’s a real example that won’t compile:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">items</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">];</span>

<span class="k">for</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">item</span><span class="p">)</span> <span class="k">in</span> <span class="n">items</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>  <span class="c1">// ← Immutable borrow starts</span>
    <span class="k">if</span> <span class="o">*</span><span class="n">item</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="n">items</span><span class="nf">.remove</span><span class="p">(</span><span class="n">index</span><span class="p">);</span>  <span class="c1">// ✗ ERROR: Can't mutably borrow while immutably borrowed!</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Rust rejects this with: <em>“cannot borrow as mutable because it is also borrowed as immutable.”</em></p>

<p>Why? While <code class="language-plaintext highlighter-rouge">.iter()</code> is reading through the list, <code class="language-plaintext highlighter-rouge">.remove()</code> tries to modify it. That’s like trying to read a book while someone’s tearing pages out—you might try to read a page that’s gone!</p>

<p><strong>The Safe Pattern: Collect-Then-Process</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">items</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">];</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">to_remove</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

<span class="c1">// Step 1: Just reading (immutable borrow)</span>
<span class="k">for</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">item</span><span class="p">)</span> <span class="k">in</span> <span class="n">items</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="o">*</span><span class="n">item</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="n">to_remove</span><span class="nf">.push</span><span class="p">(</span><span class="n">index</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>  <span class="c1">// ← Immutable borrow ends</span>

<span class="c1">// Step 2: Now we can modify (mutable borrow)</span>
<span class="k">for</span> <span class="n">index</span> <span class="k">in</span> <span class="n">to_remove</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.rev</span><span class="p">()</span> <span class="p">{</span>  <span class="c1">// Remove from back to front</span>
    <span class="n">items</span><span class="nf">.remove</span><span class="p">(</span><span class="o">*</span><span class="n">index</span><span class="p">);</span>  <span class="c1">// ✓ Safe! No conflicting borrows</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This pattern separates reading from writing. The borrow checker is happy, and we avoid crashes.</p>

<p><strong>In Our Bevy Code</strong></p>

<p>Even though Bevy’s Commands doesn’t strictly require this pattern (it’s deferred), we use it anyway because:</p>
<ol>
  <li>It makes our intent clear: “find eligible items” then “process them”</li>
  <li>It follows Rust best practices that work everywhere</li>
  <li>If we later need to work with non-deferred collections, the pattern still applies</li>
</ol>

<h3 id="wiring-it-together">Wiring It Together</h3>

<p>Create <code class="language-plaintext highlighter-rouge">src/inventory/mod.rs</code> to expose the inventory system as a plugin:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/inventory/mod.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>

<span class="k">mod</span> <span class="n">inventory</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">systems</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">use</span> <span class="nn">inventory</span><span class="p">::{</span><span class="n">ItemKind</span><span class="p">,</span> <span class="n">Pickable</span><span class="p">,</span> <span class="n">Inventory</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">systems</span><span class="p">::</span><span class="n">handle_pickups</span><span class="p">;</span>

<span class="cd">/// Plugin for inventory and pickup functionality.</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">InventoryPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">InventoryPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Inventory</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="n">handle_pickups</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What does <code class="language-plaintext highlighter-rouge">.init_resource::&lt;Inventory&gt;()</code> do?</strong></p>

<p>It creates the <code class="language-plaintext highlighter-rouge">Inventory</code> resource by calling <code class="language-plaintext highlighter-rouge">Default::default()</code> (which creates an empty HashMap) and registers it with Bevy. Now any system can access it with <code class="language-plaintext highlighter-rouge">Res&lt;Inventory&gt;</code> or <code class="language-plaintext highlighter-rouge">ResMut&lt;Inventory&gt;</code>.</p>

<h3 id="integrating-the-plugin">Integrating the Plugin</h3>

<p>Open <code class="language-plaintext highlighter-rouge">src/main.rs</code> and add the inventory module and plugin:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Add to module declarations</span>
<span class="k">mod</span> <span class="n">inventory</span><span class="p">;</span>
</code></pre></div></div>

<p>Then add the plugin to your app:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Add to the plugin chain</span>
<span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">state</span><span class="p">::</span><span class="n">StatePlugin</span><span class="p">)</span>
<span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">characters</span><span class="p">::</span><span class="n">CharactersPlugin</span><span class="p">)</span>
<span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">inventory</span><span class="p">::</span><span class="n">InventoryPlugin</span><span class="p">)</span>  <span class="c1">// Add this line</span>
<span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionPlugin</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="making-items-pickable">Making Items Pickable</h2>

<p>Now we have the infrastructure, but no items to pick up! Our procedural map generation system needs to know which decorative objects should be pickable.</p>

<h3 id="updating-the-asset-spawner">Updating the Asset Spawner</h3>

<p>Open <code class="language-plaintext highlighter-rouge">src/map/assets.rs</code>. First, add the inventory imports at the top of the file:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Add to the imports section</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">inventory</span><span class="p">::{</span><span class="n">ItemKind</span><span class="p">,</span> <span class="n">Pickable</span><span class="p">};</span>
</code></pre></div></div>

<p>Now add a method to the <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> struct that marks an asset as pickable:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Add this method to the impl block for SpawnableAsset</span>

<span class="c1">// Inside impl SpawnableAsset {</span>

<span class="cd">/// Make this asset a pickable item.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_pickable</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">kind</span><span class="p">:</span> <span class="n">ItemKind</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
    <span class="k">self</span><span class="py">.pickable</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">kind</span><span class="p">);</span>
    <span class="k">self</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This method lets us chain <code class="language-plaintext highlighter-rouge">.with_pickable(ItemKind::Plant1)</code> when defining spawnable assets.</p>

<p>Next, update the <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> struct to store the pickable item kind:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Add this field to SpawnableAsset struct</span>

<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
    <span class="n">sprite_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">,</span>
    <span class="n">grid_offset</span><span class="p">:</span> <span class="n">GridDelta</span><span class="p">,</span>
    <span class="n">offset</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="n">tile_type</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">pickable</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">ItemKind</span><span class="o">&gt;</span><span class="p">,</span>  <span class="c1">// Add this line</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Update the constructor to initialize this field:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Update the new() method</span>

<span class="k">impl</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">sprite_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">sprite_name</span><span class="p">,</span>
            <span class="n">grid_offset</span><span class="p">:</span> <span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
            <span class="n">offset</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
            <span class="n">tile_type</span><span class="p">:</span> <span class="nb">None</span><span class="p">,</span>
            <span class="n">pickable</span><span class="p">:</span> <span class="nb">None</span><span class="p">,</span>  <span class="c1">// Add this line</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// ... rest of the methods</span>

</code></pre></div></div>

<p><strong>Update the asset loading code:</strong></p>

<p>Since we added a new field to <code class="language-plaintext highlighter-rouge">SpawnableAsset</code>, we need to update the destructuring pattern in the <code class="language-plaintext highlighter-rouge">load_assets</code> function:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Update the destructuring in load_assets function</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">load_assets</span><span class="p">(</span>
    <span class="n">tilemap_handles</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TilemapHandles</span><span class="p">,</span>
    <span class="n">assets_definitions</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ModelsAssets</span><span class="o">&lt;</span><span class="n">Sprite</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">models_assets</span> <span class="o">=</span> <span class="nn">ModelsAssets</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    
    <span class="k">for</span> <span class="p">(</span><span class="n">model_index</span><span class="p">,</span> <span class="n">assets</span><span class="p">)</span> <span class="k">in</span> <span class="n">assets_definitions</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">asset_def</span> <span class="k">in</span> <span class="n">assets</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
                <span class="n">sprite_name</span><span class="p">,</span>
                <span class="n">grid_offset</span><span class="p">,</span>
                <span class="n">offset</span><span class="p">,</span>
                <span class="n">tile_type</span><span class="p">,</span>
                <span class="n">pickable</span><span class="p">,</span> <span class="c1">// Add this line</span>
            <span class="p">}</span> <span class="o">=</span> <span class="n">asset_def</span><span class="p">;</span>

            <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">)</span> <span class="o">=</span> <span class="n">TILEMAP</span><span class="nf">.sprite_index</span><span class="p">(</span><span class="n">sprite_name</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nd">panic!</span><span class="p">(</span><span class="s">"Unknown atlas sprite '{}'"</span><span class="p">,</span> <span class="n">sprite_name</span><span class="p">);</span>
            <span class="p">};</span>

            <span class="c1">// Create the spawner function that adds components</span>
            <span class="k">let</span> <span class="n">spawner</span> <span class="o">=</span> <span class="nf">create_spawner</span><span class="p">(</span><span class="n">tile_type</span><span class="p">,</span> <span class="n">pickable</span><span class="p">);</span> <span class="c1">// Line update alert</span>

            <span class="n">models_assets</span><span class="nf">.add</span><span class="p">(</span>
                <span class="n">model_index</span><span class="p">,</span>
                <span class="n">ModelAsset</span> <span class="p">{</span>
                    <span class="n">assets_bundle</span><span class="p">:</span> <span class="n">tilemap_handles</span><span class="nf">.sprite</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">),</span>
                    <span class="n">grid_offset</span><span class="p">,</span>
                    <span class="n">world_offset</span><span class="p">:</span> <span class="n">offset</span><span class="p">,</span>
                    <span class="n">spawn_commands</span><span class="p">:</span> <span class="n">spawner</span><span class="p">,</span>
                <span class="p">},</span>
            <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">models_assets</span>
<span class="p">}</span>

</code></pre></div></div>

<h3 id="making-the-spawner-attach-components">Making the Spawner Attach Components</h3>

<p>Now update the spawner function to actually attach <code class="language-plaintext highlighter-rouge">Pickable</code> components when entities are created. Find the <code class="language-plaintext highlighter-rouge">create_spawner</code> function in <code class="language-plaintext highlighter-rouge">assets.rs</code> and add cases for pickable items:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Update create_spawner function to handle pickables</span>

<span class="k">fn</span> <span class="nf">create_spawner</span><span class="p">(</span>
    <span class="n">tile_type</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">pickable</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">ItemKind</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="k">fn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">match</span> <span class="p">(</span><span class="n">tile_type</span><span class="p">,</span> <span class="n">pickable</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Tile types without pickable</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Dirt</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Dirt</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">YellowGrass</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">YellowGrass</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Shore</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Shore</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Empty</span><span class="p">),</span> <span class="nb">None</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Empty</span><span class="p">));</span>
        <span class="p">},</span>

        <span class="c1">// Pickable plants (with grass tile type)</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant1</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">((</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nn">Pickable</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant1</span><span class="p">)));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant2</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">((</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nn">Pickable</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant2</span><span class="p">)));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant3</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">((</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nn">Pickable</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant3</span><span class="p">)));</span>
        <span class="p">},</span>
        <span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant4</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">((</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">),</span> <span class="nn">Pickable</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant4</span><span class="p">)));</span>
        <span class="p">},</span>

        <span class="c1">// Default: no components</span>
        <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">_</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{},</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="marking-props-as-pickable">Marking Props as Pickable</h3>

<p>Finally, open <code class="language-plaintext highlighter-rouge">src/map/rules.rs</code>. First, add the import at the top:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Add to the imports section</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">inventory</span><span class="p">::</span><span class="n">ItemKind</span><span class="p">;</span>
</code></pre></div></div>

<p>Now find the <code class="language-plaintext highlighter-rouge">build_props_layer</code> function. This is where decorative objects like plants are defined. Add <code class="language-plaintext highlighter-rouge">.with_pickable()</code> to make them collectible:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Update the plants section in build_props_layer</span>

<span class="c1">// Plants - make them all pickable</span>
<span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
    <span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> 
    <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_1"</span><span class="p">)</span>
        <span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)</span>
        <span class="nf">.with_pickable</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant1</span><span class="p">)</span>  <span class="c1">// Add this line</span>
    <span class="p">]</span>
<span class="p">);</span>
<span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
    <span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> 
    <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_2"</span><span class="p">)</span>
        <span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)</span>
        <span class="nf">.with_pickable</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant2</span><span class="p">)</span>  <span class="c1">// Add this line</span>
    <span class="p">]</span>
<span class="p">);</span>
<span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
    <span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> 
    <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_3"</span><span class="p">)</span>
        <span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)</span>
        <span class="nf">.with_pickable</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant3</span><span class="p">)</span>  <span class="c1">// Add this line</span>
    <span class="p">]</span>
<span class="p">);</span>
<span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
    <span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> 
    <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_4"</span><span class="p">)</span>
        <span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)</span>
        <span class="nf">.with_pickable</span><span class="p">(</span><span class="nn">ItemKind</span><span class="p">::</span><span class="n">Plant4</span><span class="p">)</span>  <span class="c1">// Add this line</span>
    <span class="p">]</span>
<span class="p">);</span>

</code></pre></div></div>

<p><strong>What’s happening here?</strong></p>

<p>We’re using the builder pattern to configure each spawnable asset. <code class="language-plaintext highlighter-rouge">.with_tile_type(TileType::Grass)</code> says “this is grass for collision purposes,” and <code class="language-plaintext highlighter-rouge">.with_pickable(ItemKind::Plant1)</code> says “the player can collect this as a Plant1 item.”</p>

<p>Run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>Walk near a plant or mushroom. It should disappear, and you’ll see a log message:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Picked up Herb (total: 1) — inventory: Herb: 1
 Picked up Flower (total: 1) — inventory: Flower: 1, Herb: 1
 Picked up Herb (total: 2) — inventory: Flower: 1, Herb: 2
</code></pre></div></div>

<div style="margin: 20px 0; padding: 15px; background-color: #f8d7da; border-radius: 8px; border-left: 4px solid #dc3545;">
<strong>Spawn Troubleshooting:</strong> There's a small chance the procedural generation places the player on top of a blocking object (tree, rock) at spawn. If you can't move when the game starts, simply restart to generate a new map. This is a quirk of random generation we'll address in future chapters.
</div>

<h2 id="zooming-in-and-following-the-player">Zooming In and Following the Player</h2>

<p>Right now, our game window shows the <strong>entire map</strong> at once. While functional, this has no sense of exploration. You can see everything at a glance, removing the mystery</p>

<p>Let’s <strong>zoom in</strong> to show only part of the map, making everything larger and more detailed. But this creates a new problem: if the camera stays fixed and only shows part of the map, the player can walk off camera view and disappear!</p>

<p>So we need <strong>two changes</strong>:</p>
<ol>
  <li>First, scale up the world (larger tiles and sprites) for a zoomed-in view</li>
  <li>Then, make the camera smoothly follow the player to keep them on screen</li>
</ol>

<p>We need three things:</p>
<ol>
  <li><strong>Updated configuration</strong> - Bigger tiles, player scale, and camera settings</li>
  <li><strong>Map generation updates</strong> - Scale sprites 2× larger</li>
  <li><strong>Camera system</strong> - Logic to smoothly track the player</li>
</ol>

<h3 id="updating-the-config">Updating the Config</h3>

<p>Our <code class="language-plaintext highlighter-rouge">config.rs</code> file already has <code class="language-plaintext highlighter-rouge">player</code>, <code class="language-plaintext highlighter-rouge">pickup</code>, and <code class="language-plaintext highlighter-rouge">map</code> modules. Let’s add camera configuration.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/config.rs</code> and add this new module after the <code class="language-plaintext highlighter-rouge">map</code> module:
I</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config.rs - Add this after the map module</span>

<span class="cd">/// Camera configuration</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">camera</span> <span class="p">{</span>
    <span class="cd">/// How fast the camera interpolates toward the player (higher = snappier)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">CAMERA_LERP_SPEED</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">6.0</span><span class="p">;</span>
    
    <span class="cd">/// Z position for the camera (must be high to see all layers)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">CAMERA_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1000.0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why these values?</strong></p>

<p><code class="language-plaintext highlighter-rouge">CAMERA_LERP_SPEED</code> controls how quickly the camera catches up to the player. A value of 6.0 means the camera closes 60% of the distance each second. Higher values make the camera snappier (instantly follows), lower values make it lag behind (cinematic feel).</p>

<p><code class="language-plaintext highlighter-rouge">CAMERA_Z</code> must be high enough to see all game layers. Our player is around Z=20, props are around Z=4, and we need the camera above everything to render the scene.</p>

<p><strong>What’s “lerp”?</strong></p>

<p>“Lerp” is short for <strong>linear interpolation</strong>. It’s a smooth transition between two values. Instead of jumping from point A to point B instantly, lerp gradually moves from A to B over time.</p>

<p>Think of it like this: if the camera is at position (0, 0) and the player is at (100, 0), with a lerp factor of 0.6, the camera moves to (60, 0) in the first frame. Next frame, the player is still at (100, 0), but the camera is at (60, 0), so it moves 60% of the remaining distance (40 units) to (84, 0). It keeps closing the gap smoothly until it catches up.</p>

<p>We also need to update some existing values for better scaling. While we’re in <code class="language-plaintext highlighter-rouge">config.rs</code>, let’s update the player and map configurations:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config.rs - Update the player module</span>

<span class="cd">/// Player-related configuration</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">player</span> <span class="p">{</span>
    <span class="cd">/// Collision radius for the player's collider (in world units)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">COLLIDER_RADIUS</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">24.0</span><span class="p">;</span> <span class="c1">// Line update alert</span>
    
    <span class="cd">/// Z-position for player rendering (above terrain, below UI)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">PLAYER_Z_POSITION</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">20.0</span><span class="p">;</span>
    
    <span class="cd">/// Visual scale of the player sprite</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">PLAYER_SCALE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1.2</span><span class="p">;</span> <span class="c1">// Line update alert (was 0.8)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now update the map module:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config.rs - Update the map module</span>

<span class="cd">/// Map/terrain configuration</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">map</span> <span class="p">{</span>
    <span class="cd">/// Size of a single tile in world units (64px base * 1.0 scale = 64)</span>
    <span class="cd">/// NOTE: This must match TILE_SIZE in generate.rs!</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">TILE_SIZE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">64.0</span><span class="p">;</span> <span class="c1">// Line update alert (was 32.0)</span>
    
    <span class="cd">/// Grid dimensions</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">GRID_X</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">25</span><span class="p">;</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">GRID_Y</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span>
    
    <span class="cd">/// Z-height of each layer (used for Y-based depth sorting)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">NODE_SIZE_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="updating-map-generation">Updating Map Generation</h3>

<p>Now we need to update <code class="language-plaintext highlighter-rouge">src/map/generate.rs</code> to use our centralized config values and scale up the sprites.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/map/generate.rs</code> and make these changes:</p>

<p><strong>1. Update the imports to use config values:</strong>
<br />
We now import <code class="language-plaintext highlighter-rouge">GRID_X</code>, <code class="language-plaintext highlighter-rouge">GRID_Y</code>, <code class="language-plaintext highlighter-rouge">NODE_SIZE_Z</code>, and <code class="language-plaintext highlighter-rouge">TILE_SIZE</code> from config instead of defining them locally</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs - Update imports</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">map</span><span class="p">::{</span><span class="n">GRID_X</span><span class="p">,</span> <span class="n">GRID_Y</span><span class="p">,</span> <span class="n">NODE_SIZE_Z</span><span class="p">,</span> <span class="n">TILE_SIZE</span><span class="p">};</span> <span class="c1">// Line update alert</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::{</span>
    <span class="nn">assets</span><span class="p">::{</span><span class="n">load_assets</span><span class="p">,</span> <span class="n">prepare_tilemap_handles</span><span class="p">},</span>
    <span class="nn">rules</span><span class="p">::</span><span class="n">build_world</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>

<p><strong>2. Delete these local constants</strong> (we’re using config values now):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs </span>
<span class="c1">// DELETE THESE LINES:</span>
<span class="c1">// DELETE  pub const GRID_X: u32 = 25;</span>
<span class="c1">// DELETE  pub const GRID_Y: u32 = 18;</span>
<span class="c1">// DELETE  pub const TILE_SIZE: f32 = 32.;</span>
</code></pre></div></div>

<p><strong>3. Delete the <code class="language-plaintext highlighter-rouge">map_pixel_dimensions</code> function</strong> (no longer needed):</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs </span>
<span class="c1">// DELETE THIS ENTIRE FUNCTION:</span>
<span class="c1">// DELETE pub fn map_pixel_dimensions() -&gt; Vec2 {</span>
<span class="c1">// DELETE     Vec2::new(TILE_SIZE * GRID_X as f32, TILE_SIZE * GRID_Y as f32)</span>
<span class="c1">// DELETE }</span>
</code></pre></div></div>

<p><strong>4. Update NODE_SIZE to use the config constant:</strong>
<br />
Uses <code class="language-plaintext highlighter-rouge">NODE_SIZE_Z</code> from config for consistency</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs </span>

<span class="c1">// BEFORE:</span>
<span class="k">const</span> <span class="n">NODE_SIZE</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">TILE_SIZE</span><span class="p">,</span> <span class="n">TILE_SIZE</span><span class="p">,</span> <span class="mf">1.</span><span class="p">);</span>

<span class="c1">// AFTER:</span>
<span class="k">const</span> <span class="n">NODE_SIZE</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">TILE_SIZE</span><span class="p">,</span> <span class="n">TILE_SIZE</span><span class="p">,</span> <span class="n">NODE_SIZE_Z</span><span class="p">);</span> <span class="c1">// Line update alert</span>
</code></pre></div></div>

<p><strong>5. Update ASSETS_SCALE to 2× for larger sprites:</strong></p>

<p>Change from <code class="language-plaintext highlighter-rouge">Vec3::ONE</code> (1.0, 1.0, 1.0) to <code class="language-plaintext highlighter-rouge">Vec3::new(2.0, 2.0, 1.0)</code> to scale sprites 2× larger</p>

<p>Our tilemap uses 32px sprites but we’re rendering them at 64px. The 2× scale factor in <code class="language-plaintext highlighter-rouge">ASSETS_SCALE</code> makes this happen, creating the zoomed-in view.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs </span>

<span class="c1">// BEFORE:</span>
<span class="k">const</span> <span class="n">ASSETS_SCALE</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ONE</span><span class="p">;</span>

<span class="c1">// AFTER:</span>
<span class="k">const</span> <span class="n">ASSETS_SCALE</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">2.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span> <span class="c1">// Line update alert</span>
</code></pre></div></div>

<h3 id="building-the-camera-system">Building the Camera System</h3>

<p>Create a new folder <code class="language-plaintext highlighter-rouge">src/camera/</code> and add <code class="language-plaintext highlighter-rouge">src/camera/camera.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/camera/camera.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">camera</span><span class="p">::{</span><span class="n">CAMERA_LERP_SPEED</span><span class="p">,</span> <span class="n">CAMERA_Z</span><span class="p">};</span>

<span class="cd">/// Marker component for the main game camera.</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">MainCamera</span><span class="p">;</span>

<span class="cd">/// Spawn the main 2D camera.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">setup_camera</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span><span class="nn">Camera2d</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span> <span class="n">MainCamera</span><span class="p">));</span>
<span class="p">}</span>

<span class="cd">/// Smoothly follow the player with the camera.</span>
<span class="cd">///</span>
<span class="cd">/// Uses linear interpolation for smooth movement and snaps to pixel boundaries</span>
<span class="cd">/// to prevent subpixel rendering artifacts (grid shimmer).</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">follow_camera</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="p">(</span><span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">Changed</span><span class="o">&lt;</span><span class="n">Transform</span><span class="o">&gt;</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">camera_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span> <span class="p">(</span><span class="n">With</span><span class="o">&lt;</span><span class="n">MainCamera</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">Without</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// The Changed&lt;Transform&gt; filter in player_query means this system only processes</span>
    <span class="c1">// when the player has moved - Bevy filters it at the query level</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">player_transform</span><span class="p">)</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.next</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="k">mut</span> <span class="n">camera_transform</span><span class="p">)</span> <span class="o">=</span> <span class="n">camera_query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="n">player_pos</span> <span class="o">=</span> <span class="n">player_transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">camera_pos</span> <span class="o">=</span> <span class="n">camera_transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">();</span>

    <span class="c1">// Early exit if camera is already very close (within 0.5 pixels)</span>
    <span class="k">let</span> <span class="n">distance</span> <span class="o">=</span> <span class="n">player_pos</span><span class="nf">.distance</span><span class="p">(</span><span class="n">camera_pos</span><span class="p">);</span>
    <span class="k">if</span> <span class="n">distance</span> <span class="o">&lt;</span> <span class="mf">0.5</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Smooth interpolation toward player</span>
    <span class="k">let</span> <span class="n">lerp_factor</span> <span class="o">=</span> <span class="p">(</span><span class="n">CAMERA_LERP_SPEED</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">())</span><span class="nf">.clamp</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">new_pos</span> <span class="o">=</span> <span class="n">camera_pos</span><span class="nf">.lerp</span><span class="p">(</span><span class="n">player_pos</span><span class="p">,</span> <span class="n">lerp_factor</span><span class="p">);</span>

    <span class="c1">// Snap to pixel boundaries to prevent grid shimmer</span>
    <span class="n">camera_transform</span><span class="py">.translation.x</span> <span class="o">=</span> <span class="n">new_pos</span><span class="py">.x</span><span class="nf">.round</span><span class="p">();</span>
    <span class="n">camera_transform</span><span class="py">.translation.y</span> <span class="o">=</span> <span class="n">new_pos</span><span class="py">.y</span><span class="nf">.round</span><span class="p">();</span>
    <span class="n">camera_transform</span><span class="py">.translation.z</span> <span class="o">=</span> <span class="n">CAMERA_Z</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<p><strong>MainCamera marker</strong>: Just like we use <code class="language-plaintext highlighter-rouge">Player</code> to identify the player entity, we use <code class="language-plaintext highlighter-rouge">MainCamera</code> to find the camera entity.</p>

<p><strong>setup_camera</strong>: Spawns a 2D camera with the <code class="language-plaintext highlighter-rouge">MainCamera</code> marker. This runs once at startup.</p>

<p><strong>follow_camera</strong>: This is where the magic happens. Let’s dissect it:</p>

<ol>
  <li>
    <p><strong>Changed filter</strong>: <code class="language-plaintext highlighter-rouge">Changed&lt;Transform&gt;</code> in the query definition means Bevy only calls this system when the player’s transform has changed. This is a query-level optimization - if the player hasn’t moved, Bevy won’t even run this function. No movement? No wasted CPU cycles.</p>
  </li>
  <li>
    <p><strong>Early exits</strong>: If there’s no player or no camera, bail out early. Also, if the camera is already within 0.5 pixels of the player, don’t bother moving it.</p>
  </li>
  <li>
    <p><strong>Lerp calculation</strong>: We calculate how much to move based on <code class="language-plaintext highlighter-rouge">CAMERA_LERP_SPEED</code> and the time since last frame (<code class="language-plaintext highlighter-rouge">time.delta_secs()</code>). This gives us smooth movement regardless of frame rate.</p>
  </li>
  <li>
    <p><strong>Pixel snapping</strong>: <code class="language-plaintext highlighter-rouge">.round()</code> snaps the position to whole pixels. Without this, the camera can land on fractional pixel coordinates (like 10.3), causing the grid to look jittery. Snapping prevents this “shimmer” effect.</p>
  </li>
</ol>

<p><strong>Why clamp lerp_factor?</strong></p>

<p>If the frame rate is very slow (say, 2 FPS), <code class="language-plaintext highlighter-rouge">delta_secs()</code> could be 0.5 seconds. Multiplying by <code class="language-plaintext highlighter-rouge">CAMERA_LERP_SPEED</code> (6.0) gives 3.0, which would overshoot. <code class="language-plaintext highlighter-rouge">.clamp(0.0, 1.0)</code> ensures we never move more than 100% of the distance in a single frame.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_708d680b5317927fd005118ba278a15c.svg" alt="Comic Panel" class="comic-image" data-comic-hash="708d680b5317927fd005118ba278a15c" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="camera-module">Camera Module</h3>

<p>Now create <code class="language-plaintext highlighter-rouge">src/camera/mod.rs</code> to expose the camera systems:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/camera/mod.rs</span>
<span class="k">mod</span> <span class="n">camera</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>

<span class="c1">// Re-export public items</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">camera</span><span class="p">::</span><span class="n">MainCamera</span><span class="p">;</span>

<span class="cd">/// Plugin for camera systems.</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CameraPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">CameraPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Startup</span><span class="p">,</span>
                <span class="nn">camera</span><span class="p">::</span><span class="n">setup_camera</span><span class="p">,</span>
            <span class="p">)</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="nn">camera</span><span class="p">::</span><span class="n">follow_camera</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s happening:</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">CameraPlugin</code> bundles our camera systems:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">setup_camera</code> runs once at <code class="language-plaintext highlighter-rouge">Startup</code> to spawn the camera</li>
  <li><code class="language-plaintext highlighter-rouge">follow_camera</code> runs every frame during <code class="language-plaintext highlighter-rouge">Update</code>, but only when <code class="language-plaintext highlighter-rouge">GameState::Playing</code></li>
</ul>

<p><strong>Why gate on <code class="language-plaintext highlighter-rouge">GameState::Playing</code>?</strong></p>

<p>During the <code class="language-plaintext highlighter-rouge">Loading</code> state, the player entity might not exist yet. Running <code class="language-plaintext highlighter-rouge">follow_camera</code> would cause errors. By using <code class="language-plaintext highlighter-rouge">.run_if(in_state(GameState::Playing))</code>, we ensure the follow system only runs when the game is actually playing.</p>

<h3 id="integrating-the-camera">Integrating the Camera</h3>

<p>Now we wire everything together in <code class="language-plaintext highlighter-rouge">main.rs</code>. But first, we need to remove the old camera setup.</p>

<p>Open <code class="language-plaintext highlighter-rouge">src/main.rs</code>. Find and <strong>delete</strong> the <code class="language-plaintext highlighter-rouge">setup_camera</code> function at the bottom:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - DELETE this entire function</span>
<span class="c1">//DELETE fn setup_camera(mut commands: Commands) {</span>
<span class="c1">//DELETE     commands.spawn(Camera2d);</span>
<span class="c1">//DELETE }</span>
</code></pre></div></div>

<p>Now update the module declarations and imports:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Update module declarations at the top</span>
<span class="k">mod</span> <span class="n">map</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">characters</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">state</span><span class="p">;</span> 
<span class="k">mod</span> <span class="n">collision</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">config</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">inventory</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">camera</span><span class="p">;</span> <span class="c1">// Add this line</span>
</code></pre></div></div>

<p>Update the imports to include the <code class="language-plaintext highlighter-rouge">CameraPlugin</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Update imports</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::{</span>
    <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span>
    <span class="nn">window</span><span class="p">::{</span><span class="n">MonitorSelection</span><span class="p">,</span> <span class="n">Window</span><span class="p">,</span> <span class="n">WindowMode</span><span class="p">,</span> <span class="n">WindowPlugin</span><span class="p">},</span> <span class="c1">// Line update alert</span>
<span class="p">};</span>

<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">camera</span><span class="p">::</span><span class="n">CameraPlugin</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">generate</span><span class="p">::</span><span class="n">setup_generator</span><span class="p">;</span> <span class="c1">// Line update alert - remove map_pixel_dimensions</span>
</code></pre></div></div>

<p>Now update the <code class="language-plaintext highlighter-rouge">main</code> function to use the <code class="language-plaintext highlighter-rouge">CameraPlugin</code> instead of the old <code class="language-plaintext highlighter-rouge">setup_camera</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs - Update the main function</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.insert_resource</span><span class="p">(</span><span class="nf">ClearColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">BLACK</span><span class="p">))</span> <span class="c1">// Line update alert</span>
        <span class="nf">.add_plugins</span><span class="p">(</span>
            <span class="n">DefaultPlugins</span>
                <span class="nf">.set</span><span class="p">(</span><span class="n">AssetPlugin</span> <span class="p">{</span>
                    <span class="n">file_path</span><span class="p">:</span> <span class="s">"src/assets"</span><span class="nf">.into</span><span class="p">(),</span>
                    <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                <span class="p">})</span>
                <span class="nf">.set</span><span class="p">(</span><span class="n">WindowPlugin</span> <span class="p">{</span>
                    <span class="n">primary_window</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">Window</span> <span class="p">{</span>
                        <span class="n">title</span><span class="p">:</span> <span class="s">"Bevy Game"</span><span class="nf">.into</span><span class="p">(),</span>
                        <span class="n">mode</span><span class="p">:</span> <span class="nn">WindowMode</span><span class="p">::</span><span class="nf">BorderlessFullscreen</span><span class="p">(</span><span class="nn">MonitorSelection</span><span class="p">::</span><span class="n">Current</span><span class="p">),</span> <span class="c1">// Add this line</span>
                        <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                    <span class="p">}),</span>
                    <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                <span class="p">})</span>
                <span class="nf">.set</span><span class="p">(</span><span class="nn">ImagePlugin</span><span class="p">::</span><span class="nf">default_nearest</span><span class="p">()),</span>
        <span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">ProcGenSimplePlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="p">,</span> <span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">state</span><span class="p">::</span><span class="n">StatePlugin</span><span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="n">CameraPlugin</span><span class="p">)</span> <span class="c1">// Add this line</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">inventory</span><span class="p">::</span><span class="n">InventoryPlugin</span><span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionPlugin</span><span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">characters</span><span class="p">::</span><span class="n">CharactersPlugin</span><span class="p">)</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">setup_generator</span><span class="p">)</span> <span class="c1">// Line update alert - remove setup_camera here</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What changed:</strong></p>

<ol>
  <li>Added <code class="language-plaintext highlighter-rouge">mod camera;</code> to declare the camera module</li>
  <li>Imported <code class="language-plaintext highlighter-rouge">CameraPlugin</code> and window mode types</li>
  <li>Added <code class="language-plaintext highlighter-rouge">.add_plugins(CameraPlugin)</code> to the app</li>
  <li>Removed <code class="language-plaintext highlighter-rouge">setup_camera</code> from the <code class="language-plaintext highlighter-rouge">Startup</code> systems (it’s now inside <code class="language-plaintext highlighter-rouge">CameraPlugin</code>)</li>
  <li>Changed to <strong>borderless fullscreen mode</strong> - perfect for our zoomed-in camera view! It uses your full screen, making the zoomed world feel immersive without window borders getting in the way</li>
  <li>Changed background to black for a cleaner look</li>
</ol>

<p>Run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>Walk around using the arrow keys. Notice how the camera smoothly follows your character instead of staying fixed. The camera should feel responsive but not jarring - that’s the lerp in action!</p>

<p><img src="/assets/book_assets/chapter5/ch5.gif" alt="Inventory System Demo" /></p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_8cf87ab7f58f385947cb0541d1cf0dab.svg" alt="Comic Panel" class="comic-image" data-comic-hash="8cf87ab7f58f385947cb0541d1cf0dab" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Build an inventory system to collect items from the world, then zoom in and add smooth camera follow.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aibodh.com/assets/book_assets/chapter5/ch5.gif" /><media:content medium="image" url="https://aibodh.com/assets/book_assets/chapter5/ch5.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 4 - Let There Be Collisions</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-4/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 4 - Let There Be Collisions" /><published>2025-12-16T10:00:00+00:00</published><updated>2025-12-16T10:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-4</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-4/"><![CDATA[<style>
.tile-image {
  margin: 0 !important;
  object-fit: none !important;
  cursor: default !important;
  pointer-events: none !important;
}
</style>

<p>By the end of this chapter, your player will interact with the world properly, no more walking through trees, water, or rocks. We’ll implement z-ordering so they can walk behind objects, giving your 2D game true depth. Also, you’ll build a collision visualizer for debugging.</p>

<blockquote>
  <p><strong>Prerequisites</strong>: This is Chapter 4 of our Bevy tutorial series. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> for updates on new releases. Before starting, complete <a href="/posts/bevy-rust-game-development-chapter-1/">Chapter 1: Let There Be a Player</a>, <a href="/posts/bevy-rust-game-development-chapter-2/">Chapter 2: Let There Be a World</a>, and <a href="/posts/bevy-rust-game-development-chapter-3/">Chapter 3: Let The Data Flow</a>, or clone the Chapter 3 code from <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">this repository</a> to follow along.</p>
</blockquote>

<p><img src="/assets/book_assets/chapter4/ch4.gif" alt="Collision System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<strong>Before We Begin:</strong> <em style="font-size: 14px;">I'm constantly working to improve this tutorial and make your learning journey enjoyable. Your feedback matters - share your frustrations, questions, or suggestions on <a href="https://www.reddit.com/r/bevy/" target="_blank">Reddit</a>/<a target="_blank" href="https://discord.com/invite/cD9qEsSjUH">Discord</a>/<a href="https://www.linkedin.com/in/febinjohnjames" target="_blank">LinkedIn</a>. Loved it? Let me know what worked well for you! Together, we'll make game development with Rust and Bevy more accessible for everyone.</em>
</div>

<h2 id="systems-running-when-they-shouldnt">Systems Running When They Shouldn’t</h2>

<p>In Chapter 3, we built a character system that loads sprite data from a <code class="language-plaintext highlighter-rouge">.ron</code> file. But there’s a problem in how we handle the initialization.</p>

<p>The <code class="language-plaintext highlighter-rouge">spawn_player</code> system runs once at startup. It spawns the player entity and begins loading the character data file. So far, so good.</p>

<p>But then <code class="language-plaintext highlighter-rouge">initialize_player_character</code> runs <strong>every single frame</strong>, checking if the assets have finished loading:</p>

<p>We have a <strong>polling pattern</strong> that repeatedly checks if something is ready:</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_bbefd90623dac3d7bbc146ba6d946e26.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="bbefd90623dac3d7bbc146ba6d946e26" data-comic-settings="d2-diagram" />
</div>

<p>The system runs <strong>every frame forever</strong> with wasted checks while loading, one useful execution, then infinite no-ops causing performance waste, code clutter, and making the code difficult to extend.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_d3c88ebe600f0be28424cb463b4f4fdf.svg" alt="Comic Panel" class="comic-image" data-comic-hash="d3c88ebe600f0be28424cb463b4f4fdf" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="systems-that-run-only-when-needed">Systems That Run Only When Needed</h3>

<p><code class="language-plaintext highlighter-rouge">Startup</code> and <code class="language-plaintext highlighter-rouge">Update</code> we use to trigger our systems are <strong>schedules</strong>. Bevy’s way of organizing when systems run. <code class="language-plaintext highlighter-rouge">Startup</code> runs once at launch, <code class="language-plaintext highlighter-rouge">Update</code> runs every frame.</p>

<p>But what if we need systems that run at <em>specific moments</em>? Not every frame, not just at startup, but exactly when something happens like when assets finish loading, or when the player pauses the game.</p>

<h3 id="state-based-schedules">State-Based Schedules</h3>

<p>The solution is to organize our game into distinct <strong>phases</strong>. We call these phases <strong>states</strong>: <code class="language-plaintext highlighter-rouge">Loading</code>, <code class="language-plaintext highlighter-rouge">Playing</code>, and <code class="language-plaintext highlighter-rouge">Paused</code>. Each state represents a different mode of the game.</p>

<p>When the game transitions from one state to another (say, from <code class="language-plaintext highlighter-rouge">Loading</code> to <code class="language-plaintext highlighter-rouge">Playing</code>), Bevy provides special schedules that run exactly once:</p>

<ul>
  <li><strong>OnEnter</strong> - Runs when entering a state</li>
  <li><strong>OnExit</strong> - Runs when leaving a state</li>
</ul>

<p>This is how we eliminate polling. Instead of <code class="language-plaintext highlighter-rouge">initialize_player_character</code> checking every frame “Are assets loaded yet?”, we attach it to <code class="language-plaintext highlighter-rouge">OnExit(Loading)</code>. When assets finish loading and we leave the <code class="language-plaintext highlighter-rouge">Loading</code> state, Bevy runs it exactly once.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_dd8b17cd6fa49b259e25e75c855b8746.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="dd8b17cd6fa49b259e25e75c855b8746" data-comic-settings="d2-diagram" />
</div>

<h2 id="implementing-game-states">Implementing Game States</h2>

<p>Let’s build a state management module with loading screens and game pause functionality. Create the folder <code class="language-plaintext highlighter-rouge">state</code> inside the <code class="language-plaintext highlighter-rouge">src</code> folder.</p>

<h3 id="defining-game-states">Defining Game States</h3>

<p><strong>What states do we need?</strong></p>

<p>Think about your game’s lifecycle. Right now, when the game starts:</p>
<ol>
  <li>Assets need time to load from disk</li>
  <li>Once loaded, gameplay begins</li>
  <li>Players might want to pause</li>
</ol>

<p>These are three distinct phases, each needing different systems:</p>
<ul>
  <li><strong>Loading</strong>: Show loading screen, check if assets ready, don’t run gameplay</li>
  <li><strong>Playing</strong>: Run movement/animation, hide loading screen, allow pausing</li>
  <li><strong>Paused</strong>: Show pause menu, stop gameplay, allow un-pausing</li>
</ul>

<p>Let’s define these as an enum:</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/state/game_state.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/state/game_state.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">#[derive(States,</span> <span class="nd">Default,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Hash)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">GameState</span> <span class="p">{</span>
    <span class="nd">#[default]</span>
    <span class="n">Loading</span><span class="p">,</span>
    <span class="n">Playing</span><span class="p">,</span>
    <span class="n">Paused</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s the <code class="language-plaintext highlighter-rouge">States</code> macro?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">#[derive(States)]</code> macro implements the <code class="language-plaintext highlighter-rouge">States</code> trait, which tells Bevy:</p>
<ul>
  <li>This enum represents game phases where only one can be active at a time</li>
  <li>Bevy should track which state is active</li>
  <li>Systems can be gated to run only in specific states</li>
  <li>State transitions should trigger OnEnter/OnExit schedules</li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">#[default]</code> attribute marks which state the game starts in. Here, Bevy initializes the state to the default value (<code class="language-plaintext highlighter-rouge">Loading</code> in our case).</p>

<h3 id="loading-screen">Loading Screen</h3>

<p>Let’s create a loading screen with a full-screen dark background, animated “Loading…” text.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/state/loading.rs</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/state/loading.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">LoadingScreen</span><span class="p">;</span>

<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">LoadingText</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_loading_screen</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="n">LoadingScreen</span><span class="p">,</span>
        <span class="n">Node</span> <span class="p">{</span>
            <span class="n">width</span><span class="p">:</span> <span class="nn">Val</span><span class="p">::</span><span class="nf">Percent</span><span class="p">(</span><span class="mf">100.0</span><span class="p">),</span>
            <span class="n">height</span><span class="p">:</span> <span class="nn">Val</span><span class="p">::</span><span class="nf">Percent</span><span class="p">(</span><span class="mf">100.0</span><span class="p">),</span>
            <span class="n">justify_content</span><span class="p">:</span> <span class="nn">JustifyContent</span><span class="p">::</span><span class="n">Center</span><span class="p">,</span>
            <span class="n">align_items</span><span class="p">:</span> <span class="nn">AlignItems</span><span class="p">::</span><span class="n">Center</span><span class="p">,</span>
            <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
        <span class="p">},</span>
        <span class="nf">BackgroundColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.15</span><span class="p">)),</span>
    <span class="p">))</span><span class="nf">.with_children</span><span class="p">(|</span><span class="n">parent</span><span class="p">|</span> <span class="p">{</span>
        <span class="n">parent</span><span class="nf">.spawn</span><span class="p">((</span>
            <span class="n">LoadingText</span><span class="p">,</span>
            <span class="nn">Text</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"Loading..."</span><span class="p">),</span>
            <span class="n">TextFont</span> <span class="p">{</span>
                <span class="n">font_size</span><span class="p">:</span> <span class="mf">48.0</span><span class="p">,</span>
                <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
            <span class="p">},</span>
            <span class="nf">TextColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">),</span>
        <span class="p">));</span>
    <span class="p">});</span>
    
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Loading screen spawned"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s happening:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">LoadingScreen</code> and <code class="language-plaintext highlighter-rouge">LoadingText</code> are marker components to identify our UI entities</li>
  <li><code class="language-plaintext highlighter-rouge">Node</code> creates a full-screen container (100% width and height)</li>
  <li><code class="language-plaintext highlighter-rouge">justify_content</code> and <code class="language-plaintext highlighter-rouge">align_items</code> centered means text appears in the middle</li>
  <li><code class="language-plaintext highlighter-rouge">.with_children()</code> spawns the text as a child of the background</li>
</ul>

<p>Now append the animation function to the same file:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/state/loading.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">animate_loading</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">Text</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">LoadingText</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="k">mut</span> <span class="n">text</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">dots</span> <span class="o">=</span> <span class="p">(</span><span class="n">time</span><span class="nf">.elapsed_secs</span><span class="p">()</span> <span class="o">*</span> <span class="mf">2.0</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span> <span class="o">%</span> <span class="mi">4</span><span class="p">;</span>
        <span class="o">**</span><span class="n">text</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"Loading{}"</span><span class="p">,</span> <span class="s">"."</span><span class="nf">.repeat</span><span class="p">(</span><span class="n">dots</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This animates the text: “Loading” → “Loading.” → “Loading..” → “Loading…” (cycling through 0-3 dots).</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">**text</code>, is this like double dereferencing?</strong></p>

<p>Yes! When we call <code class="language-plaintext highlighter-rouge">.iter_mut()</code>, Bevy wraps our <code class="language-plaintext highlighter-rouge">&amp;mut Text</code> in a special type that tracks changes. The first <code class="language-plaintext highlighter-rouge">*</code> unwraps that to get <code class="language-plaintext highlighter-rouge">&amp;mut Text</code>, and the second <code class="language-plaintext highlighter-rouge">*</code> dereferences the reference to reach the actual <code class="language-plaintext highlighter-rouge">Text</code> value we can modify.</p>

<p>Finally, append the despawn function:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/state/loading.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">despawn_loading_screen</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="n">Entity</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">LoadingScreen</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">entity</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.despawn</span><span class="p">();</span>
    <span class="p">}</span>
    
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Loading screen despawned"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why despawn?</strong></p>

<p>When we transition to Playing state, the loading screen should disappear. Without despawning, the UI entities would remain in the world forever, cluttering memory and rendering on top of the game. The <code class="language-plaintext highlighter-rouge">.despawn()</code> function removes the loading screen entity. Since LoadingText is a child (via <code class="language-plaintext highlighter-rouge">.with_children()</code>), Bevy automatically removes it too.</p>

<h3 id="pause-menu">Pause Menu</h3>

<p>Now let’s create a pause menu that shows a semi-transparent overlay with “PAUSED” text when the player presses Escape, and hides when they press it again.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/state/pause.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/state/pause.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">PauseMenu</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_pause_menu</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="n">PauseMenu</span><span class="p">,</span>
        <span class="n">Node</span> <span class="p">{</span>
            <span class="n">width</span><span class="p">:</span> <span class="nn">Val</span><span class="p">::</span><span class="nf">Percent</span><span class="p">(</span><span class="mf">100.0</span><span class="p">),</span>
            <span class="n">height</span><span class="p">:</span> <span class="nn">Val</span><span class="p">::</span><span class="nf">Percent</span><span class="p">(</span><span class="mf">100.0</span><span class="p">),</span>
            <span class="n">justify_content</span><span class="p">:</span> <span class="nn">JustifyContent</span><span class="p">::</span><span class="n">Center</span><span class="p">,</span>
            <span class="n">align_items</span><span class="p">:</span> <span class="nn">AlignItems</span><span class="p">::</span><span class="n">Center</span><span class="p">,</span>
            <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
        <span class="p">},</span>
        <span class="nf">BackgroundColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="nf">srgba</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.7</span><span class="p">)),</span>
    <span class="p">))</span><span class="nf">.with_children</span><span class="p">(|</span><span class="n">parent</span><span class="p">|</span> <span class="p">{</span>
        <span class="n">parent</span><span class="nf">.spawn</span><span class="p">((</span>
            <span class="nn">Text</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"PAUSED</span><span class="se">\n\n</span><span class="s">Press ESC to resume"</span><span class="p">),</span>
            <span class="n">TextFont</span> <span class="p">{</span>
                <span class="n">font_size</span><span class="p">:</span> <span class="mf">36.0</span><span class="p">,</span>
                <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
            <span class="p">},</span>
            <span class="nf">TextColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">),</span>
            <span class="nn">TextLayout</span><span class="p">::</span><span class="nf">new_with_justify</span><span class="p">(</span><span class="nn">Justify</span><span class="p">::</span><span class="n">Center</span><span class="p">),</span>
        <span class="p">));</span>
    <span class="p">});</span>
    
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Pause menu spawned"</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">despawn_pause_menu</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="n">Entity</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">PauseMenu</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">entity</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.despawn</span><span class="p">();</span>
    <span class="p">}</span>
    
    <span class="nd">info!</span><span class="p">(</span><span class="s">"Pause menu despawned"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="state-plugin">State Plugin</h3>

<p>Now we’ll create the <code class="language-plaintext highlighter-rouge">StatePlugin</code> that wires everything together. We’ll build it piece by piece.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/state/mod.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/state/mod.rs</span>
<span class="k">mod</span> <span class="n">game_state</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">loading</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">pause</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">spawn</span><span class="p">::</span><span class="n">CharactersListResource</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="n">CharactersList</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">use</span> <span class="nn">game_state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">StatePlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">StatePlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span>
            <span class="py">.init_state</span><span class="p">::</span><span class="o">&lt;</span><span class="n">GameState</span><span class="o">&gt;</span><span class="p">()</span>
            
            <span class="c1">// Loading state systems</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="nf">OnEnter</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Loading</span><span class="p">),</span> <span class="nn">loading</span><span class="p">::</span><span class="n">spawn_loading_screen</span><span class="p">)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
                <span class="n">check_assets_loaded</span><span class="p">,</span>
                <span class="nn">loading</span><span class="p">::</span><span class="n">animate_loading</span><span class="p">,</span>
            <span class="p">)</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Loading</span><span class="p">)))</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="nf">OnExit</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Loading</span><span class="p">),</span> <span class="p">(</span>
                <span class="nn">loading</span><span class="p">::</span><span class="n">despawn_loading_screen</span><span class="p">,</span>
                <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">spawn</span><span class="p">::</span><span class="n">initialize_player_character</span><span class="p">,</span>
            <span class="p">))</span>
                <span class="c1">// Pause state systems</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="nf">OnEnter</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Paused</span><span class="p">),</span> <span class="nn">pause</span><span class="p">::</span><span class="n">spawn_pause_menu</span><span class="p">)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="nf">OnExit</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Paused</span><span class="p">),</span> <span class="nn">pause</span><span class="p">::</span><span class="n">despawn_pause_menu</span><span class="p">)</span>
            
            <span class="c1">// Pause toggle (works in Playing or Paused states)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> 
                <span class="n">toggle_pause</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)</span><span class="nf">.or</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Paused</span><span class="p">)))</span>
            <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">check_assets_loaded</span><span class="p">(</span>
    <span class="n">characters_list_res</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CharactersListResource</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_lists</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">next_state</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">NextState</span><span class="o">&lt;</span><span class="n">GameState</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_list_res</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">if</span> <span class="n">characters_lists</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">res</span><span class="py">.handle</span><span class="p">)</span><span class="nf">.is_some</span><span class="p">()</span> <span class="p">{</span>
        <span class="nd">info!</span><span class="p">(</span><span class="s">"Assets loaded, transitioning to Playing!"</span><span class="p">);</span>
        <span class="n">next_state</span><span class="nf">.set</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">toggle_pause</span><span class="p">(</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">current_state</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">State</span><span class="o">&lt;</span><span class="n">GameState</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">next_state</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">NextState</span><span class="o">&lt;</span><span class="n">GameState</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Escape</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">current_state</span><span class="nf">.get</span><span class="p">()</span> <span class="p">{</span>
            <span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nd">info!</span><span class="p">(</span><span class="s">"Game paused"</span><span class="p">);</span>
                <span class="n">next_state</span><span class="nf">.set</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Paused</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="nn">GameState</span><span class="p">::</span><span class="n">Paused</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nd">info!</span><span class="p">(</span><span class="s">"Game resumed"</span><span class="p">);</span>
                <span class="n">next_state</span><span class="nf">.set</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">{}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s happening here?</strong></p>

<p>We start by telling Bevy to track our custom <code class="language-plaintext highlighter-rouge">GameState</code> enum.</p>

<p>When the game starts and enters <code class="language-plaintext highlighter-rouge">Loading</code> state, <code class="language-plaintext highlighter-rouge">OnEnter(GameState::Loading)</code> runs <code class="language-plaintext highlighter-rouge">spawn_loading_screen</code> once, showing the loading UI.</p>

<p>While in Loading state, <code class="language-plaintext highlighter-rouge">Update.run_if(in_state(GameState::Loading))</code> runs two systems one to check if assets are loaded and another to animate the loading text.</p>

<p>Once assets load, <code class="language-plaintext highlighter-rouge">check_assets_loaded</code> requests a transition to <code class="language-plaintext highlighter-rouge">Playing</code> state. When this happens, <code class="language-plaintext highlighter-rouge">OnExit(GameState::Loading)</code> triggers, running two systems that cleans up the loading UI and initialize the player. Now player initialization can happen only once since exiting from loading state is a one-time event.</p>

<p>For pausing, we added systems on <code class="language-plaintext highlighter-rouge">OnEnter(GameState::Paused)</code> and <code class="language-plaintext highlighter-rouge">OnExit(GameState::Paused)</code> to show and hide the pause menu. The <code class="language-plaintext highlighter-rouge">toggle_pause</code> function listens for the Escape key and switches between <code class="language-plaintext highlighter-rouge">Playing</code> and <code class="language-plaintext highlighter-rouge">Paused</code> states.</p>

<p>Our <code class="language-plaintext highlighter-rouge">StatePlugin</code> now orchestrates the entire game flow. The <code class="language-plaintext highlighter-rouge">Loading</code> state handles asset loading with visual feedback, and the <code class="language-plaintext highlighter-rouge">Playing</code> state will run gameplay systems. Later in this chapter, we’ll gate our gameplay systems to only run in the <code class="language-plaintext highlighter-rouge">Playing</code> state, which will freeze the game when paused. The beauty of this design is that systems automatically attach to state transitions, no polling, no wasted frames. Everything runs exactly when needed.</p>

<p>Now open <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code> and remove <code class="language-plaintext highlighter-rouge">initialize_player_character</code> from the Update schedule. Since we have already added it through <code class="language-plaintext highlighter-rouge">StatePlugin</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>
<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">CharactersPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">RonAssetPlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="s">"characters.ron"</span><span class="p">]))</span>
            <span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">spawn</span><span class="p">::</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="nn">spawn</span><span class="p">::</span><span class="n">spawn_player</span><span class="p">)</span>
            <span class="c1">// REMOVE initialize_player_character from here!</span>
            <span class="c1">// It now runs in StatePlugin's OnExit(Loading)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
                <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
                <span class="nn">movement</span><span class="p">::</span><span class="n">move_player</span><span class="p">,</span>
                <span class="nn">movement</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
                <span class="nn">animation</span><span class="p">::</span><span class="n">animate_characters</span><span class="p">,</span>
                <span class="nn">animation</span><span class="p">::</span><span class="n">update_animation_flags</span><span class="p">,</span>
            <span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="integrating-stateplugin">Integrating StatePlugin</h3>

<p>Add the state module and plugin to <code class="language-plaintext highlighter-rouge">src/main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs</span>
<span class="k">mod</span> <span class="n">characters</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">map</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">state</span><span class="p">;</span>  <span class="c1">// Add this</span>
</code></pre></div></div>

<p><strong>Important</strong>: Add <code class="language-plaintext highlighter-rouge">StatePlugin</code> BEFORE <code class="language-plaintext highlighter-rouge">CharactersPlugin</code> so the state system is initialized before character systems try to use it.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Add state plugin inside main function of src/main.rs</span>
        <span class="c1">// Previous code as it is</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">ProcGenSimplePlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="p">,</span> <span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">state</span><span class="p">::</span><span class="n">StatePlugin</span><span class="p">)</span>  <span class="c1">// Add BEFORE CharactersPlugin!</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">characters</span><span class="p">::</span><span class="n">CharactersPlugin</span><span class="p">)</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">setup_camera</span><span class="p">)</span>
        <span class="nf">.run</span><span class="p">();</span>
</code></pre></div></div>

<p>Run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>You might not see the loading screen (assets load quickly, but you can manually add a delay if needed). The game starts and your character appears, ready to move. Press Escape to toggle the pause overlay. Note that at this point, the game will continue running in the background when paused, we’ll fix that later in the chapter by gating our gameplay systems to only run in the <code class="language-plaintext highlighter-rouge">Playing</code> state.</p>

<h2 id="the-state-pattern-for-characters">The State Pattern for Characters</h2>
<p>We just used states to control our <em>game flow</em> (<code class="language-plaintext highlighter-rouge">Loading</code> → <code class="language-plaintext highlighter-rouge">Playing</code> → <code class="language-plaintext highlighter-rouge">Paused</code>). Now let’s apply the same pattern to something else: <em>character behavior</em>.</p>

<p>Have a look at out <code class="language-plaintext highlighter-rouge">AnimationState</code> component:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationState</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">is_moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">was_moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">is_jumping</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">was_jumping</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Four booleans tracking two pieces of information: what the character is doing <em>now</em> and what they were doing <em>last frame</em>. We needed <code class="language-plaintext highlighter-rouge">was_moving</code> and <code class="language-plaintext highlighter-rouge">was_jumping</code> to detect transitions like “just started jumping” or “just stopped moving”.</p>

<p>This works to help us with animation, but it has problems.</p>

<h3 id="too-many-boolean-flags">Too Many Boolean Flags</h3>

<p>What if we add running? We’d need:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">pub</span> <span class="n">is_running</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="k">pub</span> <span class="n">was_running</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</code></pre></div></div>

<p>Attacking?</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">pub</span> <span class="n">is_attacking</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="k">pub</span> <span class="n">was_attacking</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</code></pre></div></div>

<p>Soon our component is drowning in booleans, and our animation system is drowning in transition logic:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="n">just_started_moving</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_moving</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">state</span><span class="py">.was_moving</span><span class="p">;</span>
<span class="k">let</span> <span class="n">just_stopped_moving</span> <span class="o">=</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_moving</span> <span class="o">&amp;&amp;</span> <span class="n">state</span><span class="py">.was_moving</span><span class="p">;</span>
<span class="k">let</span> <span class="n">just_started_jumping</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_jumping</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">state</span><span class="py">.was_jumping</span><span class="p">;</span>
<span class="k">let</span> <span class="n">just_stopped_jumping</span> <span class="o">=</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_jumping</span> <span class="o">&amp;&amp;</span> <span class="n">state</span><span class="py">.was_jumping</span><span class="p">;</span>
<span class="k">let</span> <span class="n">just_started_running</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_running</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">state</span><span class="py">.was_running</span><span class="p">;</span>
<span class="c1">// ... it keeps growing</span>
</code></pre></div></div>

<p>Worse, what happens if <code class="language-plaintext highlighter-rouge">is_moving</code> and <code class="language-plaintext highlighter-rouge">is_jumping</code> are both true? Or <code class="language-plaintext highlighter-rouge">is_running</code> and <code class="language-plaintext highlighter-rouge">is_attacking</code>? Boolean flags don’t prevent impossible states.</p>

<p>A developer might accidentally set both flags, or forget to clear one when setting another. Your animation system then has to decide: which flag wins? You end up writing priority logic, and bugs creep in when the priorities aren’t consistent across systems.</p>

<h3 id="the-state-pattern-solution">The State Pattern Solution</h3>

<p>Remember how <code class="language-plaintext highlighter-rouge">GameState</code> worked? We defined an enum with <code class="language-plaintext highlighter-rouge">Loading</code>, <code class="language-plaintext highlighter-rouge">Playing</code>, and <code class="language-plaintext highlighter-rouge">Paused</code>, and Bevy tracked which state we were in. We can apply the same idea to characters: define an enum of possible states, and let the current state determine behavior.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">CharacterState</span> <span class="p">{</span>
    <span class="nd">#[default]</span>
    <span class="n">Idle</span><span class="p">,</span>
    <span class="n">Walking</span><span class="p">,</span>
    <span class="n">Running</span><span class="p">,</span>
    <span class="n">Jumping</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A character can only be in <em>one</em> state at a time. No more impossible combinations. No more boolean math.</p>

<h3 id="why-this-is-better">Why This Is Better</h3>

<p><strong>1. Impossible states become impossible:</strong></p>

<p>With an enum, the compiler enforces that the character is in exactly one state:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="c1">// With booleans: you can do this (but shouldn't!)</span>
<span class="n">is_walking</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">is_jumping</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>  <span class="c1">// Now both are true - invalid!</span>

<span class="c1">// With enum: you can't have both true</span>
<span class="k">let</span> <span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span><span class="p">;</span>
<span class="c1">// state is Walking. To jump, you must replace it:</span>
<span class="k">let</span> <span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span><span class="p">;</span>  <span class="c1">// Now it's only Jumping</span>
</code></pre></div></div>

<p>The variable holds one value. You can’t be Walking and Jumping simultaneously. This approach is called <em>making illegal states unrepresentable</em>, a key principle in type-driven development. Instead of writing code to check for invalid combinations, you design your types so invalid combinations can’t exist.</p>

<p><strong>2. Bevy detects changes for us:</strong></p>

<p>Remember manually tracking <code class="language-plaintext highlighter-rouge">was_moving</code> and <code class="language-plaintext highlighter-rouge">was_jumping</code>? That was change detection done by hand. Bevy has this built in. When you use <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code>, Bevy only runs your code when the state actually changes.</p>

<p>Your animation update system only runs when the character transitions between states. Your sound effect system only runs when entering a new state. Less code, fewer bugs, and we’ll use this later in the chapter.</p>

<p><strong>3. Animation selection becomes a simple match:</strong></p>

<p>With an enum, picking the right animation is straightforward. You match on the current state, and each state maps to exactly one animation. There’s no ambiguity, no priority logic, no “what if both flags are true?” dilemma.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, don't use</span>
<span class="k">let</span> <span class="n">new_animation</span> <span class="o">=</span> <span class="k">match</span> <span class="n">state</span> <span class="p">{</span>
    <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">|</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="k">=&gt;</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span><span class="p">,</span>
    <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Running</span> <span class="k">=&gt;</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Run</span><span class="p">,</span>
    <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span> <span class="k">=&gt;</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Jump</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>

<p>The compiler warns you if you forget to handle a state. If you later add a new state to the enum, every match statement becomes a compile error until you handle the new case. The compiler forces you to think through all possibilities.</p>

<p>Now let’s put this into practice and implement <code class="language-plaintext highlighter-rouge">CharacterState</code>.</p>

<h2 id="implementing-character-states">Implementing Character States</h2>

<p>Create a new file <code class="language-plaintext highlighter-rouge">src/characters/state.rs</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>characters/
├── config.rs
├── animation.rs
├── movement.rs
├── mod.rs
├── spawn.rs
├── state.rs  ← Create this
</code></pre></div></div>

<h3 id="the-characterstate-enum">The CharacterState Enum</h3>

<p>We need an enum that represents all possible states our character can be in:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/state.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Character states. Only one can be active at a time.</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">CharacterState</span> <span class="p">{</span>
    <span class="nd">#[default]</span>
    <span class="n">Idle</span><span class="p">,</span>
    <span class="n">Walking</span><span class="p">,</span>
    <span class="n">Running</span><span class="p">,</span>
    <span class="n">Jumping</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="querying-state-with-methods">Querying State with Methods</h3>

<p>Earlier, we explained how boolean flags can create invalid combinations. Instead of tracking <code class="language-plaintext highlighter-rouge">is_jumping</code> and <code class="language-plaintext highlighter-rouge">was_jumping</code> flags separately, we now have a single <code class="language-plaintext highlighter-rouge">CharacterState</code>. But we still need to answer questions like “can this character jump right now?”</p>

<p>That’s where these query methods come in. They let us ask questions about the current state without maintaining separate flag variables:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/state.rs</span>
<span class="k">impl</span> <span class="n">CharacterState</span> <span class="p">{</span>
    <span class="cd">/// Check if this is a grounded state (can jump from here)</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_grounded</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="nd">matches!</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">|</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="p">|</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Running</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This method replaces boolean flag logic for jump control. Instead of tracking an <code class="language-plaintext highlighter-rouge">is_jumping</code> flag and checking it manually, we query the state. The logic is simple: you can only jump when grounded (Idle, Walking, or Running). You can’t jump while already in the Jumping state.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">matches!</code>?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">matches!</code> macro checks if a value matches a pattern. <code class="language-plaintext highlighter-rouge">matches!(self, CharacterState::Idle)</code> returns <code class="language-plaintext highlighter-rouge">true</code> if <code class="language-plaintext highlighter-rouge">self</code> is <code class="language-plaintext highlighter-rouge">Idle</code>, <code class="language-plaintext highlighter-rouge">false</code> otherwise. The <code class="language-plaintext highlighter-rouge">|</code> means “or” so <code class="language-plaintext highlighter-rouge">matches!(self, CharacterState::Walking | CharacterState::Running)</code> checks if it’s either Walking or Running.</p>

<h2 id="animation-refactoring">Animation Refactoring</h2>

<p>Now we’ll refactor <code class="language-plaintext highlighter-rouge">animation.rs</code> to use our new state-based approach. While we’re at it, let’s also clean up a code organization issue: currently, <code class="language-plaintext highlighter-rouge">AnimationController</code> stores both the current animation type and the facing direction. But these are owned by different systems, facing decides direction, animation decides the clip. Separating them makes each system’s responsibility clearer.</p>

<p>First, create a new file <code class="language-plaintext highlighter-rouge">src/characters/facing.rs</code>. By making <code class="language-plaintext highlighter-rouge">Facing</code> its own component, the movement system owns direction updates, and the animation system focuses on sprite animation.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/facing.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// The direction a character is facing.</span>
<span class="cd">/// Separate from movement - character can face one way while moving another.</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">Facing</span> <span class="p">{</span>
    <span class="n">Up</span><span class="p">,</span>
    <span class="nb">Left</span><span class="p">,</span>
    <span class="nd">#[default]</span>
    <span class="n">Down</span><span class="p">,</span>
    <span class="nb">Right</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Facing</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">from_velocity</span><span class="p">(</span><span class="n">velocity</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">velocity</span><span class="py">.x</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">velocity</span><span class="py">.y</span><span class="nf">.abs</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">velocity</span><span class="py">.x</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">velocity</span><span class="py">.y</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="cd">/// Helper to map direction to row offset (0, 1, 2, 3)</span>
    <span class="k">pub</span><span class="p">(</span><span class="k">crate</span><span class="p">)</span> <span class="k">fn</span> <span class="nf">direction_index</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="k">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="k">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="k">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="k">=&gt;</span> <span class="mi">3</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Expose both <code class="language-plaintext highlighter-rouge">state</code> and <code class="language-plaintext highlighter-rouge">facing</code> in <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">config</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">animation</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">movement</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">state</span><span class="p">;</span> <span class="c1">// Add this line</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">facing</span><span class="p">;</span>  <span class="c1">// Add this line</span>
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">src/characters/animation.rs</code>. We’ll update it section by section.</p>

<p>First, delete the old <code class="language-plaintext highlighter-rouge">Facing</code> enum and <code class="language-plaintext highlighter-rouge">AnimationState</code> struct. We’ll be using <code class="language-plaintext highlighter-rouge">CharacterState</code> instead.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - Delete the following sections </span>

<span class="c1">// DELETE this (Facing moved to facing.rs)</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">Facing</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="k">impl</span> <span class="n">Facing</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>

<span class="c1">// DELETE this (AnimationState replaced by CharacterState)</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationState</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">is_moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Remove <code class="language-plaintext highlighter-rouge">facing</code> from <code class="language-plaintext highlighter-rouge">AnimationController</code> since it’s now a separate component. Also derive <code class="language-plaintext highlighter-rouge">Default</code> macro for <code class="language-plaintext highlighter-rouge">AnimationController</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - Update AnimationController</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Default)]</span> <span class="c1">// Line update alert</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationController</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">current_animation</span><span class="p">:</span> <span class="n">AnimationType</span><span class="p">,</span>
    <span class="c1">// Facing is removed now, line update alert</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You’ll also need to delete the old manual <code class="language-plaintext highlighter-rouge">Default</code> implementation for <code class="language-plaintext highlighter-rouge">AnimationController</code>. Previously, it looked like this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// DELETE the following old implementation from src/characters/animation.rs</span>
<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">AnimationController</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">current_animation</span><span class="p">:</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span><span class="p">,</span>
            <span class="n">facing</span><span class="p">:</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span><span class="p">,</span> 
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Delete it entirely. Since <code class="language-plaintext highlighter-rouge">AnimationController</code> now only has one field (<code class="language-plaintext highlighter-rouge">current_animation</code>), we need <code class="language-plaintext highlighter-rouge">AnimationType</code> to have a default value. Add <code class="language-plaintext highlighter-rouge">#[derive(Default)]</code> to <code class="language-plaintext highlighter-rouge">AnimationType</code> and mark <code class="language-plaintext highlighter-rouge">Walk</code> as the default:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/config.rs - Set default animation type to Walk</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Hash,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize,</span> <span class="nd">Default)]</span> <span class="c1">// Line update alert</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">AnimationType</span> <span class="p">{</span>
    <span class="nd">#[default]</span> <span class="c1">// Line update alert</span>
    <span class="n">Walk</span><span class="p">,</span>
    <span class="n">Run</span><span class="p">,</span>
    <span class="n">Jump</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now update the imports at the top of the file. Let’s add imports for <code class="language-plaintext highlighter-rouge">CharacterState</code> and <code class="language-plaintext highlighter-rouge">Facing</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - Update imports</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">config</span><span class="p">::{</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">;</span> <span class="c1">// Line update alert</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">facing</span><span class="p">::</span><span class="n">Facing</span><span class="p">;</span> <span class="c1">// Line update alert</span>
</code></pre></div></div>

<p>Since we moved <code class="language-plaintext highlighter-rouge">Facing</code> out of <code class="language-plaintext highlighter-rouge">AnimationController</code>, <code class="language-plaintext highlighter-rouge">get_clip</code> can no longer access <code class="language-plaintext highlighter-rouge">self.facing</code>. We need to pass facing as a parameter. This is actually cleaner: the method now explicitly declares what data it needs:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - Update get_clip signature</span>
<span class="k">impl</span> <span class="n">AnimationController</span> <span class="p">{</span>
    <span class="cd">/// Get the animation clip for the current animation and facing direction.</span>
    <span class="cd">/// `facing` is passed in since it's now a separate component.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">get_clip</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">config</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">facing</span><span class="p">:</span> <span class="n">Facing</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">AnimationClip</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">def</span> <span class="o">=</span> <span class="n">config</span><span class="py">.animations</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.current_animation</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        
        <span class="k">let</span> <span class="n">row</span> <span class="o">=</span> <span class="k">if</span> <span class="n">def</span><span class="py">.directional</span> <span class="p">{</span>
            <span class="n">def</span><span class="py">.start_row</span> <span class="o">+</span> <span class="n">facing</span><span class="nf">.direction_index</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">def</span><span class="py">.start_row</span>
        <span class="p">};</span>
        
        <span class="nf">Some</span><span class="p">(</span><span class="nn">AnimationClip</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">def</span><span class="py">.frame_count</span><span class="p">,</span> <span class="n">config</span><span class="py">.atlas_columns</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Delete update_animation_flags</strong></p>

<p>We no longer need this function since we will be using <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code> instead of manual tracking.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - DELETE this entire function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_animation_flags</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
</code></pre></div></div>

<h3 id="replace-animation-system">Replace Animation System</h3>

<p>Here’s where the state pattern really pays off. The old <code class="language-plaintext highlighter-rouge">animate_characters</code> function manually tracked state changes with boolean flags. With <code class="language-plaintext highlighter-rouge">CharacterState</code>, Bevy’s <code class="language-plaintext highlighter-rouge">Changed</code> filter does this for us automatically.</p>

<p>Delete <code class="language-plaintext highlighter-rouge">animate_characters</code> entirely.</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - DELETE this entire function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">animate_characters</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
</code></pre></div></div>

<p>We’ll write one system that responds to state changes (using <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code>), and another that does animation playback. This separation means state-change logic only runs when needed, not every frame.</p>

<p><strong>System 1: Handle Character State Changes</strong></p>

<p>This system runs only when <code class="language-plaintext highlighter-rouge">CharacterState</code> changes, using Bevy’s <code class="language-plaintext highlighter-rouge">Changed</code> filter. When triggered, it updates the animation type so the playback system knows which animation to play.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - Add this new function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">on_state_change_update_animation</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span>
        <span class="p">(</span><span class="o">&amp;</span><span class="n">CharacterState</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationController</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationTimer</span><span class="p">),</span>
        <span class="n">Changed</span><span class="o">&lt;</span><span class="n">CharacterState</span><span class="o">&gt;</span>
    <span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="k">mut</span> <span class="n">controller</span><span class="p">,</span> <span class="k">mut</span> <span class="n">timer</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Select animation based on new state</span>
        <span class="k">let</span> <span class="n">new_animation</span> <span class="o">=</span> <span class="k">match</span> <span class="n">state</span> <span class="p">{</span>
            <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">|</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="k">=&gt;</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span><span class="p">,</span>
            <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Running</span> <span class="k">=&gt;</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Run</span><span class="p">,</span>
            <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span> <span class="k">=&gt;</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Jump</span><span class="p">,</span>
        <span class="p">};</span>
        
        <span class="c1">// Only update and reset timer if animation actually changed</span>
        <span class="k">if</span> <span class="n">controller</span><span class="py">.current_animation</span> <span class="o">!=</span> <span class="n">new_animation</span> <span class="p">{</span>
            <span class="n">controller</span><span class="py">.current_animation</span> <span class="o">=</span> <span class="n">new_animation</span><span class="p">;</span>
            <span class="n">timer</span><span class="na">.0</span><span class="nf">.reset</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code>?</strong></p>

<p>This is the change detection we discussed earlier! The query only returns entities whose <code class="language-plaintext highlighter-rouge">CharacterState</code> changed since last frame. No manual tracking needed.</p>

<p><strong>Why check <code class="language-plaintext highlighter-rouge">controller.current_animation != new_animation</code> if <code class="language-plaintext highlighter-rouge">Changed</code> already filters?</strong></p>

<p>Because multiple states can map to the same animation. Look at the match: both <code class="language-plaintext highlighter-rouge">Idle</code> and <code class="language-plaintext highlighter-rouge">Walking</code> use <code class="language-plaintext highlighter-rouge">AnimationType::Walk</code>. If the player transitions from <code class="language-plaintext highlighter-rouge">Idle</code> to <code class="language-plaintext highlighter-rouge">Walking</code>, <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code> fires (state changed), but the animation type stays <code class="language-plaintext highlighter-rouge">Walk</code>. Without this guard, we’d reset the timer and cause a visual stutter even though we’re playing the same animation.</p>

<p><strong>System 2: Animation Playback</strong></p>

<p>While the first system picks <em>which</em> animation to play when state changes, this one handles the frame-by-frame playback. Together they form a complete animation pipeline: state changes set up the animation, and this system keeps it running.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs - Add this new function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">animations_playback</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">CharacterState</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">Facing</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">AnimationController</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationTimer</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Sprite</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">facing</span><span class="p">,</span> <span class="n">controller</span><span class="p">,</span> <span class="k">mut</span> <span class="n">timer</span><span class="p">,</span> <span class="k">mut</span> <span class="n">sprite</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Don't animate when idle</span>
        <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">==</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="p">{</span>
            <span class="c1">// Ensure idle sprite is at frame 0</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas</span><span class="p">)</span> <span class="o">=</span> <span class="n">sprite</span><span class="py">.texture_atlas</span><span class="nf">.as_mut</span><span class="p">()</span> <span class="p">{</span>
                <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clip</span><span class="p">)</span> <span class="o">=</span> <span class="n">controller</span><span class="nf">.get_clip</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="o">*</span><span class="n">facing</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="n">atlas</span><span class="py">.index</span> <span class="o">!=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">()</span> <span class="p">{</span>
                        <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">();</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
        
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas</span><span class="p">)</span> <span class="o">=</span> <span class="n">sprite</span><span class="py">.texture_atlas</span><span class="nf">.as_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">};</span>
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clip</span><span class="p">)</span> <span class="o">=</span> <span class="n">controller</span><span class="nf">.get_clip</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="o">*</span><span class="n">facing</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">};</span>
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">anim_def</span><span class="p">)</span> <span class="o">=</span> <span class="n">config</span><span class="py">.animations</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">controller</span><span class="py">.current_animation</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">};</span>
        
        <span class="c1">// Safety: If we somehow ended up on a frame outside our clip, reset.</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">clip</span><span class="nf">.contains</span><span class="p">(</span><span class="n">atlas</span><span class="py">.index</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">();</span>
            <span class="n">timer</span><span class="na">.0</span><span class="nf">.reset</span><span class="p">();</span>
        <span class="p">}</span>
        
        <span class="c1">// Update timer duration if needed</span>
        <span class="k">let</span> <span class="n">expected_duration</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs_f32</span><span class="p">(</span><span class="n">anim_def</span><span class="py">.frame_time</span><span class="p">);</span>
        <span class="k">if</span> <span class="n">timer</span><span class="na">.0</span><span class="nf">.duration</span><span class="p">()</span> <span class="o">!=</span> <span class="n">expected_duration</span> <span class="p">{</span>
            <span class="n">timer</span><span class="na">.0</span><span class="nf">.set_duration</span><span class="p">(</span><span class="n">expected_duration</span><span class="p">);</span>
        <span class="p">}</span>
        
        <span class="c1">// Advance animation</span>
        <span class="n">timer</span><span class="nf">.tick</span><span class="p">(</span><span class="n">time</span><span class="nf">.delta</span><span class="p">());</span>
        <span class="k">if</span> <span class="n">timer</span><span class="nf">.just_finished</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.next</span><span class="p">(</span><span class="n">atlas</span><span class="py">.index</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_df050c6e070ea4096046b395f8b66e59.svg" alt="Comic Panel" class="comic-image" data-comic-hash="df050c6e070ea4096046b395f8b66e59" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h2 id="completing-the-state-based-refactoring">Completing the State-Based Refactoring</h2>

<p>We’ve updated the animation system to use <code class="language-plaintext highlighter-rouge">CharacterState</code> instead of boolean flags. But where does <code class="language-plaintext highlighter-rouge">CharacterState</code> get set? Right now, our <code class="language-plaintext highlighter-rouge">movement.rs</code> still uses the old approach, it directly modifies <code class="language-plaintext highlighter-rouge">Transform</code> and sets boolean flags in <code class="language-plaintext highlighter-rouge">AnimationState</code>. We need to refactor it to work with our new state-based design.</p>

<p>Look at the current <code class="language-plaintext highlighter-rouge">movement.rs</code>. It does three things at once:</p>
<ol>
  <li>Reads input (arrow keys, shift, space)</li>
  <li>Moves the character on screen</li>
  <li>Decides which animation to play using boolean flags</li>
</ol>

<p>This mixing of concerns made sense before, but now that we have <code class="language-plaintext highlighter-rouge">CharacterState</code>, we can separate these responsibilities. We’ll split <code class="language-plaintext highlighter-rouge">movement.rs</code> into:</p>
<ul>
  <li><strong>input.rs</strong> - Reads keyboard input and decides what the character should do</li>
  <li><strong>physics.rs</strong> - Handles moving the character based on velocity</li>
</ul>

<p>This separation means the animation system we just built will work automatically. When input changes <code class="language-plaintext highlighter-rouge">CharacterState</code>, our <code class="language-plaintext highlighter-rouge">on_state_change_update_animation</code> system reacts. When input sets <code class="language-plaintext highlighter-rouge">Velocity</code>, our physics system moves the entity. Each piece focuses on one job.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/characters/physics.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/physics.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::{</span><span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">,</span> <span class="nn">config</span><span class="p">::</span><span class="n">CharacterEntry</span><span class="p">};</span>

<span class="cd">/// Linear velocity in world units per second.</span>
<span class="cd">/// Systems that want to move an entity modify this.</span>
<span class="cd">/// A physics system reads this to update Transform.</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">Default,</span> <span class="nd">Deref,</span> <span class="nd">DerefMut)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="nf">Velocity</span><span class="p">(</span><span class="k">pub</span> <span class="n">Vec2</span><span class="p">);</span>

<span class="k">impl</span> <span class="n">Velocity</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">ZERO</span><span class="p">:</span> <span class="k">Self</span> <span class="o">=</span> <span class="k">Self</span><span class="p">(</span><span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span><span class="p">);</span>
    
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_moving</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">self</span><span class="na">.0</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now add the velocity calculation based on state:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/physics.rs</span>

<span class="cd">/// Calculate velocity based on character state, direction, and configuration.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">calculate_velocity</span><span class="p">(</span>
    <span class="n">state</span><span class="p">:</span> <span class="n">CharacterState</span><span class="p">,</span>
    <span class="n">direction</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span>
    <span class="n">character</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Velocity</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">state</span> <span class="p">{</span>
        <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span> <span class="k">=&gt;</span> <span class="nn">Velocity</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
        <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span> <span class="k">=&gt;</span> <span class="nn">Velocity</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>  <span class="c1">// No movement during jump</span>
        <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="nf">Velocity</span><span class="p">(</span><span class="n">direction</span><span class="nf">.normalize_or_zero</span><span class="p">()</span> <span class="o">*</span> <span class="n">character</span><span class="py">.base_move_speed</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Running</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="nf">Velocity</span><span class="p">(</span><span class="n">direction</span><span class="nf">.normalize_or_zero</span><span class="p">()</span> <span class="o">*</span> <span class="n">character</span><span class="py">.base_move_speed</span> <span class="o">*</span> <span class="n">character</span><span class="py">.run_speed_multiplier</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice how <code class="language-plaintext highlighter-rouge">CharacterState</code> directly determines velocity. No boolean flags, no conditionals about <code class="language-plaintext highlighter-rouge">is_jumping &amp;&amp; !is_moving</code>. The state tells us everything we need to know.</p>

<p>Finally, add the system that actually moves the character. It reads the velocity and updates the character’s position on screen:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/physics.rs</span>

<span class="cd">/// Applies velocity to transform. Pure physics, no game logic.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">apply_velocity</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Velocity</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">velocity</span><span class="p">,</span> <span class="k">mut</span> <span class="n">transform</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">velocity</span><span class="nf">.is_moving</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">transform</span><span class="py">.translation</span> <span class="o">+=</span> <span class="n">velocity</span><span class="na">.0</span><span class="nf">.extend</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This system knows nothing about input, characters, or states. It just moves things based on their velocity. Add any entity with <code class="language-plaintext highlighter-rouge">Velocity</code> and <code class="language-plaintext highlighter-rouge">Transform</code>, and it moves automatically.</p>

<h3 id="refactoring-player-input">Refactoring Player Input</h3>

<p>Now for the second half of splitting <code class="language-plaintext highlighter-rouge">movement.rs</code>. We have physics handling the “how to move” part. Now we need input handling for the “what the player wants to do” part.</p>

<p>This is where we connect everything together. The input system will:</p>
<ol>
  <li>Read keyboard input</li>
  <li>Update <code class="language-plaintext highlighter-rouge">CharacterState</code> (which triggers our animation system via <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code>)</li>
  <li>Set <code class="language-plaintext highlighter-rouge">Velocity</code> (which our physics system uses to move the entity)</li>
</ol>

<p>Create <code class="language-plaintext highlighter-rouge">src/characters/input.rs</code>. This replaces the input-handling parts of <code class="language-plaintext highlighter-rouge">movement.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/input.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::{</span>
    <span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">,</span>
    <span class="nn">physics</span><span class="p">::</span><span class="n">Velocity</span><span class="p">,</span>
    <span class="nn">facing</span><span class="p">::</span><span class="n">Facing</span><span class="p">,</span>
    <span class="nn">config</span><span class="p">::</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="nn">animation</span><span class="p">::{</span><span class="n">AnimationController</span><span class="p">,</span> <span class="n">AnimationTimer</span><span class="p">},</span>
<span class="p">};</span>

<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Player</span><span class="p">;</span>
</code></pre></div></div>

<p>We moved the <code class="language-plaintext highlighter-rouge">Player</code> marker component here since input handling is player-specific.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/input.rs</span>

<span class="cd">/// Read directional input and return a direction vector</span>
<span class="k">fn</span> <span class="nf">read_movement_input</span><span class="p">(</span><span class="n">input</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">MOVEMENT_KEYS</span><span class="p">:</span> <span class="p">[(</span><span class="n">KeyCode</span><span class="p">,</span> <span class="n">Vec2</span><span class="p">);</span> <span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowLeft</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">NEG_X</span><span class="p">),</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowRight</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">X</span><span class="p">),</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowUp</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">Y</span><span class="p">),</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowDown</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">NEG_Y</span><span class="p">),</span>
    <span class="p">];</span>
    
    <span class="n">MOVEMENT_KEYS</span><span class="nf">.iter</span><span class="p">()</span>
        <span class="nf">.filter</span><span class="p">(|(</span><span class="n">key</span><span class="p">,</span> <span class="n">_</span><span class="p">)|</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="o">*</span><span class="n">key</span><span class="p">))</span>
        <span class="nf">.map</span><span class="p">(|(</span><span class="n">_</span><span class="p">,</span> <span class="n">dir</span><span class="p">)|</span> <span class="o">*</span><span class="n">dir</span><span class="p">)</span>
        <span class="nf">.sum</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is the same input reading we had before, just isolated into its own function.</p>

<h3 id="state-machine-logic">State Machine Logic</h3>

<p>Now we need to decide what state the character should be in based on that input. Remember earlier we said “the state tells us everything we need to know”? This is where we translate raw input into meaningful state transitions.</p>

<p>Instead of scattered <code class="language-plaintext highlighter-rouge">if</code> statements that set boolean flags (<code class="language-plaintext highlighter-rouge">is_moving = true</code>, <code class="language-plaintext highlighter-rouge">is_jumping = true</code>), we have one function that returns the new state:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/input.rs</span>

<span class="k">fn</span> <span class="nf">determine_new_state</span><span class="p">(</span>
    <span class="n">current</span><span class="p">:</span> <span class="n">CharacterState</span><span class="p">,</span>
    <span class="n">direction</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span>
    <span class="n">is_running</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="n">wants_jump</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">CharacterState</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">current</span> <span class="p">{</span>
        <span class="c1">// Can't transition out of jumping until it completes</span>
        <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span> <span class="k">=&gt;</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span><span class="p">,</span>
        
        <span class="c1">// Jump takes priority when grounded</span>
        <span class="n">_</span> <span class="k">if</span> <span class="n">wants_jump</span> <span class="o">&amp;&amp;</span> <span class="n">current</span><span class="nf">.is_grounded</span><span class="p">()</span> <span class="k">=&gt;</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span><span class="p">,</span>
        
        <span class="c1">// Movement states</span>
        <span class="n">_</span> <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">is_running</span> <span class="p">{</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Running</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Walking</span> <span class="p">}</span>
        <span class="p">}</span>
        
        <span class="c1">// Default to idle</span>
        <span class="n">_</span> <span class="k">=&gt;</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s this strange pattern of having <code class="language-plaintext highlighter-rouge">if</code> conditions inside <code class="language-plaintext highlighter-rouge">match</code>?</strong></p>

<p>This is called a <em>match guard</em>. The syntax <code class="language-plaintext highlighter-rouge">_ if condition =&gt;</code> means “match anything, but only if this condition is also true.” It combines pattern matching with boolean logic.</p>

<p><strong>Why use it here?</strong></p>

<p>We need to check two things: what state we’re currently in, and what the player is doing (moving, jumping, etc.). Match guards let us handle both in one clean expression. The <code class="language-plaintext highlighter-rouge">_</code> means “any state not already matched above,” and the <code class="language-plaintext highlighter-rouge">if</code> adds the extra condition.</p>

<p>You can use this pattern when you need to match on one thing but also check something else that isn’t part of the enum itself.</p>

<p>Alright, the new approach puts all state transition logic in one function. You can read it top to bottom and understand the priority.</p>

<h3 id="the-main-input-handler">The Main Input Handler</h3>

<p>We’ve built the helper functions: <code class="language-plaintext highlighter-rouge">read_movement_input</code> reads keys, <code class="language-plaintext highlighter-rouge">determine_new_state</code> decides the state. Now we need the main system that ties them together and actually updates the entity’s components.</p>

<p>This is the new version of the old <code class="language-plaintext highlighter-rouge">move_player</code> function from <code class="language-plaintext highlighter-rouge">movement.rs</code>. Instead of directly modifying <code class="language-plaintext highlighter-rouge">Transform</code> and <code class="language-plaintext highlighter-rouge">AnimationController</code>, it now updates <code class="language-plaintext highlighter-rouge">CharacterState</code>, <code class="language-plaintext highlighter-rouge">Velocity</code>, and <code class="language-plaintext highlighter-rouge">Facing</code>. Other systems react to these changes: the animation system responds to <code class="language-plaintext highlighter-rouge">Changed&lt;CharacterState&gt;</code>, and the physics system reads <code class="language-plaintext highlighter-rouge">Velocity</code> to move the entity.</p>

<p>The function works in four steps:</p>
<ol>
  <li><strong>Read input</strong> - Check which keys are pressed (arrows for direction, shift for running, space for jump)</li>
  <li><strong>Update facing</strong> - If moving, update the facing direction so the character looks where they’re going</li>
  <li><strong>Determine new state</strong> - Use the state machine to figure out the next state based on current state and input</li>
  <li><strong>Calculate velocity</strong> - Based on the new state, calculate how fast and which direction to move</li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/input.rs</span>

<span class="cd">/// Reads player input and updates movement-related components.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">handle_player_input</span><span class="p">(</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">CharacterState</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Velocity</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Facing</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="k">mut</span> <span class="n">state</span><span class="p">,</span> <span class="k">mut</span> <span class="n">velocity</span><span class="p">,</span> <span class="k">mut</span> <span class="n">facing</span><span class="p">,</span> <span class="n">character</span><span class="p">))</span> <span class="o">=</span> <span class="n">query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="c1">// Step 1: Read what keys are pressed</span>
    <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="nf">read_movement_input</span><span class="p">(</span><span class="o">&amp;</span><span class="n">input</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">is_running</span> <span class="o">=</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ShiftLeft</span><span class="p">)</span> <span class="p">||</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ShiftRight</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">wants_jump</span> <span class="o">=</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Space</span><span class="p">);</span>
    
    <span class="c1">// Step 2: Update facing direction (which way the character looks)</span>
    <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">new_facing</span> <span class="o">=</span> <span class="nn">Facing</span><span class="p">::</span><span class="nf">from_velocity</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>
        <span class="k">if</span> <span class="o">*</span><span class="n">facing</span> <span class="o">!=</span> <span class="n">new_facing</span> <span class="p">{</span>
            <span class="o">*</span><span class="n">facing</span> <span class="o">=</span> <span class="n">new_facing</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="c1">// Step 3: Use our state machine to determine the new state</span>
    <span class="c1">// This calls the determine_new_state function we wrote earlier</span>
    <span class="k">let</span> <span class="n">new_state</span> <span class="o">=</span> <span class="nf">determine_new_state</span><span class="p">(</span><span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="n">direction</span><span class="p">,</span> <span class="n">is_running</span><span class="p">,</span> <span class="n">wants_jump</span><span class="p">);</span>
    <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">!=</span> <span class="n">new_state</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">state</span> <span class="o">=</span> <span class="n">new_state</span><span class="p">;</span>  <span class="c1">// This triggers Changed&lt;CharacterState&gt;!</span>
    <span class="p">}</span>
    
    <span class="c1">// Step 4: Calculate velocity based on state</span>
    <span class="c1">// Idle and Jumping = no movement, Walking/Running = movement</span>
    <span class="o">*</span><span class="n">velocity</span> <span class="o">=</span> <span class="k">super</span><span class="p">::</span><span class="nn">physics</span><span class="p">::</span><span class="nf">calculate_velocity</span><span class="p">(</span><span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="n">direction</span><span class="p">,</span> <span class="n">character</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="handling-jump-completion">Handling Jump Completion</h3>

<p>There’s one edge case our main input handler doesn’t cover. Look at <code class="language-plaintext highlighter-rouge">determine_new_state</code>, when the character is <code class="language-plaintext highlighter-rouge">Jumping</code>, it stays <code class="language-plaintext highlighter-rouge">Jumping</code>. But how does jumping ever end?</p>

<p>Unlike walking or running (which end when you release the key), jumping needs to complete its animation before transitioning back to idle. We need a separate system that watches for this:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/input.rs</span>

<span class="cd">/// Checks if jump animation completed and transitions back to idle</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_jump_state</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">CharacterState</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">Facing</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">AnimationController</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">AnimationTimer</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">Sprite</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="k">mut</span> <span class="n">state</span><span class="p">,</span> <span class="n">facing</span><span class="p">,</span> <span class="n">controller</span><span class="p">,</span> <span class="n">timer</span><span class="p">,</span> <span class="n">sprite</span><span class="p">,</span> <span class="n">config</span><span class="p">))</span> <span class="o">=</span> <span class="n">query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="c1">// Only check if currently jumping</span>
    <span class="k">if</span> <span class="o">*</span><span class="n">state</span> <span class="o">!=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Jumping</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas</span><span class="p">)</span> <span class="o">=</span> <span class="n">sprite</span><span class="py">.texture_atlas</span><span class="nf">.as_ref</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clip</span><span class="p">)</span> <span class="o">=</span> <span class="n">controller</span><span class="nf">.get_clip</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="o">*</span><span class="n">facing</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="c1">// Check if jump animation has completed</span>
    <span class="k">if</span> <span class="n">clip</span><span class="nf">.is_complete</span><span class="p">(</span><span class="n">atlas</span><span class="py">.index</span><span class="p">,</span> <span class="n">timer</span><span class="nf">.just_finished</span><span class="p">())</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">state</span> <span class="o">=</span> <span class="nn">CharacterState</span><span class="p">::</span><span class="n">Idle</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This system only runs meaningful logic when the player is jumping. It uses <code class="language-plaintext highlighter-rouge">clip.is_complete()</code> to check if the animation finished, then transitions to Idle. The state change triggers our <code class="language-plaintext highlighter-rouge">on_state_change_update_animation</code> system, which updates the animation to Walk.</p>

<p>Before we update <code class="language-plaintext highlighter-rouge">mod.rs</code>, we need to update <code class="language-plaintext highlighter-rouge">spawn.rs</code>. We moved <code class="language-plaintext highlighter-rouge">Player</code> to <code class="language-plaintext highlighter-rouge">input.rs</code>, and our new systems expect entities to have <code class="language-plaintext highlighter-rouge">CharacterState</code>, <code class="language-plaintext highlighter-rouge">Velocity</code>, and <code class="language-plaintext highlighter-rouge">Facing</code> components.</p>

<p>Update the imports at the top of <code class="language-plaintext highlighter-rouge">src/characters/spawn.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs - Update imports</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">animation</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">config</span><span class="p">::{</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">CharactersList</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>  <span class="c1">// Changed from movement::Player</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">CharacterState</span><span class="p">;</span>  <span class="c1">// Line update alert</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">physics</span><span class="p">::</span><span class="n">Velocity</span><span class="p">;</span>  <span class="c1">// Line update alert</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">facing</span><span class="p">::</span><span class="n">Facing</span><span class="p">;</span>  <span class="c1">// Line update alert</span>
</code></pre></div></div>

<p>Then in <code class="language-plaintext highlighter-rouge">initialize_player_character</code>, update the components attached to the player entity.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs - Inside initialize_player_character</span>
<span class="c1">// Update commands.entity(entity).insert((...)) function call</span>
<span class="c1">// Remove the line  AnimationState::default(), and add the following lines:</span>
<span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.insert</span><span class="p">((</span>
    <span class="nn">AnimationController</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="nn">CharacterState</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>   <span class="c1">// Line update alert</span>
    <span class="nn">Velocity</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>         <span class="c1">// Line update alert  </span>
    <span class="nn">Facing</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>           <span class="c1">// Line update alert</span>
    <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">)),</span>
    <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">(),</span>
    <span class="n">sprite</span><span class="p">,</span>
<span class="p">));</span>
</code></pre></div></div>

<p>Now the player entity has all the components our refactored systems need: <code class="language-plaintext highlighter-rouge">CharacterState</code> for the animation system to react to, <code class="language-plaintext highlighter-rouge">Velocity</code> for the physics system to read, and <code class="language-plaintext highlighter-rouge">Facing</code> for sprite direction.</p>

<h3 id="wiring-up-the-new-systems">Wiring Up the New Systems</h3>

<p>Update <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code> to include the new modules:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs - Update module declarations</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">animation</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">config</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">facing</span><span class="p">;</span>     <span class="c1">// Line update alert</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">input</span><span class="p">;</span>      <span class="c1">// Line update alert</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">physics</span><span class="p">;</span>    <span class="c1">// Line update alert</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">spawn</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">state</span><span class="p">;</span>      <span class="c1">// Line update alert</span>

<span class="c1">// DELETE this line:</span>
<span class="c1">// pub mod movement;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>
</code></pre></div></div>

<p>Now update the system registration. Remove the old systems and add the new ones:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs - DELETE these old systems from add_systems</span>
<span class="nn">movement</span><span class="p">::</span><span class="n">move_player</span><span class="p">,</span>           <span class="c1">// DELETE</span>
<span class="nn">movement</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>     <span class="c1">// DELETE</span>
<span class="nn">animation</span><span class="p">::</span><span class="n">animate_characters</span><span class="p">,</span>   <span class="c1">// DELETE</span>
<span class="nn">animation</span><span class="p">::</span><span class="n">update_animation_flags</span><span class="p">,</span> <span class="c1">// DELETE</span>
</code></pre></div></div>

<p>Replace them with our new systems. Notice we’re using <code class="language-plaintext highlighter-rouge">.chain()</code> to ensure they run in order, and <code class="language-plaintext highlighter-rouge">.run_if(in_state(GameState::Playing))</code> so they only run during gameplay. <strong>This is where we actually implement the freeze-on-pause behavior</strong>, when the game is in the <code class="language-plaintext highlighter-rouge">Paused</code> state, these systems won’t run, freezing all character movement and animation:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs - Add new systems</span>
<span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">handle_player_input</span><span class="p">,</span>
    <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">on_state_change_update_animation</span><span class="p">,</span>
    <span class="nn">physics</span><span class="p">::</span><span class="n">apply_velocity</span><span class="p">,</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">animations_playback</span><span class="p">,</span>
<span class="p">)</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)));</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">.chain()</code> ensures systems run in order. Input sets state and velocity, animation responds to state changes, physics moves the entity, and animation playback animates the character.</p>

<h2 id="building-a-collision-system">Building a Collision System</h2>

<p>Right now our character can walk anywhere, even through trees and into water. We need a collision system that prevents movement into obstacles.</p>

<p>Our approach is simple, each tile in our world has a <em>type</em> (grass, water, tree, etc.), and each type is either walkable or not. When the player tries to move, we check if the destination is walkable. If not, we block the move.</p>

<h3 id="defining-tile-types">Defining Tile Types</h3>

<p>First, we need to categorize what kinds of tiles exist in our world. Create <code class="language-plaintext highlighter-rouge">src/collision/tile_type.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/tile_type.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="cd">/// Tile types for collision detection.</span>
<span class="cd">/// Each type has different walkability and collision behavior.</span>
<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Hash,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">TileType</span> <span class="p">{</span>
    <span class="c1">// Walkable terrain</span>
    <span class="nd">#[default]</span>
    <span class="n">Empty</span><span class="p">,</span>
    <span class="n">Dirt</span><span class="p">,</span>
    <span class="n">Grass</span><span class="p">,</span>
    <span class="n">YellowGrass</span><span class="p">,</span>
    <span class="n">Shore</span><span class="p">,</span>  <span class="c1">// Water edges (walkable)</span>
    <span class="c1">// Non-walkable obstacles</span>
    <span class="n">Water</span><span class="p">,</span>
    <span class="n">Tree</span><span class="p">,</span>
    <span class="n">Rock</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now add a method to check walkability:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/tile_type.rs</span>
<span class="k">impl</span> <span class="n">TileType</span> <span class="p">{</span>
    <span class="cd">/// Check if this tile type allows movement through it.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_walkable</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="o">!</span><span class="nd">matches!</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span> <span class="p">|</span> <span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span> <span class="p">|</span> <span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="cd">/// Get the collision adjustment for this tile type.</span>
    <span class="cd">/// Positive = push player away, negative = allow corner cutting.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">collision_adjustment</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span> <span class="p">|</span> <span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span> <span class="k">=&gt;</span> <span class="o">-</span><span class="mf">0.2</span><span class="p">,</span>  <span class="c1">// Allow cutting corners</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="mf">0.0</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice how we define walkability: instead of listing everything that <strong>is</strong> walkable, we list what <strong>isn’t</strong>. This means new tile types are walkable by default, a safer choice since forgetting to add a tile would make it passable rather than creating invisible walls.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">collision_adjustment</code>?</strong></p>

<p>Some tiles feel better with adjusted collision. A negative value (like -0.2 for trees and rocks) lets players cut corners more naturally instead of getting stuck on edges. Positive values would push players away from tiles.</p>

<p>We also need a marker component to attach tile type information to entities:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/tile_type.rs</span>

<span class="cd">/// Component to mark entities with their collision type.</span>
<span class="cd">/// Attached to tiles during map generation.</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TileMarker</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">tile_type</span><span class="p">:</span> <span class="n">TileType</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">TileMarker</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">tile_type</span><span class="p">:</span> <span class="n">TileType</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span> <span class="n">tile_type</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_e05b4e3df5a1cfa613acfcc61fea84b6.svg" alt="Comic Panel" class="comic-image" data-comic-hash="e05b4e3df5a1cfa613acfcc61fea84b6" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="the-collision-map">The Collision Map</h3>

<p>Every frame, when the player tries to move, we need to answer “can they move there?” quickly. Checking every tree and water tile in the world each frame would be slow. Instead, we build a lookup table once: a grid where each cell knows if it’s walkable or not.</p>

<p>Here’s what a collision map looks like for a small 4x3 area:</p>

<div style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border-radius: 8px;">
<div style="display: flex; flex-direction: column; align-items: center; font-family: monospace;">
  <div style="margin-bottom: 8px; font-size: 13px; color: #666;">Collision Map (4x3 grid)</div>
  <table style="border-collapse: separate; border-spacing: 3px; font-size: 12px;">
    <tr>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Grass</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #ffcdd2; border-radius: 4px;">Water</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #ffcdd2; border-radius: 4px;">Water</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Grass</td>
    </tr>
    <tr>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Grass</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Grass</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #ffcdd2; border-radius: 4px;">Tree</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Grass</td>
    </tr>
    <tr>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Grass</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Dirt</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #c8e6c9; border-radius: 4px;">Dirt</td>
      <td style="padding: 8px 12px; text-align: center; background-color: #ffcdd2; border-radius: 4px;">Rock</td>
    </tr>
  </table>
  <div style="margin-top: 8px; font-size: 11px;">
    <span style="background-color: #c8e6c9; padding: 2px 6px; border-radius: 3px;">walkable</span>
    <span style="background-color: #ffcdd2; padding: 2px 6px; border-radius: 3px; margin-left: 6px;">blocked</span>
  </div>
</div>
</div>

<p>This is <code class="language-plaintext highlighter-rouge">CollisionMap</code>. When the level loads, we scan all tiles and record their types. During gameplay, checking “is position (x, y) walkable?” is just an array lookup.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/collision/map.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/map.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="n">TileType</span><span class="p">;</span>

<span class="cd">/// Collision map resource that stores walkability information.</span>
<span class="cd">/// Provides efficient spatial queries for movement validation.</span>
<span class="nd">#[derive(Resource)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CollisionMap</span> <span class="p">{</span>
    <span class="cd">/// Flat array of tile types (row-major order)</span>
    <span class="n">tiles</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="cd">/// Grid dimensions</span>
    <span class="n">width</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span>
    <span class="n">height</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span>
    <span class="cd">/// Size of each tile in world units</span>
    <span class="n">tile_size</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="cd">/// World position of grid origin (bottom-left corner)</span>
    <span class="n">origin_x</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="n">origin_y</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We store tiles in a flat <code class="language-plaintext highlighter-rouge">Vec</code> rather than a 2D array for better performance.</p>

<p><strong>Why a flat array?</strong></p>

<p>In memory, we flatten the grid above row by row into a single array:</p>

<div style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border-radius: 8px;">
<div style="display: flex; flex-direction: column; align-items: center; font-family: monospace;">
  <div style="margin-bottom: 8px; font-size: 13px; color: #666;">1D Array (how it's stored)</div>
  <div style="display: flex; gap: 2px; flex-wrap: wrap; justify-content: center;">
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">0</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Grass</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">1</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Dirt</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">2</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Dirt</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">3</span>
      <div style="padding: 6px 8px; background-color: #ffcdd2; border-radius: 3px; font-size: 10px;">Rock</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">4</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Grass</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">5</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Grass</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">6</span>
      <div style="padding: 6px 8px; background-color: #ffcdd2; border-radius: 3px; font-size: 10px;">Tree</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">7</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Grass</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">8</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Grass</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">9</span>
      <div style="padding: 6px 8px; background-color: #ffcdd2; border-radius: 3px; font-size: 10px;">Water</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">10</span>
      <div style="padding: 6px 8px; background-color: #ffcdd2; border-radius: 3px; font-size: 10px;">Water</div>
    </div>
    <div style="display: flex; flex-direction: column; align-items: center;">
      <span style="font-size: 9px; color: #999;">11</span>
      <div style="padding: 6px 8px; background-color: #c8e6c9; border-radius: 3px; font-size: 10px;">Grass</div>
    </div>
  </div>
  <div style="margin-top: 8px; font-size: 11px; color: #666;">Row 0 (indices 0-3) → Row 1 (indices 4-7) → Row 2 (indices 8-11)</div>
</div>
</div>

<p>All 12 tiles sit next to each other in memory. When you access index 6 (Tree), indices 7 and 8 are likely already loaded into fast memory. A 2D array (<code class="language-plaintext highlighter-rouge">Vec&lt;Vec&lt;TileType&gt;&gt;</code>) stores each row separately, which can be slower.</p>

<p><strong>Why store origin?</strong></p>

<p>In Bevy, the default camera places (0, 0) at the center of the screen. If we center a 4x4 tile grid, where does each tile sit?</p>

<p>Our grid uses <strong>tile coordinates</strong>: the bottom-left tile is (0, 0), the one to its right is (1, 0), and so on.</p>

<div style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border-radius: 8px;">
<div style="display: flex; flex-direction: column; align-items: center; font-family: monospace;">
  <div style="margin-bottom: 8px; font-size: 13px; color: #666;">Tile Grid (each tile = 32px)</div>
  <div style="display: flex; align-items: flex-end;">
    <div style="display: flex; flex-direction: column; justify-content: space-around; height: 220px; margin-right: 4px; font-size: 10px; color: #999;">
      <span>32</span>
      <span>0</span>
      <span>-32</span>
      <span>-64</span>
    </div>
    <div>
      <table style="border-collapse: separate; border-spacing: 3px; font-size: 16px;">
        <tr>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">0,3</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">1,3</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">2,3</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">3,3</td>
        </tr>
        <tr>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">0,2</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #c8e6c9; border-radius: 4px; font-weight: bold;">1,2</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #fff3cd; border-radius: 4px; font-weight: bold;">2,2</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">3,2</td>
        </tr>
        <tr>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">0,1</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">1,1</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">2,1</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">3,1</td>
        </tr>
        <tr>
          <td style="padding: 12px 16px; text-align: center; background-color: #ffcdd2; border-radius: 4px; font-weight: bold;">0,0 </td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">1,0</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">2,0</td>
          <td style="padding: 12px 16px; text-align: center; background-color: #e3f2fd; border-radius: 4px;">3,0</td>
        </tr>
      </table>
      <div style="display: flex; justify-content: space-around; margin-top: 4px; font-size: 10px; color: #999;">
        <span>-64</span>
        <span>-32</span>
        <span>0</span>
        <span>32</span>
      </div>
    </div>
  </div>
  <div style="margin-top: 8px; font-size: 10px; color: #999;">← screen X (pixels) / ↑ screen Y (pixels)</div>
  <div style="margin-top: 12px; font-size: 13px;">
    <span style="background-color: #ffcdd2; padding: 2px 8px; border-radius: 4px;"> origin (tile 0,0)</span>
    <span style="background-color: #fff3cd; padding: 2px 8px; border-radius: 4px; margin-left: 8px;"> screen center</span>
    <span style="background-color: #c8e6c9; padding: 2px 8px; border-radius: 4px; margin-left: 8px;"> player</span>
  </div>
</div>
</div>

<p>The grid is 4 tiles wide × 32 pixels each = 128 pixels total. To center it, we shift left by half: 128 ÷ 2 = 64 pixels. So the grid’s bottom-left corner (tile 0,0) sits at screen position <strong>(-64, -64)</strong>. That’s the <strong>origin</strong>.</p>

<p>Screen center (0, 0) lands inside tile (2, 2), not tile (0, 0)!</p>

<p>Now the player stands at screen position (-32, 0). Which tile?</p>

<ul>
  <li><strong>With origin</strong>: <code class="language-plaintext highlighter-rouge">(-32 - (-64)) / 32 = 1</code>, <code class="language-plaintext highlighter-rouge">(0 - (-64)) / 32 = 2</code> → Tile (1, 2) ✓</li>
  <li><strong>Without origin</strong>: <code class="language-plaintext highlighter-rouge">(-32) / 32 = -1</code>, <code class="language-plaintext highlighter-rouge">(0) / 32 = 0</code> → Tile (-1, 0) <strong>(wrong tile!)</strong></li>
</ul>

<p>That’s why our <code class="language-plaintext highlighter-rouge">CollisionMap</code> stores the origin. Let’s implement the struct with methods to handle this conversion automatically.</p>

<p>The constructor creates an empty map filled with <code class="language-plaintext highlighter-rouge">TileType::Empty</code>. We also need two internal helpers:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">xy_to_idx</code> converts 2D coordinates like (3, 7) to a single number for array access, since tiles are stored in a 1D array.</li>
  <li><code class="language-plaintext highlighter-rouge">in_bounds</code> checks if coordinates are inside the grid, this serves double duty: it prevents accessing invalid memory and treats anything outside the map as “blocked” for collision purposes.</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="k">impl</span> <span class="n">CollisionMap</span> <span class="p">{</span>
    <span class="cd">/// Create a new collision map with specified dimensions and origin.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">width</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">tile_size</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">origin_x</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">origin_y</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">size</span> <span class="o">=</span> <span class="p">(</span><span class="n">width</span> <span class="o">*</span> <span class="n">height</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span><span class="p">;</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">tiles</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Empty</span><span class="p">;</span> <span class="n">size</span><span class="p">],</span>
            <span class="n">width</span><span class="p">,</span>
            <span class="n">height</span><span class="p">,</span>
            <span class="n">tile_size</span><span class="p">,</span>
            <span class="n">origin_x</span><span class="p">,</span>
            <span class="n">origin_y</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="cd">/// Convert 2D grid coordinates to 1D array index.</span>
    <span class="nd">#[inline]</span>
    <span class="k">fn</span> <span class="nf">xy_to_idx</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
        <span class="p">(</span><span class="n">y</span> <span class="o">*</span> <span class="k">self</span><span class="py">.width</span> <span class="o">+</span> <span class="n">x</span><span class="p">)</span> <span class="k">as</span> <span class="nb">usize</span>
    <span class="p">}</span>

    <span class="cd">/// Check if grid coordinates are within bounds.</span>
    <span class="nd">#[inline]</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">in_bounds</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="n">x</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="k">self</span><span class="py">.width</span> <span class="o">&amp;&amp;</span> <span class="n">y</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="k">self</span><span class="py">.height</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Players move in screen positions like (150.5, -32.0). We need to convert these to tile coordinates like (4, -1) to check collision. That’s what these two functions do.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">#[inline]</code>?</strong></p>

<p>This hints to the compiler that these small, frequently-called functions should be inlined (copied directly into the caller function) rather than called as separate functions.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>

    <span class="cd">/// Convert world position to grid coordinates.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">world_to_grid</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">world_pos</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">IVec2</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">grid_x</span> <span class="o">=</span> <span class="p">((</span><span class="n">world_pos</span><span class="py">.x</span> <span class="o">-</span> <span class="k">self</span><span class="py">.origin_x</span><span class="p">)</span> <span class="o">/</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">grid_y</span> <span class="o">=</span> <span class="p">((</span><span class="n">world_pos</span><span class="py">.y</span> <span class="o">-</span> <span class="k">self</span><span class="py">.origin_y</span><span class="p">)</span> <span class="o">/</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="nn">IVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">grid_x</span><span class="p">,</span> <span class="n">grid_y</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="cd">/// Convert grid coordinates to world position (tile center).</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">grid_to_world</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">grid_x</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">grid_y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
        <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
            <span class="k">self</span><span class="py">.origin_x</span> <span class="o">+</span> <span class="p">(</span><span class="n">grid_x</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">,</span>
            <span class="k">self</span><span class="py">.origin_y</span> <span class="o">+</span> <span class="p">(</span><span class="n">grid_y</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">,</span>
        <span class="p">)</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><strong>Understanding <code class="language-plaintext highlighter-rouge">grid_to_world</code></strong></p>

<p>This function does the opposite of <code class="language-plaintext highlighter-rouge">world_to_grid</code>: it takes grid coordinates like (3, 7) and converts them back to a world position.</p>

<p>Notice the <code class="language-plaintext highlighter-rouge">+ 0.5</code> in the code—this returns the <strong>center</strong> of the tile rather than its corner. If tile (3, 7) spans pixels 96-127, adding 0.5 gives us the center at pixel 112:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>origin_x + (grid_x * tile_size)        // ← This gives the left edge (96)
origin_x + (grid_x + 0.5) * tile_size  // ← This gives the center (112)
</code></pre></div></div>

<p>This is useful because when you spawn a sprite in Bevy, its <code class="language-plaintext highlighter-rouge">Transform</code> position represents its center point by default. So <code class="language-plaintext highlighter-rouge">grid_to_world</code> gives you the exact position to place entities that should be visually centered in their tiles.</p>

<p>Now add tile access methods. Here’s how they fit together:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">set_tile</code> is used when building the map (during level load, we scan tiles and call <code class="language-plaintext highlighter-rouge">set_tile</code> for each one)</li>
  <li><code class="language-plaintext highlighter-rouge">get_tile</code> retrieves a tile type at grid coordinates</li>
  <li><code class="language-plaintext highlighter-rouge">is_walkable</code> asks <strong>can I walk on tile (3, 7)?</strong> It calls <code class="language-plaintext highlighter-rouge">get_tile</code>, then checks if that tile type is walkable</li>
  <li><code class="language-plaintext highlighter-rouge">is_world_pos_walkable</code> is the same question, but starts from screen position (150, -32). It converts to grid coordinates, then calls <code class="language-plaintext highlighter-rouge">is_walkable</code></li>
</ul>

<p>In practice, the circle collision code uses these internally. You rarely call them directly.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>


    <span class="cd">/// Get the tile type at grid coordinates.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">get_tile</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="nf">.in_bounds</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">Some</span><span class="p">(</span><span class="k">self</span><span class="py">.tiles</span><span class="p">[</span><span class="k">self</span><span class="nf">.xy_to_idx</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)])</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nb">None</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="cd">/// Set a tile at grid coordinates.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">set_tile</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">tile_type</span><span class="p">:</span> <span class="n">TileType</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">self</span><span class="nf">.in_bounds</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">idx</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.xy_to_idx</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span>
            <span class="k">self</span><span class="py">.tiles</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">tile_type</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="cd">/// Check if a grid position is walkable.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_walkable</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">self</span><span class="nf">.get_tile</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span><span class="nf">.map_or</span><span class="p">(</span><span class="k">false</span><span class="p">,</span> <span class="p">|</span><span class="n">t</span><span class="p">|</span> <span class="n">t</span><span class="nf">.is_walkable</span><span class="p">())</span>
    <span class="p">}</span>

    <span class="cd">/// Check if a world position is walkable.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_world_pos_walkable</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">world_pos</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">grid_pos</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.world_to_grid</span><span class="p">(</span><span class="n">world_pos</span><span class="p">);</span>
        <span class="k">self</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">grid_pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">grid_pos</span><span class="py">.y</span><span class="p">)</span>
    <span class="p">}</span>

</code></pre></div></div>

<h3 id="circle-collision">Circle Collision</h3>

<p>The methods above check if a single <strong>point</strong> is walkable. But our character isn’t a point, they have a body! If we only check the player’s center position, they could overlap walls.</p>

<p>We model the player’s collision area as a circle at their center. To check if a circle collides with a tile (rectangle), we need to solve a problem: <strong>how do you test if a circle overlaps a rectangle?</strong></p>

<p>The trick is to find the point on the rectangle closest to the circle’s center. If that point is inside the circle (distance less than radius), they overlap.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>


    <span class="cd">/// Check if a circle intersects with a tile's bounding box.</span>
    <span class="k">fn</span> <span class="nf">circle_intersects_tile</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">center</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">gx</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">gy</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="c1">// Tile bounding box</span>
        <span class="k">let</span> <span class="n">tile_min</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
            <span class="k">self</span><span class="py">.origin_x</span> <span class="o">+</span> <span class="n">gx</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">,</span>
            <span class="k">self</span><span class="py">.origin_y</span> <span class="o">+</span> <span class="n">gy</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">,</span>
        <span class="p">);</span>
        <span class="k">let</span> <span class="n">tile_max</span> <span class="o">=</span> <span class="n">tile_min</span> <span class="o">+</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="k">self</span><span class="py">.tile_size</span><span class="p">);</span>

        <span class="c1">// Find closest point on tile to circle center</span>
        <span class="k">let</span> <span class="n">closest</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
            <span class="n">center</span><span class="py">.x</span><span class="nf">.clamp</span><span class="p">(</span><span class="n">tile_min</span><span class="py">.x</span><span class="p">,</span> <span class="n">tile_max</span><span class="py">.x</span><span class="p">),</span>
            <span class="n">center</span><span class="py">.y</span><span class="nf">.clamp</span><span class="p">(</span><span class="n">tile_min</span><span class="py">.y</span><span class="p">,</span> <span class="n">tile_max</span><span class="py">.y</span><span class="p">),</span>
        <span class="p">);</span>

        <span class="c1">// Check if closest point is within radius</span>
        <span class="n">center</span><span class="nf">.distance_squared</span><span class="p">(</span><span class="n">closest</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">radius</span> <span class="o">*</span> <span class="n">radius</span>
    <span class="p">}</span>

</code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">circle_intersects_tile</code> ready, we can build <code class="language-plaintext highlighter-rouge">is_circle_clear</code>. But first, we need to prevent the player from walking off the edge of the map.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>
    <span class="cd">/// Check if a position with radius is within map bounds.</span>
    <span class="k">fn</span> <span class="nf">is_within_bounds</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">center</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">left</span> <span class="o">=</span> <span class="k">self</span><span class="py">.origin_x</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">right</span> <span class="o">=</span> <span class="k">self</span><span class="py">.origin_x</span> <span class="o">+</span> <span class="k">self</span><span class="py">.width</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">bottom</span> <span class="o">=</span> <span class="k">self</span><span class="py">.origin_y</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">top</span> <span class="o">=</span> <span class="k">self</span><span class="py">.origin_y</span> <span class="o">+</span> <span class="k">self</span><span class="py">.height</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">;</span>

        <span class="n">center</span><span class="py">.x</span> <span class="o">-</span> <span class="n">radius</span> <span class="o">&gt;=</span> <span class="n">left</span>
            <span class="o">&amp;&amp;</span> <span class="n">center</span><span class="py">.x</span> <span class="o">+</span> <span class="n">radius</span> <span class="o">&lt;=</span> <span class="n">right</span>
            <span class="o">&amp;&amp;</span> <span class="n">center</span><span class="py">.y</span> <span class="o">-</span> <span class="n">radius</span> <span class="o">&gt;=</span> <span class="n">bottom</span>
            <span class="o">&amp;&amp;</span> <span class="n">center</span><span class="py">.y</span> <span class="o">+</span> <span class="n">radius</span> <span class="o">&lt;=</span> <span class="n">top</span>
    <span class="p">}</span>

</code></pre></div></div>

<p>Now the main collision check.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>

    <span class="cd">/// Check if a circle at the given world position is clear of obstacles.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_circle_clear</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">center</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="c1">// Early bounds check</span>
        <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="nf">.is_within_bounds</span><span class="p">(</span><span class="n">center</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Point collision if no radius</span>
        <span class="k">if</span> <span class="n">radius</span> <span class="o">&lt;=</span> <span class="mf">0.0</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">self</span><span class="nf">.is_world_pos_walkable</span><span class="p">(</span><span class="n">center</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// Find grid cells that could overlap the circle</span>
        <span class="k">let</span> <span class="n">min_gx</span> <span class="o">=</span> <span class="p">((</span><span class="n">center</span><span class="py">.x</span> <span class="o">-</span> <span class="n">radius</span> <span class="o">-</span> <span class="k">self</span><span class="py">.origin_x</span><span class="p">)</span> <span class="o">/</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">max_gx</span> <span class="o">=</span> <span class="p">((</span><span class="n">center</span><span class="py">.x</span> <span class="o">+</span> <span class="n">radius</span> <span class="o">-</span> <span class="k">self</span><span class="py">.origin_x</span><span class="p">)</span> <span class="o">/</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">min_gy</span> <span class="o">=</span> <span class="p">((</span><span class="n">center</span><span class="py">.y</span> <span class="o">-</span> <span class="n">radius</span> <span class="o">-</span> <span class="k">self</span><span class="py">.origin_y</span><span class="p">)</span> <span class="o">/</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">max_gy</span> <span class="o">=</span> <span class="p">((</span><span class="n">center</span><span class="py">.y</span> <span class="o">+</span> <span class="n">radius</span> <span class="o">-</span> <span class="k">self</span><span class="py">.origin_y</span><span class="p">)</span> <span class="o">/</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>

        <span class="k">for</span> <span class="n">gy</span> <span class="k">in</span> <span class="n">min_gy</span><span class="o">..=</span><span class="n">max_gy</span> <span class="p">{</span>
            <span class="k">for</span> <span class="n">gx</span> <span class="k">in</span> <span class="n">min_gx</span><span class="o">..=</span><span class="n">max_gx</span> <span class="p">{</span>
                <span class="k">if</span> <span class="o">!</span><span class="k">self</span><span class="nf">.in_bounds</span><span class="p">(</span><span class="n">gx</span><span class="p">,</span> <span class="n">gy</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="k">false</span><span class="p">;</span>  <span class="c1">// Out of bounds = blocked</span>
                <span class="p">}</span>

                <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">tile</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.get_tile</span><span class="p">(</span><span class="n">gx</span><span class="p">,</span> <span class="n">gy</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="o">!</span><span class="n">tile</span><span class="nf">.is_walkable</span><span class="p">()</span> <span class="p">{</span>
                        <span class="c1">// Apply tile-specific collision adjustment</span>
                        <span class="k">let</span> <span class="n">effective_radius</span> <span class="o">=</span> <span class="n">radius</span> <span class="o">+</span> <span class="n">tile</span><span class="nf">.collision_adjustment</span><span class="p">()</span> <span class="o">*</span> <span class="k">self</span><span class="py">.tile_size</span><span class="p">;</span>
                        
                        <span class="k">if</span> <span class="k">self</span><span class="nf">.circle_intersects_tile</span><span class="p">(</span><span class="n">center</span><span class="p">,</span> <span class="n">effective_radius</span><span class="p">,</span> <span class="n">gx</span><span class="p">,</span> <span class="n">gy</span><span class="p">)</span> <span class="p">{</span>
                            <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">true</span>
    <span class="p">}</span>

</code></pre></div></div>

<p>Here’s how <code class="language-plaintext highlighter-rouge">is_circle_clear</code> works:</p>
<ol>
  <li>Early exit if circle is outside map bounds (<code class="language-plaintext highlighter-rouge">is_within_bounds</code>)</li>
  <li>Find all grid cells the circle might touch (based on circle bounds)</li>
  <li>For each unwalkable tile, use <code class="language-plaintext highlighter-rouge">circle_intersects_tile</code> to check overlap</li>
  <li>Apply <code class="language-plaintext highlighter-rouge">collision_adjustment()</code> to allow corner cutting on certain tiles</li>
</ol>

<p>Notice that <code class="language-plaintext highlighter-rouge">is_circle_clear</code> builds on everything we’ve created: <code class="language-plaintext highlighter-rouge">in_bounds</code>, <code class="language-plaintext highlighter-rouge">get_tile</code>, <code class="language-plaintext highlighter-rouge">is_walkable</code>, <code class="language-plaintext highlighter-rouge">is_within_bounds</code>, and <code class="language-plaintext highlighter-rouge">circle_intersects_tile</code>. Each layer builds on the previous one.</p>

<h3 id="swept-collision">Swept Collision</h3>

<p>We can now check if a position is valid using <code class="language-plaintext highlighter-rouge">is_circle_clear</code>. But there’s a problem: if a player moves fast enough in one frame, they could jump <em>over</em> a thin wall. The collision check at the destination would pass, but they’d skip right through the obstacle.</p>

<p><code class="language-plaintext highlighter-rouge">sweep_circle</code> solves this by checking the <strong>entire path</strong> from start to end. It uses <code class="language-plaintext highlighter-rouge">is_circle_clear</code> repeatedly along small steps, ensuring we catch any collision along the way:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>

    <span class="cd">/// Perform swept circle movement with axis-sliding.</span>
    <span class="cd">/// Returns the furthest valid position the circle can reach.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">sweep_circle</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">start</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">end</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">;</span>
        
        <span class="c1">// No movement needed</span>
        <span class="k">if</span> <span class="n">delta</span><span class="nf">.length</span><span class="p">()</span> <span class="o">&lt;</span> <span class="mf">0.001</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">start</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Step size (quarter tile for smooth collision)</span>
        <span class="k">let</span> <span class="n">max_step</span> <span class="o">=</span> <span class="k">self</span><span class="py">.tile_size</span> <span class="o">*</span> <span class="mf">0.25</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">steps</span> <span class="o">=</span> <span class="p">(</span><span class="n">delta</span><span class="nf">.length</span><span class="p">()</span> <span class="o">/</span> <span class="n">max_step</span><span class="p">)</span><span class="nf">.ceil</span><span class="p">()</span><span class="nf">.max</span><span class="p">(</span><span class="mf">1.0</span><span class="p">)</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">step_vec</span> <span class="o">=</span> <span class="n">delta</span> <span class="o">/</span> <span class="n">steps</span> <span class="k">as</span> <span class="nb">f32</span><span class="p">;</span>

        <span class="k">let</span> <span class="k">mut</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">start</span><span class="p">;</span>
        <span class="k">for</span> <span class="n">_</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">steps</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">candidate</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="n">step_vec</span><span class="p">;</span>

            <span class="k">if</span> <span class="k">self</span><span class="nf">.is_circle_clear</span><span class="p">(</span><span class="n">candidate</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">pos</span> <span class="o">=</span> <span class="n">candidate</span><span class="p">;</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="c1">// Try sliding along X axis only</span>
                <span class="k">let</span> <span class="n">try_x</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">candidate</span><span class="py">.x</span><span class="p">,</span> <span class="n">pos</span><span class="py">.y</span><span class="p">);</span>
                <span class="k">if</span> <span class="k">self</span><span class="nf">.is_circle_clear</span><span class="p">(</span><span class="n">try_x</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">pos</span> <span class="o">=</span> <span class="n">try_x</span><span class="p">;</span>
                    <span class="k">continue</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="c1">// Try sliding along Y axis only</span>
                <span class="k">let</span> <span class="n">try_y</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">pos</span><span class="py">.x</span><span class="p">,</span> <span class="n">candidate</span><span class="py">.y</span><span class="p">);</span>
                <span class="k">if</span> <span class="k">self</span><span class="nf">.is_circle_clear</span><span class="p">(</span><span class="n">try_y</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">pos</span> <span class="o">=</span> <span class="n">try_y</span><span class="p">;</span>
                    <span class="k">continue</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="c1">// Completely blocked</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="n">pos</span>
    <span class="p">}</span>

</code></pre></div></div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_816239a3bc783dae18f72aa648c5f6d1.svg" alt="Comic Panel" class="comic-image" data-comic-hash="816239a3bc783dae18f72aa648c5f6d1" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>We’ll be soon working on a feature to visually debug collisions. We need the following helper functions to calculate the collision map. The <code class="language-plaintext highlighter-rouge">#[cfg(debug_assertions)]</code> attribute means these only exist in debug builds, they’re completely removed from release builds:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Append to src/collision/map.rs</span>
<span class="c1">// Add this inside impl CollisionMap {</span>

    <span class="c1">// ... earlier functions inside impl CollisionMap </span>
    <span class="nd">#[cfg(debug_assertions)]</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">width</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span> <span class="k">self</span><span class="py">.width</span> <span class="p">}</span>
    
    <span class="nd">#[cfg(debug_assertions)]</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">height</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span> <span class="k">self</span><span class="py">.height</span> <span class="p">}</span>
    
    <span class="nd">#[cfg(debug_assertions)]</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">tile_size</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span> <span class="k">self</span><span class="py">.tile_size</span> <span class="p">}</span>
    
    <span class="nd">#[cfg(debug_assertions)]</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">origin</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">self</span><span class="py">.origin_x</span><span class="p">,</span> <span class="k">self</span><span class="py">.origin_y</span><span class="p">)</span> <span class="p">}</span>

</code></pre></div></div>

<h3 id="building-the-collision-map">Building the Collision Map</h3>

<p>We have the <code class="language-plaintext highlighter-rouge">CollisionMap</code> data structure, but when does it get populated? We need a system that scans all the tiles after WFC generation and builds the collision map.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/collision/systems.rs</code>:</p>

<p>The collision map should only be built once after tiles are spawned. We need a way to track this. A simple boolean resource does the job: it starts as <code class="language-plaintext highlighter-rouge">false</code>, and once we build the map, we flip it to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>We register this resource with Bevy’s scheduler using <code class="language-plaintext highlighter-rouge">run_if(resource_equals(CollisionMapBuilt(false)))</code>, which means the build system won’t even run after the map is built.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/systems.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::{</span><span class="n">HashMap</span><span class="p">,</span> <span class="nn">hash_map</span><span class="p">::</span><span class="n">Entry</span><span class="p">};</span>

<span class="k">use</span> <span class="k">super</span><span class="p">::{</span><span class="n">CollisionMap</span><span class="p">,</span> <span class="n">TileMarker</span><span class="p">,</span> <span class="n">TileType</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">map</span><span class="p">::{</span><span class="n">TILE_SIZE</span><span class="p">,</span> <span class="n">GRID_X</span><span class="p">,</span> <span class="n">GRID_Y</span><span class="p">};</span>

<span class="cd">/// Resource to track if collision map has been built.</span>
<span class="nd">#[derive(Resource,</span> <span class="nd">Default,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="nf">CollisionMapBuilt</span><span class="p">(</span><span class="k">pub</span> <span class="nb">bool</span><span class="p">);</span>
</code></pre></div></div>

<p>Now the main system that builds the map. This is the longest function we’ve written, so let’s understand what it does before looking at the code.</p>

<p>The system detects when WFC has finished by querying for tiles, if no tiles exist yet, it exits early. Once tiles exist, it:</p>

<ol>
  <li>
    <p><strong>Calculates the grid origin</strong> - Our map is centered on the screen, so the bottom-left corner of the grid isn’t at (0, 0). We need to figure out where it actually is.</p>
  </li>
  <li>
    <p><strong>Scans all tiles</strong> - Each tile entity has a <code class="language-plaintext highlighter-rouge">Transform</code> (its position in the world) and a <code class="language-plaintext highlighter-rouge">TileMarker</code> (what type of tile it is). We loop through every tile and read both.</p>
  </li>
  <li>
    <p><strong>Handles overlapping tiles</strong> - Our WFC generator can place tiles on top of each other. For example, a tree sprite sits on top of grass at the same (x, y) position but at a higher Z (depth). For collision, we only care about the topmost tile: if there’s a tree on grass, the player collides with the tree.</p>
  </li>
  <li>
    <p><strong>Tracks bounds</strong> - We track the leftmost, rightmost, topmost, and bottommost tile coordinates. From these, we calculate the dimensions of the <code class="language-plaintext highlighter-rouge">CollisionMap</code>.</p>
  </li>
  <li>
    <p><strong>Creates and populates the CollisionMap</strong> - Using the <code class="language-plaintext highlighter-rouge">world_to_grid</code> logic we built earlier, we convert each tile’s world position to grid coordinates and call <code class="language-plaintext highlighter-rouge">set_tile</code> to record its type.</p>
  </li>
  <li>
    <p><strong>Post-processes</strong> - Finally, we run shore conversion to turn water tiles at the edge of lakes into walkable shore tiles. This gives players a better experience at water boundaries.</p>
  </li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/systems.rs</span>

<span class="cd">/// System that builds the collision map from spawned tiles.</span>
<span class="cd">/// Handles multi-layer maps by keeping only the TOPMOST tile at each (x,y).</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_collision_map</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">built</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">CollisionMapBuilt</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">tile_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">TileMarker</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Transform</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Need at least one tile to proceed</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">tile_iter</span> <span class="o">=</span> <span class="n">tile_query</span><span class="nf">.iter</span><span class="p">();</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">((</span><span class="n">first_marker</span><span class="p">,</span> <span class="n">first_transform</span><span class="p">))</span> <span class="o">=</span> <span class="n">tile_iter</span><span class="nf">.next</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span> <span class="c1">// WFC hasn't generated tiles yet</span>
    <span class="p">};</span>

    <span class="c1">// Calculate grid origin (centered map)</span>
    <span class="k">let</span> <span class="n">grid_origin_x</span> <span class="o">=</span> <span class="o">-</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_X</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">grid_origin_y</span> <span class="o">=</span> <span class="o">-</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_Y</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">;</span>

    <span class="c1">// Track bounds and layer info</span>
    <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">min_x</span><span class="p">,</span> <span class="k">mut</span> <span class="n">max_x</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="nn">i32</span><span class="p">::</span><span class="n">MAX</span><span class="p">,</span> <span class="nn">i32</span><span class="p">::</span><span class="n">MIN</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">min_y</span><span class="p">,</span> <span class="k">mut</span> <span class="n">max_y</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="nn">i32</span><span class="p">::</span><span class="n">MAX</span><span class="p">,</span> <span class="nn">i32</span><span class="p">::</span><span class="n">MIN</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">layer_tracker</span><span class="p">:</span> <span class="n">HashMap</span><span class="o">&lt;</span><span class="p">(</span><span class="nb">i32</span><span class="p">,</span> <span class="nb">i32</span><span class="p">),</span> <span class="p">(</span><span class="n">TileType</span><span class="p">,</span> <span class="nb">f32</span><span class="p">)</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">HashMap</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">tile_count</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// Process all tiles, keeping only the topmost at each position</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">process_tile</span> <span class="o">=</span> <span class="p">|</span><span class="n">marker</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TileMarker</span><span class="p">,</span> <span class="n">transform</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Transform</span><span class="p">|</span> <span class="p">{</span>
        <span class="n">tile_count</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">world_x</span> <span class="o">=</span> <span class="n">transform</span><span class="py">.translation.x</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">world_y</span> <span class="o">=</span> <span class="n">transform</span><span class="py">.translation.y</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">world_z</span> <span class="o">=</span> <span class="n">transform</span><span class="py">.translation.z</span><span class="p">;</span>
        
        <span class="k">let</span> <span class="n">grid_x</span> <span class="o">=</span> <span class="p">((</span><span class="n">world_x</span> <span class="o">-</span> <span class="n">grid_origin_x</span><span class="p">)</span> <span class="o">/</span> <span class="n">TILE_SIZE</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">grid_y</span> <span class="o">=</span> <span class="p">((</span><span class="n">world_y</span> <span class="o">-</span> <span class="n">grid_origin_y</span><span class="p">)</span> <span class="o">/</span> <span class="n">TILE_SIZE</span><span class="p">)</span><span class="nf">.floor</span><span class="p">()</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>

        <span class="n">min_x</span> <span class="o">=</span> <span class="n">min_x</span><span class="nf">.min</span><span class="p">(</span><span class="n">grid_x</span><span class="p">);</span>
        <span class="n">max_x</span> <span class="o">=</span> <span class="n">max_x</span><span class="nf">.max</span><span class="p">(</span><span class="n">grid_x</span><span class="p">);</span>
        <span class="n">min_y</span> <span class="o">=</span> <span class="n">min_y</span><span class="nf">.min</span><span class="p">(</span><span class="n">grid_y</span><span class="p">);</span>
        <span class="n">max_y</span> <span class="o">=</span> <span class="n">max_y</span><span class="nf">.max</span><span class="p">(</span><span class="n">grid_y</span><span class="p">);</span>

        <span class="c1">// Keep only the topmost layer (highest Z)</span>
        <span class="k">match</span> <span class="n">layer_tracker</span><span class="nf">.entry</span><span class="p">((</span><span class="n">grid_x</span><span class="p">,</span> <span class="n">grid_y</span><span class="p">))</span> <span class="p">{</span>
            <span class="nn">Entry</span><span class="p">::</span><span class="nf">Occupied</span><span class="p">(</span><span class="k">mut</span> <span class="n">entry</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">if</span> <span class="n">world_z</span> <span class="o">&gt;</span> <span class="n">entry</span><span class="nf">.get</span><span class="p">()</span><span class="na">.1</span> <span class="p">{</span>
                    <span class="o">*</span><span class="n">entry</span><span class="nf">.get_mut</span><span class="p">()</span> <span class="o">=</span> <span class="p">(</span><span class="n">marker</span><span class="py">.tile_type</span><span class="p">,</span> <span class="n">world_z</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="nn">Entry</span><span class="p">::</span><span class="nf">Vacant</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="n">entry</span><span class="nf">.insert</span><span class="p">((</span><span class="n">marker</span><span class="py">.tile_type</span><span class="p">,</span> <span class="n">world_z</span><span class="p">));</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">};</span>

    <span class="c1">// Process first tile and remaining</span>
    <span class="nf">process_tile</span><span class="p">(</span><span class="n">first_marker</span><span class="p">,</span> <span class="n">first_transform</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">marker</span><span class="p">,</span> <span class="n">transform</span><span class="p">)</span> <span class="k">in</span> <span class="n">tile_iter</span> <span class="p">{</span>
        <span class="nf">process_tile</span><span class="p">(</span><span class="n">marker</span><span class="p">,</span> <span class="n">transform</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Calculate actual dimensions</span>
    <span class="k">let</span> <span class="n">actual_width</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_x</span> <span class="o">-</span> <span class="n">min_x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">actual_height</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_y</span> <span class="o">-</span> <span class="n">min_y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="nb">i32</span><span class="p">;</span>

    <span class="c1">// Create the collision map</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">map</span> <span class="o">=</span> <span class="nn">CollisionMap</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
        <span class="n">actual_width</span><span class="p">,</span>
        <span class="n">actual_height</span><span class="p">,</span>
        <span class="n">TILE_SIZE</span><span class="p">,</span>
        <span class="n">grid_origin_x</span><span class="p">,</span>
        <span class="n">grid_origin_y</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Populate the map from layer tracker</span>
    <span class="k">for</span> <span class="p">((</span><span class="n">grid_x</span><span class="p">,</span> <span class="n">grid_y</span><span class="p">),</span> <span class="p">(</span><span class="n">tile_type</span><span class="p">,</span> <span class="n">_z</span><span class="p">))</span> <span class="k">in</span> <span class="n">layer_tracker</span><span class="nf">.iter</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Convert world grid to local array coordinates</span>
        <span class="k">let</span> <span class="n">local_x</span> <span class="o">=</span> <span class="n">grid_x</span> <span class="o">-</span> <span class="n">min_x</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">local_y</span> <span class="o">=</span> <span class="n">grid_y</span> <span class="o">-</span> <span class="n">min_y</span><span class="p">;</span>
        <span class="n">map</span><span class="nf">.set_tile</span><span class="p">(</span><span class="n">local_x</span><span class="p">,</span> <span class="n">local_y</span><span class="p">,</span> <span class="o">*</span><span class="n">tile_type</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Post-processing: Convert water edges to shore</span>
    <span class="nf">convert_water_edges_to_shore</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">map</span><span class="p">);</span>
    <span class="c1">// Insert as resource and mark built</span>
    <span class="n">commands</span><span class="nf">.insert_resource</span><span class="p">(</span><span class="n">map</span><span class="p">);</span>
    <span class="n">built</span><span class="na">.0</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="shore-conversion">Shore Conversion</h3>

<p>Imagine a lake in your game. Water tiles block movement completely, but what about the edge of the lake? If the player walks up to water at the edge of grass, they should be able to get close to the waterline rather than stopping a full tile away.</p>

<p>Shore conversion solves this by finding water tiles that touch walkable terrain and marking them as <code class="language-plaintext highlighter-rouge">Shore</code>. Shore tiles are walkable (remember our <code class="language-plaintext highlighter-rouge">TileType::Shore</code> from earlier), so the player can walk right up to the water’s edge while still being blocked by the deep water in the middle of the lake.</p>

<p>The algorithm is straightforward: scan every tile, find water tiles, check their 8 neighbors, and if any neighbor is walkable, mark this water tile as shore.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/systems.rs</span>

<span class="cd">/// Convert water tiles adjacent to walkable tiles into shore tiles.</span>
<span class="k">fn</span> <span class="nf">convert_water_edges_to_shore</span><span class="p">(</span><span class="n">map</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">CollisionMap</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">shores</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Find water tiles that touch walkable tiles</span>
    <span class="k">for</span> <span class="n">y</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">map</span><span class="nf">.height</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">x</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">map</span><span class="nf">.width</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">map</span><span class="nf">.get_tile</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="o">!=</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="c1">// Check 8 neighbors</span>
            <span class="k">let</span> <span class="n">neighbors</span> <span class="o">=</span> <span class="p">[</span>
                <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span>     <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span>     <span class="c1">// left, right</span>
                <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">-</span> <span class="mi">1</span><span class="p">),</span>     <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span>     <span class="c1">// down, up</span>
                <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">y</span> <span class="o">-</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">y</span> <span class="o">-</span> <span class="mi">1</span><span class="p">),</span> <span class="c1">// bottom corners</span>
                <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="c1">// top corners</span>
            <span class="p">];</span>

            <span class="k">for</span> <span class="p">(</span><span class="n">nx</span><span class="p">,</span> <span class="n">ny</span><span class="p">)</span> <span class="k">in</span> <span class="n">neighbors</span> <span class="p">{</span>
                <span class="k">if</span> <span class="n">map</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">nx</span><span class="p">,</span> <span class="n">ny</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">shores</span><span class="nf">.push</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">));</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="k">in</span> <span class="n">shores</span> <span class="p">{</span>
        <span class="n">map</span><span class="nf">.set_tile</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="nn">TileType</span><span class="p">::</span><span class="n">Shore</span><span class="p">);</span>
    <span class="p">}</span>


<span class="p">}</span>
</code></pre></div></div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_e9bab5b734aa9132f8950f584e6ed7a9.svg" alt="Comic Panel" class="comic-image" data-comic-hash="e9bab5b734aa9132f8950f584e6ed7a9" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="debug-visualization">Debug Visualization</h3>

<p>When collision isn’t working, you need to see what’s happening. Is the player’s collider in the right place? Are tiles marked correctly? Visual debugging makes these problems obvious.</p>

<p>We’ll create a debug overlay that:</p>
<ul>
  <li>Shows green for walkable tiles, red for blocked</li>
  <li>Draws the player’s collision circle</li>
  <li>Highlights which grid cell the player is in</li>
  <li>Only exists in debug builds (removed from release)</li>
</ul>

<p>Create <code class="language-plaintext highlighter-rouge">src/collision/debug.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/debug.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="n">CollisionMap</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">collider</span><span class="p">::</span><span class="n">Collider</span><span class="p">;</span>

<span class="cd">/// Resource to toggle debug visualization.</span>
<span class="nd">#[derive(Resource,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="nf">DebugCollisionEnabled</span><span class="p">(</span><span class="k">pub</span> <span class="nb">bool</span><span class="p">);</span>
</code></pre></div></div>

<p>This <code class="language-plaintext highlighter-rouge">DebugCollisionEnabled</code> resource controls whether debug drawing is active. We start with it off and let the player toggle it with a key.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/debug.rs</span>

<span class="cd">/// Toggle collision debug visualization with F3 key.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">toggle_debug_collision</span><span class="p">(</span>
    <span class="n">keyboard</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">debug_enabled</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">DebugCollisionEnabled</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">keyboard</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">F3</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">debug_enabled</span><span class="na">.0</span> <span class="o">=</span> <span class="o">!</span><span class="n">debug_enabled</span><span class="na">.0</span><span class="p">;</span>
        <span class="k">if</span> <span class="n">debug_enabled</span><span class="na">.0</span> <span class="p">{</span>
            <span class="nd">info!</span><span class="p">(</span><span class="s">"🔍 Collision debug ENABLED (F3 to toggle)"</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nd">info!</span><span class="p">(</span><span class="s">"Collision debug disabled"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why <code class="language-plaintext highlighter-rouge">debug_enabled.0</code> instead of just <code class="language-plaintext highlighter-rouge">debug_enabled</code>?</strong></p>

<p>Our <code class="language-plaintext highlighter-rouge">DebugCollisionEnabled</code> is a “tuple struct” - a struct that wraps a single value. The <code class="language-plaintext highlighter-rouge">.0</code> accesses the first (and only) field inside it. This pattern is common in Rust for creating type-safe wrappers: instead of passing around a raw <code class="language-plaintext highlighter-rouge">bool</code>, we wrap it in a named type that makes the code’s intent clearer.</p>

<p>We have used F3 as keyboard shortcut to toggle the debug overlay on and off in the gameplay.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">Gizmos</code>?</strong></p>

<p>Gizmos are Bevy’s built-in tool for drawing debug shapes like lines, circles, and rectangles. Unlike regular sprites, gizmos only last for one frame and automatically disappear. This makes them perfect for debug visualization: you draw what you need each frame, and when you stop drawing, they’re gone.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/debug.rs</span>

<span class="cd">/// Draw colored rectangles over tiles showing walkability.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">debug_draw_collision</span><span class="p">(</span>
    <span class="n">map</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CollisionMap</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">debug_enabled</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">DebugCollisionEnabled</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">gizmos</span><span class="p">:</span> <span class="n">Gizmos</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">debug_enabled</span><span class="na">.0</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">=</span> <span class="n">map</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">};</span>

    <span class="k">let</span> <span class="n">tile_size</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.tile_size</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">origin</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.origin</span><span class="p">();</span>

    <span class="c1">// Draw each tile</span>
    <span class="k">for</span> <span class="n">y</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">map</span><span class="nf">.height</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">x</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">map</span><span class="nf">.width</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">world_pos</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
                <span class="n">origin</span><span class="py">.x</span> <span class="o">+</span> <span class="p">(</span><span class="n">x</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="n">tile_size</span><span class="p">,</span>
                <span class="n">origin</span><span class="py">.y</span> <span class="o">+</span> <span class="p">(</span><span class="n">y</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="n">tile_size</span><span class="p">,</span>
            <span class="p">);</span>

            <span class="k">let</span> <span class="n">color</span> <span class="o">=</span> <span class="k">if</span> <span class="n">map</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
                <span class="nn">Color</span><span class="p">::</span><span class="nf">srgba</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.25</span><span class="p">)</span>  <span class="c1">// Green, 25% opacity</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nn">Color</span><span class="p">::</span><span class="nf">srgba</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">)</span>   <span class="c1">// Red, 40% opacity</span>
            <span class="p">};</span>

            <span class="n">gizmos</span><span class="nf">.rect_2d</span><span class="p">(</span>
                <span class="n">world_pos</span><span class="p">,</span>
                <span class="nn">Vec2</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">tile_size</span> <span class="o">*</span> <span class="mf">0.9</span><span class="p">),</span>
                <span class="n">color</span><span class="p">,</span>
            <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This loops through every tile and draws a colored rectangle: green for walkable, red for blocked. The 0.9 multiplier makes each rectangle slightly smaller than the tile so you can see the grid lines.</p>

<p>Next, we visualize the player’s collider:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/debug.rs</span>

<span class="cd">/// Draw player position and collider visualization.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">debug_player_position</span><span class="p">(</span>
    <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Collider</span><span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">map</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CollisionMap</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">debug_enabled</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">DebugCollisionEnabled</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">gizmos</span><span class="p">:</span> <span class="n">Gizmos</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">debug_enabled</span><span class="na">.0</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">=</span> <span class="n">map</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">};</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="n">transform</span><span class="p">,</span> <span class="n">collider</span><span class="p">))</span> <span class="o">=</span> <span class="n">player_query</span><span class="nf">.single</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">};</span>

    <span class="k">let</span> <span class="n">center</span> <span class="o">=</span> <span class="n">transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">();</span>
    
    <span class="c1">// Get actual collider position</span>
    <span class="k">let</span> <span class="n">collider_pos</span> <span class="o">=</span> <span class="n">collider</span><span class="nf">.world_position</span><span class="p">(</span><span class="n">transform</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">grid</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.world_to_grid</span><span class="p">(</span><span class="n">collider_pos</span><span class="p">);</span>

    <span class="c1">// Draw line from center to collider (shows offset)</span>
    <span class="n">gizmos</span><span class="nf">.line_2d</span><span class="p">(</span><span class="n">center</span><span class="p">,</span> <span class="n">collider_pos</span><span class="p">,</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgba</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">));</span>

    <span class="c1">// Draw actual collider circle</span>
    <span class="n">gizmos</span><span class="nf">.circle_2d</span><span class="p">(</span><span class="n">collider_pos</span><span class="p">,</span> <span class="n">collider</span><span class="py">.radius</span><span class="p">,</span> <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">));</span>

    <span class="c1">// Draw current grid cell outline</span>
    <span class="k">if</span> <span class="n">map</span><span class="nf">.in_bounds</span><span class="p">(</span><span class="n">grid</span><span class="py">.x</span><span class="p">,</span> <span class="n">grid</span><span class="py">.y</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">cell_center</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.grid_to_world</span><span class="p">(</span><span class="n">grid</span><span class="py">.x</span><span class="p">,</span> <span class="n">grid</span><span class="py">.y</span><span class="p">);</span>
        <span class="n">gizmos</span><span class="nf">.rect_2d</span><span class="p">(</span>
            <span class="n">cell_center</span><span class="p">,</span>
            <span class="nn">Vec2</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">map</span><span class="nf">.tile_size</span><span class="p">()),</span>
            <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
        <span class="p">);</span>

        <span class="c1">// Draw red X if on unwalkable tile</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">map</span><span class="nf">.is_walkable</span><span class="p">(</span><span class="n">grid</span><span class="py">.x</span><span class="p">,</span> <span class="n">grid</span><span class="py">.y</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">offset</span> <span class="o">=</span> <span class="mf">15.0</span><span class="p">;</span>
            <span class="n">gizmos</span><span class="nf">.line_2d</span><span class="p">(</span>
                <span class="n">collider_pos</span> <span class="o">+</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="o">-</span><span class="n">offset</span><span class="p">),</span>
                <span class="n">collider_pos</span> <span class="o">+</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">offset</span><span class="p">),</span>
                <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
            <span class="p">);</span>
            <span class="n">gizmos</span><span class="nf">.line_2d</span><span class="p">(</span>
                <span class="n">collider_pos</span> <span class="o">+</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">-</span><span class="n">offset</span><span class="p">,</span> <span class="n">offset</span><span class="p">),</span>
                <span class="n">collider_pos</span> <span class="o">+</span> <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="o">-</span><span class="n">offset</span><span class="p">),</span>
                <span class="nn">Color</span><span class="p">::</span><span class="nf">srgb</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">),</span>
            <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This draws:</p>
<ul>
  <li><strong>Cyan circle</strong> - the player’s collision radius</li>
  <li><strong>Yellow rectangle</strong> - which grid cell the player is in</li>
  <li><strong>Red X</strong> - appears if the player is somehow on an unwalkable tile</li>
</ul>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_d3fd5ebff99fff7f31e2f21dccdde964.svg" alt="Comic Panel" class="comic-image" data-comic-hash="d3fd5ebff99fff7f31e2f21dccdde964" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="wiring-it-together-the-collision-module">Wiring It Together: The Collision Module</h3>

<p>Now we tie everything together in <code class="language-plaintext highlighter-rouge">src/collision/mod.rs</code>. This file declares our submodules, re-exports the public types, and defines the <code class="language-plaintext highlighter-rouge">CollisionPlugin</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/collision/mod.rs</span>
<span class="k">mod</span> <span class="n">tile_type</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">map</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">systems</span><span class="p">;</span>

<span class="nd">#[cfg(debug_assertions)]</span>
<span class="k">mod</span> <span class="n">debug</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">state</span><span class="p">::</span><span class="n">GameState</span><span class="p">;</span>

<span class="c1">// Re-export commonly used types</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">tile_type</span><span class="p">::{</span><span class="n">TileType</span><span class="p">,</span> <span class="n">TileMarker</span><span class="p">};</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">map</span><span class="p">::</span><span class="n">CollisionMap</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">systems</span><span class="p">::</span><span class="n">CollisionMapBuilt</span><span class="p">;</span>

<span class="nd">#[cfg(debug_assertions)]</span>
<span class="k">pub</span> <span class="k">use</span> <span class="nn">debug</span><span class="p">::</span><span class="n">DebugCollisionEnabled</span><span class="p">;</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">pub use</code> lines make these types available to other modules. Instead of writing <code class="language-plaintext highlighter-rouge">collision::tile_type::TileType</code>, other code can simply use <code class="language-plaintext highlighter-rouge">collision::TileType</code>.</p>

<p>Now the plugin that registers everything with Bevy:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/collision/mod.rs</span>

<span class="cd">/// Plugin for collision detection functionality</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CollisionPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">CollisionPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="n">CollisionMapBuilt</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="nf">.add_systems</span><span class="p">(</span>
                <span class="n">Update</span><span class="p">,</span>
                <span class="nn">systems</span><span class="p">::</span><span class="n">build_collision_map</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">resource_equals</span><span class="p">(</span><span class="nf">CollisionMapBuilt</span><span class="p">(</span><span class="k">false</span><span class="p">)))</span>
                    <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
            <span class="p">);</span>

        <span class="c1">// Debug systems - only in debug builds</span>
        <span class="nd">#[cfg(debug_assertions)]</span>
        <span class="p">{</span>
            <span class="n">app</span><span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="n">DebugCollisionEnabled</span><span class="o">&gt;</span><span class="p">()</span>
                <span class="nf">.add_systems</span><span class="p">(</span>
                    <span class="n">Update</span><span class="p">,</span>
                    <span class="p">(</span>
                        <span class="nn">debug</span><span class="p">::</span><span class="n">toggle_debug_collision</span><span class="p">,</span>
                        <span class="nn">debug</span><span class="p">::</span><span class="n">debug_draw_collision</span><span class="p">,</span>
                        <span class="nn">debug</span><span class="p">::</span><span class="n">debug_player_position</span><span class="p">,</span>
                    <span class="p">)</span>
                        <span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)),</span>
                <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice the two <code class="language-plaintext highlighter-rouge">run_if</code> conditions on <code class="language-plaintext highlighter-rouge">build_collision_map</code>:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">resource_equals(CollisionMapBuilt(false))</code> - Only runs when the map hasn’t been built yet</li>
  <li><code class="language-plaintext highlighter-rouge">in_state(GameState::Playing)</code> - Only runs during gameplay, not menus or loading</li>
</ul>

<p>The debug systems are wrapped in <code class="language-plaintext highlighter-rouge">#[cfg(debug_assertions)]</code>, so they don’t exist in release builds at all.</p>

<p>Also add the collision module to your <code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In main.rs</span>
<span class="k">mod</span> <span class="n">collision</span><span class="p">;</span> <span class="c1">// Line update alert</span>

<span class="c1">// In your app setup:</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">ProcGenSimplePlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="p">,</span> <span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">state</span><span class="p">::</span><span class="n">StatePlugin</span><span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionPlugin</span><span class="p">)</span> <span class="c1">// Line update alert</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">characters</span><span class="p">::</span><span class="n">CharactersPlugin</span><span class="p">)</span> 
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="p">(</span><span class="n">setup_camera</span><span class="p">,</span> <span class="n">setup_generator</span><span class="p">))</span>
        <span class="nf">.run</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="integrating-collision-with-map-assets">Integrating Collision with Map Assets</h3>

<p>Now we need to modify our map generation to attach <code class="language-plaintext highlighter-rouge">TileMarker</code> components to spawned tiles. This connects the visual tiles to the collision system.</p>

<p>First, update <code class="language-plaintext highlighter-rouge">src/map/assets.rs</code> to support tile types:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Updated imports</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::{</span><span class="n">TileMarker</span><span class="p">,</span> <span class="n">TileType</span><span class="p">};</span> <span class="c1">// Line update alert</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">tilemap</span><span class="p">::</span><span class="n">TILEMAP</span><span class="p">;</span>
</code></pre></div></div>

<p>Update the <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> struct to include tile type. Note: We are replacing <code class="language-plaintext highlighter-rouge">components_spawner</code> we had earlier with <code class="language-plaintext highlighter-rouge">tile_type</code>, so this include multiple changes in struct, function arguments, etc.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Updated SpawnableAsset struct</span>
<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
    <span class="cd">/// Name of the sprite inside our tilemap atlas</span>
    <span class="n">sprite_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">,</span>
    <span class="cd">/// Offset in grid coordinates (for multi-tile objects)</span>
    <span class="n">grid_offset</span><span class="p">:</span> <span class="n">GridDelta</span><span class="p">,</span>
    <span class="cd">/// Offset in world coordinates (fine positioning)</span>
    <span class="n">offset</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="cd">/// The tile type for collision detection</span>
    <span class="n">tile_type</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span> <span class="c1">// Line update alert</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">sprite_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">sprite_name</span><span class="p">,</span>
            <span class="n">grid_offset</span><span class="p">:</span> <span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
            <span class="n">offset</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
            <span class="n">tile_type</span><span class="p">:</span> <span class="nb">None</span><span class="p">,</span> <span class="c1">// Line update alert</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="cd">/// Set grid offset for multi-tile objects.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_grid_offset</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">offset</span><span class="p">:</span> <span class="n">GridDelta</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.grid_offset</span> <span class="o">=</span> <span class="n">offset</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>

    <span class="cd">/// Set tile type for collision detection.</span>
    <span class="c1">// Function added alert</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_tile_type</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">tile_type</span><span class="p">:</span> <span class="n">TileType</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span> 
        <span class="k">self</span><span class="py">.tile_type</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">tile_type</span><span class="p">);</span>
        <span class="k">self</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The builder pattern lets us chain methods: <code class="language-plaintext highlighter-rouge">SpawnableAsset::new("grass").with_tile_type(TileType::Grass)</code>.</p>

<p>Update the <code class="language-plaintext highlighter-rouge">load_assets</code> function to use a spawner:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Updated load_assets function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">load_assets</span><span class="p">(</span>
    <span class="n">tilemap_handles</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TilemapHandles</span><span class="p">,</span>
    <span class="n">assets_definitions</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ModelsAssets</span><span class="o">&lt;</span><span class="n">Sprite</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">models_assets</span> <span class="o">=</span> <span class="nn">ModelsAssets</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    
    <span class="k">for</span> <span class="p">(</span><span class="n">model_index</span><span class="p">,</span> <span class="n">assets</span><span class="p">)</span> <span class="k">in</span> <span class="n">assets_definitions</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">asset_def</span> <span class="k">in</span> <span class="n">assets</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
                <span class="n">sprite_name</span><span class="p">,</span>
                <span class="n">grid_offset</span><span class="p">,</span>
                <span class="n">offset</span><span class="p">,</span>
                <span class="n">tile_type</span><span class="p">,</span> <span class="c1">// Line update alert</span>
            <span class="p">}</span> <span class="o">=</span> <span class="n">asset_def</span><span class="p">;</span>

            <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">)</span> <span class="o">=</span> <span class="n">TILEMAP</span><span class="nf">.sprite_index</span><span class="p">(</span><span class="n">sprite_name</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nd">panic!</span><span class="p">(</span><span class="s">"Unknown atlas sprite '{}'"</span><span class="p">,</span> <span class="n">sprite_name</span><span class="p">);</span>
            <span class="p">};</span>

            <span class="c1">// Create the spawner function that adds components</span>
            <span class="k">let</span> <span class="n">spawner</span> <span class="o">=</span> <span class="nf">create_spawner</span><span class="p">(</span><span class="n">tile_type</span><span class="p">);</span> <span class="c1">// Line update alert</span>

            <span class="n">models_assets</span><span class="nf">.add</span><span class="p">(</span>
                <span class="n">model_index</span><span class="p">,</span>
                <span class="n">ModelAsset</span> <span class="p">{</span>
                    <span class="n">assets_bundle</span><span class="p">:</span> <span class="n">tilemap_handles</span><span class="nf">.sprite</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">),</span>
                    <span class="n">grid_offset</span><span class="p">,</span>
                    <span class="n">world_offset</span><span class="p">:</span> <span class="n">offset</span><span class="p">,</span>
                    <span class="n">spawn_commands</span><span class="p">:</span> <span class="n">spawner</span><span class="p">,</span> <span class="c1">// Line update alert</span>
                <span class="p">},</span>
            <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">models_assets</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now add the <code class="language-plaintext highlighter-rouge">create_spawner</code> function that generates the right component insertion based on tile type:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs - Add create_spawner function</span>

<span class="k">fn</span> <span class="nf">create_spawner</span><span class="p">(</span>
    <span class="n">tile_type</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">TileType</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="k">fn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">tile_type</span> <span class="p">{</span>
        <span class="c1">// Tile types without pickable</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Dirt</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Dirt</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">YellowGrass</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">YellowGrass</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Shore</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Shore</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Empty</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">e</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{</span>
            <span class="n">e</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">TileMarker</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Empty</span><span class="p">));</span>
        <span class="p">},</span>
        <span class="c1">// Default: no components</span>
        <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">|</span><span class="n">_</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">|</span> <span class="p">{},</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="updating-map-rules-with-tile-types">Updating Map Rules with Tile Types</h3>

<p>Now update <code class="language-plaintext highlighter-rouge">src/map/rules.rs</code> to specify tile types for each spawnable asset. Add the import at the top:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Updated imports</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">TileType</span><span class="p">;</span> <span class="c1">//Line update alert</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">assets</span><span class="p">::</span><span class="n">SpawnableAsset</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">models</span><span class="p">::</span><span class="n">TerrainModelBuilder</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">sockets</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
</code></pre></div></div>

<p>Now update each layer function. Here’s the dirt layer:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Updated build_dirt_layer</span>
<span class="k">fn</span> <span class="nf">build_dirt_layer</span><span class="p">(</span>
    <span class="n">terrain_model_builder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">TerrainModelBuilder</span><span class="p">,</span>
    <span class="n">terrain_sockets</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TerrainSockets</span><span class="p">,</span>
    <span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.layer_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="c1">//Line update alert</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"dirt"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Dirt</span><span class="p">)],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="mf">20.</span><span class="p">);</span>

    <span class="n">socket_collection</span><span class="nf">.add_connections</span><span class="p">(</span><span class="nd">vec!</span><span class="p">[(</span>
        <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">],</span>
    <span class="p">)]);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The grass layer (showing the main tile and one corner as example), you will need to repeat this to all other grass corner and side tiles.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Updated build_grass_layer (partial)</span>
<span class="c1">// Main grass tile</span>
<span class="n">terrain_model_builder</span>
    <span class="nf">.create_model</span><span class="p">(</span>
        <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Multiple</span> <span class="p">{</span>
            <span class="n">x_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
            <span class="n">x_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
            <span class="n">z_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span>
                <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
                <span class="n">terrain_sockets</span><span class="py">.grass.grass_fill_up</span><span class="p">,</span>
            <span class="p">],</span>
            <span class="n">z_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">],</span>
            <span class="n">y_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
            <span class="n">y_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
        <span class="p">},</span>
        <span class="c1">//Line update alert</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)],</span>
    <span class="p">)</span>
    <span class="nf">.with_weight</span><span class="p">(</span><span class="mf">5.</span><span class="p">);</span>

<span class="c1">// Outer corners</span>
<span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
    <span class="n">green_grass_corner_out</span><span class="nf">.clone</span><span class="p">(),</span>
    <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_out_tl"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)],</span>
<span class="p">);</span>
<span class="c1">// ... repeat for all corner and side variants with .with_tile_type(TileType::Grass)</span>
</code></pre></div></div>

<p>Ensure to repeat for all corner and side variants with <code class="language-plaintext highlighter-rouge">.with_tile_type(TileType::Grass)</code>, if you are confused check the github repo for the full code.</p>

<p>The yellow grass layer follows the same pattern:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Main yellow grass tile</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">YellowGrass</span><span class="p">)]</span>

<span class="c1">// All yellow grass corners and sides</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_out_tl"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">YellowGrass</span><span class="p">)]</span>
<span class="c1">// ... etc</span>
</code></pre></div></div>

<p>Ensure to repeat for all corner and side variants with <code class="language-plaintext highlighter-rouge">.with_tile_type(TileType::YellowGrass)</code>.</p>

<p>The water layer:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Main water tile</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">)]</span>

<span class="c1">// All water corners and sides</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_out_tl"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Water</span><span class="p">)]</span>
<span class="c1">// ... etc</span>
</code></pre></div></div>

<p>Ensure to repeat for all corner and side variants with <code class="language-plaintext highlighter-rouge">.with_tile_type(TileType::Water)</code>.</p>

<p>The props layer has mixed tile types. Trees and rocks block movement, plants are walkable:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Updated build_props_layer (key examples)</span>

<span class="c1">// Small tree - bottom blocks, top is walkable (canopy)</span>
<span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
    <span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span>
    <span class="nd">vec!</span><span class="p">[</span>
        <span class="c1">// Line update alert</span>
        <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"small_tree_bottom"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">),</span>
        <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"small_tree_top"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
    <span class="p">],</span>
<span class="p">);</span>

<span class="c1">// Big tree 1 - left side (base blocks, canopy walkable)</span>
<span class="nd">vec!</span><span class="p">[</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_bl"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">),</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_tl"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
<span class="p">]</span>

<span class="c1">// Big tree 1 - right side</span>
<span class="nd">vec!</span><span class="p">[</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_br"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">),</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_tr"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
<span class="p">]</span>

<span class="c1">// Big tree 2 - left side</span>
<span class="nd">vec!</span><span class="p">[</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_bl"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">),</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_tl"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
<span class="p">]</span>

<span class="c1">// Big tree 2 - right side</span>
<span class="nd">vec!</span><span class="p">[</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_br"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">),</span>
    <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_tr"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
<span class="p">]</span>

<span class="c1">// Tree stumps block movement</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"tree_stump_1"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"tree_stump_2"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"tree_stump_3"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Tree</span><span class="p">)]</span>

<span class="c1">// Rocks block movement</span>
<span class="c1">// Lines updated</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_1"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_2"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_3"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_4"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Rock</span><span class="p">)]</span>

<span class="c1">// Plants are walkable (grass tile type)</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_1"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_2"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_3"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)]</span>
<span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_4"</span><span class="p">)</span><span class="nf">.with_tile_type</span><span class="p">(</span><span class="nn">TileType</span><span class="p">::</span><span class="n">Grass</span><span class="p">)]</span>
</code></pre></div></div>

<p>Notice tree canopies (the “top” parts) don’t have a tile type. This is intentional: the player can walk under tree canopies but collides with the trunk.</p>

<h3 id="centralizing-configuration">Centralizing Configuration</h3>

<p>Before we create the player’s collider, let’s centralize our magic numbers. Scattered constants make tuning gameplay tedious. Create a single configuration file:</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/config.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/config.rs</span>
<span class="cd">//! Centralized configuration constants for the game.</span>

<span class="cd">/// Player-related configuration</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">player</span> <span class="p">{</span>
    <span class="cd">/// Collision radius for the player's collider (in world units)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">COLLIDER_RADIUS</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">16.0</span><span class="p">;</span>
    
    
    <span class="cd">/// Z-position for player rendering (above terrain, below UI)</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">PLAYER_Z_POSITION</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">20.0</span><span class="p">;</span>
    
    <span class="cd">/// Visual scale of the player sprite</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">PLAYER_SCALE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.8</span><span class="p">;</span>
<span class="p">}</span>

<span class="cd">/// Map/terrain configuration</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">map</span> <span class="p">{</span>
    <span class="cd">/// Size of a single tile in world units</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">TILE_SIZE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">32.0</span><span class="p">;</span>
    
    <span class="cd">/// Grid dimensions</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">GRID_X</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">25</span><span class="p">;</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="n">GRID_Y</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>These constants define our collision radius (16 pixels) and map dimensions. Having them in one place means you can tweak collision behavior without hunting through multiple files.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_6edcb041817ce22de8e1ea39a082069a.svg" alt="Comic Panel" class="comic-image" data-comic-hash="6edcb041817ce22de8e1ea39a082069a" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>Add this module to <code class="language-plaintext highlighter-rouge">src/main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs</span>
<span class="k">mod</span> <span class="n">config</span><span class="p">;</span>  <span class="c1">// Add this line</span>
</code></pre></div></div>

<h3 id="creating-the-player-collider">Creating the Player Collider</h3>

<p>Now we need a component that represents the player’s collision shape. We’ll use a circle positioned at the player’s center.</p>

<p>Create <code class="language-plaintext highlighter-rouge">src/characters/collider.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/collider.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">collision</span><span class="p">::</span><span class="n">CollisionMap</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">physics</span><span class="p">::</span><span class="n">Velocity</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">player</span><span class="p">::{</span><span class="n">COLLIDER_RADIUS</span><span class="p">};</span>

<span class="cd">/// A circular collider for collision detection.</span>
<span class="cd">/// </span>
<span class="cd">/// The collider position is offset from the entity's transform,</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Collider</span> <span class="p">{</span>
    <span class="cd">/// Radius of the circular collider in world units</span>
    <span class="k">pub</span> <span class="n">radius</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="cd">/// Offset from entity center</span>
    <span class="k">pub</span> <span class="n">offset</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">Collider</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">radius</span><span class="p">:</span> <span class="n">COLLIDER_RADIUS</span><span class="p">,</span>
            <span class="n">offset</span><span class="p">:</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Collider</span> <span class="p">{</span>
    <span class="cd">/// Get the world position of this collider given an entity's transform.</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">world_position</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">transform</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Transform</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
        <span class="n">transform</span><span class="py">.translation</span><span class="nf">.truncate</span><span class="p">()</span> <span class="o">+</span> <span class="k">self</span><span class="py">.offset</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="validating-movement-against-collision">Validating Movement Against Collision</h3>

<p>Now the crucial system: checking if the player can actually move where they want to go.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/characters/collider.rs</span>

<span class="cd">/// System that validates movement against the collision map.</span>
<span class="cd">/// </span>
<span class="cd">/// Runs after input (which sets velocity) but before physics (which applies velocity).</span>
<span class="cd">/// Modifies velocity to prevent movement into unwalkable tiles.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">validate_movement</span><span class="p">(</span>
    <span class="n">map</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CollisionMap</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Transform</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Velocity</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">Collider</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">=</span> <span class="n">map</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">};</span>

    <span class="k">for</span> <span class="p">(</span><span class="n">transform</span><span class="p">,</span> <span class="k">mut</span> <span class="n">velocity</span><span class="p">,</span> <span class="n">collider</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Skip if not moving</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">velocity</span><span class="nf">.is_moving</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// Current collider position</span>
        <span class="k">let</span> <span class="n">current_pos</span> <span class="o">=</span> <span class="n">collider</span><span class="nf">.world_position</span><span class="p">(</span><span class="n">transform</span><span class="p">);</span>
        
        <span class="c1">// Desired new position based on velocity</span>
        <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">velocity</span><span class="na">.0</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">desired_pos</span> <span class="o">=</span> <span class="n">current_pos</span> <span class="o">+</span> <span class="n">delta</span><span class="p">;</span>

        <span class="c1">// Use swept collision to find valid position</span>
        <span class="k">let</span> <span class="n">valid_pos</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.sweep_circle</span><span class="p">(</span><span class="n">current_pos</span><span class="p">,</span> <span class="n">desired_pos</span><span class="p">,</span> <span class="n">collider</span><span class="py">.radius</span><span class="p">);</span>

        <span class="c1">// Calculate what velocity would get us to valid_pos</span>
        <span class="k">let</span> <span class="n">actual_delta</span> <span class="o">=</span> <span class="n">valid_pos</span> <span class="o">-</span> <span class="n">current_pos</span><span class="p">;</span>
        
        <span class="c1">// Only update velocity if collision modified our path</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">actual_delta</span> <span class="o">-</span> <span class="n">delta</span><span class="p">)</span><span class="nf">.length_squared</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mf">0.001</span> <span class="p">{</span>
            <span class="c1">// Convert position delta back to velocity</span>
            <span class="k">let</span> <span class="n">dt</span> <span class="o">=</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
            <span class="k">if</span> <span class="n">dt</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span>
                <span class="n">velocity</span><span class="na">.0</span> <span class="o">=</span> <span class="n">actual_delta</span> <span class="o">/</span> <span class="n">dt</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How this works:</strong></p>

<ol>
  <li>Get the current collider position</li>
  <li>Calculate where the player <em>wants</em> to go based on their velocity</li>
  <li>Use <code class="language-plaintext highlighter-rouge">sweep_circle</code> to find the furthest valid position along that path</li>
  <li>If collision blocked part of the movement, adjust velocity accordingly</li>
</ol>

<p>The key insight: we don’t block movement entirely. If the player tries to walk diagonally into a wall, <code class="language-plaintext highlighter-rouge">sweep_circle</code> might allow sliding along the wall’s surface. This feels much better than getting stuck.</p>

<h3 id="updating-the-characters-module">Updating the Characters Module</h3>

<p>Add the collider module to <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs - Add at the top</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">collider</span><span class="p">;</span>  <span class="c1">// Add this line</span>
</code></pre></div></div>

<p>Now update the system registration. We need <code class="language-plaintext highlighter-rouge">validate_movement</code> to run <strong>after</strong> input sets velocity but <strong>before</strong> physics applies it:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs - Update the system chain</span>
<span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
    <span class="c1">// 1. Input determines state + velocity + facing</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">handle_player_input</span><span class="p">,</span>
    <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
    <span class="nn">input</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
    
    <span class="c1">// 2. State changes trigger animation updates</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">on_state_change_update_animation</span><span class="p">,</span>
    
    <span class="c1">// 3. Collision validation adjusts velocity  &lt;-- Line update alert!</span>
    <span class="nn">collider</span><span class="p">::</span><span class="n">validate_movement</span><span class="p">,</span>
    
    <span class="c1">// 4. Physics applies velocity to transform</span>
    <span class="nn">physics</span><span class="p">::</span><span class="n">apply_velocity</span><span class="p">,</span>
    
    <span class="c1">// 5. Animation ticks frames</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">tick_animations</span><span class="p">,</span>
<span class="p">)</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)));</span>
</code></pre></div></div>

<h3 id="attaching-collider-to-the-player">Attaching Collider to the Player</h3>

<p>Finally, update <code class="language-plaintext highlighter-rouge">src/characters/spawn.rs</code> to attach the <code class="language-plaintext highlighter-rouge">Collider</code> component when initializing the player.</p>

<p>Update the imports:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs - Add import</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">collider</span><span class="p">::</span><span class="n">Collider</span><span class="p">;</span> <span class="c1">// Line update alert!</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">player</span><span class="p">::{</span><span class="n">PLAYER_SCALE</span><span class="p">,</span> <span class="n">PLAYER_Z_POSITION</span><span class="p">};</span> <span class="c1">// Line update alert!</span>
</code></pre></div></div>

<p>Also remove these existing constants since we are importing the constants from the config.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Delete these lines</span>
<span class="c1">// const PLAYER_SCALE: f32 = 0.8;</span>
<span class="c1">// const PLAYER_Z_POSITION: f32 = 20.0;</span>
</code></pre></div></div>

<p>Then in <code class="language-plaintext highlighter-rouge">initialize_player_character</code>, add <code class="language-plaintext highlighter-rouge">Collider::default()</code> to the component bundle:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs - In initialize_player_character</span>
<span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.insert</span><span class="p">((</span>
    <span class="nn">AnimationController</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="nn">CharacterState</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="nn">Velocity</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="nn">Facing</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="nn">Collider</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>  <span class="c1">// Line update alert!</span>
    <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">)),</span>
    <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">(),</span>
    <span class="n">sprite</span><span class="p">,</span>
<span class="p">));</span>
</code></pre></div></div>

<p>Now run the game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>Walk your character around. You should now collide with trees, rocks, and water! The character slides smoothly along obstacles rather than getting stuck. Press F3 to toggle the debug overlay and see the collision map visualized.</p>

<h2 id="the-illusion-of-depth">The Illusion of Depth</h2>

<p>There’s one more problem. Walk your character behind a tree. Notice anything odd?</p>

<p>The player always renders on top. They don’t disappear behind the tree trunk. In a top-down game, objects higher on the screen (further away from you) should appear “behind” objects lower on the screen (closer to you). When your character walks upward and passes behind a tree, they should disappear behind it.</p>

<p>This is <strong>Y-based depth sorting</strong>. The concept is simple: objects with a lower Y position (closer to the bottom of the screen) are “in front” of objects with a higher Y position.</p>

<p>In 2D rendering, Z determines draw order. Higher Z draws on top. So we need:</p>
<ul>
  <li><strong>Higher Y</strong> (top of screen) → <strong>Lower Z</strong> (drawn first, appears behind)</li>
  <li><strong>Lower Y</strong> (bottom of screen) → <strong>Higher Z</strong> (drawn last, appears in front)</li>
</ul>

<p>Our tilemap generator already does this for tiles using <code class="language-plaintext highlighter-rouge">with_z_offset_from_y(true)</code>. But the player’s Z position is fixed! We need to update the player’s Z dynamically based on their Y position.</p>

<h3 id="creating-the-rendering-module">Creating the Rendering Module</h3>

<p>Create <code class="language-plaintext highlighter-rouge">src/characters/rendering.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/rendering.rs</span>
<span class="cd">//! Rendering utilities for depth sorting.</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">input</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">map</span><span class="p">::{</span><span class="n">GRID_Y</span><span class="p">,</span> <span class="n">TILE_SIZE</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">config</span><span class="p">::</span><span class="nn">player</span><span class="p">::</span><span class="n">PLAYER_SCALE</span><span class="p">;</span>

<span class="cd">/// Z-depth constants for proper layering.</span>
<span class="cd">/// The tilemap uses `with_z_offset_from_y(true)` which assigns Z based on Y position.</span>
<span class="cd">/// We need to match this formula for the player.</span>
<span class="k">const</span> <span class="n">NODE_SIZE_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span>  <span class="c1">// Same as tilemap generator</span>
<span class="k">const</span> <span class="n">PLAYER_BASE_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">4.0</span><span class="p">;</span>  <span class="c1">// Match props layer Z range</span>
<span class="k">const</span> <span class="n">PLAYER_Z_OFFSET</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.5</span><span class="p">;</span>  <span class="c1">// Small offset to stay above ground props</span>
</code></pre></div></div>

<p>These constants need to match how the tilemap generator calculates Z. <code class="language-plaintext highlighter-rouge">PLAYER_BASE_Z</code> positions the player in the same Z range as props (trees, rocks). <code class="language-plaintext highlighter-rouge">PLAYER_Z_OFFSET</code> adds a tiny buffer so the player doesn’t z-fight with ground-level decorations.</p>

<p>Now the depth sorting system. The key is to dynamically adjust the player’s Z position based on their Y position. When the player moves up the screen (higher Y), we lower their Z so they render behind objects. When they move down (lower Y), we raise their Z so they render in front.</p>

<p>But there’s a subtlety: we use the player’s <strong>feet position</strong>, not their sprite center, for depth calculation. Why? Imagine your character standing just in front of a tree. The sprite’s center is at chest level, but visually, what matters is where their feet are. If we used the center, the player’s head might incorrectly poke out above a tree they’re standing in front of. Using feet position makes occlusion look natural.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append to src/rendering.rs</span>

<span class="cd">/// System to update player depth based on Y position.</span>
<span class="cd">/// </span>
<span class="cd">/// Objects with lower Y (further down screen) get higher Z (rendered in front).</span>
<span class="cd">/// This creates proper occlusion when walking behind trees.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_player_depth</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">player_query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span> <span class="p">(</span><span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">Changed</span><span class="o">&lt;</span><span class="n">Transform</span><span class="o">&gt;</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Map dimensions for normalization</span>
    <span class="k">let</span> <span class="n">map_height</span> <span class="o">=</span> <span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_Y</span> <span class="k">as</span> <span class="nb">f32</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">map_y0</span> <span class="o">=</span> <span class="o">-</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_Y</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">;</span>  <span class="c1">// Map origin Y (centered)</span>
    
    <span class="c1">// Player sprite height for feet position calculation</span>
    <span class="k">let</span> <span class="n">player_sprite_height</span> <span class="o">=</span> <span class="mf">64.0</span> <span class="o">*</span> <span class="n">PLAYER_SCALE</span><span class="p">;</span>

    <span class="k">for</span> <span class="k">mut</span> <span class="n">transform</span> <span class="k">in</span> <span class="n">player_query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">player_center_y</span> <span class="o">=</span> <span class="n">transform</span><span class="py">.translation.y</span><span class="p">;</span>

        <span class="c1">// Use player's FEET position for depth sorting (not center)</span>
        <span class="k">let</span> <span class="n">player_feet_y</span> <span class="o">=</span> <span class="n">player_center_y</span> <span class="o">-</span> <span class="p">(</span><span class="n">player_sprite_height</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">);</span>

        <span class="c1">// Normalize feet Y to [0, 1] across the grid height</span>
        <span class="k">let</span> <span class="n">t</span> <span class="o">=</span> <span class="p">((</span><span class="n">player_feet_y</span> <span class="o">-</span> <span class="n">map_y0</span><span class="p">)</span> <span class="o">/</span> <span class="n">map_height</span><span class="p">)</span><span class="nf">.clamp</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>

        <span class="c1">// Y-to-Z formula:</span>
        <span class="c1">// Lower Y (bottom of screen) = higher t = lower Z offset = rendered in front</span>
        <span class="c1">// Higher Y (top of screen) = lower t = higher Z offset = rendered behind</span>
        <span class="k">let</span> <span class="n">player_z</span> <span class="o">=</span> <span class="n">PLAYER_BASE_Z</span> <span class="o">+</span> <span class="n">NODE_SIZE_Z</span> <span class="o">*</span> <span class="p">(</span><span class="mf">1.0</span> <span class="o">-</span> <span class="n">t</span><span class="p">)</span> <span class="o">+</span> <span class="n">PLAYER_Z_OFFSET</span><span class="p">;</span>

        <span class="n">transform</span><span class="py">.translation.z</span> <span class="o">=</span> <span class="n">player_z</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">Changed&lt;Transform&gt;</code>?</strong></p>

<p>This is a Bevy query filter that only matches entities whose <code class="language-plaintext highlighter-rouge">Transform</code> was modified this frame. It’s a performance optimization: we only recalculate Z when the player actually moves. If they’re standing still, the system skips them entirely.</p>

<h3 id="integrating-depth-sorting">Integrating Depth Sorting</h3>

<p>Now, you can add it to the character system chain in <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code> after <code class="language-plaintext highlighter-rouge">physics::apply_velocity</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>

<span class="k">mod</span> <span class="n">rendering</span><span class="p">;</span> <span class="c1">// Line update alert!</span>


<span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
    <span class="c1">// ... existing systems ...</span>
    <span class="nn">physics</span><span class="p">::</span><span class="n">apply_velocity</span><span class="p">,</span>
    <span class="nn">rendering</span><span class="p">::</span><span class="n">update_player_depth</span><span class="p">,</span>  <span class="c1">// Add after physics , line update alert!</span>
    <span class="nn">animation</span><span class="p">::</span><span class="n">tick_animations</span><span class="p">,</span>
<span class="p">)</span><span class="nf">.chain</span><span class="p">()</span><span class="nf">.run_if</span><span class="p">(</span><span class="nf">in_state</span><span class="p">(</span><span class="nn">GameState</span><span class="p">::</span><span class="n">Playing</span><span class="p">)));</span>
</code></pre></div></div>

<h3 id="adjusting-water-generation">Adjusting Water Generation</h3>

<p>Before running the game, make one small tweak to prevent the player from spawning in water. Open <code class="language-plaintext highlighter-rouge">src/map/rules.rs</code> and find the water layer weight:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - In build_water_layer function</span>
<span class="k">const</span> <span class="n">WATER_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.001</span><span class="p">;</span>  <span class="c1">// Reduce from 0.02 to 0.001</span>
</code></pre></div></div>

<p>This reduces water generation, making it less likely the player spawns in an inaccessible area.</p>

<div style="margin: 20px 0; padding: 15px; background-color: #f8d7da; border-radius: 8px; border-left: 4px solid #dc3545;">
<strong>Spawn Troubleshooting:</strong> There's a small chance the procedural generation places the player on top of a blocking object (tree, rock) at spawn. If you can't move when the game starts, simply restart to generate a new map. This is a quirk of random generation we'll address in future chapters.
</div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_70e5635eba23c5a921821f6603cf7d69.svg" alt="Comic Panel" class="comic-image" data-comic-hash="70e5635eba23c5a921821f6603cf7d69" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>Run the game again:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>Now walk behind a tree. Your character should disappear behind the trunk! Walk back down, and they reappear. The illusion of depth makes the world feel three-dimensional even though it’s all 2D sprites.</p>

<p><img src="/assets/book_assets/chapter4/ch4.gif" alt="Collision System Demo" /></p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_09bc748e606596f7d57ea897f9eb8643.svg" alt="Comic Panel" class="comic-image" data-comic-hash="09bc748e606596f7d57ea897f9eb8643" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h2 id="what-we-built">What We Built</h2>

<p>Let’s take a step back and appreciate what we’ve accomplished in this chapter:</p>

<ol>
  <li>
    <p><strong>Game States</strong> - Proper lifecycle management with Loading, Playing, and Paused states. No more polling patterns.</p>
  </li>
  <li>
    <p><strong>Character States</strong> - An enum-based state machine that prevents impossible states and simplifies animation logic.</p>
  </li>
  <li>
    <p><strong>Collision System</strong> - A tile-based collision map with circle colliders, swept collision for smooth wall-sliding, and shore detection for water edges.</p>
  </li>
  <li>
    <p><strong>Debug Visualization</strong> - Press F3 to see the collision map, player collider, and current grid cell.</p>
  </li>
  <li>
    <p><strong>Depth Sorting</strong> - Y-based rendering that creates the illusion of walking behind objects.</p>
  </li>
</ol>

<p>Your character can now walk through a procedurally generated world, colliding realistically with obstacles, sliding around corners, and disappearing behind trees. That’s a lot of systems working together!</p>

<p>In the next chapter, we’ll add interactivity: picking up items and building an inventory system. Until then, experiment with the collision parameters, try different collider sizes, and see how the debug visualization helps you understand what’s happening under the hood.</p>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Let's make the player interact with the world properly, no more walking through trees, water, or rocks.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aibodh.com/assets/book_assets/chapter4/ch4.gif" /><media:content medium="image" url="https://aibodh.com/assets/book_assets/chapter4/ch4.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 3 - Let The Data Flow</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-3/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 3 - Let The Data Flow" /><published>2025-11-19T10:00:00+00:00</published><updated>2025-11-19T10:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-3</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-3/"><![CDATA[<style>
.tile-image {
  margin: 0 !important;
  object-fit: none !important;
  cursor: default !important;
  pointer-events: none !important;
}
</style>

<p>I was supposed to release this on Halloween, but my code was so scary it kept running away. 🎃 Now that we’ve debugged the ghosts in the machine, let’s begin!</p>

<p>By the end of this tutorial, you’ll have a flexible, data-driven character system that supports character switching, multiple animation types (Walk, Run, Jump) configured through a data file.</p>

<blockquote>
  <p><strong>Prerequisites</strong>: This is Chapter 3 of our Bevy tutorial series. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> for updates on new releases. Before starting, complete <a href="/posts/bevy-rust-game-development-chapter-1/">Chapter 1: Let There Be a Player</a> and <a href="/posts/bevy-rust-game-development-chapter-2/">Chapter 2: Let There Be a World</a>, or clone the Chapter 2 code from <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">this repository</a> to follow along.</p>
</blockquote>

<p><img src="/assets/book_assets/chapter3/chapter3.gif" alt="Animation System Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<strong>Before We Begin:</strong> <em style="font-size: 14px;">I'm constantly working to improve this tutorial and make your learning journey enjoyable. Your feedback matters - share your frustrations, questions, or suggestions on <a href="https://www.reddit.com/r/rust/comments/1p46n9c/the_impatient_programmers_guide_to_bevy_and_rust/" target="_blank">Reddit</a>/<a target="_blank" href="https://discord.com/invite/cD9qEsSjUH">Discord</a>/<a href="https://www.linkedin.com/posts/febinjohnjames_chapter-3-let-the-data-flow-continuing-activity-7397051447600664576-C3Bd/?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAlx1JIBRLKRFr1OsUTf1LYBNPYbgdfxjbc" target="_blank">LinkedIn</a>. Loved it? Let me know what worked well for you! Together, we'll make game development with Rust and Bevy more accessible for everyone.</em>
</div>

<h2 id="the-problem-with-hardcoded-characters">The Problem with Hardcoded Characters</h2>

<p>In Chapter 1, we built a player with movement and animation, however <strong>everything was hardcoded and tightly coupled</strong>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code warning, don't use</span>
<span class="c1">// From Chapter 1 player.rs - Everything is hardcoded!</span>
<span class="k">const</span> <span class="n">TILE_SIZE</span> <span class="o">...</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span>        <span class="c1">// ← Hardcoded sprite size</span>
<span class="k">const</span> <span class="n">WALK_FRAMES</span> <span class="o">...</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>     <span class="c1">// ← Hardcoded frame count</span>
<span class="k">const</span> <span class="n">MOVE_SPEED</span> <span class="o">...</span> <span class="o">=</span> <span class="mf">140.0</span><span class="p">;</span>    <span class="c1">// ← Hardcoded movement speed</span>
<span class="k">const</span> <span class="n">ANIM_DT</span> <span class="o">...</span> <span class="o">=</span> <span class="mf">0.1</span><span class="p">;</span>         <span class="c1">// ← Hardcoded animation timing</span>
</code></pre></div></div>

<h3 id="maintenance-nightmare">Maintenance Nightmare</h3>

<p>Let’s see what happens when you add a second character. You’d need to duplicate everything:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code warning, don't use</span>
<span class="c1">// First character</span>
<span class="k">const</span> <span class="n">WARRIOR_TILE_SIZE</span> <span class="o">...</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span>
<span class="k">const</span> <span class="n">WARRIOR_WALK_FRAMES</span> <span class="o">...</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>
<span class="k">const</span> <span class="n">WARRIOR_MOVE_SPEED</span> <span class="o">...</span> <span class="o">=</span> <span class="mf">140.0</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">spawn_warrior</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* 50 lines of code */</span> <span class="p">}</span>
<span class="k">fn</span> <span class="nf">animate_warrior</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* 80 lines of animation logic */</span> <span class="p">}</span>
<span class="k">fn</span> <span class="nf">warrior_row_zero_based</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* row mapping */</span> <span class="p">}</span>

<span class="c1">// Second character - DUPLICATE ALL THE CODE</span>
<span class="k">const</span> <span class="n">MAGE_TILE_SIZE</span> <span class="o">...</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span>
<span class="k">const</span> <span class="n">MAGE_WALK_FRAMES</span> <span class="o">...</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
<span class="k">const</span> <span class="n">MAGE_MOVE_SPEED</span> <span class="o">...</span> <span class="o">=</span> <span class="mf">100.0</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">spawn_mage</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* 50 lines of IDENTICAL code */</span> <span class="p">}</span>
<span class="k">fn</span> <span class="nf">animate_mage</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* 80 lines of IDENTICAL animation logic */</span> <span class="p">}</span>
<span class="k">fn</span> <span class="nf">mage_row_zero_based</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* different row mapping */</span> <span class="p">}</span>
</code></pre></div></div>

<p>Now imagine you discover a bug in your animation system. You need to fix it in:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">animate_warrior()</code></li>
  <li><code class="language-plaintext highlighter-rouge">animate_mage()</code></li>
  <li><code class="language-plaintext highlighter-rouge">animate_rogue()</code></li>
  <li><code class="language-plaintext highlighter-rouge">animate_paladin()</code></li>
  <li>…and 6 more character functions</li>
</ul>

<p>Miss one? That character breaks. Want to add a “jump” animation? Update 10 functions. Want to change how movement works? Touch every single character’s code.</p>

<p>This is the copy-paste maintenance nightmare you want to avoid.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_d41aef861aa6f0c9133ed868a568000a.svg" alt="Comic Panel" class="comic-image" data-comic-hash="d41aef861aa6f0c9133ed868a568000a" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="data-driven-design">Data-Driven Design</h3>

<p>The solution lies in <strong>data-oriented programming</strong>, a design approach where we <strong>separate what things are (data) from what they do (behavior)</strong>.</p>

<p>Instead of tightly coupling character attributes with character-specific code, we:</p>

<p><strong>1. Separate Data from Code</strong></p>

<p>Move character properties into a single external <code class="language-plaintext highlighter-rouge">.ron</code> configuration file:</p>

<pre><code class="language-ron">// characters.ron, All characters in one file!
(
    characters: [
        (
            name: "Warrior",
            base_move_speed: 140.0,
            max_health: 150.0,
            animations: {...}
        ),
        (
            name: "Mage",
            base_move_speed: 100.0,
            max_health: 80.0,
            animations: {...}
        ),
    ]
)
</code></pre>

<p><strong>What’s a <code class="language-plaintext highlighter-rouge">.ron</code> file?</strong></p>

<p>RON stands for <strong>Rusty Object Notation</strong>, a data format similar to JSON but designed for Rust. It’s human-readable, supports Rust types like tuples and structs, and allows comments. Think of it as JSON that feels native to Rust developers.</p>

<table>
  <thead>
    <tr>
      <th>JSON</th>
      <th>RON</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Requires quotes on every key</td>
      <td>Optional quotes for simple identifiers</td>
    </tr>
    <tr>
      <td>No comment support</td>
      <td>Inline and multiline comments, document your data directly</td>
    </tr>
    <tr>
      <td>Trailing commas cause syntax errors</td>
      <td>Trailing commas allowed</td>
    </tr>
    <tr>
      <td>Limited to JavaScript types</td>
      <td>Native Rust types (tuples, structs, enums), matches your code</td>
    </tr>
  </tbody>
</table>

<p>RON eliminates JSON’s verbosity while adding features that Rust developers need, making it ideal for game configuration.</p>

<p><strong>2. Write Systems that Operate on Data</strong></p>

<p>Build generic systems that work with <strong>any</strong> character data:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code warning, don't use</span>
<span class="c1">// Before: Code + Data mixed together</span>
<span class="k">fn</span> <span class="nf">animate_warrior</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* hardcoded warrior logic */</span> <span class="p">}</span>
<span class="k">fn</span> <span class="nf">animate_mage</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* hardcoded mage logic */</span> <span class="p">}</span>

<span class="c1">// After: Flexible system that operates on data</span>
<span class="k">fn</span> <span class="nf">animate_characters</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> 
    <span class="c1">// Reads character data and animates accordingly</span>
    <span class="c1">// Works for warrior, mage, rogue, or any future character!</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Benefits of Separation</strong></p>

<p>When data lives separately from code, the same animation system adapts to any character automatically.</p>

<p>Fix a bug? <strong>One place</strong>. Change animation speeds or frame counts? <strong>Update the data file, no code changes</strong>. Switch characters? <strong>Load different data from the file</strong>.</p>

<p>But the benefits extend far beyond maintenance. This separation helps with powerful capabilities that would be difficult or impossible with hardcoded values.</p>

<p>You can load characters from a network server at runtime, enabling downloadable content and live game updates without redistributing your entire game binary.</p>

<p>Players can create their own custom characters by simply editing the <code class="language-plaintext highlighter-rouge">.ron</code> file, opening the door to user-generated content.</p>

<p><strong>What We’ll Build in This Chapter:</strong></p>

<p>While the data-driven approach opens up all these possibilities, we’ll start with the foundation:</p>
<ol>
  <li><strong>Creating &amp; loading external <code class="language-plaintext highlighter-rouge">.ron</code> file</strong> containing all character data</li>
  <li><strong>Generic animation system</strong> that works with any character</li>
  <li><strong>Runtime character switching</strong> with number keys (1-6)</li>
  <li><strong>Multiple animation types</strong> (Walk, Run, Jump) per character</li>
</ol>

<p>Ready to build this data-driven character system? Let’s dive in!</p>

<h2 id="setting-up-the-characters-module">Setting Up the Characters Module</h2>

<p>Find the Chapter 3 project files from the <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust/tree/main/chapter3/src/assets">repo</a> and copy these spritesheets into your project’s <code class="language-plaintext highlighter-rouge">src/assets/</code> directory:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>male_spritesheet.png
female_spritesheet.png
crimson_count_spritesheet.png
graveyard_reaper_spritesheet.png
lantern_warden_spritesheet.png
starlit_oracle_spritesheet.png
</code></pre></div></div>

<p><img src="/assets/book_assets/chapter3/sprite_sheets.gif" alt="Sprite Sheets" /></p>

<h3 id="character-schema">Character Schema</h3>
<p>Every entry in <code class="language-plaintext highlighter-rouge">characters.ron</code> follows the same structure:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">name</code>: Identifier that shows up in logs/UI.</li>
  <li><code class="language-plaintext highlighter-rouge">max_health</code>, <code class="language-plaintext highlighter-rouge">base_move_speed</code>, <code class="language-plaintext highlighter-rouge">run_speed_multiplier</code>: Gameplay attributes.</li>
  <li><code class="language-plaintext highlighter-rouge">texture_path</code>: Which spritesheet to load.</li>
  <li><code class="language-plaintext highlighter-rouge">tile_size</code>: Each frame’s width/height in pixels.</li>
  <li><code class="language-plaintext highlighter-rouge">atlas_columns</code>: How many columns exist in the spritesheet grid.</li>
  <li><code class="language-plaintext highlighter-rouge">animations</code>: Map where the key is an <code class="language-plaintext highlighter-rouge">AnimationType</code> (<code class="language-plaintext highlighter-rouge">Walk</code>, <code class="language-plaintext highlighter-rouge">Run</code>, <code class="language-plaintext highlighter-rouge">Jump</code>) and the value is:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">start_row</code>: Row number in the spritesheet grid, counting from 0 at the top.</li>
      <li><code class="language-plaintext highlighter-rouge">frame_count</code>: Number of frames for that animation.</li>
      <li><code class="language-plaintext highlighter-rouge">frame_time</code>: Seconds per frame.</li>
      <li><code class="language-plaintext highlighter-rouge">directional</code>: <code class="language-plaintext highlighter-rouge">true</code> when the spritesheet contains four direction rows (Up, Left, Down, Right) stacked vertically for that animation. If <code class="language-plaintext highlighter-rouge">false</code>, Bevy uses the same row regardless of facing direction.</li>
    </ul>
  </li>
</ul>

<p>Create a folder <code class="language-plaintext highlighter-rouge">src/assets/characters/</code> and copy the <code class="language-plaintext highlighter-rouge">characters.ron</code> file from the <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust/blob/main/chapter3/src/assets/characters/characters.ron">repo</a> and add place it inside the folder. It includes data for all 6 characters.</p>

<h3 id="setting-up-the-config">Setting Up the Config</h3>

<p>With our data file in place, we need code that reads it and spawns the player. We’re going to replace the Chapter 1 <code class="language-plaintext highlighter-rouge">player.rs</code> with a new <code class="language-plaintext highlighter-rouge">characters</code> module.</p>

<p>Hence delete <code class="language-plaintext highlighter-rouge">src/player.rs</code> and remove <code class="language-plaintext highlighter-rouge">mod player;</code> and <code class="language-plaintext highlighter-rouge">PlayerPlugin</code> usage from <code class="language-plaintext highlighter-rouge">main.rs</code>.</p>

<p>Create a new folder <code class="language-plaintext highlighter-rouge">src/characters/</code> and create the file <code class="language-plaintext highlighter-rouge">config.rs</code> inside it.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>characters/
├── config.rs
</code></pre></div></div>

<p>First we need to define what type of animation are possible. Presently we only support <code class="language-plaintext highlighter-rouge">Walk</code>, <code class="language-plaintext highlighter-rouge">Run</code> and <code class="language-plaintext highlighter-rouge">Jump</code> animations. <code class="language-plaintext highlighter-rouge">AnimationDefinition</code> captures where each animation lives in the spritesheet, how many frames it has, and how fast it should play.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// characters/config.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">serde</span><span class="p">::{</span><span class="n">Deserialize</span><span class="p">,</span> <span class="n">Serialize</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::</span><span class="n">HashMap</span><span class="p">;</span>

<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Hash,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">AnimationType</span> <span class="p">{</span>
    <span class="n">Walk</span><span class="p">,</span>
    <span class="n">Run</span><span class="p">,</span>
    <span class="n">Jump</span>
<span class="p">}</span>

<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationDefinition</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">start_row</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">frame_count</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">frame_time</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">directional</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="c1">// true = 4 rows (one per direction), false = 1 row</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">Hash</code>, <code class="language-plaintext highlighter-rouge">Serialize</code>, and <code class="language-plaintext highlighter-rouge">Deserialize</code>?</strong><br />
<code class="language-plaintext highlighter-rouge">Hash</code> lets us use <code class="language-plaintext highlighter-rouge">AnimationType</code> as a key inside <code class="language-plaintext highlighter-rouge">HashMap</code>, so retrieving the settings for <code class="language-plaintext highlighter-rouge">AnimationType::Run</code> is just a dictionary lookup. <code class="language-plaintext highlighter-rouge">Serialize</code> and <code class="language-plaintext highlighter-rouge">Deserialize</code> allow Rust to turn these structs into <code class="language-plaintext highlighter-rouge">.ron</code> text (and back) automatically when you load or save character data.</p>

<p>With the animation schema in place, we can define <code class="language-plaintext highlighter-rouge">CharacterEntry</code>, the asset we load from <code class="language-plaintext highlighter-rouge">characters.ron</code>. It bundles character attributes, sprite metadata, and the animation map so every system pulls the info it needs from a single struct record.</p>

<p>Append the following code to <code class="language-plaintext highlighter-rouge">characters/config.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to characters/config.rs</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Asset,</span> <span class="nd">TypePath,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CharacterEntry</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">name</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">max_health</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">base_move_speed</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">run_speed_multiplier</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">texture_path</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">tile_size</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">atlas_columns</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">animations</span><span class="p">:</span> <span class="n">HashMap</span><span class="o">&lt;</span><span class="n">AnimationType</span><span class="p">,</span> <span class="n">AnimationDefinition</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">CharacterEntry</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">calculate_max_animation_row</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.animations</span>
            <span class="nf">.values</span><span class="p">()</span>
            <span class="nf">.map</span><span class="p">(|</span><span class="n">def</span><span class="p">|</span> <span class="k">if</span> <span class="n">def</span><span class="py">.directional</span> <span class="p">{</span> <span class="n">def</span><span class="py">.start_row</span> <span class="o">+</span> <span class="mi">3</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">def</span><span class="py">.start_row</span> <span class="p">})</span>
            <span class="nf">.max</span><span class="p">()</span>
            <span class="nf">.unwrap_or</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[derive(Asset,</span> <span class="nd">TypePath,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CharactersList</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">characters</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">CharacterEntry</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">calculate_max_animation_row</code> helper inspects every animation definition to figure out how many rows the texture atlas needs.</p>

<p>Directional animations like <code class="language-plaintext highlighter-rouge">Walk</code> often consume four stacked rows (Up, Left, Down, Right), while others, say a climb animation, may only need a single row regardless of facing. This helper keeps those differences data-driven so atlas loading code can stay generic.</p>

<p><strong>Is <code class="language-plaintext highlighter-rouge">self.animations</code> calling multiple functions sequentially?</strong><br />
Yes! This is called <strong>method chaining</strong>. Each function runs in order: first <code class="language-plaintext highlighter-rouge">.values()</code> gets all animation definitions from the HashMap, then <code class="language-plaintext highlighter-rouge">.map()</code> transforms each one, then <code class="language-plaintext highlighter-rouge">.max()</code> finds the largest value. They execute top-to-bottom, one after another.</p>

<p><strong>What’s map doing, also is that a closure inside map?</strong><br />
<code class="language-plaintext highlighter-rouge">map</code> transforms each item in the collection. Yes, <code class="language-plaintext highlighter-rouge">|def|</code> is a <strong>closure</strong> as we studied in the previous chapter. It takes each animation definition (<code class="language-plaintext highlighter-rouge">def</code>) and calculates its maximum row: if the animation is directional (4 rows), it returns <code class="language-plaintext highlighter-rouge">start_row + 3</code>; otherwise just <code class="language-plaintext highlighter-rouge">start_row</code>. Think of it as “for each animation, calculate its end row.”</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">unwrap_or</code>?</strong><br />
<code class="language-plaintext highlighter-rouge">max()</code> returns an <code class="language-plaintext highlighter-rouge">Option&lt;usize&gt;</code>, it could be <code class="language-plaintext highlighter-rouge">Some(number)</code> if there are animations, or <code class="language-plaintext highlighter-rouge">None</code> if the HashMap is empty. <code class="language-plaintext highlighter-rouge">unwrap_or(0)</code> says “if you got a number, give it to me; if you got <code class="language-plaintext highlighter-rouge">None</code>, use <code class="language-plaintext highlighter-rouge">0</code> instead.” This prevents crashes when a character has no animations defined.</p>

<p><code class="language-plaintext highlighter-rouge">CharactersList</code> groups all your <code class="language-plaintext highlighter-rouge">CharacterEntry</code> configs into one loadable asset, so Bevy can read every character’s data from a single JSON/RON file instead of loading many separate files.</p>

<p><strong>What’s the purpose of using <code class="language-plaintext highlighter-rouge">Asset</code>, <code class="language-plaintext highlighter-rouge">TypePath</code> macros?</strong><br />
<code class="language-plaintext highlighter-rouge">Asset</code> simply tells Bevy “this struct is something you can load from disk and store in the asset server.” <code class="language-plaintext highlighter-rouge">TypePath</code> gives Bevy a unique name for the type so it knows exactly which asset you’re asking for later. Together they turn <code class="language-plaintext highlighter-rouge">CharacterEntry</code>/<code class="language-plaintext highlighter-rouge">CharactersList</code> into first-class loadable data, the same way textures or audio files already work.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">HashMap&lt;AnimationType, AnimationDefinition&gt;</code> doing?</strong><br />
Each character needs different timing and sprite rows for <code class="language-plaintext highlighter-rouge">Walk</code>, <code class="language-plaintext highlighter-rouge">Run</code>, <code class="language-plaintext highlighter-rouge">Jump</code>, etc. The <code class="language-plaintext highlighter-rouge">HashMap</code> is simply a lookup table with key as <code class="language-plaintext highlighter-rouge">AnimationType</code>, so when the animation system asks for <code class="language-plaintext highlighter-rouge">AnimationType::Run</code>, it instantly receives the corresponding <code class="language-plaintext highlighter-rouge">AnimationDefinition</code> (start row, frame count, frame speed, directional flag).</p>

<p>Now that we have a data structure to hold our character information, we need a system to bring it to life.</p>

<h2 id="the-animation-engine">The Animation Engine</h2>

<p>Here we will be building the animation engine to interpret our data structure.</p>

<p>Create a new file <code class="language-plaintext highlighter-rouge">src/characters/animation.rs</code>. This will house the logic that brings our sprites to life.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>characters/
├── config.rs
├── animation.rs  &lt;- Create this
</code></pre></div></div>

<p>Our animation engine needs to help us with the following:</p>

<ol>
  <li><strong>Direction Tracking</strong>: When your character moves right, you want them to face right. When they move up, they should face up. We need a system to convert movement into facing direction.</li>
  <li><strong>State Management</strong>: We need to know <em>when</em> to change animations. Did the player just start running? Just stop? Just jump? These transitions are when we reset the animation.</li>
  <li><strong>Frame Calculation</strong>: Given a character’s current state (“running left”), which exact frame from the spritesheet should we display right now?</li>
</ol>

<p>Let’s build each piece.</p>

<h3 id="direction-tracking">Direction Tracking</h3>

<p><img src="/assets/book_assets/chapter3/walk.gif" alt="Sprite Sheets" /></p>

<p>When your player presses the arrow keys, we get a velocity vector like <code class="language-plaintext highlighter-rouge">Vec2 { x: 1.0, y: 0.0 }</code> for moving right. But our spritesheet doesn’t understand vectors—it has specific rows for Up, Down, Left, and Right animations.</p>

<p>We need to translate “moving in this direction” into “show this specific row of sprites.” That’s what the <code class="language-plaintext highlighter-rouge">Facing</code> enum does.</p>

<p>Add this to <code class="language-plaintext highlighter-rouge">src/characters/animation.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/animation.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">serde</span><span class="p">::{</span><span class="n">Deserialize</span><span class="p">,</span> <span class="n">Serialize</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">config</span><span class="p">::{</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">};</span>

<span class="c1">// Default animation timing (10 FPS = 0.1 seconds per frame)</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.1</span><span class="p">;</span>

<span class="nd">#[derive(Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq,</span> <span class="nd">Hash,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">Facing</span> <span class="p">{</span>
    <span class="n">Up</span><span class="p">,</span>
    <span class="nb">Left</span><span class="p">,</span>
    <span class="n">Down</span><span class="p">,</span>
    <span class="nb">Right</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Facing</span> <span class="p">{</span>
    <span class="c1">// Convert a velocity vector into a discrete direction</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">from_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">:</span> <span class="n">Vec2</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">direction</span><span class="py">.x</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">direction</span><span class="py">.y</span><span class="nf">.abs</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">direction</span><span class="py">.x</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">direction</span><span class="py">.y</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="c1">// Helper to map direction to row offset (0, 1, 2, 3)</span>
    <span class="k">fn</span> <span class="nf">direction_index</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="k">=&gt;</span> <span class="mi">0</span><span class="p">,</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="k">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="k">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
            <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="k">=&gt;</span> <span class="mi">3</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Converting Movement to Facing</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">from_direction</code> function takes a velocity vector and figures out which direction is dominant. If the player is moving diagonally (both x and y are non-zero), we pick the stronger component. Moving mostly right with a bit of up? Face right. Moving mostly up with a bit of right? Face up. This ensures your character always faces the most relevant direction during gameplay.</p>

<p><strong>Mapping Direction to Sprite Rows</strong></p>

<p>Our spritesheets follow a convention. For directional animations like “Walk”, the rows are organized as:</p>
<ul>
  <li>Row 0: Walk Up</li>
  <li>Row 1: Walk Left</li>
  <li>Row 2: Walk Down</li>
  <li>Row 3: Walk Right</li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">direction_index</code> function converts our <code class="language-plaintext highlighter-rouge">Facing</code> enum into these row offsets (0, 1, 2, 3). So when we know the player is facing <code class="language-plaintext highlighter-rouge">Down</code> and we want to play the “Walk” animation starting at row 8, we calculate: <code class="language-plaintext highlighter-rouge">8 + Facing::Down.direction_index()</code> = <code class="language-plaintext highlighter-rouge">8 + 2</code> = row 10. That’s where the “Walk Down” frames live in the atlas.</p>

<p><img src="/assets/book_assets/chapter3/graveyard_reaper_spritesheet.png" alt="Walk Spritesheet" style="display: block; margin: 0 auto; width: 50%;" /></p>

<h3 id="tracking-animation-state">Tracking Animation State</h3>

<p>We need to know <em>when</em> to change animations. If your character transitions from standing still to running, we need to detect that moment and restart the animation from frame 0. Otherwise, the run animation might start mid-cycle, looking janky.</p>

<p>We need these components to track the <em>current</em> state of the animation.</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">AnimationController</code></strong>:  It knows what animation we <em>want</em> to play (e.g., “Run”) and which way we are facing.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">AnimationState</code></strong>: It tracks if we are moving or jumping, and importantly, if we <em>were</em> moving last frame. This helps us detect state <em>changes</em> (like starting to run) so we can reset the animation timer.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">AnimationTimer</code></strong>: Controls how fast frames update.</li>
</ul>

<p>Add these components to <code class="language-plaintext highlighter-rouge">src/characters/animation.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/animation.rs</span>
<span class="c1">// Component that holds animation configuration</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationController</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">current_animation</span><span class="p">:</span> <span class="n">AnimationType</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">facing</span><span class="p">:</span> <span class="n">Facing</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">AnimationController</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">current_animation</span><span class="p">:</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span><span class="p">,</span>
            <span class="n">facing</span><span class="p">:</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">#[derive(Component,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationState</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">is_moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">was_moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">is_jumping</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">was_jumping</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">#[derive(Component,</span> <span class="nd">Deref,</span> <span class="nd">DerefMut)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="nf">AnimationTimer</span><span class="p">(</span><span class="k">pub</span> <span class="n">Timer</span><span class="p">);</span>
</code></pre></div></div>

<div style="margin: 20px 0; padding: 15px; background-color: #f8d7da; border-radius: 8px; border-left: 4px solid #dc3545;">
<em>You might notice we're using boolean flags (<code>is_moving</code>, <code>was_moving</code>, <code>is_jumping</code>, <code>was_jumping</code>) to track animation state. While this works for our current simple case, it's not the best approach for managing state transitions.As your game grows more complex (adding attacks, dodges, climbing, etc.), managing all these boolean combinations becomes error-prone and hard to maintain. 
<br /><br />
In Chapter 4, we'll learn about the <strong>state machine design pattern</strong>, which provides a much cleaner and more scalable way to handle state transitions. 

For now, we'll use this simpler approach to keep our focus on understanding the animation system fundamentals.</em>
</div>

<h3 id="frame-calculation">Frame Calculation</h3>

<p>Now we know <em>which</em> animation to play (Walk, Run, Jump) and <em>which</em> direction the character is facing. But how do we translate that into “show frame 47 from the texture atlas right now”?</p>

<p>Think of the spritesheet as a numbered grid, reading left-to-right, top-to-bottom. If “Walk Down” is on row 2 and the grid has 12 columns, the first frame of that animation is at position 24 (because we skip the first 2 rows: 2 × 12 = 24). If the animation has 6 frames, we’ll cycle through positions 24, 25, 26, 27, 28, 29, then loop back to 24.</p>

<p>We’ll create an <code class="language-plaintext highlighter-rouge">AnimationClip</code> struct to handle this math for us.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/animation.rs</span>
<span class="c1">// Runtime animation clip helper</span>
<span class="nd">#[derive(Clone,</span> <span class="nd">Copy)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">AnimationClip</span> <span class="p">{</span>
    <span class="n">first</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">last</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">AnimationClip</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">row</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">frame_count</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">atlas_columns</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">first</span> <span class="o">=</span> <span class="n">row</span> <span class="o">*</span> <span class="n">atlas_columns</span><span class="p">;</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">first</span><span class="p">,</span>
            <span class="n">last</span><span class="p">:</span> <span class="n">first</span> <span class="o">+</span> <span class="n">frame_count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">start</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.first</span>
    <span class="p">}</span>
    
    <span class="c1">// Check if a frame index belongs to this clip</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">contains</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="p">(</span><span class="k">self</span><span class="py">.first</span><span class="o">..=</span><span class="k">self</span><span class="py">.last</span><span class="p">)</span><span class="nf">.contains</span><span class="p">(</span><span class="o">&amp;</span><span class="n">index</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="c1">// Calculate the next frame, looping back to start if needed</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">next</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">index</span> <span class="o">&gt;=</span> <span class="k">self</span><span class="py">.last</span> <span class="p">{</span>
            <span class="k">self</span><span class="py">.first</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">index</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="c1">// Check if animation has completed (used for non-looping animations like Jump)</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">is_complete</span><span class="p">(</span><span class="k">self</span><span class="p">,</span> <span class="n">current_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span> <span class="n">timer_finished</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
        <span class="n">current_index</span> <span class="o">&gt;=</span> <span class="k">self</span><span class="py">.last</span> <span class="o">&amp;&amp;</span> <span class="n">timer_finished</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">AnimationClip</code> struct stores just two numbers: the <code class="language-plaintext highlighter-rouge">first</code> and <code class="language-plaintext highlighter-rouge">last</code> frame indices for a specific animation sequence. The <code class="language-plaintext highlighter-rouge">new</code> method calculates these indices from the row, frame count, and atlas width.</p>

<p>The <code class="language-plaintext highlighter-rouge">start</code> method returns where the animation begins. The <code class="language-plaintext highlighter-rouge">contains</code> method checks if a given frame index belongs to this clip (useful for detecting if we’ve wandered into the wrong animation). The <code class="language-plaintext highlighter-rouge">next</code> method advances to the next frame, automatically looping back to the start when we reach the end.</p>

<p>The <code class="language-plaintext highlighter-rouge">is_complete</code> method checks if we’ve reached the last frame and the timer has finished—this is crucial for non-looping animations like Jump, where we need to know when to transition back to Walk.</p>

<p><strong>Connecting Clips to Controllers</strong></p>

<p>Now that we have a way to represent frame ranges, we need to connect it to our <code class="language-plaintext highlighter-rouge">AnimationController</code>. Remember, the controller knows <em>what</em> animation to play (“Run”) and <em>which</em> direction we’re facing (“Left”). We’ll add a helper method that combines this information with the character’s configuration data to produce the correct <code class="language-plaintext highlighter-rouge">AnimationClip</code>.</p>

<p>Now we can add a method to <code class="language-plaintext highlighter-rouge">AnimationController</code> to easily get the current clip based on the character’s config:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/animation.rs</span>
<span class="k">impl</span> <span class="n">AnimationController</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">get_clip</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">config</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">AnimationClip</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 1. Get the definition (e.g. "Walk" data)</span>
        <span class="k">let</span> <span class="n">def</span> <span class="o">=</span> <span class="n">config</span><span class="py">.animations</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.current_animation</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        
        <span class="c1">// 2. Calculate the actual row based on facing direction</span>
        <span class="k">let</span> <span class="n">row</span> <span class="o">=</span> <span class="k">if</span> <span class="n">def</span><span class="py">.directional</span> <span class="p">{</span>
            <span class="n">def</span><span class="py">.start_row</span> <span class="o">+</span> <span class="k">self</span><span class="py">.facing</span><span class="nf">.direction_index</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">def</span><span class="py">.start_row</span>
        <span class="p">};</span>
        
        <span class="c1">// 3. Create the clip</span>
        <span class="nf">Some</span><span class="p">(</span><span class="nn">AnimationClip</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">def</span><span class="py">.frame_count</span><span class="p">,</span> <span class="n">config</span><span class="py">.atlas_columns</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="animating-characters">Animating Characters</h3>

<p>Finally, the system that ties it all together. This system runs every frame and:</p>
<ol>
  <li>Checks if the animation changed (e.g., started moving).</li>
  <li>If changed, resets the timer and frame index.</li>
  <li>If not changed, ticks the timer and advances the frame.</li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/animation.rs</span>
<span class="c1">// Generic animation system - works for ALL entities</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">animate_characters</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">AnimationController</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">AnimationState</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationTimer</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Sprite</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">animated</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="k">mut</span> <span class="n">timer</span><span class="p">,</span> <span class="k">mut</span> <span class="n">sprite</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas</span><span class="p">)</span> <span class="o">=</span> <span class="n">sprite</span><span class="py">.texture_atlas</span><span class="nf">.as_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">};</span>
        
        <span class="c1">// Get the correct clip for current state/facing</span>
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clip</span><span class="p">)</span> <span class="o">=</span> <span class="n">animated</span><span class="nf">.get_clip</span><span class="p">(</span><span class="n">config</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">};</span>
        
        <span class="c1">// Get timing info</span>
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">anim_def</span><span class="p">)</span> <span class="o">=</span> <span class="n">config</span><span class="py">.animations</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">animated</span><span class="py">.current_animation</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span><span class="p">;</span> <span class="p">};</span>
        
        <span class="c1">// Safety: If we somehow ended up on a frame outside our clip, reset.</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">clip</span><span class="nf">.contains</span><span class="p">(</span><span class="n">atlas</span><span class="py">.index</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">();</span>
            <span class="n">timer</span><span class="na">.0</span><span class="nf">.reset</span><span class="p">();</span>
        <span class="p">}</span>
        
        <span class="c1">// Detect state changes</span>
        <span class="k">let</span> <span class="n">just_started_moving</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_moving</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">state</span><span class="py">.was_moving</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">just_stopped_moving</span> <span class="o">=</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_moving</span> <span class="o">&amp;&amp;</span> <span class="n">state</span><span class="py">.was_moving</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">just_started_jumping</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_jumping</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">state</span><span class="py">.was_jumping</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">just_stopped_jumping</span> <span class="o">=</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_jumping</span> <span class="o">&amp;&amp;</span> <span class="n">state</span><span class="py">.was_jumping</span><span class="p">;</span>
        
        <span class="k">let</span> <span class="n">should_animate</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_jumping</span> <span class="p">||</span> <span class="n">state</span><span class="py">.is_moving</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">animation_changed</span> <span class="o">=</span> <span class="n">just_started_moving</span> <span class="p">||</span> <span class="n">just_started_jumping</span> 
                              <span class="p">||</span> <span class="n">just_stopped_moving</span> <span class="p">||</span> <span class="n">just_stopped_jumping</span><span class="p">;</span>
        
        <span class="k">if</span> <span class="n">animation_changed</span> <span class="p">{</span>
            <span class="c1">// Reset animation</span>
            <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">();</span>
            <span class="n">timer</span><span class="na">.0</span><span class="nf">.set_duration</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs_f32</span><span class="p">(</span><span class="n">anim_def</span><span class="py">.frame_time</span><span class="p">));</span>
            <span class="n">timer</span><span class="na">.0</span><span class="nf">.reset</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">should_animate</span> <span class="p">{</span>
            <span class="c1">// Advance animation</span>
            <span class="n">timer</span><span class="nf">.tick</span><span class="p">(</span><span class="n">time</span><span class="nf">.delta</span><span class="p">());</span>
            <span class="k">if</span> <span class="n">timer</span><span class="nf">.just_finished</span><span class="p">()</span> <span class="p">{</span>
                <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.next</span><span class="p">(</span><span class="n">atlas</span><span class="py">.index</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// When idle (not moving or jumping), stay on frame 0</span>
            <span class="k">if</span> <span class="n">atlas</span><span class="py">.index</span> <span class="o">!=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">()</span> <span class="p">{</span>
                <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">clip</span><span class="nf">.start</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Helper to update "was_moving" flags at the end of the frame</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_animation_flags</span><span class="p">(</span><span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">AnimationState</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="k">mut</span> <span class="n">state</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">state</span><span class="py">.was_moving</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_moving</span><span class="p">;</span>
        <span class="n">state</span><span class="py">.was_jumping</span> <span class="o">=</span> <span class="n">state</span><span class="py">.is_jumping</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p><strong>How the Animation System Works</strong></p>

<p>The animation system has three branches that handle different scenarios:</p>

<ol>
  <li><strong>Animation changed</strong> (starting/stopping movement or jumping): Reset to frame 0 and update the timer duration for the new animation.</li>
  <li><strong>Should animate</strong> (actively moving or jumping): Tick the timer and advance through frames.</li>
  <li><strong>Idle state</strong> (standing still): Ensure we stay on frame 0, the neutral standing pose.</li>
</ol>

<p><strong>Why State Change Detection Matters</strong></p>

<p>The first branch relies on detecting the <em>exact moment</em> a state changes. <code class="language-plaintext highlighter-rouge">is_moving</code> tells us the current state (“am I moving right now?”), while <code class="language-plaintext highlighter-rouge">was_moving</code> tells us the previous frame’s state (“was I moving last frame?”). When <code class="language-plaintext highlighter-rouge">is_moving</code> is true but <code class="language-plaintext highlighter-rouge">was_moving</code> is false, we know the player <em>just</em> pressed a movement key.</p>

<p>This detection is crucial for smooth transitions. Without it, animations would continue from wherever they left off. Imagine your character’s walk cycle is on frame 5, then you press jump—without resetting, the jump animation would start at frame 5 instead of frame 0, looking broken.</p>

<p>The third branch (idle state) handles a different case: when the player <em>stops</em> moving, we transition to Walk animation but need to ensure we display the idle pose (frame 0), not whatever frame the walk cycle was on when they stopped.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_6597074f1ceebe6f5a2887f6aa9ac8a6.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="6597074f1ceebe6f5a2887f6aa9ac8a6" data-comic-settings="d2-diagram" />
</div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_0cb66936e9621dfa74761cd2f96552ea.svg" alt="Comic Panel" class="comic-image" data-comic-hash="0cb66936e9621dfa74761cd2f96552ea" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>Why do we need <code class="language-plaintext highlighter-rouge">update_animation_flags</code>?</strong><br />
We need <code class="language-plaintext highlighter-rouge">update_animation_flags</code> to run <em>after</em> all logic is done, so that in the <em>next</em> frame, <code class="language-plaintext highlighter-rouge">was_moving</code> correctly reflects the previous frame’s state. This allows us to detect the exact moment a state changes.</p>

<h2 id="the-movement-system">The Movement System</h2>
<p><img src="/assets/book_assets/chapter3/run.gif" alt="Sprite Sheets" /></p>

<p>Our animation engine can display the right frames, but it needs to know <em>what</em> the player is doing. The <code class="language-plaintext highlighter-rouge">AnimationController</code> we built earlier stores the <em>current</em> animation state (“I’m running left”), but something needs to <em>update</em> that state based on player input. That’s where the movement system comes in. It reads keyboard input, moves the character, and tells the <code class="language-plaintext highlighter-rouge">AnimationController</code> which animation to play.</p>

<p>Create a new file <code class="language-plaintext highlighter-rouge">src/characters/movement.rs</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>characters/
├── config.rs
├── animation.rs
├── movement.rs  &lt;- Create this
</code></pre></div></div>

<p>The movement system has three responsibilities:</p>

<ol>
  <li><strong>Input Reading</strong>: Convert arrow key presses into a direction vector</li>
  <li><strong>Movement Calculation</strong>: Apply speed and delta time to move the character smoothly</li>
  <li><strong>Animation Coordination</strong>: Tell the animation system when to switch between Walk, Run, and Jump</li>
</ol>

<p>Here’s how we’ll tackle this:</p>

<h3 id="reading-player-input">Reading Player Input</h3>

<p>When the player presses arrow keys, we need to convert those discrete button presses into a continuous direction vector. If they press Up and Right simultaneously, we want <code class="language-plaintext highlighter-rouge">Vec2 { x: 1.0, y: 1.0 }</code> for diagonal movement.</p>

<p>Add this to <code class="language-plaintext highlighter-rouge">src/characters/movement.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/movement.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">animation</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">config</span><span class="p">::{</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">AnimationType</span><span class="p">};</span>

<span class="cd">/// Read directional input and return a direction vector</span>
<span class="k">fn</span> <span class="nf">read_movement_input</span><span class="p">(</span><span class="n">input</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">MOVEMENT_KEYS</span><span class="p">:</span> <span class="p">[(</span><span class="n">KeyCode</span><span class="p">,</span> <span class="n">Vec2</span><span class="p">);</span> <span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowLeft</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">NEG_X</span><span class="p">),</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowRight</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">X</span><span class="p">),</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowUp</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">Y</span><span class="p">),</span>
        <span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowDown</span><span class="p">,</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">NEG_Y</span><span class="p">),</span>
    <span class="p">];</span>
    
    <span class="n">MOVEMENT_KEYS</span><span class="nf">.iter</span><span class="p">()</span>
        <span class="nf">.filter</span><span class="p">(|(</span><span class="n">key</span><span class="p">,</span> <span class="n">_</span><span class="p">)|</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="o">*</span><span class="n">key</span><span class="p">))</span>
        <span class="nf">.map</span><span class="p">(|(</span><span class="n">_</span><span class="p">,</span> <span class="n">dir</span><span class="p">)|</span> <span class="o">*</span><span class="n">dir</span><span class="p">)</span>
        <span class="nf">.sum</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s happening here?</strong></p>

<p>We define a constant array mapping each arrow key to its direction vector. <code class="language-plaintext highlighter-rouge">Vec2::NEG_X</code> means “negative X direction” (left), <code class="language-plaintext highlighter-rouge">Vec2::X</code> means “positive X direction” (right), and so on.</p>

<p>Then we iterate through all four keys, filter to only the ones currently pressed, extract their direction vectors, and sum them. If both Up and Right are pressed, we get <code class="language-plaintext highlighter-rouge">Vec2::Y + Vec2::X</code> = <code class="language-plaintext highlighter-rouge">Vec2 { x: 1.0, y: 1.0 }</code>.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">_</code> in <code class="language-plaintext highlighter-rouge">(keys, _)</code> and in <code class="language-plaintext highlighter-rouge">(_, dir)</code>?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">_</code> (underscore) is Rust’s “I don’t care” placeholder in pattern matching. When destructuring tuples, you use <code class="language-plaintext highlighter-rouge">_</code> to ignore values you don’t need:</p>

<ul>
  <li>In <code class="language-plaintext highlighter-rouge">(key, _)</code>: We only need the <code class="language-plaintext highlighter-rouge">key</code> to check if it’s pressed, so we ignore the direction with <code class="language-plaintext highlighter-rouge">_</code></li>
  <li>In <code class="language-plaintext highlighter-rouge">(_, dir)</code>: We only need the <code class="language-plaintext highlighter-rouge">dir</code> (direction vector), so we ignore the key with <code class="language-plaintext highlighter-rouge">_</code></li>
</ul>

<p>This is more readable than naming unused variables like <code class="language-plaintext highlighter-rouge">(key, _unused_dir)</code> or <code class="language-plaintext highlighter-rouge">(_unused_key, dir)</code>. Rust’s compiler also knows these values are intentionally ignored, so you won’t get warnings about unused variables.</p>

<h3 id="calculating-movement-speed">Calculating Movement Speed</h3>

<p>Different characters move at different speeds. The Male character might be slower, while the Female character is faster. We also need to support running (holding Shift).</p>

<p>Add this helper function:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/movement.rs</span>
<span class="cd">/// Calculate movement speed based on character config and running state</span>
<span class="k">fn</span> <span class="nf">calculate_movement_speed</span><span class="p">(</span><span class="n">character</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">is_running</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">is_running</span> <span class="p">{</span>
        <span class="n">character</span><span class="py">.base_move_speed</span> <span class="o">*</span> <span class="n">character</span><span class="py">.run_speed_multiplier</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">character</span><span class="py">.base_move_speed</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This reads the character’s <code class="language-plaintext highlighter-rouge">base_move_speed</code> from our data file and multiplies it by <code class="language-plaintext highlighter-rouge">run_speed_multiplier</code> if the player is holding Shift. All the speed values are data-driven—no hardcoded constants!</p>

<h3 id="the-player-marker">The Player Marker</h3>

<p>We need a way to identify which entity is the player. We’ll use a simple marker component:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/movement.rs</span>
<span class="cd">/// Marker component for the player entity</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">Player</span><span class="p">;</span>
</code></pre></div></div>

<p>This component has no data, it’s just a tag. When we spawn the player entity, we’ll attach this component. Then our movement system can query for entities with <code class="language-plaintext highlighter-rouge">Player</code> to find the player. We have already studied this in the first chapter.</p>

<h3 id="the-movement-system-1">The Movement System</h3>

<p>Now we tie it all together. This system runs every frame, reads input, calculates movement, and updates the animation state:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/movement.rs</span>
<span class="cd">/// Handle player movement input and update transform/animation</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">move_player</span><span class="p">(</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span> 
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationController</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationState</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="k">mut</span> <span class="n">transform</span><span class="p">,</span> <span class="k">mut</span> <span class="n">animated</span><span class="p">,</span> <span class="k">mut</span> <span class="n">state</span><span class="p">,</span> <span class="n">character</span><span class="p">))</span> <span class="o">=</span> <span class="n">query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">direction</span> <span class="o">=</span> <span class="nf">read_movement_input</span><span class="p">(</span><span class="o">&amp;</span><span class="n">input</span><span class="p">);</span>
    
    <span class="c1">// Check for jump input (space key)</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">Space</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">state</span><span class="py">.is_jumping</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
        <span class="n">animated</span><span class="py">.current_animation</span> <span class="o">=</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Jump</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// Check if running</span>
    <span class="k">let</span> <span class="n">is_running</span> <span class="o">=</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ShiftLeft</span><span class="p">)</span> <span class="p">||</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ShiftRight</span><span class="p">);</span>
    
    <span class="c1">// Handle movement</span>
    <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">move_speed</span> <span class="o">=</span> <span class="nf">calculate_movement_speed</span><span class="p">(</span><span class="n">character</span><span class="p">,</span> <span class="n">is_running</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">direction</span><span class="nf">.normalize</span><span class="p">()</span> <span class="o">*</span> <span class="n">move_speed</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
        <span class="n">transform</span><span class="py">.translation</span> <span class="o">+=</span> <span class="n">delta</span><span class="nf">.extend</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
        
        <span class="n">animated</span><span class="py">.facing</span> <span class="o">=</span> <span class="nn">Facing</span><span class="p">::</span><span class="nf">from_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">);</span>
        
        <span class="c1">// Only update animation if not jumping</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_jumping</span> <span class="p">{</span>
            <span class="n">state</span><span class="py">.is_moving</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
            <span class="n">animated</span><span class="py">.current_animation</span> <span class="o">=</span> <span class="k">if</span> <span class="n">is_running</span> <span class="p">{</span>
                <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Run</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span>
            <span class="p">};</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_jumping</span> <span class="p">{</span>
        <span class="n">state</span><span class="py">.is_moving</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
        <span class="n">animated</span><span class="py">.current_animation</span> <span class="o">=</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<ol>
  <li><strong>Query for the player</strong>: <code class="language-plaintext highlighter-rouge">With&lt;Player&gt;</code> filters to only entities with the <code class="language-plaintext highlighter-rouge">Player</code> component. <code class="language-plaintext highlighter-rouge">single_mut()</code> gets the one player entity.</li>
  <li><strong>Read input</strong>: Get the direction vector from arrow keys.</li>
  <li><strong>Handle jumping</strong>: If Space was just pressed, set <code class="language-plaintext highlighter-rouge">is_jumping</code> and switch to the Jump animation.</li>
  <li><strong>Check for running</strong>: Are either Shift keys pressed?</li>
  <li><strong>Move the character</strong>: If there’s input, normalize the direction (so diagonal movement isn’t faster), multiply by speed and delta time, and update the transform.</li>
</ol>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_0c5c60b49c614e1948f6750f4daa4bd2.svg" alt="Comic Panel" class="comic-image" data-comic-hash="0c5c60b49c614e1948f6750f4daa4bd2" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<ol>
  <li><strong>Update facing</strong>: Use our <code class="language-plaintext highlighter-rouge">Facing::from_direction</code> helper to determine which way to face.</li>
  <li><strong>Update animation</strong>: If not jumping, set the animation to Run or Walk based on whether <code class="language-plaintext highlighter-rouge">Shift</code> is held.</li>
</ol>

<h3 id="handling-jump-completion">Handling Jump Completion</h3>

<p><img src="/assets/book_assets/chapter3/jump.gif" alt="Sprite Sheets" /></p>

<p>Jump animations are special, they have a beginning and an end. Unlike Walk or Run, which loop forever, Jump plays once and then we need to return to the idle state.</p>

<p>Add this system:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/movement.rs</span>
<span class="cd">/// Monitor jump animation completion and reset state</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">update_jump_state</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationController</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationState</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">AnimationTimer</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">Sprite</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
    <span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">mut</span> <span class="n">animated</span><span class="p">,</span> <span class="k">mut</span> <span class="n">state</span><span class="p">,</span> <span class="n">timer</span><span class="p">,</span> <span class="n">sprite</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">state</span><span class="py">.is_jumping</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
        
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas</span><span class="p">)</span> <span class="o">=</span> <span class="n">sprite</span><span class="py">.texture_atlas</span><span class="nf">.as_ref</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">};</span>
        
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">clip</span><span class="p">)</span> <span class="o">=</span> <span class="n">animated</span><span class="nf">.get_clip</span><span class="p">(</span><span class="n">config</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">};</span>
        
        <span class="c1">// Check if jump animation has completed</span>
        <span class="k">if</span> <span class="n">clip</span><span class="nf">.is_complete</span><span class="p">(</span><span class="n">atlas</span><span class="py">.index</span><span class="p">,</span> <span class="n">timer</span><span class="nf">.just_finished</span><span class="p">())</span> <span class="p">{</span>
            <span class="n">state</span><span class="py">.is_jumping</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
            <span class="n">animated</span><span class="py">.current_animation</span> <span class="o">=</span> <span class="nn">AnimationType</span><span class="p">::</span><span class="n">Walk</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This system checks if the jump animation has reached its last frame and the timer has finished (using the <code class="language-plaintext highlighter-rouge">is_complete</code> method we defined earlier in <code class="language-plaintext highlighter-rouge">AnimationClip</code>). If so, it resets <code class="language-plaintext highlighter-rouge">is_jumping</code> to false and switches back to the Walk animation. The player can then move normally again.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">.as_ref()</code>?</strong></p>

<p>In the animation system, we used <code class="language-plaintext highlighter-rouge">.as_mut()</code> to get a mutable reference to the texture atlas so we could change the frame index. Here, we only need to <em>read</em> the current frame index, not modify it. The <code class="language-plaintext highlighter-rouge">.as_ref()</code> method converts <code class="language-plaintext highlighter-rouge">Option&lt;TextureAtlas&gt;</code> into <code class="language-plaintext highlighter-rouge">Option&lt;&amp;TextureAtlas&gt;</code>, giving us a read-only reference.</p>

<h2 id="spawning-characters">Spawning Characters</h2>

<p>We have animation, movement, and data structures, but no actual character on screen yet! The spawn system is responsible for:</p>

<ol>
  <li>Loading the <code class="language-plaintext highlighter-rouge">characters.ron</code> file</li>
  <li>Creating the player entity with all necessary components</li>
  <li>Setting up the texture atlas from the spritesheet</li>
  <li>Allowing runtime character switching with number keys (1-6)</li>
</ol>

<p>Create a new file <code class="language-plaintext highlighter-rouge">src/characters/spawn.rs</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>characters/
├── config.rs
├── animation.rs
├── movement.rs
├── spawn.rs  &lt;- Create this
</code></pre></div></div>

<h3 id="resources-for-character-management">Resources for Character Management</h3>

<p>We’ll need two resources: one to track which character is currently active, and another to hold a reference to our loaded character data file.</p>

<p>Add this to <code class="language-plaintext highlighter-rouge">src/characters/spawn.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/spawn.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">animation</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">config</span><span class="p">::{</span><span class="n">CharacterEntry</span><span class="p">,</span> <span class="n">CharactersList</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">characters</span><span class="p">::</span><span class="nn">movement</span><span class="p">::</span><span class="n">Player</span><span class="p">;</span>

<span class="k">const</span> <span class="n">PLAYER_SCALE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.8</span><span class="p">;</span>
<span class="k">const</span> <span class="n">PLAYER_Z_POSITION</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">20.0</span><span class="p">;</span>

<span class="nd">#[derive(Resource,</span> <span class="nd">Default)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CurrentCharacterIndex</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">#[derive(Resource)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">CharactersListResource</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">handle</span><span class="p">:</span> <span class="n">Handle</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What are these resources for?</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CurrentCharacterIndex</code>: Tracks which character is currently active (0 = first character, 1 = second, etc.)</li>
  <li><code class="language-plaintext highlighter-rouge">CharactersListResource</code>: Stores the handle to our loaded <code class="language-plaintext highlighter-rouge">characters.ron</code> file. A handle is like a reference to an asset that Bevy is loading or has loaded.</li>
</ul>

<p><strong>How to decide between using resources and components?</strong></p>

<p>Use <strong>Components</strong> for data that belongs to specific entities (like a player’s health, position, or animation state). Use <strong>Resources</strong> for global data that isn’t tied to any particular entity (like the current level number, game settings, or in our case, which character is active). Think of it this way: if you’d ask “which entity does this belong to?” and the answer is “all of them” or “none of them,” it’s probably a Resource.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_028cf9d120d02a06bc729b08fce6fb4d.svg" alt="Comic Panel" class="comic-image" data-comic-hash="028cf9d120d02a06bc729b08fce6fb4d" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>What’s the <code class="language-plaintext highlighter-rouge">Default</code> macro?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">Default</code> derive macro automatically implements the <code class="language-plaintext highlighter-rouge">Default</code> trait, which provides a default value for the struct. For <code class="language-plaintext highlighter-rouge">usize</code>, Rust’s default is <code class="language-plaintext highlighter-rouge">0</code>. When you later in the chapter use <code class="language-plaintext highlighter-rouge">init_resource::&lt;CurrentCharacterIndex&gt;()</code>, Bevy internally calls <code class="language-plaintext highlighter-rouge">CurrentCharacterIndex::default()</code>, which creates <code class="language-plaintext highlighter-rouge">CurrentCharacterIndex { index: 0 }</code>.</p>

<p>This is equivalent to manually writing:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code warning, don't use</span>
<span class="k">impl</span> <span class="nb">Default</span> <span class="k">for</span> <span class="n">CurrentCharacterIndex</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">default</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span> <span class="n">index</span><span class="p">:</span> <span class="mi">0</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But the derive macro does it for us! Different types have different defaults: <code class="language-plaintext highlighter-rouge">bool</code> → <code class="language-plaintext highlighter-rouge">false</code>, <code class="language-plaintext highlighter-rouge">String</code> → <code class="language-plaintext highlighter-rouge">""</code>, <code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code> → <code class="language-plaintext highlighter-rouge">None</code>, etc.</p>

<h3 id="creating-the-texture-atlas-layout">Creating the Texture Atlas Layout</h3>

<p>Remember how we talked about spritesheets being grids of frames? Bevy doesn’t automatically know where one frame ends and another begins. We need to give it instructions: “Each frame is 64×64 pixels, there are 12 columns, and 8 rows.” That’s what a texture atlas layout does, it’s like a map that tells Bevy how to navigate the spritesheet.</p>

<p>This helper function creates that map:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/spawn.rs</span>
<span class="cd">/// Create a texture atlas layout for a character</span>
<span class="k">fn</span> <span class="nf">create_character_atlas_layout</span><span class="p">(</span>
    <span class="n">atlas_layouts</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">character_entry</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">CharacterEntry</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Handle</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">max_row</span> <span class="o">=</span> <span class="n">character_entry</span><span class="nf">.calculate_max_animation_row</span><span class="p">();</span>
    
    <span class="n">atlas_layouts</span><span class="nf">.add</span><span class="p">(</span><span class="nn">TextureAtlasLayout</span><span class="p">::</span><span class="nf">from_grid</span><span class="p">(</span>
        <span class="nn">UVec2</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">character_entry</span><span class="py">.tile_size</span><span class="p">),</span>
        <span class="n">character_entry</span><span class="py">.atlas_columns</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
        <span class="p">(</span><span class="n">max_row</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span>
        <span class="nb">None</span><span class="p">,</span>
        <span class="nb">None</span><span class="p">,</span>
    <span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">calculate_max_animation_row()</code>: We defined this earlier in <code class="language-plaintext highlighter-rouge">CharacterEntry</code>. It figures out how many rows the spritesheet needs based on all the animations.</li>
  <li><code class="language-plaintext highlighter-rouge">UVec2::splat(tile_size)</code>: Creates a 2D vector where both x and y are the tile size (e.g., 64×64 pixels per frame).</li>
  <li><code class="language-plaintext highlighter-rouge">from_grid(...)</code>: Tells Bevy “this texture is a grid with X columns and Y rows, each cell is this size.”</li>
</ul>

<h3 id="spawning-the-player-entity">Spawning the Player Entity</h3>

<p>Now we spawn the player. But here’s the catch: loading files from disk takes time. We can’t wait for <code class="language-plaintext highlighter-rouge">characters.ron</code> to load before creating the player entity, that would freeze the game during startup.</p>

<p>So we use a two stage approach: create a “placeholder” entity immediately, then fill in the details once the data finishes loading.</p>

<p><strong>Stage 1: Create the entity</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/spawn.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">spawn_player</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">character_index</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Load the characters list</span>
    <span class="k">let</span> <span class="n">characters_list_handle</span><span class="p">:</span> <span class="n">Handle</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="s">"characters/characters.ron"</span><span class="p">);</span>
    
    <span class="c1">// Store the handle in a resource</span>
    <span class="n">commands</span><span class="nf">.insert_resource</span><span class="p">(</span><span class="n">CharactersListResource</span> <span class="p">{</span>
        <span class="n">handle</span><span class="p">:</span> <span class="n">characters_list_handle</span><span class="p">,</span>
    <span class="p">});</span>
    
    <span class="c1">// Initialize with first character</span>
    <span class="n">character_index</span><span class="py">.index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    
    <span class="c1">// Spawn player entity (will be initialized once asset loads)</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="n">Player</span><span class="p">,</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">PLAYER_Z_POSITION</span><span class="p">))</span>
            <span class="nf">.with_scale</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">PLAYER_SCALE</span><span class="p">)),</span>
        <span class="nn">Sprite</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This system runs once at startup. It loads the <code class="language-plaintext highlighter-rouge">characters.ron</code> file, stores the handle in a resource, and spawns a player entity with just the <code class="language-plaintext highlighter-rouge">Player</code> marker, a <code class="language-plaintext highlighter-rouge">Transform</code>, and an empty <code class="language-plaintext highlighter-rouge">Sprite</code>. We can’t fully initialize it yet because the asset is still loading.</p>

<p><strong>Why not load everything immediately?</strong></p>

<p>Asset loading in Bevy is asynchronous. When you call <code class="language-plaintext highlighter-rouge">asset_server.load()</code>, Bevy starts loading the file in the background. It might take a few frames (or longer for large files). We need to wait until it’s ready.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_7819f8eb2e8154ae83bf30ee07fc507a.svg" alt="Comic Panel" class="comic-image" data-comic-hash="7819f8eb2e8154ae83bf30ee07fc507a" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>Stage 2: Initialize once loaded</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/spawn.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">initialize_player_character</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_lists</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">character_index</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">characters_list_res</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CharactersListResource</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="n">Entity</span><span class="p">,</span> <span class="p">(</span><span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">Without</span><span class="o">&lt;</span><span class="n">AnimationController</span><span class="o">&gt;</span><span class="p">)</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list_res</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_list_res</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">for</span> <span class="n">entity</span> <span class="k">in</span> <span class="n">query</span><span class="nf">.iter_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_lists</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">characters_list_res</span><span class="py">.handle</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">};</span>
        
        <span class="k">if</span> <span class="n">character_index</span><span class="py">.index</span> <span class="o">&gt;=</span> <span class="n">characters_list</span><span class="py">.characters</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">};</span>
        
        <span class="k">let</span> <span class="n">character_entry</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">characters_list</span><span class="py">.characters</span><span class="p">[</span><span class="n">character_index</span><span class="py">.index</span><span class="p">];</span>
        
        <span class="k">let</span> <span class="n">texture</span> <span class="o">=</span> <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="o">&amp;</span><span class="n">character_entry</span><span class="py">.texture_path</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nf">create_character_atlas_layout</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">,</span> <span class="n">character_entry</span><span class="p">);</span>
        
        <span class="k">let</span> <span class="n">sprite</span> <span class="o">=</span> <span class="nn">Sprite</span><span class="p">::</span><span class="nf">from_atlas_image</span><span class="p">(</span>
            <span class="n">texture</span><span class="p">,</span>
            <span class="n">TextureAtlas</span> <span class="p">{</span>
                <span class="n">layout</span><span class="p">,</span>
                <span class="n">index</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">);</span>
        
        <span class="n">commands</span><span class="nf">.entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span><span class="nf">.insert</span><span class="p">((</span>
            <span class="nn">AnimationController</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nn">AnimationState</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
            <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">DEFAULT_ANIMATION_FRAME_TIME</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">)),</span>
            <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">(),</span>
            <span class="n">sprite</span><span class="p">,</span>
        <span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Earlier in Stage 1, we started loading <code class="language-plaintext highlighter-rouge">characters.ron</code> in the background. But we don’t know <em>when</em> it will finish—could be the next frame, could be 10 frames later.</p>

<p>This system runs every frame, checking: “Is the file loaded yet? Is there a player entity that still needs initialization?”</p>

<p>The query <code class="language-plaintext highlighter-rouge">(With&lt;Player&gt;, Without&lt;AnimationController&gt;)</code> finds player entities that exist but haven’t been fully set up yet. Once the file loads, <code class="language-plaintext highlighter-rouge">characters_lists.get()</code> succeeds, and we can finally add all the animation components.</p>

<p>We grab the character data, load its texture, create the atlas layout, and insert all the necessary components.</p>

<h3 id="character-switching">Character Switching</h3>

<p>One of the coolest features of our data-driven system: switching characters at runtime by pressing number keys!</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this to src/characters/spawn.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">switch_character</span><span class="p">(</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">character_index</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">characters_lists</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">characters_list_res</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">CharactersListResource</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">CharacterEntry</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Sprite</span><span class="p">,</span>
    <span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Map digit keys to indices</span>
    <span class="k">const</span> <span class="n">DIGIT_KEYS</span><span class="p">:</span> <span class="p">[</span><span class="n">KeyCode</span><span class="p">;</span> <span class="mi">9</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
        <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit1</span><span class="p">,</span> <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit2</span><span class="p">,</span> <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit3</span><span class="p">,</span>
        <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit4</span><span class="p">,</span> <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit5</span><span class="p">,</span> <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit6</span><span class="p">,</span>
        <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit7</span><span class="p">,</span> <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit8</span><span class="p">,</span> <span class="nn">KeyCode</span><span class="p">::</span><span class="n">Digit9</span><span class="p">,</span>
    <span class="p">];</span>
    
    <span class="c1">// Find which digit key was pressed</span>
    <span class="k">let</span> <span class="n">new_index</span> <span class="o">=</span> <span class="n">DIGIT_KEYS</span><span class="nf">.iter</span><span class="p">()</span>
        <span class="nf">.position</span><span class="p">(|</span><span class="o">&amp;</span><span class="n">key</span><span class="p">|</span> <span class="n">input</span><span class="nf">.just_pressed</span><span class="p">(</span><span class="n">key</span><span class="p">));</span>
    
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">new_index</span><span class="p">)</span> <span class="o">=</span> <span class="n">new_index</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list_res</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_list_res</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">characters_list</span><span class="p">)</span> <span class="o">=</span> <span class="n">characters_lists</span><span class="nf">.get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">characters_list_res</span><span class="py">.handle</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">if</span> <span class="n">new_index</span> <span class="o">&gt;=</span> <span class="n">characters_list</span><span class="py">.characters</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// Update character index</span>
    <span class="n">character_index</span><span class="py">.index</span> <span class="o">=</span> <span class="n">new_index</span><span class="p">;</span>
    
    <span class="c1">// Update player entity</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="k">mut</span> <span class="n">current_entry</span><span class="p">,</span> <span class="k">mut</span> <span class="n">sprite</span><span class="p">))</span> <span class="o">=</span> <span class="n">query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">character_entry</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">characters_list</span><span class="py">.characters</span><span class="p">[</span><span class="n">new_index</span><span class="p">];</span>
    
    <span class="c1">// Update character entry</span>
    <span class="o">*</span><span class="n">current_entry</span> <span class="o">=</span> <span class="n">character_entry</span><span class="nf">.clone</span><span class="p">();</span>
    
    <span class="c1">// Update sprite with new texture</span>
    <span class="k">let</span> <span class="n">texture</span> <span class="o">=</span> <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="o">&amp;</span><span class="n">character_entry</span><span class="py">.texture_path</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="nf">create_character_atlas_layout</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">,</span> <span class="n">character_entry</span><span class="p">);</span>
    
    <span class="o">*</span><span class="n">sprite</span> <span class="o">=</span> <span class="nn">Sprite</span><span class="p">::</span><span class="nf">from_atlas_image</span><span class="p">(</span>
        <span class="n">texture</span><span class="p">,</span>
        <span class="n">TextureAtlas</span> <span class="p">{</span>
            <span class="n">layout</span><span class="p">,</span>
            <span class="n">index</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">},</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>How it works:</strong></p>

<ol>
  <li><strong>Detect key press</strong>: Check if any digit key (1-9) was just pressed.</li>
  <li><strong>Validate index</strong>: Make sure the index is within bounds (we have 6 characters, so keys 1-6 work).</li>
  <li><strong>Update resources</strong>: Set <code class="language-plaintext highlighter-rouge">character_index.index</code> to the new value.</li>
  <li><strong>Swap the character</strong>: Replace the <code class="language-plaintext highlighter-rouge">CharacterEntry</code> component with the new character’s data.</li>
  <li><strong>Update the sprite</strong>: Load the new character’s texture and create a new atlas layout.</li>
</ol>

<p>This is the payoff of our data-driven design! Remember how in Chapter 1, adding a second character would have meant duplicating all the animation and movement code? Here, we just swap out the data. The animation system doesn’t care if it’s animating a Male character or a Crimson Count, it just reads the <code class="language-plaintext highlighter-rouge">CharacterEntry</code> component and does its job. Same with movement: it reads <code class="language-plaintext highlighter-rouge">base_move_speed</code> and <code class="language-plaintext highlighter-rouge">run_speed_multiplier</code> from the new character’s data. No code changes needed.</p>

<p><strong>How does <code class="language-plaintext highlighter-rouge">.position()</code> work?</strong></p>

<p>This iterator method searches through the array and returns the index of the first item where the condition is true. The closure <code class="language-plaintext highlighter-rouge">|&amp;key| input.just_pressed(key)</code> checks each key: “was this key just pressed?” If the player presses <code class="language-plaintext highlighter-rouge">Digit3</code>, <code class="language-plaintext highlighter-rouge">.position()</code> returns <code class="language-plaintext highlighter-rouge">Some(2)</code> (because <code class="language-plaintext highlighter-rouge">Digit3</code> is at index 2 in the array). If no digit key is pressed, it returns <code class="language-plaintext highlighter-rouge">None</code>. It’s similar to the filter operation.</p>

<p><strong>What’s the <code class="language-plaintext highlighter-rouge">*</code> in <code class="language-plaintext highlighter-rouge">*current_entry</code>?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">*</code> is the dereference operator. <code class="language-plaintext highlighter-rouge">current_entry</code> is a mutable reference (<code class="language-plaintext highlighter-rouge">&amp;mut CharacterEntry</code>), not the actual data. To modify the data it points to, we need to dereference it with <code class="language-plaintext highlighter-rouge">*</code>. Think of it like this: <code class="language-plaintext highlighter-rouge">current_entry</code> is a pointer to a box, <code class="language-plaintext highlighter-rouge">*current_entry</code> is the contents of the box. We’re replacing the contents, not the pointer.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_204f104f6d7746b008edf8e9163fa490.svg" alt="Comic Panel" class="comic-image" data-comic-hash="204f104f6d7746b008edf8e9163fa490" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h2 id="bringing-it-all-together">Bringing It All Together</h2>

<p>We’ve built all the pieces: animation, movement, spawning, and character switching. Now we need to package them into a plugin and integrate it into our game.</p>

<h3 id="adding-the-ron-asset-loader">Adding the RON Asset Loader</h3>

<p>First, we need to add dependencies that let Bevy load <code class="language-plaintext highlighter-rouge">.ron</code> files and serialize/deserialize our data structures. Open <code class="language-plaintext highlighter-rouge">Cargo.toml</code> and add these to your <code class="language-plaintext highlighter-rouge">[dependencies]</code> section:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">bevy_common_assets</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.15.0-rc.1"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["ron"]</span> <span class="p">}</span>
<span class="nn">serde</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"1.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="nn">["derive"]</span> <span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">bevy_common_assets</code> crate provides asset loaders for common file formats. We’re using the <code class="language-plaintext highlighter-rouge">ron</code> feature to load our <code class="language-plaintext highlighter-rouge">characters.ron</code> file. The <code class="language-plaintext highlighter-rouge">serde</code> crate with the <code class="language-plaintext highlighter-rouge">derive</code> feature allows us to use <code class="language-plaintext highlighter-rouge">#[derive(Serialize, Deserialize)]</code> on our structs, which we used in <code class="language-plaintext highlighter-rouge">config.rs</code> and <code class="language-plaintext highlighter-rouge">animation.rs</code>.</p>

<h3 id="creating-the-characters-plugin">Creating the Characters Plugin</h3>

<p>Create <code class="language-plaintext highlighter-rouge">src/characters/mod.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/characters/mod.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">animation</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">config</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">movement</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">spawn</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy_common_assets</span><span class="p">::</span><span class="nn">ron</span><span class="p">::</span><span class="n">RonAssetPlugin</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">config</span><span class="p">::</span><span class="n">CharactersList</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">CharactersPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">CharactersPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">RonAssetPlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">CharactersList</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="s">"characters.ron"</span><span class="p">]))</span>
            <span class="py">.init_resource</span><span class="p">::</span><span class="o">&lt;</span><span class="nn">spawn</span><span class="p">::</span><span class="n">CurrentCharacterIndex</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="nn">spawn</span><span class="p">::</span><span class="n">spawn_player</span><span class="p">)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span>
                <span class="nn">spawn</span><span class="p">::</span><span class="n">initialize_player_character</span><span class="p">,</span>
                <span class="nn">spawn</span><span class="p">::</span><span class="n">switch_character</span><span class="p">,</span>
                <span class="nn">movement</span><span class="p">::</span><span class="n">move_player</span><span class="p">,</span>
                <span class="nn">movement</span><span class="p">::</span><span class="n">update_jump_state</span><span class="p">,</span>
                <span class="nn">animation</span><span class="p">::</span><span class="n">animate_characters</span><span class="p">,</span>
                <span class="nn">animation</span><span class="p">::</span><span class="n">update_animation_flags</span><span class="p">,</span>
            <span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<ul>
  <li><strong>Module declarations</strong>: <code class="language-plaintext highlighter-rouge">pub mod animation;</code> etc. make our submodules accessible.</li>
  <li><strong>RonAssetPlugin</strong>: Registers the <code class="language-plaintext highlighter-rouge">.ron</code> file loader for our <code class="language-plaintext highlighter-rouge">CharactersList</code> type.</li>
  <li><strong>init_resource</strong>: Creates the <code class="language-plaintext highlighter-rouge">CurrentCharacterIndex</code> resource with its default value (0).</li>
  <li><strong>Startup systems</strong>: <code class="language-plaintext highlighter-rouge">spawn_player</code> runs once at game start.</li>
  <li><strong>Update systems</strong>: All other systems run every frame.</li>
</ul>

<p><strong>Why are we keeping <code class="language-plaintext highlighter-rouge">initialize_player_character</code> in the Update instead of Startup? Does it initialize player character every frame?</strong></p>

<p>Good catch! The system <em>does</em> run every frame, but it doesn’t initialize the player every frame. Look at the query: <code class="language-plaintext highlighter-rouge">Query&lt;Entity, (With&lt;Player&gt;, Without&lt;AnimationController&gt;)&gt;</code>. This only matches player entities that <em>don’t have</em> an <code class="language-plaintext highlighter-rouge">AnimationController</code> yet.</p>

<p>Once we add the <code class="language-plaintext highlighter-rouge">AnimationController</code> component (which happens inside the system), the entity no longer matches the query, so the system does nothing on subsequent frames. It’s a self-terminating system—it runs until it finds and initializes uninitialized players, then effectively becomes a no-op.</p>

<p>We can’t put it in Startup because the <code class="language-plaintext highlighter-rouge">characters.ron</code> file might not be loaded yet when Startup runs. By putting it in Update, it keeps checking every frame: “Is the file loaded? Is there an uninitialized player?” Once both conditions are true, it initializes the player and then stops doing anything.</p>

<h3 id="plugging-into-the-game">Plugging Into the Game</h3>

<p>Now we connect our plugin to the main game. Open <code class="language-plaintext highlighter-rouge">src/main.rs</code> and add the module declaration at the top:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Update line in src/main.rs</span>
<span class="k">mod</span> <span class="n">map</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">characters</span><span class="p">;</span>  <span class="c1">// Add this line</span>
</code></pre></div></div>

<p>Then add the plugin to your app:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Update line in src/main.rs</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">map_size</span> <span class="o">=</span> <span class="nf">map_pixel_dimensions</span><span class="p">();</span>

    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.insert_resource</span><span class="p">(</span><span class="nf">ClearColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">))</span>
        <span class="nf">.add_plugins</span><span class="p">(</span>
            <span class="n">DefaultPlugins</span>
                <span class="nf">.set</span><span class="p">(</span><span class="n">AssetPlugin</span> <span class="p">{</span>
                    <span class="n">file_path</span><span class="p">:</span> <span class="s">"src/assets"</span><span class="nf">.into</span><span class="p">(),</span>
                    <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                <span class="p">})</span>
                <span class="nf">.set</span><span class="p">(</span><span class="n">WindowPlugin</span> <span class="p">{</span>
                    <span class="n">primary_window</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">Window</span> <span class="p">{</span>
                        <span class="n">resolution</span><span class="p">:</span> <span class="nn">WindowResolution</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">map_size</span><span class="py">.x</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">map_size</span><span class="py">.y</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">),</span>
                        <span class="n">resizable</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
                        <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                    <span class="p">}),</span>
                    <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                <span class="p">})</span>
                <span class="nf">.set</span><span class="p">(</span><span class="nn">ImagePlugin</span><span class="p">::</span><span class="nf">default_nearest</span><span class="p">()),</span>
        <span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">ProcGenSimplePlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="p">,</span> <span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">characters</span><span class="p">::</span><span class="n">CharactersPlugin</span><span class="p">)</span>  <span class="c1">// Add this line</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="p">(</span><span class="n">setup_camera</span><span class="p">,</span> <span class="n">setup_generator</span><span class="p">))</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That’s it! Run your game with <code class="language-plaintext highlighter-rouge">cargo run</code>, and you should see your character on screen. Press the arrow keys to move, hold Shift to run, press Space to jump, and press number keys 1-6 to switch characters!</p>

<p><img src="/assets/book_assets/chapter3/chapter3.gif" alt="Animation System Demo" /></p>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Learn to build a data-driven character system in Bevy.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aibodh.com/assets/book_assets/chapter3/chapter3.gif" /><media:content medium="image" url="https://aibodh.com/assets/book_assets/chapter3/chapter3.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 2 - Let There Be a World</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-2/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 2 - Let There Be a World" /><published>2025-10-11T10:00:00+00:00</published><updated>2025-10-11T10:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-2</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-2/"><![CDATA[<style>
.tile-image {
  margin: 0 !important;
  object-fit: none !important;
  cursor: default !important;
  pointer-events: none !important;
}
</style>

<p>By the end of this tutorial, you’ll build a procedurally generated game world with layered terrain, water bodies, and props.</p>

<blockquote>
  <p><strong>Prerequisites</strong>: This is Chapter 2 of our Bevy tutorial series. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> for updates on new releases. Before starting, complete <a href="/posts/bevy-rust-game-development-chapter-1/">Chapter 1: Let There Be a Player</a>, or clone the Chapter 1 code from <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">this repository</a> to follow along.</p>
</blockquote>

<p><img src="/assets/book_assets/chapter2/ch2.gif" alt="Player Movement Demo" /></p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<strong>Before We Begin:</strong> <em style="font-size: 14px;">I'm constantly working to improve this tutorial and make your learning journey enjoyable. Your feedback matters - share your frustrations, questions, or suggestions on <a href="https://www.reddit.com/r/bevy/comments/1o3y2hr/the_impatient_programmers_guide_to_bevy_and_rust/" target="_blank">Reddit</a>/<a target="_blank" href="https://discord.com/invite/cD9qEsSjUH">Discord</a>/<a href="https://www.linkedin.com/posts/febinjohnjames_chapter-2-let-there-be-a-world-continuing-activity-7382797407824039936-WXFD?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAlx1JIBRLKRFr1OsUTf1LYBNPYbgdfxjbc" target="_blank">LinkedIn</a>. Loved it? Let me know what worked well for you! Together, we'll make game development with Rust and Bevy more accessible for everyone.</em>
</div>

<h2 id="procedural-generation">Procedural Generation</h2>

<p>I respect artists who hand craft tiles to build game worlds. But I belong to the impatient/lazy species.</p>

<p>I went on an exploration and came across procedural generation.</p>

<p>Little did I know the complexities involved. I was on the verge of giving up, however because of the comments and messages from readers of previous chapter, I kept going. And the enlightenment came three days ago, all the pieces fit together.</p>

<p>Basically it’s about automatically fitting things together like a jigsaw puzzle. To solve this problem, let’s again think in systems.</p>

<p><strong>What do we need to generate the game world procedurally?</strong></p>
<ol>
  <li>Tileset.</li>
  <li>Sockets for tiles because only compatible tiles should fit.</li>
  <li>Compatibility rules.</li>
  <li>Magic algorithm that uses these components to generate a coherent world.</li>
</ol>

<p><strong>How does this magic algorithm work?</strong></p>

<p>That “magic algorithm” has a name: Wave Function Collapse (WFC). The easiest way to see it is with a tiny Sudoku. Same idea: pick the cell with the fewest valid options, place a value, update neighbors, and repeat. If a choice leads to a dead end, undo that guess and try the next option.</p>

<p><strong>Small 4×4 Sudoku</strong></p>

<p>Let’s solve this step by step, focusing on the most constrained cells first.</p>

<link href="https://fonts.googleapis.com/css2?family=VT323&amp;display=swap" rel="stylesheet" />

<div style="margin: 20px 0; padding: 15px; background-color: #d1ecf1; border-radius: 8px; border-left: 4px solid #17a2b8;">
<strong>Initial Puzzle:</strong> We need to fill in the empty cells (marked with dots) following Sudoku rules.
</div>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7 !important; border-radius: 8px !important;">?</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">2</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">3</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">1</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">4</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
</table>
</div>
</div>

<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
<strong>Step 1 — Finding the most constrained cell:</strong><br />
Let's analyze the top-left 2×2 box:
<ul>
<li>Row 1 already has: 2</li>
<li>Column 1 already has: 4</li>
<li>Top-left box already has: 3</li>
<li>Available numbers: 1, 2, 3, 4</li>
<li>Eliminating: 2 (in row), 4 (in column), 3 (in box)</li>
<li><strong>Only 1 remains!</strong></li>
</ul>
</div>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9 !important; border-radius: 8px !important; font-weight: bold;">1</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">2</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">3</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">1</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">4</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
</table>
</div>
</div>

<div style="margin: 20px 0; padding: 15px; background-color: #d1ecf1; border-radius: 8px; border-left: 4px solid #17a2b8;">
<strong>Propagation Effect:</strong> Now that we placed 1, we can eliminate 1 from:
<ul>
<li>Row 1: ✓ (already done)</li>
<li>Column 1: ✓ (already done)</li>
<li>Top-left 2×2 box: ✓ (already done)</li>
</ul>
This makes other cells more constrained!
</div>

<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
<strong>Step 2 — Next most constrained cell:</strong><br />
Now let's find the next cell with the fewest options. 
</div>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9 !important; border-radius: 8px !important;">1</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7 !important; border-radius: 8px !important;">?</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">2</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">3</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">1</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">4</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
</table>
</div>
</div>

<div style="margin: 20px 0; padding: 15px; background-color: #d1ecf1; border-radius: 8px; border-left: 4px solid #17a2b8;">
<strong>Analysis for the position:</strong>
<ul>
<li>Row 1 already has: 1, 2</li>
<li>Column 2 already has: 3</li>
<li>Top-left box already has: 1, 3</li>
<li>Available numbers: 1, 2, 3, 4</li>
<li>Eliminating: 1 (in row), 2 (in row), 3 (in column and box)</li>
<li><strong>Only 4 remains!</strong></li>
</ul>
</div>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9 !important; border-radius: 8px !important;">1</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9 !important; border-radius: 8px !important; font-weight: bold;">4</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">2</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #e3f2fd !important; border-radius: 8px !important;">3</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">1</td>
</tr>
<tr>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">4</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
<td style="padding: 12px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #f5f5f5 !important; border-radius: 8px !important;">.</td>
</tr>
</table>
</div>
</div>

<div style="margin: 20px 0; padding: 15px; background-color: #d4edda; border-radius: 8px; border-left: 4px solid #28a745;">
<strong>Key Insight</strong> <br /> This is the essence of constraint propagation! Each placement immediately reduces the options for neighboring cells, making the puzzle progressively easier to solve. 
<br /><br />
We continue this process: pick the most constrained cell → place the only possible value → propagate constraints → repeat. 
<br /><br />
If any cell ends up with zero possibilities, we've hit a contradiction—in Sudoku, you backtrack and try a different value.
</div>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_e2a91f98ca2cbdb175c032912f683fb9.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="e2a91f98ca2cbdb175c032912f683fb9" data-comic-settings="d2-diagram" />
</div>

<p><strong>For our tile-based world:</strong> Imagine each grid cell as a Sudoku cell, but instead of numbers, we’re placing tiles.</p>

<p>Then we craft rules for valid connections:</p>
<ul>
  <li><strong>Rule 1</strong>: Water center tiles connect to other water tiles on all sides</li>
  <li><strong>Rule 2</strong>: Water edge tiles have two types of sides - water-facing sides connect to other water tiles, land-facing sides connect to the shore</li>
</ul>

<p>The algorithm uses these rules to ensure tiles fit together properly, creating coherent water bodies with natural-looking shorelines.</p>

<p>Let’s see this in action:</p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 24px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water.png" alt="Water center" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_side_t.png" alt="Water top" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_side_b.png" alt="Water bottom" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_side_l.png" alt="Water left" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_side_r.png" alt="Water right" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
</tr>
<tr>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_in_tl.png" alt="Water corner in TL" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_in_tr.png" alt="Water corner in TR" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_in_bl.png" alt="Water corner in BL" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_in_br.png" alt="Water corner in BR" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_out_tl.png" alt="Water corner out TL" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
</tr>
<tr>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_out_tr.png" alt="Water corner out TR" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_out_bl.png" alt="Water corner out BL" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>
<td style="padding: 4px; text-align: center; width: 50px; height: 50px; border: none !important; background-color: #f5f5f5; border-radius: 6px !important;"><img src="/assets/book_assets/tile_layers/water_corner_out_br.png" alt="Water corner out BR" class="tile-image" style="width: 50px; height: 42px; display: block;" /></td>

</tr>
</table>
</div>
</div>

<p><strong>Step 1 - Initial Grid</strong></p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
</tr>
<tr>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
</tr>
</table>
</div>
</div>

<p>We start with an empty grid where every cell can potentially hold any tile. The <code class="language-plaintext highlighter-rouge">?</code> symbols represent the “superposition” - each cell contains all possible tiles until we begin constraining them through the algorithm.</p>

<p><strong>Step 2 - First Placement</strong></p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
</tr>
<tr>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 8px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
</tr>
</table>
</div>
</div>

<p>The algorithm starts by placing the initial water center tile. Following <strong>Rule 1</strong>, this center tile needs other water tiles on all sides. This immediately constrains the neighboring cells - they must be water tiles that can connect to the center.</p>

<p><strong>Step 3 - Propagate Constraints</strong></p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_side_t.png" alt="Water edge" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_side_t.png" alt="Water edge" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
</tr>
<tr>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #ffeaa7; border-radius: 8px !important;">?</td>
</tr>
</table>
</div>
</div>

<p>Constraint propagation kicks in! The algorithm expands the water area by placing more center tiles. The edge tiles at the top follow <strong>Rule 2</strong> - their bottom sides (water-facing) connect to the center tiles, while their top sides (land-facing) will connect to the shore.</p>

<p><strong>Step 4 - Final Result</strong></p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_corner_out_tl.png" alt="Water" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_side_t.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_side_t.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_corner_out_tr.png" alt="Water" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
</tr>
<tr>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_side_l.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/water_side_r.png" alt="Water center" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
</tr>
</table>
</div>
</div>

<p>The algorithm completes by filling the edges with appropriate boundary tiles. Notice how our rules create perfect connections - center tiles (<strong>Rule 1</strong>) have water on all sides, while edge and corner tiles (<strong>Rule 2</strong>) have water-facing sides connecting inward and land-facing sides connecting to the shore, creating a coherent geography.</p>

<p>This demonstrates the core Wave Function Collapse algorithm in action:</p>
<ol>
  <li><strong>Find the most constrained cell</strong> - the one with the fewest valid tiles that could fit</li>
  <li><strong>Place a tile</strong> whose sockets are compatible with its neighbors</li>
  <li><strong>Propagate constraints</strong> - this placement immediately reduces the valid options for surrounding cells</li>
  <li><strong>Repeat</strong> until the grid is complete</li>
</ol>

<p>When we hit a dead end (no valid tiles for a cell), our implementation takes a simpler approach than Sudoku: instead of backtracking through previous choices, we restart with a fresh random seed (up to a retry limit) and run the entire process again until we generate a valid map.</p>

<p><strong>What do you mean by fresh random seed?</strong></p>

<p>A “random seed” is a starting number that controls which “random” sequence the algorithm will follow. Same seed = same tile placement order every time. When we hit a dead end, instead of backtracking, we generate a new random seed and start over—this gives us a completely different sequence of tile choices to try.</p>

<p><strong>Can configuring this randomness help us customize maps?</strong></p>

<p>Yes! The algorithm’s randomness comes from the order in which it picks cells and tiles, and we can control this to influence the final result. By adjusting the random seed or the selection strategy, we can:</p>

<ul>
  <li><strong>Bias toward certain patterns</strong> - Weight certain tiles more heavily to create specific landscape types.</li>
  <li><strong>Control size and complexity</strong> - Influence whether we get small ponds or large lakes.</li>
  <li><strong>Create predictable variations</strong> - Use the same seed for consistent results, or different seeds for variety.</li>
</ul>

<p>The same tileset can generate endless variations of coherent landscapes, from simple ponds to complex branching river systems, all by tweaking the randomness probability configuration.</p>

<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
While Wave Function Collapse is powerful, it has its limitations.
<ul>
<li><strong>No large-scale structure control</strong> - WFC focuses on tile compatibility, so it won't automatically create big patterns like "one large lake" or "mountain ranges".</li>
<li><strong>Can get stuck</strong> - Complex rules might lead to impossible situations where no valid tiles remain, requiring restarts.</li>
<li><strong>Performance depends on complexity</strong> - More tile types and stricter rules increase computation time and failure rates.</li>
<li><strong>Requires careful rule design</strong> - Poorly designed compatibility rules can lead to unrealistic or broken landscapes.</li>
</ul>
We'll address these limitations in a later chapter. For now, we'll focus on building a functional section of our game world that will become the foundation for building larger game worlds.
</div>

<h2 id="from-theory-to-implementation">From Theory to Implementation</h2>

<p>Now that we understand <strong>how</strong> Wave Function Collapse works—the constraint propagation, socket compatibility, and tile placement logic. It’s time to transform this knowledge into actual running code.</p>

<p><strong>The reality of implementation:</strong></p>

<p>Building a WFC algorithm from scratch is complex. You’d need to implement:</p>
<ul>
  <li>Constraint propagation across the entire grid</li>
  <li>Backtracking when hitting dead ends</li>
  <li>Efficient data structures for tracking possibilities</li>
  <li>Grid coordinate management</li>
  <li>Random selection with proper probability weights</li>
</ul>

<p>That’s a lot of algorithmic complexity before we even get to the game-specific parts, like sprites, rules, and world design.</p>

<p><strong>Our approach:</strong></p>

<p>Instead of reinventing the wheel, we’ll use a library that handles the WFC algorithm internals. This lets us focus on what makes our game unique: the tiles, the rules, the world aesthetics. We define <strong>what</strong> we want; the library figures out <strong>how</strong> to achieve it.</p>

<h2 id="setting-up-our-toolkit">Setting Up Our Toolkit</h2>

<p>Let’s add the procedural generation library to our project. We’ll be using the <code class="language-plaintext highlighter-rouge">bevy_procedural_tilemaps</code> <a href="https://crates.io/crates/bevy_procedural_tilemaps">crate</a>, which I built by forking <code class="language-plaintext highlighter-rouge">ghx_proc_gen</code> <a href="https://crates.io/crates/ghx_proc_gen">library</a>. I created this fork primarily to ensure compatibility with latest Bevy and to simplify this tutorial.</p>

<p>If you need advanced features, check out the original <code class="language-plaintext highlighter-rouge">ghx_proc_gen</code> <a href="https://crates.io/crates/ghx_proc_gen">crate</a> by Gilles Henaux, which includes 3D capabilities and debugging tools.</p>

<p>Hope you are following the code from first chapter. Here’s the <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">source code</a>.</p>

<p>Update your <code class="language-plaintext highlighter-rouge">Cargo.toml</code> with the bevy_procedural_tilemaps crate.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">package</span><span class="p">]</span>
<span class="n">name</span> <span class="o">=</span> <span class="s">"bevy_game"</span>
<span class="n">version</span> <span class="o">=</span> <span class="s">"0.1.0"</span>
<span class="n">edition</span> <span class="o">=</span> <span class="s">"2024"</span> 

<span class="p">[</span><span class="n">dependencies</span><span class="p">]</span>
<span class="n">bevy</span> <span class="o">=</span> <span class="s">"0.18"</span> <span class="c1">// Line update alert </span>
<span class="n">bevy_procedural_tilemaps</span> <span class="o">=</span> <span class="s">"0.2.0"</span> <span class="c1">// Line update alert</span>
</code></pre></div></div>

<h2 id="bevy-procedural-tilemaps">Bevy Procedural Tilemaps</h2>

<p>The <code class="language-plaintext highlighter-rouge">bevy_procedural_tilemaps</code> library handles the complex logic of generating coherent, rule-based worlds.</p>

<p><strong>What the library handles</strong></p>

<p>The library takes care of the <strong>algorithmic complexity</strong> of procedural generation:</p>

<ul>
  <li><strong>Rule Processing</strong>: Converts our game rules into the library’s internal format</li>
  <li><strong>Generator Creation</strong>: Builds the procedural generation engine with our configuration</li>
  <li><strong>Constraint Solving</strong>: Figures out which tiles can go where based on rules</li>
  <li><strong>Grid Management</strong>: Handles the 2D grid system and coordinate transformations</li>
  <li><strong>Entity Spawning</strong>: Creates Bevy entities and positions them correctly</li>
</ul>

<p><strong>What we need to provide</strong></p>

<p>We need to give the library the <strong>game-specific information</strong> it needs:</p>

<ul>
  <li><strong>Sprite Definitions</strong>: What sprites to use for each tile type</li>
  <li><strong>Compatibility Rules</strong>: Which tiles can be placed next to each other</li>
  <li><strong>Generation Configuration</strong>: The patterns and constraints for our specific game world</li>
  <li><strong>Asset Data</strong>: Sprite information, positioning, and custom components</li>
</ul>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_56ee0a9e75f2707d7f630cb65435018e.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="56ee0a9e75f2707d7f630cb65435018e" data-comic-settings="d2-diagram" />
</div>

<p>Now that we understand how the procedural generation system works, let’s build our map module.</p>

<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
<strong>Important Note</strong> <br /> <br /> Unlike Chapter 1 where you could see immediate results, procedural generation requires doing some ground work. You'll create asset loaders, tile spawning infrastructure before seeing your generated world.  <br /> <br />

The payoff comes when you finish the grass layer, at that point, you've learned the complete pattern. Adding water and props becomes straightforward repetition with different sprites and connection rules. Along the way, you'll understand Rust concepts (lifetimes, trait bounds, closures) that apply to any Rust project.
  <br /> <br />

<b>Don't give up if concepts feel unclear on first read. That's normal with procedural generation. Revisit confusing sections, experiment with the code, and understanding will emerge. Mastery comes through tinkering, not perfect comprehension on the first pass.</b>
</div>

<h2 id="the-map-module">The Map Module</h2>

<p>We’ll create a dedicated <code class="language-plaintext highlighter-rouge">map</code> folder inside the <code class="language-plaintext highlighter-rouge">src</code> folder to house all our world generation logic.</p>

<p><strong>Why create a separate folder for map generation?</strong></p>

<p>The map system requires multiple components working together. World generation involves:</p>

<ul>
  <li><strong>Asset management</strong> - Loading and organizing hundreds of tile images.</li>
  <li><strong>Rule definitions</strong> - Compatibility rules between different terrain types.</li>
  <li><strong>Grid setup</strong> - Configuring map dimensions and coordinate systems.</li>
</ul>

<p>Trying to fit all this logic into a single file would create a large file that can become difficult to navigate.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>src/
├── main.rs
├── player.rs
└── map/
    ├── mod.rs       
    ├── assets.rs       
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">mod.rs</code></strong></p>

<p>The <code class="language-plaintext highlighter-rouge">mod.rs</code> file is Rust’s way of declaring what modules exist in a folder. It’s like the “table of contents” for our map module. Add the following line to your <code class="language-plaintext highlighter-rouge">mod.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/mod.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">assets</span><span class="p">;</span>   <span class="c1">// Exposes assets.rs as a module</span>
</code></pre></div></div>

<p><strong>Why <code class="language-plaintext highlighter-rouge">mod.rs</code> specifically?</strong></p>

<p>It’s Rust convention, when you create a folder, Rust looks for <code class="language-plaintext highlighter-rouge">mod.rs</code> to understand the module structure.</p>

<h3 id="creating-spawnableasset">Creating SpawnableAsset</h3>

<p>Let’s start by creating our <code class="language-plaintext highlighter-rouge">assets.rs</code> file inside the <code class="language-plaintext highlighter-rouge">map</code> folder. This will be the foundation that defines how we spawn sprites in our world.</p>

<p>The <code class="language-plaintext highlighter-rouge">bevy_procedural_tilemaps</code> library needs to know <strong>what to actually place</strong> at each generated location.</p>

<p>It requires the following details:</p>
<ol>
  <li>Which sprite to use from our tilemap atlas?</li>
  <li>Where exactly to position it?</li>
  <li>What components to add (collision, physics, etc.)?</li>
</ol>

<p>The library expects us to provide this information in a very specific format. And doing 
this for every single tile type in your game - grass, dirt, trees, rocks, water, etc will result in redundant code.</p>

<p>This is where <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> comes in. It’s our <strong>abstraction layer</strong> to help you avoid unnecessary boilerplate.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::{</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span> <span class="nn">sprite</span><span class="p">::</span><span class="n">Anchor</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
    <span class="cd">/// Name of the sprite inside our tilemap atlas</span>
    <span class="n">sprite_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">,</span>
    <span class="cd">/// Offset in grid coordinates (for multi-tile objects)</span>
    <span class="n">grid_offset</span><span class="p">:</span> <span class="n">GridDelta</span><span class="p">,</span>
    <span class="cd">/// Offset in world coordinates (fine positioning)</span>
    <span class="n">offset</span><span class="p">:</span> <span class="n">Vec3</span><span class="p">,</span>
    <span class="cd">/// Function to add custom components (like collision, physics, etc.)</span>
    <span class="n">components_spawner</span><span class="p">:</span> <span class="k">fn</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">EntityCommands</span><span class="p">),</span>
<span class="p">}</span>

</code></pre></div></div>

<p><strong>SpawnableAsset Struct</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> struct contains all the information needed to spawn a tile in our world. The <code class="language-plaintext highlighter-rouge">sprite_name</code> field gives a name to your sprite (like “grass”, “tree”, “rock”).</p>

<p>The <code class="language-plaintext highlighter-rouge">grid_offset</code> is used for objects that span multiple tiles - it’s a positioning within the tile grid itself.</p>

<p>For example, the follow tree asset needs four tiles.</p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/big_tree_1_tl.png" alt="Tree top-left" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/big_tree_1_tr.png" alt="Tree top-right" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
</tr>
<tr>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/big_tree_1_bl.png" alt="Tree bottom-left" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important;"><img src="/assets/book_assets/tile_layers/big_tree_1_br.png" alt="Tree bottom-right" class="tile-image" style="width: 60px; height: 50px; display: block;" /></td>
</tr>
</table>
</div>
</div>

<p><strong>Grid Offset</strong></p>

<table>
  <thead>
    <tr>
      <th>Tree Part</th>
      <th>Grid Offset</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bottom-left</td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0)</code></td>
      <td>Stays at original position</td>
    </tr>
    <tr>
      <td>Bottom-right</td>
      <td><code class="language-plaintext highlighter-rouge">(1, 0)</code></td>
      <td>Moves one tile right</td>
    </tr>
    <tr>
      <td>Top-left</td>
      <td><code class="language-plaintext highlighter-rouge">(0, 1)</code></td>
      <td>Moves one tile up</td>
    </tr>
    <tr>
      <td>Top-right</td>
      <td><code class="language-plaintext highlighter-rouge">(1, 1)</code></td>
      <td>Moves one tile up and right</td>
    </tr>
  </tbody>
</table>

<p><br /><br />
The <code class="language-plaintext highlighter-rouge">offset</code> field, on the other hand, is for fine-tuning the position within the tile - like moving a rock slightly to the left or making sure a tree trunk is perfectly centered within its tile space.</p>

<p>Let’s see how <code class="language-plaintext highlighter-rouge">offset</code> works with rock positioning:</p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<table style="border-collapse: separate; border-spacing: 2px; font-family: 'VT323', monospace; font-size: 32px; border-radius: 8px; overflow: hidden; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 3px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important; position: relative;"><img src="/assets/book_assets/tile_layers/rock_1.png" alt="Rock 1" class="tile-image" style="width: 60px; height: 50px; display: block; transform: translate(0px, 0px);" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important; position: relative;"><img src="/assets/book_assets/tile_layers/rock_2.png" alt="Rock 2" class="tile-image" style="width: 60px; height: 50px; display: block; transform: translate(-8px, -6px);" /></td>
<td style="padding: 6px; text-align: center; width: 60px; height: 60px; border: none !important; background-color: #c8e6c9; border-radius: 8px !important; position: relative;"><img src="/assets/book_assets/tile_layers/rock_3.png" alt="Rock 3" class="tile-image" style="width: 60px; height: 50px; display: block; transform: translate(6px, 5px);" /></td>
</tr>
</table>
</div>
</div>

<p><strong>Offset</strong></p>

<table>
  <thead>
    <tr>
      <th>Rock</th>
      <th>Offset</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Rock 1</td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0)</code></td>
      <td>Centered in tile</td>
    </tr>
    <tr>
      <td>Rock 2</td>
      <td><code class="language-plaintext highlighter-rouge">(-8, -6)</code></td>
      <td>Moved slightly left and up</td>
    </tr>
    <tr>
      <td>Rock 3</td>
      <td><code class="language-plaintext highlighter-rouge">(6, 5)</code></td>
      <td>Moved slightly right and down</td>
    </tr>
  </tbody>
</table>

<p>Finally, the <code class="language-plaintext highlighter-rouge">components_spawner</code> is a function that adds custom behavior like collision, physics, or other game mechanics.
<br /><br /></p>

<p><strong>Why is sprite name defined as <code class="language-plaintext highlighter-rouge">&amp;'static str?</code></strong></p>

<p>Let’s break down <code class="language-plaintext highlighter-rouge">&amp;'static str</code> piece by piece to understand why we use it for sprite names.</p>

<p>The <code class="language-plaintext highlighter-rouge">&amp;</code> symbol means “reference to” - instead of making a new copy of the text, we just note where the original text is located.</p>

<p>The <code class="language-plaintext highlighter-rouge">'static</code> is a special lifetime annotation that tells Rust “this text will exist for the entire duration of your game.” When you write <code class="language-plaintext highlighter-rouge">"grass"</code> directly in your code, Rust bakes it into your game file when you build it. It’s always there, from game startup to shutdown.</p>

<p><strong>What’s a lifetime and what has <code class="language-plaintext highlighter-rouge">'static</code> got to do with it?</strong></p>

<p>A <strong>lifetime</strong> is Rust’s way of tracking how long data lives in memory. Rust needs to know when it’s safe to use data and when it might be deleted.</p>

<p>Most data has a limited lifetime. For example:</p>
<ul>
  <li>Local variables live only while a function runs</li>
  <li>Function parameters live only while the function executes</li>
  <li>Data created in a loop might be deleted when the loop ends</li>
</ul>

<p>But some data lives forever - like string literals embedded in your program. The <code class="language-plaintext highlighter-rouge">'static</code> lifetime means “this data lives for the entire duration of the program” - it never gets deleted.</p>

<p>This is perfect for our sprite names because they’re hardcoded in our source code (like <code class="language-plaintext highlighter-rouge">"grass"</code>, <code class="language-plaintext highlighter-rouge">"tree"</code>, <code class="language-plaintext highlighter-rouge">"rock"</code>) and will never change or be deleted while the program runs. Rust can safely let us use these references anywhere in our code because it knows the data will always be there.</p>

<p><strong>What’s a string literal?</strong></p>

<p>A string literal is text you write directly in quotes in your code: <code class="language-plaintext highlighter-rouge">"grass"</code>, <code class="language-plaintext highlighter-rouge">"dirt"</code>, <code class="language-plaintext highlighter-rouge">"tree"</code>.</p>

<p><strong>Why does Rust need to know when it’s safe to use data? Other languages don’t seem to care about this.</strong></p>

<p>Most languages (like C, C++, Java, Python) handle memory safety differently:</p>

<ul>
  <li><strong>C/C++</strong>: Don’t track lifetimes at all - you can accidentally use deleted data, leading to crashes or security vulnerabilities</li>
  <li><strong>Java/Python/C#</strong>: Use garbage collection - the runtime automatically deletes unused data, but this adds overhead and unpredictable pauses</li>
  <li><strong>Rust</strong>: Tracks lifetimes at compile time - prevents crashes without runtime overhead</li>
</ul>

<p><strong>The Problem Other Languages Have</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Psuedo code warning, don't use</span>
<span class="c1">// This would crash in C++ or cause undefined behavior</span>
<span class="k">let</span> <span class="n">sprite_name</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">temp</span> <span class="o">=</span> <span class="s">"grass"</span><span class="p">;</span>
    <span class="o">&amp;</span><span class="n">temp</span>  <span class="c1">// temp gets deleted here!</span>
<span class="p">};</span> 
<span class="nd">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">sprite_name</span><span class="p">);</span> <span class="c1">// CRASH! Using deleted data</span>
</code></pre></div></div>

<p><strong>Rust Prevents This</strong>
<br />
Rust’s compiler analyzes your code and says “Hey, you’re trying to use data that might be deleted. I won’t let you compile this unsafe code.” This catches bugs before your game even runs.</p>

<p><strong>Does <code class="language-plaintext highlighter-rouge">str</code> mean String data type?</strong>
<br />
Not quite. <code class="language-plaintext highlighter-rouge">str</code> represents text data, known as a <strong>string slice</strong>, but you can only use it through a reference like <code class="language-plaintext highlighter-rouge">&amp;str</code> (a view of text stored somewhere else). <code class="language-plaintext highlighter-rouge">String</code> is text you own and can modify. Our sprite names like “grass” are baked into the program, so <code class="language-plaintext highlighter-rouge">&amp;str</code> just points to that text without copying it - much more efficient than using <code class="language-plaintext highlighter-rouge">String</code>.</p>

<p><code class="language-plaintext highlighter-rouge">&amp;'static str</code> means “a reference (<code class="language-plaintext highlighter-rouge">&amp;</code>), to a string slice (<code class="language-plaintext highlighter-rouge">str</code>) that lives for the entire program duration (<code class="language-plaintext highlighter-rouge">'static</code>).” This gives us the best of all worlds: memory efficiency (no copying), performance (direct access), and safety (Rust knows the data will always be valid).</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_0682b2d4e2ca2f17297f1c25b9d7a68a.svg" alt="Comic Panel" class="comic-image" data-comic-hash="0682b2d4e2ca2f17297f1c25b9d7a68a" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">GridDelta</code>?</strong></p>

<p><code class="language-plaintext highlighter-rouge">GridDelta</code> is a struct that represents movement in grid coordinates. It specifies “how many tiles to move” in each direction. For example, <code class="language-plaintext highlighter-rouge">GridDelta::new(1, 0, 0)</code> means “move one tile to the right”, while <code class="language-plaintext highlighter-rouge">GridDelta::new(0, 1, 0)</code> means “move one tile up”. It’s used for positioning multi-tile objects like the tree sprite with multiple tiles we mentioned earlier in grid offset.</p>

<p><strong>Why’s components_spawner defined as <code class="language-plaintext highlighter-rouge">fn(&amp;mut EntityCommands)</code>?</strong></p>

<p>This is a function pointer that takes a mutable reference to <code class="language-plaintext highlighter-rouge">EntityCommands</code> (Bevy’s way of adding components to entities). Looking at the code in <code class="language-plaintext highlighter-rouge">assets.rs</code>, we can see it defaults to an empty function that does nothing.</p>

<p>The function pointer allows us to customize what components get added to each spawned entity. For example, a tree sprite might need collision components for physics, while a decorative flower might only need basic rendering components. Each sprite can have its own custom set of components without affecting others.</p>

<p><strong>Why do we need a mutable reference to EntityCommands?</strong></p>

<p>Yes! In Rust, you need a mutable reference (<code class="language-plaintext highlighter-rouge">&amp;mut</code>) when you want to modify something. <code class="language-plaintext highlighter-rouge">EntityCommands</code> needs to be mutable because it’s used to add, remove, or modify components on entities.</p>

<p>Now let’s add some helpful methods to our <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> struct to make it easier to create and configure sprite assets.</p>

<p>Append the following code to the same <code class="language-plaintext highlighter-rouge">assets.rs</code> file.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs</span>
<span class="k">impl</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">sprite_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">sprite_name</span><span class="p">,</span>
            <span class="n">grid_offset</span><span class="p">:</span> <span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
            <span class="n">offset</span><span class="p">:</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
            <span class="n">components_spawner</span><span class="p">:</span> <span class="p">|</span><span class="n">_</span><span class="p">|</span> <span class="p">{},</span> <span class="c1">// Default: no extra components</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">with_grid_offset</span><span class="p">(</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> <span class="n">offset</span><span class="p">:</span> <span class="n">GridDelta</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.grid_offset</span> <span class="o">=</span> <span class="n">offset</span><span class="p">;</span>
        <span class="k">self</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">-&gt; Self</code>?</strong></p>

<p>In Rust, you must specify the return type of functions (unlike some languages that can infer it). The <code class="language-plaintext highlighter-rouge">-&gt; Self</code> tells the compiler exactly what type the function returns, which helps catch errors at compile time. <code class="language-plaintext highlighter-rouge">Self</code> means “the same type as the struct this method belongs to” - so <code class="language-plaintext highlighter-rouge">Self</code> refers to <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> here.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">|_| {}</code>?</strong></p>

<p>This is a closure (anonymous function) that does nothing. The <code class="language-plaintext highlighter-rouge">|_|</code> means “takes one parameter but ignores it” (the underscore means we don’t use the parameter), and <code class="language-plaintext highlighter-rouge">{}</code> is an empty function body.</p>

<p>We need this because our <code class="language-plaintext highlighter-rouge">SpawnableAsset</code> struct requires a <code class="language-plaintext highlighter-rouge">components_spawner</code> field (as we saw in the struct definition), but for basic sprites we don’t want to add any custom components. This empty closure serves as a “do nothing” default. We’ll learn how to use this field to add custom components in later chapters, but for now it’s just a placeholder that satisfies the struct’s requirements.</p>

<p><strong>What’s a closure? What do you mean by anonymous function?</strong></p>

<p>A closure is a function that can “capture” variables from its surrounding environment. An anonymous function means it doesn’t have a name - you define it inline where you need it, rather than declaring it separately like fn my_function().</p>

<p><strong>Example usage</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Psuedo code warning, don't use</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">player_health</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>

<span class="c1">// This closure captures 'player_health' by mutable reference</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">take_damage</span> <span class="o">=</span> <span class="p">||</span> <span class="p">{</span>
    <span class="n">player_health</span> <span class="o">-=</span> <span class="mi">25</span><span class="p">;</span>  <span class="c1">// Modifies the original variable</span>
    <span class="n">player_health</span>
<span class="p">};</span>

<span class="c1">// The closure can modify the original variable</span>
<span class="nf">take_damage</span><span class="p">();</span>  <span class="c1">// player_health is now 75</span>
<span class="nf">take_damage</span><span class="p">();</span>  <span class="c1">// player_health is now 50</span>
</code></pre></div></div>

<p><strong>Why use closures here?</strong></p>

<p>In our SpawnableAsset struct closure can be used to allow each sprite to have custom behavior when spawned. For example, a tree might need collision components, while a decorative flower might need different components. The closure can capture game state and configuration to customize spawning behavior for each sprite type.</p>

<p><strong>Why is semicolon missing in the last line of these functions?</strong></p>

<p>In Rust, the last expression in a function is automatically returned without needing a <code class="language-plaintext highlighter-rouge">return</code> keyword or semicolon. This makes it easier to specify what value should be returned - you just write the expression you want to return, and Rust handles the rest. This is Rust’s way of making code cleaner and more concise.</p>

<p><strong>Why can’t you manipulate or retrieve <code class="language-plaintext highlighter-rouge">grid_offset</code> directly?</strong></p>

<p>The fields of <code class="language-plaintext highlighter-rouge">GridDelta</code> are private (they have no <code class="language-plaintext highlighter-rouge">pub</code> keyword), which means they can only be accessed from within the module where <code class="language-plaintext highlighter-rouge">GridDelta</code> is defined. This is called “encapsulation” - it prevents developers from making mistakes by modifying the struct’s data directly, which could break the internal logic. We provide the public method <code class="language-plaintext highlighter-rouge">with_grid_offset()</code> to safely modify it while maintaining the struct’s integrity.</p>

<p>Now that we understand how to define our sprites with <code class="language-plaintext highlighter-rouge">SpawnableAsset</code>, <strong>how do we load and use these sprites in our game?</strong></p>

<h3 id="loading-sprite-assets">Loading Sprite Assets</h3>

<p>Our game uses a <strong>sprite atlas</strong> - a single large image containing all our sprites. Bevy needs to know where each sprite is located within this image, and we need to avoid reloading the same image multiple times.</p>

<p>Create a folder <code class="language-plaintext highlighter-rouge">tile_layers</code> in your <code class="language-plaintext highlighter-rouge">src/assets</code> folder and place <code class="language-plaintext highlighter-rouge">tilemap.png</code> inside it, you can get it from this <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">github repo</a>.</p>

<div style="margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
<div style="text-align: center;">
  <img src="/assets/book_assets/tile_layers/tilemap.png" alt="Tilemap sprite atlas" style="max-width: 100%; height: auto; pointer-events: none;" />
</div>
The tilemap assets used in this example are based on <a target="_blank" href="https://opengameart.org/content/16x16-game-assets">16x16 Game Assets</a>  by George Bailey, available on OpenGameArt under CC-BY 4.0 license. <strong>However, to follow this tutorial, please use tilemap.png provide from the chapter's <a target="_blank" style="font-weight:650" href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust"> github repo</a>.</strong> 
</div>

<p>Now inside <code class="language-plaintext highlighter-rouge">src/map</code> folder create a file <code class="language-plaintext highlighter-rouge">tilemap.rs</code>. When you add a file inside map folder, ensure to register it in <code class="language-plaintext highlighter-rouge">mod.rs</code> by adding the line <code class="language-plaintext highlighter-rouge">pub mod tilemap</code>.</p>

<p>This is where our tilemap definition comes in - it acts as a “map” that tells Bevy the coordinates of every sprite in our atlas.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">math</span><span class="p">::{</span><span class="n">URect</span><span class="p">,</span> <span class="n">UVec2</span><span class="p">};</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="nb">str</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">pixel_x</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">pixel_y</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">TilemapDefinition</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">tile_width</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">tile_height</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">atlas_width</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">atlas_height</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">sprites</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">'static</span> <span class="p">[</span><span class="n">TilemapSprite</span><span class="p">],</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">TilemapSprite</code> struct represents a single sprite within our atlas. It stores the sprite’s name (like “dirt” or “green_grass”) and its exact pixel coordinates within the atlas image.</p>

<p>The <code class="language-plaintext highlighter-rouge">TilemapDefinition</code> struct serves as the “blueprint” that Bevy uses to understand how to slice up our atlas image into individual sprites.</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">tile_width</code> and <code class="language-plaintext highlighter-rouge">tile_height</code></strong> - How big each individual sprite is (in our case, 32×32 pixels)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">atlas_width</code> and <code class="language-plaintext highlighter-rouge">atlas_height</code></strong> - The total size of your entire sprite atlas image (the big image containing all sprites)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">sprites</code></strong> - The list of all sprites in your atlas, each with its name and location</li>
</ol>

<p>Though our tilemap stores sprite names and pixel coordinates, Bevy’s texture atlas system requires numeric indices and rectangular regions. These methods perform the necessary conversions.</p>

<p>Append the following code to your <code class="language-plaintext highlighter-rouge">tilemap.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs</span>

<span class="k">impl</span> <span class="n">TilemapDefinition</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="nf">tile_size</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">UVec2</span> <span class="p">{</span>
        <span class="nn">UVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">self</span><span class="py">.tile_width</span><span class="p">,</span> <span class="k">self</span><span class="py">.tile_height</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">const</span> <span class="k">fn</span> <span class="nf">atlas_size</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">UVec2</span> <span class="p">{</span>
        <span class="nn">UVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">self</span><span class="py">.atlas_width</span><span class="p">,</span> <span class="k">self</span><span class="py">.atlas_height</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">sprite_index</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">usize</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.sprites</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.position</span><span class="p">(|</span><span class="n">sprite</span><span class="p">|</span> <span class="n">sprite</span><span class="py">.name</span> <span class="o">==</span> <span class="n">name</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">sprite_rect</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">URect</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">sprite</span> <span class="o">=</span> <span class="o">&amp;</span><span class="k">self</span><span class="py">.sprites</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
        <span class="k">let</span> <span class="n">min</span> <span class="o">=</span> <span class="nn">UVec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">sprite</span><span class="py">.pixel_x</span><span class="p">,</span> <span class="n">sprite</span><span class="py">.pixel_y</span><span class="p">);</span>
        <span class="nn">URect</span><span class="p">::</span><span class="nf">from_corners</span><span class="p">(</span><span class="n">min</span><span class="p">,</span> <span class="n">min</span> <span class="o">+</span> <span class="k">self</span><span class="nf">.tile_size</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">tile_size()</code> method converts our tile dimensions into a <code class="language-plaintext highlighter-rouge">UVec2</code> (unsigned 2D vector), which Bevy uses for size calculations. Similarly, <code class="language-plaintext highlighter-rouge">atlas_size()</code> provides the total atlas dimensions as a <code class="language-plaintext highlighter-rouge">UVec2</code>, which Bevy uses to create the texture atlas layout.</p>

<p>The <code class="language-plaintext highlighter-rouge">sprite_index()</code> method helps in finding sprites by name. When we want to render a “dirt” tile, this method searches through our sprite array and returns the index position of that sprite.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">sprite_rect()</code> takes a sprite index and calculates the exact rectangular region within our atlas that contains that sprite. It uses <code class="language-plaintext highlighter-rouge">URect</code> (unsigned rectangle) to define the boundaries, which Bevy’s texture atlas system requires to know which part of the large image to display.</p>

<p>Now let’s put our tilemap definition to use by adding our first sprite - the dirt tile.</p>

<h3 id="adding-the-dirt-tile">Adding the Dirt Tile</h3>

<p>Let’s start with a simple dirt tile to test our tilemap system. The dirt tile sits at pixel coordinates (128, 0) in our 256x320 atlas image. We’ll add more sprites later as we build out our game world.</p>

<p>Append this code to <code class="language-plaintext highlighter-rouge">tilemap.rs</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">TILEMAP</span><span class="p">:</span> <span class="n">TilemapDefinition</span> <span class="o">=</span> <span class="n">TilemapDefinition</span> <span class="p">{</span>
    <span class="n">tile_width</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">tile_height</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">atlas_width</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
    <span class="n">atlas_height</span><span class="p">:</span> <span class="mi">320</span><span class="p">,</span>
    <span class="n">sprites</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span>
          <span class="n">TilemapSprite</span> <span class="p">{</span>
            <span class="n">name</span><span class="p">:</span> <span class="s">"dirt"</span><span class="p">,</span>
            <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
            <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">},</span>
    <span class="p">]</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Notice how we’re using a const definition - this means all this sprite metadata is determined at compile time.</p>

<h3 id="connecting-the-tilemap-to-asset-loading">Connecting the Tilemap to Asset Loading</h3>

<p>Now that we’ve defined our tilemap and sprites in <code class="language-plaintext highlighter-rouge">tilemap.rs</code>, we need to connect this to our asset loading system in <code class="language-plaintext highlighter-rouge">assets.rs</code>.</p>

<p>Let’s update the imports in <code class="language-plaintext highlighter-rouge">assets.rs</code> to bring in our <code class="language-plaintext highlighter-rouge">TILEMAP</code> definition:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span> 
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">tilemap</span><span class="p">::</span><span class="n">TILEMAP</span><span class="p">;</span> <span class="c1">// &lt;--- line update alert</span>
</code></pre></div></div>

<p>With the import in place, we can now build the three key functions that helps our procedural rendering system:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">TilemapHandles</code> - Container that holds our loaded atlas and layout data</li>
  <li><code class="language-plaintext highlighter-rouge">prepare_tilemap_handles</code> - Loads the atlas image from disk and creates the texture atlas layout defining each sprite’s rectangular region</li>
  <li><code class="language-plaintext highlighter-rouge">load_assets</code> - Converts sprite names into <code class="language-plaintext highlighter-rouge">Sprite</code> data structures ready for rendering</li>
</ol>

<p>Let’s build these step by step.</p>

<h3 id="creating-the-tilemaphandles-struct">Creating the TilemapHandles Struct</h3>

<p>First, we need a way to hold references to both the atlas image and its layout. Go ahead and append this code into your <code class="language-plaintext highlighter-rouge">assets.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs</span>
<span class="nd">#[derive(Clone)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TilemapHandles</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">image</span><span class="p">:</span> <span class="n">Handle</span><span class="o">&lt;</span><span class="n">Image</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">layout</span><span class="p">:</span> <span class="n">Handle</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">TilemapHandles</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">sprite</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">atlas_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Sprite</span> <span class="p">{</span>
        <span class="nn">Sprite</span><span class="p">::</span><span class="nf">from_atlas_image</span><span class="p">(</span>
            <span class="k">self</span><span class="py">.image</span><span class="nf">.clone</span><span class="p">(),</span>
            <span class="nn">TextureAtlas</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="k">self</span><span class="py">.layout</span><span class="nf">.clone</span><span class="p">())</span><span class="nf">.with_index</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">),</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">TilemapHandles</code> struct is a container for two handles: <code class="language-plaintext highlighter-rouge">image</code> points to our loaded sprite sheet file, while <code class="language-plaintext highlighter-rouge">layout</code> points to the atlas layout that tells Bevy how to slice that image into individual sprites.</p>

<p>The <code class="language-plaintext highlighter-rouge">sprite(atlas_index)</code> method is a convenience function that creates a ready-to-render <code class="language-plaintext highlighter-rouge">Sprite</code> by combining the image and layout with a specific index. For example, if the dirt tile is at index 0, calling <code class="language-plaintext highlighter-rouge">tilemap_handles.sprite(0)</code> gives us a <code class="language-plaintext highlighter-rouge">Sprite</code> configured to display just the dirt tile from our atlas.</p>

<h3 id="loading-the-atlas-from-disk">Loading the Atlas from Disk</h3>

<p>Now let’s create the function that actually loads the atlas image file and sets up the layout. We will be using our <code class="language-plaintext highlighter-rouge">TILEMAP</code> definition from earlier.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">prepare_tilemap_handles</span><span class="p">(</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">atlas_layouts</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">assets_directory</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">tilemap_file</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TilemapHandles</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">image</span> <span class="o">=</span> <span class="n">asset_server</span><span class="py">.load</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Image</span><span class="o">&gt;</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{assets_directory}/{tilemap_file}"</span><span class="p">));</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">layout</span> <span class="o">=</span> <span class="nn">TextureAtlasLayout</span><span class="p">::</span><span class="nf">new_empty</span><span class="p">(</span><span class="n">TILEMAP</span><span class="nf">.atlas_size</span><span class="p">());</span>
    <span class="k">for</span> <span class="n">index</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="n">TILEMAP</span><span class="py">.sprites</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">layout</span><span class="nf">.add_texture</span><span class="p">(</span><span class="n">TILEMAP</span><span class="nf">.sprite_rect</span><span class="p">(</span><span class="n">index</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="n">atlas_layouts</span><span class="nf">.add</span><span class="p">(</span><span class="n">layout</span><span class="p">);</span>

    <span class="n">TilemapHandles</span> <span class="p">{</span> <span class="n">image</span><span class="p">,</span> <span class="n">layout</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Breaking it down:</strong></p>

<ol>
  <li><strong>Load the image</strong>: <code class="language-plaintext highlighter-rouge">asset_server.load()</code> requests the atlas image file from disk</li>
  <li><strong>Create empty layout</strong>: <code class="language-plaintext highlighter-rouge">TextureAtlasLayout::new_empty(TILEMAP.atlas_size())</code> creates a layout matching our 256x320 atlas</li>
  <li><strong>Register each sprite</strong>: The loop iterates through all sprites in <code class="language-plaintext highlighter-rouge">TILEMAP</code>, using <code class="language-plaintext highlighter-rouge">TILEMAP.sprite_rect(index)</code> to get each sprite’s coordinates and adding them to the layout</li>
  <li><strong>Store and return</strong>: The layout is added to Bevy’s asset system, and we return a <code class="language-plaintext highlighter-rouge">TilemapHandles</code> containing both handles</li>
</ol>

<p>This is where <code class="language-plaintext highlighter-rouge">TILEMAP.atlas_size()</code> and <code class="language-plaintext highlighter-rouge">TILEMAP.sprite_rect()</code> from our tilemap definition come into play - they tell Bevy exactly how to slice up our atlas image!</p>

<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
This function loads the atlas into memory and sets up the layout structure, but it doesn't actually generate the game world yet. We're just preparing the tools that the procedural generator will use later to create the map.
</div>

<h3 id="converting-sprite-names-to-renderable-sprites">Converting Sprite Names to Renderable Sprites</h3>

<p>Finally, we need a way to convert sprite names (like “dirt”) into <code class="language-plaintext highlighter-rouge">Sprite</code> objects that can be rendered.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/assets.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">load_assets</span><span class="p">(</span>
    <span class="n">tilemap_handles</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TilemapHandles</span><span class="p">,</span>
    <span class="n">assets_definitions</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ModelsAssets</span><span class="o">&lt;</span><span class="n">Sprite</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">models_assets</span> <span class="o">=</span> <span class="nn">ModelsAssets</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">model_index</span><span class="p">,</span> <span class="n">assets</span><span class="p">)</span> <span class="k">in</span> <span class="n">assets_definitions</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">asset_def</span> <span class="k">in</span> <span class="n">assets</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">SpawnableAsset</span> <span class="p">{</span>
                <span class="n">sprite_name</span><span class="p">,</span>
                <span class="n">grid_offset</span><span class="p">,</span>
                <span class="n">offset</span><span class="p">,</span>
                <span class="n">components_spawner</span><span class="p">,</span>
            <span class="p">}</span> <span class="o">=</span> <span class="n">asset_def</span><span class="p">;</span>

            <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">)</span> <span class="o">=</span> <span class="n">TILEMAP</span><span class="nf">.sprite_index</span><span class="p">(</span><span class="n">sprite_name</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nd">panic!</span><span class="p">(</span><span class="s">"Unknown atlas sprite '{}'"</span><span class="p">,</span> <span class="n">sprite_name</span><span class="p">);</span>
            <span class="p">};</span>

            <span class="n">models_assets</span><span class="nf">.add</span><span class="p">(</span>
                <span class="n">model_index</span><span class="p">,</span>
                <span class="n">ModelAsset</span> <span class="p">{</span>
                    <span class="n">assets_bundle</span><span class="p">:</span> <span class="n">tilemap_handles</span><span class="nf">.sprite</span><span class="p">(</span><span class="n">atlas_index</span><span class="p">),</span>
                    <span class="n">grid_offset</span><span class="p">,</span>
                    <span class="n">world_offset</span><span class="p">:</span> <span class="n">offset</span><span class="p">,</span>
                    <span class="n">spawn_commands</span><span class="p">:</span> <span class="n">components_spawner</span><span class="p">,</span>
                <span class="p">},</span>
            <span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">models_assets</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why the two loops?</strong></p>

<p>Some tiles are simple and need just one sprite (like dirt). Others are complex and need multiple sprites (like a tree that needs 4 parts).</p>

<p>The outer loop says “for each type of tile,” and the inner loop says “for each sprite that tile needs.”</p>

<p><strong>Let’s walk through what happens when we load a dirt tile:</strong></p>

<ol>
  <li>We have: <code class="language-plaintext highlighter-rouge">SpawnableAsset { sprite_name: "dirt", ... }</code></li>
  <li>The function asks TILEMAP: “Where is ‘dirt’?” → TILEMAP replies: “Index 0”</li>
  <li>It then asks TilemapHandles: “Give me a Sprite for index 0” → Gets back a <code class="language-plaintext highlighter-rouge">Sprite</code> object</li>
  <li>Finally, it packages everything together with the positioning info and stores it</li>
</ol>

<p><strong>What does the final data look like?</strong></p>

<p>After <code class="language-plaintext highlighter-rouge">load_assets</code> completes, we have a collection of <code class="language-plaintext highlighter-rouge">ModelAsset</code> objects in memory. Here’s what the data structure looks like for a few tiles:</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Field</th>
      <th>Value</th>
      <th>What It Means</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dirt</strong></td>
      <td><code class="language-plaintext highlighter-rouge">assets_bundle</code></td>
      <td><code class="language-plaintext highlighter-rouge">Sprite(atlas_index: 0)</code></td>
      <td>Points to dirt sprite in atlas</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">grid_offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0, 0)</code></td>
      <td>No grid offset needed</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">world_offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0, 0)</code></td>
      <td>No world offset needed</td>
    </tr>
    <tr>
      <td><strong>Tree (bottom)</strong></td>
      <td><code class="language-plaintext highlighter-rouge">assets_bundle</code></td>
      <td><code class="language-plaintext highlighter-rouge">Sprite(atlas_index: 31)</code></td>
      <td>Points to tree bottom sprite</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">grid_offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0, 0)</code></td>
      <td>Placed at base position</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">world_offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0, 0)</code></td>
      <td>Centered</td>
    </tr>
    <tr>
      <td><strong>Tree (top)</strong></td>
      <td><code class="language-plaintext highlighter-rouge">assets_bundle</code></td>
      <td><code class="language-plaintext highlighter-rouge">Sprite(atlas_index: 30)</code></td>
      <td>Points to tree top sprite</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">grid_offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">(0, 1, 0)</code></td>
      <td>One tile up from bottom</td>
    </tr>
    <tr>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">world_offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">(0, 0, 0)</code></td>
      <td>Centered</td>
    </tr>
  </tbody>
</table>

<p><strong>Important:</strong> These are just data structures in memory - nothing is drawn on screen yet! The actual rendering happens later when the procedural generator uses these prepared <code class="language-plaintext highlighter-rouge">ModelAsset</code> objects to spawn entities.</p>

<div style="margin: 20px 0; padding: 15px; background-color: #d4edda; border-radius: 8px; border-left: 4px solid #28a745;">
<strong>Great Progress!</strong> You've made it through the foundation layer - sprites, tilemaps, and asset loading. Now we have the visual pieces (assets), but how does the generator know which tiles can be placed next to each other? That's where models and sockets come in!
</div>

<h2 id="from-tiles-to-models">From Tiles to Models</h2>

<p>You already understand <strong>tiles</strong> - the individual visual pieces like grass, dirt, and water. Now we need to build models by adding sockets to these tiles and define connection rules so the generator can figure out valid placements.</p>

<h3 id="how-models-expose-sockets">How Models Expose Sockets</h3>

<p>Models expose <strong>sockets</strong> - labeled connection points on each edge. Let’s look at a green grass model and see how it exposes sockets in different directions.</p>

<p><strong>Horizontal Plane (x and y directions)</strong></p>

<div style="display: flex; flex-direction: column; align-items: center; margin: 40px 0; font-family: monospace;">
  <!-- Top socket (y_pos) -->
  <div style="text-align: center; margin-bottom: 15px;">
    <div style="margin-bottom: 10px; font-size: 24px; color: #1976d2;">↑</div>
    <div style="padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2; display: inline-block;">
      <strong>up (y_pos)</strong><br />
      grass.material
    </div>
    <div style="margin-top: 5px; font-size: 24px;">↓</div>
  </div>
  
  <!-- Middle row: left, center, right -->
  <div style="display: flex; align-items: center; gap: 15px; margin: 20px 0;">
    <div style="display: flex; align-items: center; gap: 10px;">
      <div style="font-size: 24px; color: #1976d2;">←</div>
      <div style="padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
        <strong>left (x_neg)</strong><br />
        grass.material
      </div>
    </div>
    
    <div style="font-size: 24px;">→</div>
    
    <div style="padding: 40px 50px; background-color: #90EE90; border-radius: 8px; border-left: 4px solid #28a745; font-size: 18px; font-weight: bold; text-align: center;">
      GREEN<br />GRASS
    </div>
    
    <div style="font-size: 24px;">←</div>
    
    <div style="display: flex; align-items: center; gap: 10px;">
      <div style="padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2;">
        <strong>right (x_pos)</strong><br />
        grass.material
      </div>
      <div style="font-size: 24px; color: #1976d2;">→</div>
    </div>
  </div>
  
  <!-- Bottom socket (y_neg) -->
  <div style="text-align: center; margin-top: 15px;">
    <div style="margin-bottom: 5px; font-size: 24px;">↑</div>
    <div style="padding: 15px; background-color: #e3f2fd; border-radius: 8px; border-left: 4px solid #1976d2; display: inline-block;">
      <strong>down (y_neg)</strong><br />
      grass.material
    </div>
    <div style="margin-top: 10px; font-size: 24px; color: #1976d2;">↓</div>
  </div>
</div>

<p><strong>Vertical Axis (z direction)</strong></p>

<div style="display: flex; flex-direction: column; align-items: center; margin: 40px 0; font-family: monospace;">
  <!-- Top socket (z_pos) -->
  <div style="text-align: center; margin-bottom: 15px;">
    <div style="margin-bottom: 10px; font-size: 24px; color: #6c757d;">↑</div>
    <div style="padding: 15px; background-color: #f0f0f0; border-radius: 8px; border-left: 4px solid #6c757d; display: inline-block;">
      <strong>top (z_pos)</strong><br />
      grass.layer_up
    </div>
    <div style="margin-top: 5px; font-size: 24px;">↓</div>
  </div>
  
  <!-- Center tile -->
  <div style="padding: 40px 50px; background-color: #90EE90; border-radius: 8px; border-left: 4px solid #28a745; font-size: 18px; font-weight: bold; text-align: center; margin: 20px 0;">
    GREEN<br />GRASS
  </div>
  
  <!-- Bottom socket (z_neg) -->
  <div style="text-align: center; margin-top: 15px;">
    <div style="margin-bottom: 5px; font-size: 24px;">↑</div>
    <div style="padding: 15px; background-color: #f0f0f0; border-radius: 8px; border-left: 4px solid #6c757d; display: inline-block;">
      <strong>bottom (z_neg)</strong><br />
      grass.layer_down
    </div>
    <div style="margin-top: 10px; font-size: 24px; color: #6c757d;">↓</div>
  </div>
</div>

<p><strong>How does z-axis make sense in a 2D game?</strong></p>

<p>Even though we’re building a 2D game, the z-axis represents <strong>layering</strong> - Imagine stacking transparent sheets on top of each other. Here’s how it works with our yellow grass example:</p>

<p><strong>The Layering System:</strong></p>
<ul>
  <li><strong>Dirt tiles</strong> form the base layer (ground level)</li>
  <li><strong>Green grass tiles</strong> can sit on top of dirt (one layer up)</li>
  <li><strong>Yellow grass tiles</strong> can sit on top of green grass (another layer up)</li>
</ul>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_99a5c9abc09772693226ac80e01e18b5.svg" alt="Comic Panel" class="comic-image" data-comic-hash="99a5c9abc09772693226ac80e01e18b5" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="building-models">Building Models</h3>

<p>Now that we understand how models expose sockets in all six directions, we need a way to create these models and link them to their visual sprites.</p>

<p>We’ll use a helper called <code class="language-plaintext highlighter-rouge">TerrainModelBuilder</code> that keeps models and sprites paired correctly as we build our world.</p>

<h3 id="the-terrainmodelbuilder">The TerrainModelBuilder</h3>

<p>Create a new file <code class="language-plaintext highlighter-rouge">models.rs</code> inside the <code class="language-plaintext highlighter-rouge">map</code> folder, and don’t forget to add <code class="language-plaintext highlighter-rouge">pub mod models;</code> to your <code class="language-plaintext highlighter-rouge">mod.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/models.rs</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">assets</span><span class="p">::</span><span class="n">SpawnableAsset</span><span class="p">;</span>

<span class="cd">/// Utility wrapper that ensures model declarations and their asset bindings stay aligned.</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TerrainModelBuilder</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">models</span><span class="p">:</span> <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">assets</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">TerrainModelBuilder</code> holds:</p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">models</code></strong>: What the WFC algorithm uses</li>
  <li><strong><code class="language-plaintext highlighter-rouge">assets</code></strong>: The sprites for the respective model</li>
</ol>

<p>Now let’s add these methods to the builder.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/models.rs</span>
<span class="k">impl</span> <span class="n">TerrainModelBuilder</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="k">Self</span> <span class="p">{</span>
            <span class="n">models</span><span class="p">:</span> <span class="nn">ModelCollection</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
            <span class="n">assets</span><span class="p">:</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="n">create_model</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span>
        <span class="n">template</span><span class="p">:</span> <span class="n">T</span><span class="p">,</span>
        <span class="n">assets</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Model</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span>
    <span class="k">where</span>
        <span class="n">T</span><span class="p">:</span> <span class="nb">Into</span><span class="o">&lt;</span><span class="n">ModelTemplate</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="p">{</span>
        <span class="k">let</span> <span class="n">model_ref</span> <span class="o">=</span> <span class="k">self</span><span class="py">.models</span><span class="nf">.create</span><span class="p">(</span><span class="n">template</span><span class="p">);</span>
        <span class="k">self</span><span class="py">.assets</span><span class="nf">.push</span><span class="p">(</span><span class="n">assets</span><span class="p">);</span>
        <span class="n">model_ref</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">into_parts</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="p">(</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span> <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">(</span><span class="k">self</span><span class="py">.assets</span><span class="p">,</span> <span class="k">self</span><span class="py">.models</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">new()</code> method creates an empty builder to start with.</p>

<p>The <code class="language-plaintext highlighter-rouge">create_model()</code> method both a socket definition and the corresponding sprites, then adds them to their respective collections at the same index.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">into_parts()</code> splits the builder back into separate collections when you’re done building, so the assets can go to the renderer and the models can go to the WFC generator.</p>

<p><strong>What’s <code class="language-plaintext highlighter-rouge">&lt;T&gt;</code> doing in <code class="language-plaintext highlighter-rouge">pub fn create_model&lt;T&gt;</code>?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">&lt;T&gt;</code> is Rust’s <strong>generic type parameter</strong> - it’s a placeholder that gets filled in with the actual type when you call the function. In our case, we might pass in different types of socket definitions (simple single-socket tiles or complex multi-socket tiles), but we want to perform the same operation on all of them.</p>

<p>Generics let us write one function that works with multiple types, as long as they can all be converted into a <code class="language-plaintext highlighter-rouge">ModelTemplate</code>. This is powerful because it means we can add new socket definition types in the future without changing our <code class="language-plaintext highlighter-rouge">TerrainModelBuilder</code> code.</p>

<p><strong>What’s this <code class="language-plaintext highlighter-rouge">where T: Into&lt;ModelTemplate&lt;Cartesian3D&gt;&gt;</code>?</strong></p>

<p>This is a <strong>trait bound</strong> that tells Rust what capabilities the generic type <code class="language-plaintext highlighter-rouge">T</code> must have. The <code class="language-plaintext highlighter-rouge">where</code> clause says “T must be able to convert itself into a <code class="language-plaintext highlighter-rouge">ModelTemplate&lt;Cartesian3D&gt;</code> (a 3D model template).”</p>

<p><code class="language-plaintext highlighter-rouge">Into</code> is Rust’s way of saying “this type knows how to transform itself into that type” - like how a string can be converted into a number, or how our socket definitions can be converted into model templates. This means we can pass in any type that knows how to become a <code class="language-plaintext highlighter-rouge">ModelTemplate</code> - whether it’s simple single-socket tiles, complex multi-socket tiles, or even a custom socket type you create later.</p>

<p>This gives us flexibility while ensuring type safety. The compiler will catch any attempts to pass in a type that can’t be converted, preventing runtime errors!</p>

<h2 id="building-the-foundation">Building the Foundation</h2>

<p>Now that we understand how to keep models and assets synchronized, let’s start building our procedural world from the ground up. The dirt layer forms the foundation that everything else sits on.</p>

<p><strong>Layers Make WFC Simpler</strong></p>

<p>Without layers, we’d need to cram all our rules into a single layer: “water connects to water and grass”, “grass connects to grass and dirt”, “trees connect to grass”, “dirt connects to dirt” - plus all the edge cases and special connections.</p>

<p>This creates a massive web of interdependencies that makes the WFC algorithm struggle to find valid solutions.</p>

<p>By using layers, we break this complexity into manageable pieces. Each layer only needs to worry about its own connections, making the WFC algorithm much more likely to find valid solutions quickly.</p>

<p>Let’s create our dirt layer, make a new file <code class="language-plaintext highlighter-rouge">sockets.rs</code> inside the <code class="language-plaintext highlighter-rouge">map</code> folder, and don’t forget to add <code class="language-plaintext highlighter-rouge">pub mod sockets;</code> to your <code class="language-plaintext highlighter-rouge">mod.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">DirtLayerSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">layer_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>      <span class="c1">// What can sit on top of dirt</span>
    <span class="k">pub</span> <span class="n">layer_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>     <span class="c1">// What dirt can sit on</span>
    <span class="k">pub</span> <span class="n">material</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>       <span class="c1">// What dirt connects to horizontally</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The dirt layer needs three types of sockets.</p>

<ol>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">layer_up</code></strong> - This socket handles what can be placed in the layer above dirt. Remember layers are to separate rule cram concerns (water can be above grass without touching it).</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">layer_down</code></strong> - It handles what layer the dirt itself can be placed on. For the base layer, this will connect to void (empty space).</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">material</code></strong> - This takes care of horizontal connections between dirt tiles, ensuring they connect properly to form continuous ground.</p>
  </li>
</ol>

<p><strong>Initializing the Sockets</strong></p>

<p>Now we need to actually create these socket instances. Append this function to <code class="language-plaintext highlighter-rouge">sockets.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">new_socket</span> <span class="o">=</span> <span class="p">||</span> <span class="k">-&gt;</span> <span class="n">Socket</span> <span class="p">{</span> <span class="n">socket_collection</span><span class="nf">.create</span><span class="p">()</span> <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">sockets</span> <span class="o">=</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
        <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
    <span class="p">};</span>
    <span class="n">sockets</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">create_sockets</code> function takes a <code class="language-plaintext highlighter-rouge">SocketCollection</code> and creates all our socket instances. The <code class="language-plaintext highlighter-rouge">new_socket</code> closure is a helper that calls <code class="language-plaintext highlighter-rouge">socket_collection.create()</code> to generate unique socket IDs. Each socket gets a unique identifier that the WFC algorithm uses to track compatibility rules.</p>

<h3 id="building-the-dirt-layer">Building the Dirt Layer</h3>

<p>Now that we have our socket system defined and initialized, we need to create the rules that tell the WFC algorithm how to use these sockets. This is where we define models and how they connect to each other.</p>

<p>Create a new file <code class="language-plaintext highlighter-rouge">rules.rs</code> inside the <code class="language-plaintext highlighter-rouge">map</code> folder, and don’t forget to add <code class="language-plaintext highlighter-rouge">pub mod rules;</code> to your <code class="language-plaintext highlighter-rouge">mod.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">assets</span><span class="p">::</span><span class="n">SpawnableAsset</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">models</span><span class="p">::</span><span class="n">TerrainModelBuilder</span><span class="p">;</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">sockets</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">build_dirt_layer</span><span class="p">(</span>
    <span class="n">terrain_model_builder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">TerrainModelBuilder</span><span class="p">,</span>
    <span class="n">terrain_sockets</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TerrainSockets</span><span class="p">,</span>
    <span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span> <span class="c1">// right</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span> <span class="c1">// left</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.layer_up</span><span class="p">,</span> <span class="c1">// top </span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.layer_down</span><span class="p">,</span> <span class="c1">// bottom</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span> <span class="c1">// up</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span> <span class="c1">// down</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"dirt"</span><span class="p">)],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="mf">20.</span><span class="p">);</span>

    <span class="n">socket_collection</span><span class="nf">.add_connections</span><span class="p">(</span><span class="nd">vec!</span><span class="p">[(</span>
        <span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">,</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.dirt.material</span><span class="p">],</span>
    <span class="p">)]);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Understanding the Dirt Layer Rules:</strong></p>

<ol>
  <li><strong>Creates a dirt model</strong> - Defines a tile that <strong>exposes</strong> sockets on all six sides</li>
  <li><strong>Exposes socket types</strong> - Horizontal sides expose <code class="language-plaintext highlighter-rouge">dirt.material</code>, vertical sides expose layer sockets</li>
  <li><strong>Assigns a sprite</strong> - <code class="language-plaintext highlighter-rouge">SpawnableAsset::new("dirt")</code> tells the renderer which sprite to use</li>
  <li><strong>Sets the weight</strong> - <code class="language-plaintext highlighter-rouge">.with_weight(20.)</code> makes dirt tiles 20 times more likely to be placed</li>
  <li><strong>Defines connection rules</strong> - <code class="language-plaintext highlighter-rouge">add_connections</code> tells WFC that <code class="language-plaintext highlighter-rouge">dirt.material</code> can connect to other <code class="language-plaintext highlighter-rouge">dirt.material</code></li>
</ol>

<p>This creates a simple but effective foundation layer that can form continuous ground while supporting other layers on top!</p>

<p>Now let’s append the <code class="language-plaintext highlighter-rouge">build_world</code> function that the generator will call to get all our dirt layer rules and models:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_world</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span>
    <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">socket_collection</span> <span class="o">=</span> <span class="nn">SocketCollection</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">terrain_sockets</span> <span class="o">=</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">);</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">terrain_model_builder</span> <span class="o">=</span> <span class="nn">TerrainModelBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Build dirt layer</span>
    <span class="nf">build_dirt_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">)</span> <span class="o">=</span> <span class="n">terrain_model_builder</span><span class="nf">.into_parts</span><span class="p">();</span>

    <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What This Function Does:</strong></p>

<ol>
  <li><strong>Creates the socket collection</strong> - This is where all our socket connection rules are stored</li>
  <li><strong>Gets our socket definitions</strong> - Calls <code class="language-plaintext highlighter-rouge">create_sockets()</code> to get all the socket types we defined</li>
  <li><strong>Creates the model builder</strong> - This keeps our models and assets synchronized</li>
  <li><strong>Builds the dirt layer</strong> - Calls our <code class="language-plaintext highlighter-rouge">build_dirt_layer</code> function to create all the dirt models and rules</li>
  <li><strong>Returns the three collections</strong> - Assets for rendering, models for WFC rules, and socket collection for connections</li>
</ol>

<p>This function is what the generator calls to get all the rules and models needed to create our procedural world!</p>

<h3 id="generating-dirt">Generating Dirt</h3>

<p>Now that we have all our components - assets, models, sockets, and rules - we need to configure the procedural generation engine.</p>

<p>Create a new file <code class="language-plaintext highlighter-rouge">generate.rs</code> inside the <code class="language-plaintext highlighter-rouge">map</code> folder, and don’t forget to add <code class="language-plaintext highlighter-rouge">pub mod generate;</code> to your <code class="language-plaintext highlighter-rouge">mod.rs</code>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs</span>
<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::{</span>
    <span class="nn">assets</span><span class="p">::{</span><span class="n">load_assets</span><span class="p">,</span> <span class="n">prepare_tilemap_handles</span><span class="p">},</span>
    <span class="nn">rules</span><span class="p">::</span><span class="n">build_world</span><span class="p">,</span>
<span class="p">};</span>

<span class="c1">// -----------------  Configurable values ---------------------------</span>
<span class="cd">/// Modify these values to control the map size.</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">GRID_X</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">25</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">GRID_Y</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span>

<span class="c1">// ------------------------------------------------------------------</span>

<span class="k">const</span> <span class="n">ASSETS_PATH</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"tile_layers"</span><span class="p">;</span>
<span class="k">const</span> <span class="n">TILEMAP_FILE</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"tilemap.png"</span><span class="p">;</span>
<span class="cd">/// Size of a block in world units (in Bevy 2d, 1 pixel is 1 world unit)</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">TILE_SIZE</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">32.</span><span class="p">;</span>
<span class="cd">/// Size of a grid node in world units</span>
<span class="k">const</span> <span class="n">NODE_SIZE</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">TILE_SIZE</span><span class="p">,</span> <span class="n">TILE_SIZE</span><span class="p">,</span> <span class="mf">1.</span><span class="p">);</span>

<span class="k">const</span> <span class="n">ASSETS_SCALE</span><span class="p">:</span> <span class="n">Vec3</span> <span class="o">=</span> <span class="nn">Vec3</span><span class="p">::</span><span class="n">ONE</span><span class="p">;</span>
<span class="cd">/// Number of z layers in the map, derived from the default terrain layers.</span>
<span class="k">const</span> <span class="n">GRID_Z</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">fn</span> <span class="nf">map_pixel_dimensions</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">Vec2</span> <span class="p">{</span>
    <span class="nn">Vec2</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_X</span> <span class="k">as</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">GRID_Y</span> <span class="k">as</span> <span class="nb">f32</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Understanding the Configuration Constants:</strong></p>

<p>Let’s break down what each of these constants controls:</p>

<ol>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">GRID_X</code> and <code class="language-plaintext highlighter-rouge">GRID_Y</code></strong> - These define the size of our generated world in tiles. A 25×18 grid means 450 total tiles (25 × 18 = 450). You can adjust these to create larger or smaller worlds, though larger grids may cause the WFC algorithm to struggle - we’ll address scaling issues in a later chapter.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">TILE_SIZE</code></strong> - This is the size of each tile in world units. Since we’re using 32×32 pixel sprites, each tile takes up 32 world units. This affects how big your world appears on screen.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">NODE_SIZE</code></strong> - This tells the generator how much space each grid cell occupies in the 3D world. Equal values = perfect tile fit, smaller NODE_SIZE = overlapping sprites, larger NODE_SIZE = gaps between tiles.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">GRID_Z</code></strong> - This defines how many layers our world has. We’re currently using 1 layer for dirt, but we’ll add more layers later to stack different terrain types on top of each other (dirt, grass, yellow grass, water, props).</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">ASSETS_SCALE</code></strong> - This controls the size multiplier for sprites. <code class="language-plaintext highlighter-rouge">Vec3::ONE</code> means sprites render at their original size.</p>
  </li>
</ol>

<p>Now let’s append the <code class="language-plaintext highlighter-rouge">setup_generator</code> function that sets up our procedural generation engine:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">setup_generator</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 1. Rules Initialization - Get tile definitions and connection rules</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">assets_definitions</span><span class="p">,</span> <span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span> <span class="o">=</span> <span class="nf">build_world</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">rules</span> <span class="o">=</span> <span class="nn">RulesBuilder</span><span class="p">::</span><span class="nf">new_cartesian_3d</span><span class="p">(</span><span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span>
        <span class="c1">// Use ZForward as the up axis (rotation axis for models) since we are using Bevy in 2D</span>
        <span class="nf">.with_rotation_axis</span><span class="p">(</span><span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">)</span>
        <span class="nf">.build</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// 2. Grid - Create 3D world space with wrapping behavior (false, false, false)</span>
    <span class="k">let</span> <span class="n">grid</span> <span class="o">=</span> <span class="nn">CartesianGrid</span><span class="p">::</span><span class="nf">new_cartesian_3d</span><span class="p">(</span><span class="n">GRID_X</span><span class="p">,</span> <span class="n">GRID_Y</span><span class="p">,</span> <span class="n">GRID_Z</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>

    <span class="c1">// 3. Configuring the Algorithm - Set up WFC behavior</span>
    <span class="k">let</span> <span class="n">gen_builder</span> <span class="o">=</span> <span class="nn">GeneratorBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.with_rules</span><span class="p">(</span><span class="n">rules</span><span class="p">)</span>
        <span class="nf">.with_grid</span><span class="p">(</span><span class="n">grid</span><span class="nf">.clone</span><span class="p">())</span>
        <span class="nf">.with_rng</span><span class="p">(</span><span class="nn">RngMode</span><span class="p">::</span><span class="n">RandomSeed</span><span class="p">)</span>
        <span class="nf">.with_node_heuristic</span><span class="p">(</span><span class="nn">NodeSelectionHeuristic</span><span class="p">::</span><span class="n">MinimumRemainingValue</span><span class="p">)</span>
        <span class="nf">.with_model_heuristic</span><span class="p">(</span><span class="nn">ModelSelectionHeuristic</span><span class="p">::</span><span class="n">WeightedProbability</span><span class="p">);</span>
    
    <span class="k">let</span> <span class="n">generator</span> <span class="o">=</span> <span class="n">gen_builder</span><span class="nf">.build</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// 4. Loading Assets - Load sprite atlas and convert to renderable assets</span>
    <span class="k">let</span> <span class="n">tilemap_handles</span> <span class="o">=</span>
        <span class="nf">prepare_tilemap_handles</span><span class="p">(</span><span class="o">&amp;</span><span class="n">asset_server</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">,</span> <span class="n">ASSETS_PATH</span><span class="p">,</span> <span class="n">TILEMAP_FILE</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">models_assets</span> <span class="o">=</span> <span class="nf">load_assets</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tilemap_handles</span><span class="p">,</span> <span class="n">assets_definitions</span><span class="p">);</span>

    <span class="c1">// 5. Spawning the Generator - Create entity with Transform and NodesSpawner</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="n">Vec3</span> <span class="p">{</span>
            <span class="n">x</span><span class="p">:</span> <span class="o">-</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">grid</span><span class="nf">.size_x</span><span class="p">()</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">/</span> <span class="mf">2.</span><span class="p">,</span>
            <span class="n">y</span><span class="p">:</span> <span class="o">-</span><span class="n">TILE_SIZE</span> <span class="o">*</span> <span class="n">grid</span><span class="nf">.size_y</span><span class="p">()</span> <span class="k">as</span> <span class="nb">f32</span> <span class="o">/</span> <span class="mf">2.</span><span class="p">,</span>
            <span class="n">z</span><span class="p">:</span> <span class="mf">0.</span><span class="p">,</span>
        <span class="p">}),</span>
        <span class="n">grid</span><span class="p">,</span>
        <span class="n">generator</span><span class="p">,</span>
        <span class="nn">NodesSpawner</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">models_assets</span><span class="p">,</span> <span class="n">NODE_SIZE</span><span class="p">,</span> <span class="n">ASSETS_SCALE</span><span class="p">)</span><span class="nf">.with_z_offset_from_y</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
    <span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Rules Initialization</strong></p>

<p>This creates the <strong>constraint solver</strong> that the WFC algorithm uses. It takes our tile definitions and connection rules and converts them into a format the algorithm can understand.</p>

<p><strong>Why <code class="language-plaintext highlighter-rouge">Direction::ZForward</code>?</strong></p>

<p>Since we’re building a 2D game, we need to tell the system which axis to use for rotations. <code class="language-plaintext highlighter-rouge">Direction::ZForward</code> means tiles rotate around the Z-axis (pointing toward/away from the screen), which makes sense for a 2D top-down view.</p>

<p><strong>Grid</strong></p>

<p>This creates our world space where tiles will be placed. The three boolean parameters control wrapping behavior:</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">(false, false, false)</code></strong> - Most games like Minecraft, Terraria (hard boundaries)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">(true, true, false)</code></strong> - Classic Asteroids or Pac-Man (wraps left-right and up-down)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">(true, true, true)</code></strong> - Advanced simulations with infinite-feeling 3D worlds</li>
</ol>

<p><strong>Configuring the Algorithm</strong></p>

<p>This is where we configure how the WFC algorithm behaves:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">RngMode::RandomSeed</code> - Uses random seeds (same seed = same world every time)</li>
  <li><code class="language-plaintext highlighter-rouge">NodeSelectionHeuristic::MinimumRemainingValue</code> - Always picks the most constrained cell (fewest valid tiles)</li>
  <li><code class="language-plaintext highlighter-rouge">ModelSelectionHeuristic::WeightedProbability</code> - Picks tiles based on their weights (higher weight = more likely)</li>
</ol>

<p><strong>Loading Assets and Spawning the Generator</strong></p>

<p><code class="language-plaintext highlighter-rouge">prepare_tilemap_handles()</code> loads our sprite atlas from disk, while <code class="language-plaintext highlighter-rouge">load_assets()</code> converts our sprite definitions into renderable assets.</p>

<p>The <code class="language-plaintext highlighter-rouge">commands.spawn()</code> creates the generator entity with a <code class="language-plaintext highlighter-rouge">Transform</code> that centers the world on screen and a <code class="language-plaintext highlighter-rouge">NodesSpawner</code> that handles the actual tile creation.</p>

<p>The <code class="language-plaintext highlighter-rouge">with_z_offset_from_y(true)</code> setting uses Y coordinates for Z-layer positioning - tiles higher up on screen render in front, creating natural depth ordering (e.g., tree at Y=10 appears in front of rock at Y=5).</p>

<h4 id="final-module-structure">Final Module Structure</h4>

<p>Throughout this chapter, we’ve been building our procedural generation system across multiple files. Before we integrate everything into your main game, let’s make sure your <code class="language-plaintext highlighter-rouge">src/map/mod.rs</code> file includes all the modules we’ve created:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/mod.rs</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">generate</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">assets</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">models</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">rules</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">sockets</span><span class="p">;</span>
<span class="k">pub</span> <span class="k">mod</span> <span class="n">tilemap</span><span class="p">;</span>
</code></pre></div></div>

<p>Make sure your <code class="language-plaintext highlighter-rouge">mod.rs</code> file matches this structure before proceeding to the integration step.</p>

<h4 id="integrating-the-generator-into-your-game">Integrating the Generator into Your Game</h4>

<p>Now that we’ve built our procedural generation system, we need to integrate it into our main game. We’ll update the <code class="language-plaintext highlighter-rouge">main.rs</code> file to include the procedural generation plugin and set up the window size to match our generated world.</p>

<p><strong>Updating main.rs</strong></p>

<p>We need to add the procedural generation plugin and configure the window size to match our generated world. Update your <code class="language-plaintext highlighter-rouge">main.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs</span>
<span class="k">mod</span> <span class="n">map</span><span class="p">;</span>
<span class="k">mod</span> <span class="n">player</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">bevy</span><span class="p">::{</span>
    <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">,</span>
    <span class="nn">window</span><span class="p">::{</span><span class="n">Window</span><span class="p">,</span> <span class="n">WindowPlugin</span><span class="p">,</span> <span class="n">WindowResolution</span><span class="p">},</span>
<span class="p">};</span>

<span class="k">use</span> <span class="nn">bevy_procedural_tilemaps</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">map</span><span class="p">::</span><span class="nn">generate</span><span class="p">::{</span><span class="n">map_pixel_dimensions</span><span class="p">,</span> <span class="n">setup_generator</span><span class="p">};</span>
<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">player</span><span class="p">::</span><span class="n">PlayerPlugin</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">map_size</span> <span class="o">=</span> <span class="nf">map_pixel_dimensions</span><span class="p">();</span>

    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.insert_resource</span><span class="p">(</span><span class="nf">ClearColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">))</span>
        <span class="nf">.add_plugins</span><span class="p">(</span>
            <span class="n">DefaultPlugins</span>
                <span class="nf">.set</span><span class="p">(</span><span class="n">AssetPlugin</span> <span class="p">{</span>
                    <span class="n">file_path</span><span class="p">:</span> <span class="s">"src/assets"</span><span class="nf">.into</span><span class="p">(),</span>
                    <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                <span class="p">})</span>
                <span class="nf">.set</span><span class="p">(</span><span class="n">WindowPlugin</span> <span class="p">{</span>
                    <span class="n">primary_window</span><span class="p">:</span> <span class="nf">Some</span><span class="p">(</span><span class="n">Window</span> <span class="p">{</span>
                        <span class="n">resolution</span><span class="p">:</span> <span class="nn">WindowResolution</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">map_size</span><span class="py">.x</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">map_size</span><span class="py">.y</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">),</span>
                        <span class="n">resizable</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
                        <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                    <span class="p">}),</span>
                    <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
                <span class="p">})</span>
                <span class="nf">.set</span><span class="p">(</span><span class="nn">ImagePlugin</span><span class="p">::</span><span class="nf">default_nearest</span><span class="p">()),</span>
        <span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="nn">ProcGenSimplePlugin</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="p">,</span> <span class="n">Sprite</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="p">(</span><span class="n">setup_camera</span><span class="p">,</span> <span class="n">setup_generator</span><span class="p">))</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="n">PlayerPlugin</span><span class="p">)</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">setup_camera</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">(</span><span class="n">Camera2d</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s New:</strong></p>

<ol>
  <li><strong>Map module import</strong> - <code class="language-plaintext highlighter-rouge">mod map;</code> brings in our procedural generation code</li>
  <li><strong>Window sizing</strong> - <code class="language-plaintext highlighter-rouge">map_pixel_dimensions()</code> calculates the window size based on our grid dimensions</li>
  <li><strong>Procedural generation plugin</strong> - <code class="language-plaintext highlighter-rouge">ProcGenSimplePlugin</code> handles the WFC algorithm execution</li>
  <li><strong>Generator setup</strong> - <code class="language-plaintext highlighter-rouge">setup_generator</code> runs at startup to create our world</li>
  <li><strong>Image filtering</strong> - <code class="language-plaintext highlighter-rouge">ImagePlugin::default_nearest()</code> keeps pixel art crisp</li>
</ol>

<p><strong>Running Your Procedural World</strong></p>

<p>Now run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>You should see a procedurally generated world with dirt tiles following the rules we defined! The world will be centered on screen, and the window size will match your grid dimensions (25×18 tiles = 800×576 pixels).</p>

<p><img src="/assets/book_assets/chapter2/dirt_layer_without_player.png" alt="Dirt Layer" /></p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_4bb7eed0880dd6c8ab2d745364fabfed.svg" alt="Comic Panel" class="comic-image" data-comic-hash="4bb7eed0880dd6c8ab2d745364fabfed" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<div style="margin: 20px 0; padding: 15px; background-color: #d1ecf1; border-radius: 8px; border-left: 4px solid #17a2b8;">
<strong>Linux Users with Wayland:</strong> If you're seeing white lines appearing between tile rows in your tilemap, this is a known Bevy <a href="https://github.com/bevyengine/bevy/issues/16918" target="_blank">rendering issue</a> that affects Linux systems using the Wayland display protocol. To resolve this, run your application using XWayland instead:
<br /><br />
<code>WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= cargo run</code>
</div>

<p><strong>Where’s the player?</strong></p>

<p>The player is actually there, but it’s rendering behind the dirt tiles.</p>

<p>We need to make the player render on top of other layers we have.</p>

<p><strong>Add a Z position constant</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/player.rs, please it below ANIM_DT const</span>
<span class="k">const</span> <span class="n">PLAYER_Z</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">20.0</span><span class="p">;</span> 
</code></pre></div></div>

<p><strong>Update the spawn function</strong> to use this Z value and scale the player slightly down (for better visual proportion with our generated world).</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/player.rs - Update the Transform line in spawn_player</span>
<span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mf">0.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">,</span> <span class="n">PLAYER_Z</span><span class="p">))</span><span class="nf">.with_scale</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="mf">0.8</span><span class="p">)),</span>
</code></pre></div></div>

<p>Run your again:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>Your player renders in front of all tiles and looks proportional to the 32×32 tile world!</p>

<p><img src="/assets/book_assets/chapter2/dirt_layer_with_player.png" alt="Dirt Layer" /></p>

<h2 id="adding-the-grass-layer">Adding the Grass Layer</h2>

<p>Now that we have a working dirt foundation, let’s add grass on top. The grass layer will create patches of green grass that sit on the dirt, with proper edge tiles for smooth transitions.</p>

<h3 id="step-1-adding-grass-sprites-to-the-tilemap">Step 1: Adding Grass Sprites to the Tilemap</h3>

<p>First, we need to add all the grass sprite coordinates to our tilemap. Append these sprites to the <code class="language-plaintext highlighter-rouge">sprites</code> array in <code class="language-plaintext highlighter-rouge">tilemap.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs - Add these to the sprites array after the dirt sprite inside TilemapDefinition struct</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_in_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_in_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_in_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_in_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_out_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_out_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_out_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_corner_out_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_side_t"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_side_r"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_side_l"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"green_grass_side_b"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>

<p>These sprites include the main grass tile, inner corners, outer corners, and side edges for smooth transitions between grass and dirt.</p>

<h3 id="step-2-adding-grass-sockets">Step 2: Adding Grass Sockets</h3>

<p>Now we need to define the sockets for the grass layer. Update your <code class="language-plaintext highlighter-rouge">sockets.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Add this struct after DirtLayerSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">GrassLayerSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">layer_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">layer_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">material</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">void_and_grass</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">grass_and_void</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">grass_fill_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then update the <code class="language-plaintext highlighter-rouge">TerrainSockets</code> struct to include grass:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update TerrainSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">void</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span> <span class="c1">// line update alert</span>
    <span class="k">pub</span> <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span><span class="p">,</span> <span class="c1">// line update alert</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, update the <code class="language-plaintext highlighter-rouge">create_sockets</code> function to initialize the grass sockets:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update create_sockets function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">new_socket</span> <span class="o">=</span> <span class="p">||</span> <span class="k">-&gt;</span> <span class="n">Socket</span> <span class="p">{</span> <span class="n">socket_collection</span><span class="nf">.create</span><span class="p">()</span> <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">sockets</span> <span class="o">=</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
        <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
         <span class="c1">// line update alert</span>
        <span class="n">void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span> 
         <span class="c1">// lines update alert</span>
        <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">void_and_grass</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_and_void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_fill_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
    <span class="p">};</span>
    <span class="n">sockets</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why does grass need more sockets than dirt?</strong></p>

<p>Dirt is simple - it fills the entire base layer, so every dirt tile connects to another dirt tile. Grass is different - it creates patches on top of dirt, which means grass tiles need to handle edges where grass meets empty space.</p>

<p>Here’s what each socket handles:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">material</code></strong> - Connects grass to grass (like dirt’s material socket)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">layer_up</code> and <code class="language-plaintext highlighter-rouge">layer_down</code></strong> - Vertical connections (like dirt)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">void_and_grass</code></strong> - Transitions from empty space (left) to grass (right)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">grass_and_void</code></strong> - Transitions from grass (left) to empty space (right)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">grass_fill_up</code></strong> - Allows layers above to fill down into grass areas</li>
</ul>

<p>These transition sockets (<code class="language-plaintext highlighter-rouge">void_and_grass</code> and <code class="language-plaintext highlighter-rouge">grass_and_void</code>) are what create smooth edges. Without them, grass patches would have hard, blocky borders instead of the curved corners and sides we want.</p>

<h3 id="step-3-building-the-grass-layer-rules">Step 3: Building the Grass Layer Rules</h3>

<p>Now let’s create the function that builds the grass layer. Append this function to <code class="language-plaintext highlighter-rouge">rules.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Add this function after build_dirt_layer</span>
<span class="k">fn</span> <span class="nf">build_grass_layer</span><span class="p">(</span>
    <span class="n">terrain_model_builder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">TerrainModelBuilder</span><span class="p">,</span>
    <span class="n">terrain_sockets</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TerrainSockets</span><span class="p">,</span>
    <span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Void model - empty space above dirt where no grass exists</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
            <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
            <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">,</span>
            <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
    <span class="p">);</span>

    <span class="c1">// Main grass tile</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Multiple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span>
                    <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
                    <span class="n">terrain_sockets</span><span class="py">.grass.grass_fill_up</span><span class="p">,</span>
                <span class="p">],</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">],</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass"</span><span class="p">)],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="mf">5.</span><span class="p">);</span>

    <span class="c1">// Outer corner template</span>
    <span class="k">let</span> <span class="n">green_grass_corner_out</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">();</span>

    <span class="c1">// Inner corner template</span>
    <span class="k">let</span> <span class="n">green_grass_corner_in</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">();</span>

    <span class="c1">// Side edge template</span>
    <span class="k">let</span> <span class="n">green_grass_side</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">();</span>

    <span class="c1">// Create rotated versions of outer corners</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_out</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_out_tl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_out_bl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_out_br"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_out_tr"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Create rotated versions of inner corners</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_in</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_in_tl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_in_bl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_in_br"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_corner_in_tr"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Create rotated versions of side edges</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_side</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_side_t"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_side_l"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_side_b"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">green_grass_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"green_grass_side_r"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Add connection rules</span>
    <span class="n">socket_collection</span><span class="nf">.add_rotated_connection</span><span class="p">(</span>
        <span class="n">terrain_sockets</span><span class="py">.dirt.layer_up</span><span class="p">,</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.layer_down</span><span class="p">],</span>
    <span class="p">);</span>
    <span class="n">socket_collection</span><span class="nf">.add_connections</span><span class="p">(</span><span class="nd">vec!</span><span class="p">[</span>
        <span class="p">(</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">]),</span>
        <span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">],</span>
        <span class="p">),</span>
        <span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">],</span>
        <span class="p">),</span>
    <span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Understanding the Grass Layer Function</strong></p>

<p>This function does several things - let’s break it down step by step.</p>

<p><strong>1. The Void Model - Empty Space</strong></p>

<p>This creates an “invisible” tile - a spot where no grass grows. Notice <code class="language-plaintext highlighter-rouge">Vec::new()</code> means no sprite is rendered. The WFC algorithm needs this to create patches of grass rather than covering everything.</p>

<p><strong>2. The Main Grass Tile</strong></p>

<p>This is the center grass tile. All four horizontal sides use <code class="language-plaintext highlighter-rouge">grass.material</code>, meaning they connect to other grass tiles. The <code class="language-plaintext highlighter-rouge">z_pos</code> has two options - allowing either another layer above OR the special <code class="language-plaintext highlighter-rouge">grass_fill_up</code> socket for yellow grass later.</p>

<p>A <strong>template</strong> is a reusable socket pattern. Instead of writing the same socket configuration four times (once for each rotation), we create it once and rotate it. The <code class="language-plaintext highlighter-rouge">.to_template()</code> converts it into a format that can be rotated.</p>

<p><strong>4. Understanding Rotation - What’s Actually Happening?</strong></p>

<p>We’re rotating the <strong>socket pattern</strong>.</p>

<p>Each tile has a sprite (the visual) and sockets (the connection rules). When we rotate a template, the sockets shift to different edges.</p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<div style="text-align: center; margin-bottom: 10px; font-weight: bold; font-size: 16px; font-family: 'Space Mono', monospace;">How Socket Rotation Works</div>
<table style="border-collapse: separate; border-spacing: 4px; font-family: 'Space Mono', monospace; font-size: 13px; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 15px; text-align: center; background-color: #e3f2fd; border-radius: 8px;">
<strong style="font-size: 15px;">Original Template (0°)</strong><br /><br />
<img src="/assets/book_assets/tile_layers/green_grass_corner_out_tl.png" style="width: 70px; height: 70px; pointer-events: none;" /><br /><br />
<div style="text-align: left; line-height: 1.6;">
<strong>Sockets:</strong><br />
Left: void<br />
Right: void_and_grass<br />
Forward: void<br />
Backward: grass_and_void
</div>
</td>
<td style="padding: 15px; text-align: center; background-color: #fff3e0; border-radius: 8px;">
<strong style="font-size: 15px;">After 90° Rotation</strong><br /><br />
<img src="/assets/book_assets/tile_layers/green_grass_corner_out_bl.png" style="width: 70px; height: 70px; pointer-events: none;" /><br /><br />
<div style="text-align: left; line-height: 1.6;">
<strong>Sockets:</strong><br />
Left: void<br />
Right: grass_and_void<br />
Forward: void_and_grass<br />
Backward: void
</div>
</td>
</tr>
</table>
</div>
</div>

<p>Notice: the sprites are different (top-left vs bottom-left corner), but the socket pattern shifted clockwise by 90°. The <code class="language-plaintext highlighter-rouge">void_and_grass</code> socket moved from the right edge to the forward edge.</p>

<p>This is powerful because we define one socket pattern and pair it with different sprites at different rotations. The result: four unique corner models from one template definition.</p>

<p>We do the same for corner inside and side edges of grass tiles as well.</p>

<p><strong>5. How Simple Rules Create Coherent Shapes</strong></p>

<p>Here’s where the magic happens. We define only three connection rules, yet they create complex, natural-looking grass patches. Let’s see how.</p>

<p><strong>The Three Rules:</strong></p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">void</code> connects to <code class="language-plaintext highlighter-rouge">void</code></strong> - Empty space stays empty</li>
  <li><strong><code class="language-plaintext highlighter-rouge">grass.material</code> connects to <code class="language-plaintext highlighter-rouge">grass.material</code></strong> - Grass centers connect to each other</li>
  <li><strong><code class="language-plaintext highlighter-rouge">void_and_grass</code> connects to <code class="language-plaintext highlighter-rouge">grass_and_void</code></strong> - Transition sockets create smooth edges</li>
</ol>

<p>That’s it! But how do these simple rules create coherent grass patches? Let’s visualize a 3×3 grass patch forming:</p>

<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<div style="text-align: center; margin-bottom: 12px; font-weight: bold; font-size: 15px; font-family: 'Space Mono', monospace;">How a Grass Patch Forms</div>
<table style="border-collapse: separate; border-spacing: 3px; border: none; display: inline-block; margin-bottom: 20px;">
<tr>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
</tr>
<tr>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_corner_out_tl.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_side_t.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_corner_out_tr.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
</tr>
<tr>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_side_l.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_side_r.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
</tr>
<tr>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #f5f5f5; border-radius: 8px;"></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_corner_out_bl.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_side_b.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
<td style="padding: 0; text-align: center; width: 65px; height: 65px; background-color: #e8f5e9; border-radius: 8px;"><img src="/assets/book_assets/tile_layers/green_grass_corner_out_br.png" style="width: 65px; height: 65px; display: block; border-radius: 8px; pointer-events: none;" /></td>
</tr>
</table>
</div>
</div>

<p><strong>Why Every Edge Matches Perfectly:</strong></p>

<p>Let’s trace through the top row to understand how sockets work. Remember: each tile has sockets on its edges that define what can connect to it.</p>

<p>Look at the grass tile on the top left - <strong>green_grass_corner_out_tl</strong> tile (second row, second column in the grid above). This tile has a socket called <code class="language-plaintext highlighter-rouge">void_and_grass</code> on its <strong>right edge</strong></p>

<p>Now look at the <strong>green_grass_side_t</strong> tile immediately to its right. This tile has a socket called <code class="language-plaintext highlighter-rouge">grass_and_void</code> on its <strong>left edge</strong>. When these two tiles sit next to each other, their edges touch. The <code class="language-plaintext highlighter-rouge">void_and_grass</code> socket (from the corner) connects to the <code class="language-plaintext highlighter-rouge">grass_and_void</code> socket (from the side) because of Rule 3</p>

<p>The same pattern repeats across the entire grid. <strong>green_grass_side_t</strong> has <code class="language-plaintext highlighter-rouge">grass_and_void</code> on its <strong>right</strong> edge. <strong>green_grass_corner_out_tr</strong> has <code class="language-plaintext highlighter-rouge">void_and_grass</code> on its <strong>left</strong> edge. Where they touch, these sockets match perfectly!</p>

<p>The <strong>green_grass</strong> center tile has <code class="language-plaintext highlighter-rouge">material</code> sockets on all edges, so it connects to any adjacent grass tile that also has <code class="language-plaintext highlighter-rouge">material</code> on the touching edge.</p>

<p>The WFC algorithm uses these three simple rules to check every tile placement. Before placing a tile, it verifies that all its sockets match the sockets of neighboring tiles. The result: organic-looking grass patches with smooth, curved edges!</p>

<p><strong>6. Layer Connections</strong></p>

<p>This tells the WFC algorithm that grass can sit on top of dirt. The <code class="language-plaintext highlighter-rouge">add_rotated_connection</code> means this rule applies regardless of how the tiles are rotated - grass can always sit on dirt.</p>

<h3 id="step-4-calling-the-grass-layer-function">Step 4: Calling the Grass Layer Function</h3>

<p>Now update the <code class="language-plaintext highlighter-rouge">build_world</code> function to call <code class="language-plaintext highlighter-rouge">build_grass_layer</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Update build_world function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_world</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span>
    <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">socket_collection</span> <span class="o">=</span> <span class="nn">SocketCollection</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">terrain_sockets</span> <span class="o">=</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">);</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">terrain_model_builder</span> <span class="o">=</span> <span class="nn">TerrainModelBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Build dirt layer</span>
    <span class="nf">build_dirt_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Line update alert</span>
    <span class="c1">// Build grass layer</span>
    <span class="nf">build_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">)</span> <span class="o">=</span> <span class="n">terrain_model_builder</span><span class="nf">.into_parts</span><span class="p">();</span>

    <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="step-5-updating-grid-layers">Step 5: Updating Grid Layers</h3>

<p>Finally, update <code class="language-plaintext highlighter-rouge">generate.rs</code> to use 2 layers instead of 1:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs - Update GRID_Z constant</span>
<span class="k">const</span> <span class="n">GRID_Z</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</code></pre></div></div>

<p>Now run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>You should see patches of green grass growing on top of the dirt layer, with smooth edges and corners transitioning between grass and dirt!</p>

<p><img src="/assets/book_assets/chapter2/green_grass.png" alt="Grass Layer" /></p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_00dc4012af16cfd8064dfb73265f7c61.svg" alt="Comic Panel" class="comic-image" data-comic-hash="00dc4012af16cfd8064dfb73265f7c61" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h2 id="adding-the-yellow-grass-layer">Adding the Yellow Grass Layer</h2>

<p>Now that we have green grass, let’s add yellow grass patches that can grow on top of it! Yellow grass creates visual variety and demonstrates how layers can stack.</p>

<h3 id="step-1-add-yellow-grass-sprites-to-tilemap">Step 1: Add Yellow Grass Sprites to Tilemap</h3>

<p>First, let’s add the yellow grass sprites to our tilemap definition. Open <code class="language-plaintext highlighter-rouge">src/map/tilemap.rs</code> and add these entries to the <code class="language-plaintext highlighter-rouge">sprites</code> array:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs - Add these to the sprites array after the dirt sprite inside TilemapDefinition struct</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_in_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_in_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_in_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">288</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_in_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">288</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_out_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_out_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_out_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">288</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_corner_out_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">288</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_side_t"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_side_r"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">256</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_side_l"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">288</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"yellow_grass_side_b"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">288</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>

<h3 id="step-2-define-yellow-grass-sockets">Step 2: Define Yellow Grass Sockets</h3>

<p>Yellow grass has a special behavior - it sits <strong>on top of green grass</strong>, not on dirt. This means it needs different socket connections.</p>

<p>Add the socket structure to <code class="language-plaintext highlighter-rouge">src/map/sockets.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Add after GrassLayerSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">YellowGrassLayerSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">layer_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">layer_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">yellow_grass_fill_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then update <code class="language-plaintext highlighter-rouge">TerrainSockets</code> to include yellow grass:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update TerrainSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">void</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">yellow_grass</span><span class="p">:</span> <span class="n">YellowGrassLayerSockets</span><span class="p">,</span> <span class="c1">// Add this line</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, initialize the yellow grass sockets in <code class="language-plaintext highlighter-rouge">create_sockets</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update create_sockets function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">new_socket</span> <span class="o">=</span> <span class="p">||</span> <span class="k">-&gt;</span> <span class="n">Socket</span> <span class="p">{</span> <span class="n">socket_collection</span><span class="nf">.create</span><span class="p">()</span> <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">sockets</span> <span class="o">=</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
        <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">void_and_grass</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_and_void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_fill_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">yellow_grass</span><span class="p">:</span> <span class="n">YellowGrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">yellow_grass_fill_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
    <span class="p">};</span>
    <span class="n">sockets</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Why does yellow grass only need 3 sockets?</strong></p>

<p>Unlike green grass, yellow grass doesn’t need <code class="language-plaintext highlighter-rouge">void_and_grass</code> transition sockets. Why? Because yellow grass <strong>reuses the green grass edges</strong>. When yellow grass meets empty space, the green grass layer below provides the edge tiles. Yellow grass only appears where green grass already exists, so it uses green grass’s <code class="language-plaintext highlighter-rouge">material</code> socket for horizontal connections.</p>

<p>The <code class="language-plaintext highlighter-rouge">yellow_grass_fill_down</code> socket is special - it connects to green grass’s <code class="language-plaintext highlighter-rouge">grass_fill_up</code> socket, allowing yellow grass to “fill down” into the green grass layer below.</p>

<h3 id="step-3-building-the-yellow-grass-layer-rules">Step 3: Building the Yellow Grass Layer Rules</h3>

<p>Now let’s create the function that builds the yellow grass layer. Add this function to <code class="language-plaintext highlighter-rouge">rules.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Add this function after build_grass_layer</span>
<span class="k">fn</span> <span class="nf">build_yellow_grass_layer</span><span class="p">(</span>
    <span class="n">terrain_model_builder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">TerrainModelBuilder</span><span class="p">,</span>
    <span class="n">terrain_sockets</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TerrainSockets</span><span class="p">,</span>
    <span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Void model - empty space where no yellow grass exists</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
            <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_up</span><span class="p">,</span>
            <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_down</span><span class="p">,</span>
            <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
    <span class="p">);</span>

    <span class="c1">// Main yellow grass tile</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.yellow_grass_fill_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass"</span><span class="p">)],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="mf">5.</span><span class="p">);</span>

    <span class="c1">// Outer corner template</span>
    <span class="k">let</span> <span class="n">yellow_grass_corner_out</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.yellow_grass_fill_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">();</span>

    <span class="c1">// Inner corner template</span>
    <span class="k">let</span> <span class="n">yellow_grass_corner_in</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.yellow_grass_fill_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">();</span>

    <span class="c1">// Side edge template</span>
    <span class="k">let</span> <span class="n">yellow_grass_side</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.void_and_grass</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.grass_and_void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.yellow_grass.yellow_grass_fill_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.grass.material</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">();</span>

    <span class="c1">// Create rotated versions of outer corners</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_out</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_out_tl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_out_bl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_out_br"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_out_tr"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Create rotated versions of inner corners</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_in</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_in_tl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_in_bl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_in_br"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_corner_in_tr"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Create rotated versions of side edges</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_side</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_side_t"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_side_l"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_side_b"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">yellow_grass_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"yellow_grass_side_r"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Add connection rules</span>
    <span class="n">socket_collection</span>
        <span class="nf">.add_rotated_connection</span><span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.grass.layer_up</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_down</span><span class="p">],</span>
        <span class="p">)</span>
        <span class="nf">.add_rotated_connection</span><span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.yellow_grass.yellow_grass_fill_down</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.grass.grass_fill_up</span><span class="p">],</span>
        <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice how the yellow grass models reuse green grass’s transition sockets (<code class="language-plaintext highlighter-rouge">void_and_grass</code> and <code class="language-plaintext highlighter-rouge">grass_and_void</code>) for horizontal connections. This is the clever part - yellow grass doesn’t define its own edges, it borrows them from green grass!</p>

<p>The connection rules establish two important relationships:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">grass.layer_up</code> connects to <code class="language-plaintext highlighter-rouge">yellow_grass.layer_down</code> - Yellow grass sits on top of green grass</li>
  <li><code class="language-plaintext highlighter-rouge">yellow_grass_fill_down</code> connects to <code class="language-plaintext highlighter-rouge">grass_fill_up</code> - This allows yellow grass to appear where green grass has the special “fill up” socket</li>
</ol>

<h3 id="step-4-calling-the-yellow-grass-layer-function">Step 4: Calling the Yellow Grass Layer Function</h3>

<p>Update <code class="language-plaintext highlighter-rouge">build_world</code> in <code class="language-plaintext highlighter-rouge">rules.rs</code> to call <code class="language-plaintext highlighter-rouge">build_yellow_grass_layer</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Update build_world function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_world</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span>
    <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">socket_collection</span> <span class="o">=</span> <span class="nn">SocketCollection</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">terrain_sockets</span> <span class="o">=</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">);</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">terrain_model_builder</span> <span class="o">=</span> <span class="nn">TerrainModelBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Build dirt layer</span>
    <span class="nf">build_dirt_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Build grass layer</span>
    <span class="nf">build_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Line update alert</span>
    <span class="c1">// Build yellow grass layer</span>
    <span class="nf">build_yellow_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">)</span> <span class="o">=</span> <span class="n">terrain_model_builder</span><span class="nf">.into_parts</span><span class="p">();</span>

    <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="step-5-updating-grid-layers-1">Step 5: Updating Grid Layers</h3>

<p>We need one more layer for yellow grass. Update the constant in <code class="language-plaintext highlighter-rouge">generate.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs - Update GRID_Z</span>
<span class="k">const</span> <span class="n">GRID_Z</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span> <span class="c1">// Changed from 2 to 3</span>
</code></pre></div></div>

<p>Now run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>You should see yellow grass patches appearing on top of green grass, creating a beautiful layered terrain!</p>

<p><img src="/assets/book_assets/chapter2/yellow_grass.png" alt="Yellow Grass Layer" /></p>

<h2 id="adding-the-water-layer">Adding the Water Layer</h2>

<p>Water adds life to our procedural world! Unlike grass layers that stack on top of each other, water appears <strong>alongside</strong> yellow grass at the same layer level. This creates interesting terrain where water bodies can form near grassy areas.</p>

<h3 id="step-1-add-water-sprites-to-tilemap">Step 1: Add Water Sprites to Tilemap</h3>

<p>First, let’s add the water sprites to our tilemap definition. Open <code class="language-plaintext highlighter-rouge">src/map/tilemap.rs</code> and add these entries to the <code class="language-plaintext highlighter-rouge">sprites</code> array:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs - Add water sprites to the sprites array</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_in_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_in_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_in_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_in_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_out_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_out_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_out_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_corner_out_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_side_t"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_side_r"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_side_l"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"water_side_b"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>

<h3 id="step-2-define-water-sockets">Step 2: Define Water Sockets</h3>

<p>Water goes on the next Z-layer above yellow grass. “Layer” here refers to the Z-coordinate in our 3D grid, not geological layers.</p>

<p>We’ve been stacking these: dirt at <code class="language-plaintext highlighter-rouge">Z=0</code>, green grass at <code class="language-plaintext highlighter-rouge">Z=1</code>, yellow grass at <code class="language-plaintext highlighter-rouge">Z=2</code>, and now water at <code class="language-plaintext highlighter-rouge">Z=3</code>.</p>

<p>At any grid position, you can have dirt at the bottom Z-level and water at a higher Z-level—they occupy the same X,Y position but different Z heights.</p>

<p>Add the socket structure to <code class="language-plaintext highlighter-rouge">src/map/sockets.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Add after YellowGrassLayerSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">WaterLayerSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">layer_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">layer_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">material</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">void_and_water</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">water_and_void</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">ground_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then update <code class="language-plaintext highlighter-rouge">TerrainSockets</code> to include water:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update TerrainSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">void</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">yellow_grass</span><span class="p">:</span> <span class="n">YellowGrassLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">water</span><span class="p">:</span> <span class="n">WaterLayerSockets</span><span class="p">,</span> <span class="c1">// Add this line</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, initialize the water sockets in <code class="language-plaintext highlighter-rouge">create_sockets</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update create_sockets function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">new_socket</span> <span class="o">=</span> <span class="p">||</span> <span class="k">-&gt;</span> <span class="n">Socket</span> <span class="p">{</span> <span class="n">socket_collection</span><span class="nf">.create</span><span class="p">()</span> <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">sockets</span> <span class="o">=</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
        <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">void_and_grass</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_and_void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_fill_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">yellow_grass</span><span class="p">:</span> <span class="n">YellowGrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">yellow_grass_fill_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="c1">// Line update alert</span>
        <span class="n">water</span><span class="p">:</span> <span class="n">WaterLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">void_and_water</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">water_and_void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">ground_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
    <span class="p">};</span>
    <span class="n">sockets</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Water has 6 sockets because it behaves like green grass - it creates patches with transitions:</p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">material</code></strong> - Connects water to water (like grass’s material socket)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">layer_up</code> and <code class="language-plaintext highlighter-rouge">layer_down</code></strong> - Vertical connections</li>
  <li><strong><code class="language-plaintext highlighter-rouge">void_and_water</code></strong> and <strong><code class="language-plaintext highlighter-rouge">water_and_void</code></strong> - Transition sockets for smooth edges</li>
  <li><strong><code class="language-plaintext highlighter-rouge">ground_up</code></strong> - Special socket that allows props above to know they’re not on water</li>
</ol>

<h3 id="step-3-building-the-water-layer-rules">Step 3: Building the Water Layer Rules</h3>

<p>Now let’s create the function that builds the water layer. Add this function to <code class="language-plaintext highlighter-rouge">rules.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Add this function after build_yellow_grass_layer</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_water_layer</span><span class="p">(</span>
    <span class="n">terrain_model_builder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">TerrainModelBuilder</span><span class="p">,</span>
    <span class="n">terrain_sockets</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TerrainSockets</span><span class="p">,</span>
    <span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Void model - represents land areas where no water exists</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Multiple</span> <span class="p">{</span>
            <span class="n">x_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
            <span class="n">x_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
            <span class="n">z_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span>
                <span class="n">terrain_sockets</span><span class="py">.water.layer_up</span><span class="p">,</span>
                <span class="n">terrain_sockets</span><span class="py">.water.ground_up</span><span class="p">,</span>
            <span class="p">],</span>
            <span class="n">z_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.water.layer_down</span><span class="p">],</span>
            <span class="n">y_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
            <span class="n">y_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
        <span class="p">},</span>
        <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
    <span class="p">);</span>

    <span class="c1">// Main water tile</span>
    <span class="k">const</span> <span class="n">WATER_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.02</span><span class="p">;</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water"</span><span class="p">)],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="mf">10.</span> <span class="o">*</span> <span class="n">WATER_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Outer corner template</span>
    <span class="k">let</span> <span class="n">water_corner_out</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.void_and_water</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.water_and_void</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">()</span>
    <span class="nf">.with_weight</span><span class="p">(</span><span class="n">WATER_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Inner corner template</span>
    <span class="k">let</span> <span class="n">water_corner_in</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.water_and_void</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.void_and_water</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">()</span>
    <span class="nf">.with_weight</span><span class="p">(</span><span class="n">WATER_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Side edge template</span>
    <span class="k">let</span> <span class="n">water_side</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.void_and_water</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.water_and_void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.layer_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">()</span>
    <span class="nf">.with_weight</span><span class="p">(</span><span class="n">WATER_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Create rotated versions of outer corners</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_out</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_out_tl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_out_bl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_out_br"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_out</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_out_tr"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Create rotated versions of inner corners</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_in</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_in_tl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_in_bl"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_in_br"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_corner_in</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_corner_in_tr"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Create rotated versions of side edges</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_side</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_side_t"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot90</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_side_l"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot180</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_side_b"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">water_side</span><span class="nf">.rotated</span><span class="p">(</span><span class="nn">ModelRotation</span><span class="p">::</span><span class="n">Rot270</span><span class="p">,</span> <span class="nn">Direction</span><span class="p">::</span><span class="n">ZForward</span><span class="p">),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"water_side_r"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Add connection rules</span>
    <span class="n">socket_collection</span><span class="nf">.add_connections</span><span class="p">(</span><span class="nd">vec!</span><span class="p">[</span>
        <span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.water.material</span><span class="p">],</span>
        <span class="p">),</span>
        <span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.water.water_and_void</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.water.void_and_water</span><span class="p">],</span>
        <span class="p">),</span>
    <span class="p">]);</span>

    <span class="c1">// Connect water layer to yellow grass layer</span>
    <span class="n">socket_collection</span><span class="nf">.add_rotated_connection</span><span class="p">(</span>
        <span class="n">terrain_sockets</span><span class="py">.yellow_grass.layer_up</span><span class="p">,</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.water.layer_down</span><span class="p">],</span>
    <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Key Points About Water:</strong></p>

<ol>
  <li>
    <p><strong>Low Weight Values</strong> - Notice <code class="language-plaintext highlighter-rouge">WATER_WEIGHT: f32 = 0.02</code>. This makes water appear less frequently than grass, creating occasional water bodies instead of covering everything.</p>
  </li>
  <li>
    <p><strong>Multiple z_pos Options</strong> - The void model has two options for <code class="language-plaintext highlighter-rouge">z_pos</code>: <code class="language-plaintext highlighter-rouge">water.layer_up</code> (another water layer could go here) and <code class="language-plaintext highlighter-rouge">water.ground_up</code> (props can sit here). This prepares us for the props layer we’ll add next.</p>
  </li>
  <li>
    <p><strong>Same Pattern as Grass</strong> - Water uses the same template and rotation approach as grass, demonstrating how the WFC pattern scales to different terrain types.</p>
  </li>
</ol>

<h3 id="step-4-calling-the-water-layer-function">Step 4: Calling the Water Layer Function</h3>

<p>Update <code class="language-plaintext highlighter-rouge">build_world</code> in <code class="language-plaintext highlighter-rouge">rules.rs</code> to call <code class="language-plaintext highlighter-rouge">build_water_layer</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Update build_world function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_world</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span>
    <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">socket_collection</span> <span class="o">=</span> <span class="nn">SocketCollection</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">terrain_sockets</span> <span class="o">=</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">);</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">terrain_model_builder</span> <span class="o">=</span> <span class="nn">TerrainModelBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Build dirt layer</span>
    <span class="nf">build_dirt_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Build grass layer</span>
    <span class="nf">build_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Build yellow grass layer</span>
    <span class="nf">build_yellow_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Line update alert</span>
    <span class="c1">// Build water layer</span>
    <span class="nf">build_water_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">)</span> <span class="o">=</span> <span class="n">terrain_model_builder</span><span class="nf">.into_parts</span><span class="p">();</span>

    <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="step-5-updating-grid-layers-2">Step 5: Updating Grid Layers</h3>

<p>We need one more layer for water. Update the constant in <code class="language-plaintext highlighter-rouge">generate.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs - Update GRID_Z</span>
<span class="k">const</span> <span class="n">GRID_Z</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span> <span class="c1">// Changed from 3 to 4</span>
</code></pre></div></div>

<p>Now run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>You should see water bodies forming on your terrain, creating lakes and ponds alongside the grass patches!</p>

<p><img src="/assets/book_assets/chapter2/water.png" alt="Water Layer" /></p>

<h2 id="adding-the-props-layer">Adding the Props Layer</h2>

<p>Props are the final layer that brings our world to life! Trees, rocks, plants, and stumps should appear on land but not in water.</p>

<p>This layer sits at the top of our Z-stack and uses special connection rules to ensure props only spawn on solid ground.</p>

<h3 id="step-1-add-props-sprites-to-tilemap">Step 1: Add Props Sprites to Tilemap</h3>

<p>First, let’s add all the props sprites to our tilemap definition. Open <code class="language-plaintext highlighter-rouge">src/map/tilemap.rs</code> and add these entries to the <code class="language-plaintext highlighter-rouge">sprites</code> array:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/tilemap.rs - Add these after the water sprites</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_1_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_1_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_1_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_1_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_2_tl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_2_tr"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_2_bl"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"big_tree_2_br"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"plant_1"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"plant_2"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"plant_3"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"plant_4"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rock_1"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rock_2"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rock_3"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"rock_4"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">96</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"small_tree_top"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"small_tree_bottom"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">160</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"tree_stump_1"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"tree_stump_2"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">224</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">128</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">TilemapSprite</span> <span class="p">{</span>
    <span class="n">name</span><span class="p">:</span> <span class="s">"tree_stump_3"</span><span class="p">,</span>
    <span class="n">pixel_x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">pixel_y</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>

<h3 id="step-2-define-props-sockets">Step 2: Define Props Sockets</h3>

<p>Props need special socket handling because they must only appear on land, never in water. They also include multi-tile objects like big trees that span multiple grid positions.</p>

<p>Add the socket structure to <code class="language-plaintext highlighter-rouge">src/map/sockets.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Add after WaterLayerSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">PropsLayerSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">layer_up</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">layer_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">props_down</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">big_tree_1_base</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">big_tree_2_base</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then update <code class="language-plaintext highlighter-rouge">TerrainSockets</code> to include props:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update TerrainSockets</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">void</span><span class="p">:</span> <span class="n">Socket</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">yellow_grass</span><span class="p">:</span> <span class="n">YellowGrassLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">water</span><span class="p">:</span> <span class="n">WaterLayerSockets</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">props</span><span class="p">:</span> <span class="n">PropsLayerSockets</span><span class="p">,</span> <span class="c1">// Add this line</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, initialize the props sockets in <code class="language-plaintext highlighter-rouge">create_sockets</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/sockets.rs - Update create_sockets function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">new_socket</span> <span class="o">=</span> <span class="p">||</span> <span class="k">-&gt;</span> <span class="n">Socket</span> <span class="p">{</span> <span class="n">socket_collection</span><span class="nf">.create</span><span class="p">()</span> <span class="p">};</span>
    
    <span class="k">let</span> <span class="n">sockets</span> <span class="o">=</span> <span class="n">TerrainSockets</span> <span class="p">{</span>
        <span class="n">dirt</span><span class="p">:</span> <span class="n">DirtLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="n">grass</span><span class="p">:</span> <span class="n">GrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">void_and_grass</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_and_void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">grass_fill_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">yellow_grass</span><span class="p">:</span> <span class="n">YellowGrassLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">yellow_grass_fill_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="n">water</span><span class="p">:</span> <span class="n">WaterLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">material</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">void_and_water</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">water_and_void</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">ground_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
        <span class="c1">// Line update alert</span>
        <span class="n">props</span><span class="p">:</span> <span class="n">PropsLayerSockets</span> <span class="p">{</span>
            <span class="n">layer_up</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">layer_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">props_down</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">big_tree_1_base</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
            <span class="n">big_tree_2_base</span><span class="p">:</span> <span class="nf">new_socket</span><span class="p">(),</span>
        <span class="p">},</span>
    <span class="p">};</span>
    <span class="n">sockets</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Understanding Props Sockets:</strong></p>

<p>Props have 5 sockets with special purposes:</p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">layer_up</code> and <code class="language-plaintext highlighter-rouge">layer_down</code></strong> - Standard vertical connections</li>
  <li><strong><code class="language-plaintext highlighter-rouge">props_down</code></strong> - Connects to water’s <code class="language-plaintext highlighter-rouge">ground_up</code> socket (ensures props only on land)</li>
  <li><strong><code class="language-plaintext highlighter-rouge">big_tree_1_base</code> and <code class="language-plaintext highlighter-rouge">big_tree_2_base</code></strong> - Special sockets for multi-tile trees that need to connect their base parts</li>
</ol>

<h3 id="step-3-building-the-props-layer-rules">Step 3: Building the Props Layer Rules</h3>

<p>Now let’s create the function that builds the props layer. Add this function to <code class="language-plaintext highlighter-rouge">rules.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Add this function after build_water_layer</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_props_layer</span><span class="p">(</span>
    <span class="n">terrain_model_builder</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">TerrainModelBuilder</span><span class="p">,</span>
    <span class="n">terrain_sockets</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">TerrainSockets</span><span class="p">,</span>
    <span class="n">socket_collection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Void model - represents areas where no props exist</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Multiple</span> <span class="p">{</span>
            <span class="n">x_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
            <span class="n">x_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
            <span class="n">z_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.props.layer_up</span><span class="p">],</span>
            <span class="n">z_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.props.layer_down</span><span class="p">],</span>
            <span class="n">y_pos</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
            <span class="n">y_neg</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">],</span>
        <span class="p">},</span>
        <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
    <span class="p">);</span>

    <span class="c1">// Weight constants for different prop types</span>
    <span class="k">const</span> <span class="n">PROPS_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.025</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">ROCKS_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.008</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">PLANTS_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.025</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">STUMPS_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.012</span><span class="p">;</span>

    <span class="c1">// Base prop template - single tile props</span>
    <span class="k">let</span> <span class="n">prop</span> <span class="o">=</span> <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
        <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.layer_up</span><span class="p">,</span>
        <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.props_down</span><span class="p">,</span>
        <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
        <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="nf">.to_template</span><span class="p">()</span>
    <span class="nf">.with_weight</span><span class="p">(</span><span class="n">PROPS_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Create different prop types with different weights</span>
    <span class="k">let</span> <span class="n">plant_prop</span> <span class="o">=</span> <span class="n">prop</span><span class="nf">.clone</span><span class="p">()</span><span class="nf">.with_weight</span><span class="p">(</span><span class="n">PLANTS_WEIGHT</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">stump_prop</span> <span class="o">=</span> <span class="n">prop</span><span class="nf">.clone</span><span class="p">()</span><span class="nf">.with_weight</span><span class="p">(</span><span class="n">STUMPS_WEIGHT</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">rock_prop</span> <span class="o">=</span> <span class="n">prop</span><span class="nf">.clone</span><span class="p">()</span><span class="nf">.with_weight</span><span class="p">(</span><span class="n">ROCKS_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Small tree (2 tiles high)</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span>
            <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"small_tree_bottom"</span><span class="p">),</span>
            <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"small_tree_top"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
        <span class="p">],</span>
    <span class="p">);</span>

    <span class="c1">// Big tree 1 (2x2 tiles)</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.big_tree_1_base</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.props_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_bl"</span><span class="p">),</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_tl"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
            <span class="p">],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="n">PROPS_WEIGHT</span><span class="p">);</span>

    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.big_tree_1_base</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.props_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_br"</span><span class="p">),</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_1_tr"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
            <span class="p">],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="n">PROPS_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Big tree 2 (2x2 tiles)</span>
    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.big_tree_2_base</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.props_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_bl"</span><span class="p">),</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_tl"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
            <span class="p">],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="n">PROPS_WEIGHT</span><span class="p">);</span>

    <span class="n">terrain_model_builder</span>
        <span class="nf">.create_model</span><span class="p">(</span>
            <span class="nn">SocketsCartesian3D</span><span class="p">::</span><span class="n">Simple</span> <span class="p">{</span>
                <span class="n">x_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">x_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.big_tree_2_base</span><span class="p">,</span>
                <span class="n">z_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.layer_up</span><span class="p">,</span>
                <span class="n">z_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.props.props_down</span><span class="p">,</span>
                <span class="n">y_pos</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
                <span class="n">y_neg</span><span class="p">:</span> <span class="n">terrain_sockets</span><span class="py">.void</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="nd">vec!</span><span class="p">[</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_br"</span><span class="p">),</span>
                <span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"big_tree_2_tr"</span><span class="p">)</span><span class="nf">.with_grid_offset</span><span class="p">(</span><span class="nn">GridDelta</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)),</span>
            <span class="p">],</span>
        <span class="p">)</span>
        <span class="nf">.with_weight</span><span class="p">(</span><span class="n">PROPS_WEIGHT</span><span class="p">);</span>

    <span class="c1">// Tree stumps</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">stump_prop</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"tree_stump_1"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">stump_prop</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"tree_stump_2"</span><span class="p">)],</span>
    <span class="p">);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span>
        <span class="n">stump_prop</span><span class="nf">.clone</span><span class="p">(),</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"tree_stump_3"</span><span class="p">)],</span>
    <span class="p">);</span>

    <span class="c1">// Rocks</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">rock_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_1"</span><span class="p">)]);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">rock_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_2"</span><span class="p">)]);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">rock_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_3"</span><span class="p">)]);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">rock_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"rock_4"</span><span class="p">)]);</span>

    <span class="c1">// Plants</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_1"</span><span class="p">)]);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_2"</span><span class="p">)]);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_3"</span><span class="p">)]);</span>
    <span class="n">terrain_model_builder</span><span class="nf">.create_model</span><span class="p">(</span><span class="n">plant_prop</span><span class="nf">.clone</span><span class="p">(),</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">SpawnableAsset</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"plant_4"</span><span class="p">)]);</span>

    <span class="c1">// Add connection rules</span>
    <span class="n">socket_collection</span><span class="nf">.add_connections</span><span class="p">(</span><span class="nd">vec!</span><span class="p">[</span>
        <span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.props.big_tree_1_base</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.props.big_tree_1_base</span><span class="p">],</span>
        <span class="p">),</span>
        <span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.props.big_tree_2_base</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.props.big_tree_2_base</span><span class="p">],</span>
        <span class="p">),</span>
    <span class="p">]);</span>

    <span class="c1">// Connect props to water layer</span>
    <span class="n">socket_collection</span>
        <span class="nf">.add_rotated_connection</span><span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.water.layer_up</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.props.layer_down</span><span class="p">],</span>
        <span class="p">)</span>
        <span class="nf">.add_rotated_connection</span><span class="p">(</span>
            <span class="n">terrain_sockets</span><span class="py">.props.props_down</span><span class="p">,</span>
            <span class="nd">vec!</span><span class="p">[</span><span class="n">terrain_sockets</span><span class="py">.water.ground_up</span><span class="p">],</span>
        <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Key Points About Props:</strong></p>

<ol>
  <li><strong>Multi-tile Objects</strong> - Big trees use <code class="language-plaintext highlighter-rouge">GridDelta::new(0, 1, 0)</code> to place the top half one tile up</li>
  <li><strong>Weight System</strong> - Different prop types have different spawn probabilities (rocks are rarer than plants)</li>
  <li><strong>Land-only Rule</strong> - <code class="language-plaintext highlighter-rouge">props_down</code> connects to <code class="language-plaintext highlighter-rouge">water.ground_up</code>, ensuring props never spawn in water</li>
  <li><strong>Base Sockets</strong> - Big trees use special base sockets to connect their left and right halves</li>
</ol>

<h3 id="step-4-calling-the-props-layer-function">Step 4: Calling the Props Layer Function</h3>

<p>Update <code class="language-plaintext highlighter-rouge">build_world</code> in <code class="language-plaintext highlighter-rouge">rules.rs</code> to call <code class="language-plaintext highlighter-rouge">build_props_layer</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/rules.rs - Update build_world function</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">build_world</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span>
    <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">SpawnableAsset</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">ModelCollection</span><span class="o">&lt;</span><span class="n">Cartesian3D</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">SocketCollection</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">socket_collection</span> <span class="o">=</span> <span class="nn">SocketCollection</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">terrain_sockets</span> <span class="o">=</span> <span class="nf">create_sockets</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">);</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">terrain_model_builder</span> <span class="o">=</span> <span class="nn">TerrainModelBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// Build dirt layer</span>
    <span class="nf">build_dirt_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Build grass layer</span>
    <span class="nf">build_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Build yellow grass layer</span>
    <span class="nf">build_yellow_grass_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Build water layer</span>
    <span class="nf">build_water_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="c1">// Line update alert</span>
    <span class="c1">// Build props layer</span>
    <span class="nf">build_props_layer</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">terrain_model_builder</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">terrain_sockets</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="n">socket_collection</span><span class="p">,</span>
    <span class="p">);</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">)</span> <span class="o">=</span> <span class="n">terrain_model_builder</span><span class="nf">.into_parts</span><span class="p">();</span>

    <span class="p">(</span><span class="n">assets</span><span class="p">,</span> <span class="n">models</span><span class="p">,</span> <span class="n">socket_collection</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="step-5-updating-grid-layers-3">Step 5: Updating Grid Layers</h3>

<p>We need one final layer for props. Update the constant in <code class="language-plaintext highlighter-rouge">generate.rs</code>:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/map/generate.rs - Update GRID_Z</span>
<span class="k">const</span> <span class="n">GRID_Z</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// Changed from 4 to 5</span>
</code></pre></div></div>

<p>Now run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>You should see a complete procedural world with dirt, grass, water, and props! Trees and rocks will only appear on land, never in water, creating a realistic and varied landscape.</p>

<p><img src="/assets/book_assets/chapter2/props.png" alt="Props Layer" /></p>

<h3 id="congratulations">Congratulations!</h3>

<p>You’ve successfully built a complete procedural terrain generation system using Wave Function Collapse! Your world now has:</p>

<ul>
  <li><strong>Dirt base layer</strong> - The foundation</li>
  <li><strong>Green grass patches</strong> - With smooth edges and corners</li>
  <li><strong>Yellow grass variety</strong> - Stacking on green grass</li>
  <li><strong>Water bodies</strong> - Creating lakes and ponds</li>
  <li><strong>Props</strong> - Trees, rocks, and plants that only appear on land</li>
</ul>

<p>This demonstrates the power of WFC for creating coherent, natural-looking game worlds with just a few simple rules!</p>

<h2 id="wait-just-one-more-thing">Wait, Just one more thing!</h2>

<p>Go to rules.rs and change the water weight.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">WATER_WEIGHT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.07</span><span class="p">;</span>
</code></pre></div></div>

<p>Now run your game:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p><img src="/assets/book_assets/chapter2/water_weight.png" alt="Props Layer" /></p>

<p>Woah, with a simple modification you are able to change the world to have more water! This demonstrates the power of procedural generation—by tweaking just a few numbers, you can create completely different landscapes.</p>

<p>Try experimenting with the weight values for different layers to see how dramatically you can transform your world.</p>

<p>Also notice our player can walk on water. We will work on collision detection and also on approaches to build larger maps in our upcoming chapters.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_c90827f3860437884d81914595bfd0e6.svg" alt="Comic Panel" class="comic-image" data-comic-hash="c90827f3860437884d81914595bfd0e6" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Learn procedural world generation using Wave Function Collapse.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://aibodh.com/assets/book_assets/chapter2/ch2.gif" /><media:content medium="image" url="https://aibodh.com/assets/book_assets/chapter2/ch2.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Impatient Programmer’s Guide to Bevy and Rust: Chapter 1 - Let There Be a Player</title><link href="https://aibodh.com/posts/bevy-rust-game-development-chapter-1/" rel="alternate" type="text/html" title="The Impatient Programmer’s Guide to Bevy and Rust: Chapter 1 - Let There Be a Player" /><published>2025-09-16T10:00:00+00:00</published><updated>2025-09-16T10:00:00+00:00</updated><id>https://aibodh.com/posts/bevy-rust-game-development-chapter-1</id><content type="html" xml:base="https://aibodh.com/posts/bevy-rust-game-development-chapter-1/"><![CDATA[<p>This is chapter 1 of my rust-bevy tutorial series “The Impatient Programmer’s Guide to Bevy and Rust: Build a 2D Game from Scratch”. I will be going in-depth into bevy and game development in this series, also cover NPCs powered by AI Agents. <a href="https://discord.com/invite/cD9qEsSjUH">Join our community</a> to be updated on new releases on this series. Source code for this chapter is <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">available here</a>.</p>

<p>Here’s what you will be able to achieve by the end of this tutorial.</p>

<p><img src="/assets/book_assets/chapter-final.gif" alt="Final Chapter Result" /></p>

<p><br /></p>
<h2 id="setup-instructions"><strong>Setup</strong> Instructions</h2>

<p>If you haven’t setup rust yet, please follow the <a href="https://www.rust-lang.org/tools/install">official guide</a> to set it up.</p>

<ol>
  <li>Create a fresh project:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo new bevy_game
<span class="nb">cd </span>bevy_game
</code></pre></div>    </div>
  </li>
  <li>Open <code class="language-plaintext highlighter-rouge">Cargo.toml</code> and add Bevy:
    <div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[dependencies]</span>
<span class="py">bevy</span> <span class="p">=</span> <span class="s">"0.18"</span>
</code></pre></div>    </div>
    <p><br /></p>
  </li>
</ol>

<p>I am going to assume you have programming experience in any one language like javascript or python.  <br /><br /></p>

<h2 id="thinking-in-systems">Thinking in Systems</h2>

<p>What do we need to build a simple game that allows player to move from keyboard input?</p>
<ul>
  <li><strong>World System</strong>: Create a game world where the player can move.</li>
  <li><strong>Input System</strong>: Monitor keyboard input and translate it into player movement.</li>
</ul>

<p>We see the above pattern very common in building games, the pattern of create/setup and the pattern of monitoring game state changes and updating the game world entities.</p>

<ul>
  <li><strong>Setup</strong>: Spawn enemy with different behaviors, load enemy sprites and animations, configure enemy health and damage values.</li>
  <li><strong>Update</strong>: Move enemies toward the player, check if enemies are hit by bullets, remove dead enemies and spawn new enemies at intervals.</li>
</ul>

<p>At the core of Bevy, we have this simple <strong>setup and update system</strong>. This foundational pattern is what makes Bevy both powerful and easy to understand - once you grasp this concept, building with bevy becomes easier.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_758ef62c09fa98d97ea4f1807c773bea.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="758ef62c09fa98d97ea4f1807c773bea" data-comic-settings="d2-diagram" />
</div>

<h3 id="setup-system">Setup System</h3>

<p>Imagine a function called setup, that gives you “commands” as an argument, and it give you spawn ability to create or spawn anything you want. Well, bevy gives you exactly that.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Pseudo code, doesn't compile</span>
<span class="k">fn</span> <span class="nf">setup</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Create anything you want</span>
	<span class="n">commands</span><span class="nf">.spawn</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s mut?</strong></p>

<p><code class="language-plaintext highlighter-rouge">mut</code> is short for mutate. In Rust we need to explicitly mention that we are planning to change this value. By default, Rust treats declared values as read only. <code class="language-plaintext highlighter-rouge">mut</code> tells Rust we plan to change the value. Rust uses this knowledge to prevent a whole class of bugs.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_baa4e26e54c3cbff158ef372f0ed5bdf.svg" alt="Comic Panel" class="comic-image" data-comic-hash="baa4e26e54c3cbff158ef372f0ed5bdf" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_a36b7d7c73ae4901770df9c0ae82494f.svg" alt="Comic Panel" class="comic-image" data-comic-hash="a36b7d7c73ae4901770df9c0ae82494f" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>And what’s this <code class="language-plaintext highlighter-rouge">mut commands: Commands</code>?</strong></p>

<p>It’s from bevy library, that allows you to add things to your game world. Rust likes explicit types, so <code class="language-plaintext highlighter-rouge">:Commands</code> lets the compiler know this parameter is Bevy’s command interface. Skip the hint and Rust can’t be sure what shows up, so it blocks the build.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_0c2b60e5d53bf12e7953a2299d82294c.svg" alt="Comic Panel" class="comic-image" data-comic-hash="0c2b60e5d53bf12e7953a2299d82294c" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>What’s a type?</strong></p>

<p>A type tells Rust what kind of value you are handling—numbers, text, timers, Bevy commands, and so on. Once the compiler knows the type, it can check that every operation you perform on it actually makes sense.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_8b8bd2ea17ad1b5edb207445defecfd2.svg" alt="Comic Panel" class="comic-image" data-comic-hash="8b8bd2ea17ad1b5edb207445defecfd2" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>Isn’t this too much work?</strong></p>

<p>It prevents mistakes and helps you with performance, ex: If you try to add a string to a number, the compiler stops you before the game runs. On top of that, knowing the exact type lets Rust pack data tightly.</p>

<p>A <code class="language-plaintext highlighter-rouge">Vec2</code> is just two numbers, so Rust stores exactly two numbers, no surprise extra space, which keeps things fast when you have thousands of game entities. These helps your game to be memory efficient.</p>

<p>When you create a Vec2 object in JavaScript or Python, you’re not just storing two numbers. The runtime adds type metadata, property information, and prototype references - turning your simple 8-byte data structure into ~48 bytes of memory.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_199ad29624f5b189553f47320022eae1.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="199ad29624f5b189553f47320022eae1" data-comic-settings="d2-diagram" />
</div>

<p>Rust’s type system works at compile time. When you declare a Vec2 struct with two f32 fields, that’s exactly what gets stored - just 8 bytes, no extra metadata. The compiler already knows the types, so no runtime type information is needed.</p>

<p>With 1000 game entities, this difference becomes dramatic:</p>

<table>
  <thead>
    <tr>
      <th>Language</th>
      <th>Memory Usage</th>
      <th>Overhead</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Rust</td>
      <td>8KB</td>
      <td>None</td>
    </tr>
    <tr>
      <td>Dynamic language</td>
      <td>~48KB+</td>
      <td>6x overhead</td>
    </tr>
  </tbody>
</table>

<p>This isn’t just about memory usage - it’s about performance. Smaller, predictable memory layouts mean better CPU cache utilization, which directly translates to faster frame rates in games where you’re processing thousands of entities every frame. The compiler catches type errors before your game runs, and the tight memory packing keeps it running fast.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_a44b18893fc7410da5aa1c09f751e205.svg" alt="Comic Panel" class="comic-image" data-comic-hash="a44b18893fc7410da5aa1c09f751e205" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="setting-up-a-camera">Setting up a Camera</h3>

<p><strong>What should we setup first?</strong></p>

<p>We need a camera because nothing shows on screen without one. The world can exist in data, but the camera decides what actually gets drawn.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//Pseudo code, don't use this yet</span>
<span class="k">fn</span> <span class="nf">setup</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">commands</span><span class="nf">.spawn</span><span class="p">(</span><span class="n">Camera2d</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s Camera2d?</strong></p>

<p><code class="language-plaintext highlighter-rouge">Camera2d</code> is Bevy’s built-in 2D camera bundle. Spawn it and you get a ready-to-go view of your 2D scene.</p>

<p><strong>What’s a bundle?</strong></p>

<p>A bundle is just a group of components you often spawn together. For example, Bevy ships a <code class="language-plaintext highlighter-rouge">SpriteBundle</code> that packs position, texture, color tint, and visibility; spawning that bundle gives a sprite entity in one call.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_c1116e593ce08a422c83b5f99a1316a7.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="c1116e593ce08a422c83b5f99a1316a7" data-comic-settings="d2-diagram" />
</div>

<h3 id="registering-our-setup-function">Registering our setup function</h3>

<p>We need to register our setup function with bevy to be triggered on Startup.</p>

<p>Update your <code class="language-plaintext highlighter-rouge">src/main.rs</code> with the following code.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Replace your main.rs with the following code.</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
	<span class="nf">.add_plugins</span><span class="p">(</span><span class="n">DefaultPlugins</span><span class="p">)</span>
	<span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">setup</span><span class="p">)</span>
	<span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">setup</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">commands</span><span class="nf">.spawn</span><span class="p">(</span><span class="n">Camera2d</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s bevy::prelude?</strong></p>

<p><code class="language-plaintext highlighter-rouge">bevy::prelude</code> is like a starter kit, things you reach for constantly when building a game—<code class="language-plaintext highlighter-rouge">App</code>, <code class="language-plaintext highlighter-rouge">Commands</code>, components, math helpers, etc.</p>

<p><strong>What’s App::new()…?</strong></p>

<p><code class="language-plaintext highlighter-rouge">App::new()</code> creates the game application, <code class="language-plaintext highlighter-rouge">add_plugins(DefaultPlugins)</code> loads rendering, input, audio, and other systems, <code class="language-plaintext highlighter-rouge">add_systems(Startup, setup)</code> registers our startup function, and <code class="language-plaintext highlighter-rouge">run()</code> hands control to Bevy’s main loop.</p>

<p><strong>Why register a startup function? What does it do?</strong></p>

<p>Startup systems run once before the first frame. We use them to set up cameras, players, and other things that should exist as soon as the window opens.</p>

<p><strong>What’s bevy main loop?</strong></p>

<p>After startup, Bevy enters its main loop: it polls input, runs your systems, updates the world, and renders a frame. That loop repeats until you quit the game.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_1a78a014c19c771ce97749d30c8d8048.svg" alt="Comic Panel" class="comic-image" data-comic-hash="1a78a014c19c771ce97749d30c8d8048" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>Let’s run it</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p><img src="/assets/book_assets/simple-world.png" alt="Simple World Setup" /></p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_f8a8a8e35b182101a6e914641e6756b9.svg" alt="Comic Panel" class="comic-image" data-comic-hash="f8a8a8e35b182101a6e914641e6756b9" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>A blank screen? Yup, we have only setup the camera, now let’s add our player.</p>

<h2 id="setting-up-the-player">Setting up the Player</h2>

<p>Now let’s create our player! Remember our <strong>Setup System</strong> and <strong>Update System</strong> from earlier? Well, in Bevy, everything is an <strong>Entity</strong> with <strong>Components</strong> that systems can work with.</p>

<p>Think of an entity as a unique ID (like a social security number) and components as the data attached to it. For our player, we need components like movement speed, health, or special abilities. In Rust, the perfect way to create these components is with a <strong>struct</strong>.</p>

<p><code class="language-plaintext highlighter-rouge">struct</code> is one of the core building blocks of rust. It groups similar data together. Here we declare an empty <code class="language-plaintext highlighter-rouge">Player</code> struct so the type itself acts as a tag we can attach to the player entity. Later we can add things like player health.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Place this before main function in main.rs</span>
<span class="nd">#[derive(Component)]</span>
<span class="k">struct</span> <span class="n">Player</span><span class="p">;</span>
</code></pre></div></div>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_247952cd1167b89bec02b20aa33f1f68.svg" alt="Comic Panel" class="comic-image" data-comic-hash="247952cd1167b89bec02b20aa33f1f68" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><strong>Why tag?</strong></p>

<p>Tag marks an entity for later lookup. Because <code class="language-plaintext highlighter-rouge">Player</code> is attached to only our hero, systems can ask Bevy, “give me the entity with the Player tag” and work with just that one.</p>

<p><strong>What’s this #[derive(component)]?</strong></p>

<p><code class="language-plaintext highlighter-rouge">derive</code> tells Rust to attach the component macro code to this struct. A macro is Rust’s way of generating  pre-defined template code for you. <code class="language-plaintext highlighter-rouge">#[derive(Component)]</code> automatically injects the boilerplate Bevy needs, so it can store and find <code class="language-plaintext highlighter-rouge">Player</code> entities, saving us from copying the same glue code everywhere. We’ll take a closer look at macros later in the series. This is the moment our player type becomes a component.</p>

<p><strong>What’s a component, and why should the player be a component?</strong></p>

<p>A component is a piece of data attached to an entity. Position, velocity, health, and even the idea of “this is the player” all live in components. By making <code class="language-plaintext highlighter-rouge">Player</code> a component we can query for that entity later, add more components (like health or inventory), and let Bevy’s systems pick out exactly the entities they need to update.</p>

<p>For now we will represent our character on screen using the “@” symbol. Modify the setup function.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Replace existing setup function in main.rs with the following code</span>
<span class="k">fn</span> <span class="nf">setup</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">(</span><span class="n">Camera2d</span><span class="p">);</span>	
    
    <span class="c1">// Code Update Alert</span>
    <span class="c1">// Append the following lines to your setup function.</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="nn">Text2d</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"@"</span><span class="p">),</span>
        <span class="n">TextFont</span> <span class="p">{</span>
            <span class="n">font_size</span><span class="p">:</span> <span class="mf">12.0</span><span class="p">,</span>	
            <span class="n">font</span><span class="p">:</span> <span class="nf">default</span><span class="p">(),</span>
            <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
        <span class="p">},</span>
        <span class="nf">TextColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">),</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">),</span>
        <span class="n">Player</span><span class="p">,</span>
    <span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">commands.spawn(( ... ))</code> takes that tuple and treats it as a bundle of components. This single call adds the text we want to show, its font settings, the color, the position, and the <code class="language-plaintext highlighter-rouge">Player</code> tag that identifies the entity.</p>

<p><strong>What’s a tuple?</strong></p>

<p>A tuple is an ordered list of values written in parentheses. Rust keeps track of each position, so <code class="language-plaintext highlighter-rouge">(Text2d::new("@"), TextColor(Color::WHITE))</code> holds two values side by side without needing to create a struct.</p>

<p><strong>Whats an entity?</strong></p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_173392890061e9c4edf1715b1f948b23.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="173392890061e9c4edf1715b1f948b23" data-comic-settings="d2-diagram" />
</div>

<p>An entity is the unique ID Bevy uses to tie components together. By itself it holds no data, but once you attach components to it, that ID represents something in your game world.</p>

<p>Each bundle produces a new entity with a unique ID and the listed components, which you can picture like this:</p>

<table>
  <thead>
    <tr>
      <th>Entity</th>
      <th>Components it carries</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>#42</td>
      <td><code class="language-plaintext highlighter-rouge">Camera2d</code></td>
    </tr>
    <tr>
      <td>#43</td>
      <td><code class="language-plaintext highlighter-rouge">Text2d("@")</code>, <code class="language-plaintext highlighter-rouge">TextFont</code>, <code class="language-plaintext highlighter-rouge">TextColor</code>, <code class="language-plaintext highlighter-rouge">Transform</code>, <code class="language-plaintext highlighter-rouge">Player</code></td>
    </tr>
  </tbody>
</table>

<p>Once the queue flushes, those entities live in the world, ready for systems to discover them by the tags (components) they carry. We will be using this later when we want to do things like moving or animating them, or making them attack enemies, etc.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_887ad36fbccf42ddd1189a1cf8460abb.svg" alt="Comic Panel" class="comic-image" data-comic-hash="887ad36fbccf42ddd1189a1cf8460abb" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="implementing-player-movement">Implementing Player Movement</h3>

<p>Now let’s create our <strong>Update System</strong> for player movement! Think about what we need to move a player:</p>

<ol>
  <li><strong>Keyboard input</strong> - to know which keys are pressed</li>
  <li><strong>Time</strong> - to make movement smooth regardless of frame rate</li>
  <li><strong>Player position</strong> - to actually move the player</li>
</ol>

<p>In other game engines, you’d spend time manually connecting these systems together. But here’s the magic of Bevy - you just ask for what you need in your function parameters, and Bevy automatically provides it!</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_378c5d19a650a2ab7b4aa79879ea3242.svg" alt="Comic Panel" class="comic-image" data-comic-hash="378c5d19a650a2ab7b4aa79879ea3242" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p>Let’s write our move player function.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append this code to main.rs</span>
<span class="k">fn</span> <span class="nf">move_player</span><span class="p">(</span>
    <span class="c1">// "Bevy, give me keyboard input"</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>           
    <span class="c1">// "Bevy, give me the game timer"</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>                            
    <span class="c1">// "Bevy, give me the player's position"</span>
    <span class="k">mut</span> <span class="n">player_transform</span><span class="p">:</span> <span class="n">Single</span><span class="o">&lt;&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span> 
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">direction</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span><span class="p">;</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowLeft</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.x</span> <span class="o">-=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowRight</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.x</span> <span class="o">+=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowUp</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.y</span> <span class="o">+=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowDown</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.y</span> <span class="o">-=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">speed</span> <span class="o">=</span> <span class="mf">300.0</span><span class="p">;</span> <span class="c1">// pixels per second</span>
        <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">direction</span><span class="nf">.normalize</span><span class="p">()</span> <span class="o">*</span> <span class="n">speed</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
        <span class="n">player_transform</span><span class="py">.translation.x</span> <span class="o">+=</span> <span class="n">delta</span><span class="py">.x</span><span class="p">;</span>
        <span class="n">player_transform</span><span class="py">.translation.y</span> <span class="o">+=</span> <span class="n">delta</span><span class="py">.y</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s Res?</strong></p>

<p>Res or Resources are pieces of game-wide information that are not tied to any single entity. For example, <code class="language-plaintext highlighter-rouge">Res&lt;Time&gt;</code> gives you the game’s master clock, so every system reads the same “time since last frame” value.</p>

<p><b> Explain Single&lt;&amp;mut Transform, With<Player>&gt;? <b></b></Player></b></p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_58c5d073eddf9a6c41419d3c96c5235e.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="58c5d073eddf9a6c41419d3c96c5235e" data-comic-settings="d2-diagram" />
</div>

<p><code class="language-plaintext highlighter-rouge">Single&lt;&amp;mut Transform, With&lt;Player&gt;&gt;</code> asks Bevy for exactly one entity that has a <code class="language-plaintext highlighter-rouge">Transform</code> component and also carries the <code class="language-plaintext highlighter-rouge">Player</code> tag. The <code class="language-plaintext highlighter-rouge">&amp;mut Transform</code> part means we intend to modify that transform or player position (Remember, we added transform component in the setup function). If more than one player existed, this extractor would complain, which is perfect for a single-hero game.</p>

<p><strong>What’s Vec2::ZERO?</strong></p>

<p><code class="language-plaintext highlighter-rouge">Vec2::ZERO</code> is a two-dimensional vector with both values set to zero: <code class="language-plaintext highlighter-rouge">Vec2 { x: 0.0, y: 0.0 }</code>. We use it as the starting direction before reading keyboard input.</p>

<p><strong>What’s this KeyCode::… pattern?</strong></p>

<p><code class="language-plaintext highlighter-rouge">KeyCode::ArrowLeft</code>, <code class="language-plaintext highlighter-rouge">KeyCode::ArrowRight</code> … are enums (will cover enums later) that represent specific keys on the keyboard. Checking <code class="language-plaintext highlighter-rouge">input.pressed(KeyCode::ArrowLeft)</code> simply asks Bevy whether that key is held down during the current frame.</p>

<p>We ignore zero direction so the player stands still when no keys are pressed. Once we have input, <code class="language-plaintext highlighter-rouge">normalize()</code> converts the vector to length 1 so diagonal movement isn’t faster than straight movement. <code class="language-plaintext highlighter-rouge">speed</code> says how many pixels per second to move, and <code class="language-plaintext highlighter-rouge">time.delta_secs()</code> returns the frame time—the number of seconds since the previous frame—so multiplying them gives the distance we should travel this update. Finally we add that delta to the player’s transform translation to move the sprite on screen.</p>

<h3 id="registering-the-movement-system">Registering the Movement System</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Update main function</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="n">DefaultPlugins</span><span class="p">)</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">setup</span><span class="p">)</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="n">move_player</span><span class="p">)</span> <span class="c1">// Line update alert</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_7f53f21ef5eb666455c9f46d335b0020.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="7f53f21ef5eb666455c9f46d335b0020" data-comic-settings="d2-diagram" />
</div>

<p><strong>Why is move_player added as Update? What’s Update?</strong></p>

<p><code class="language-plaintext highlighter-rouge">move_player</code> runs every frame, so we plug it into the <code class="language-plaintext highlighter-rouge">Update</code> schedule. <code class="language-plaintext highlighter-rouge">Update</code> is Bevy’s built-in stage that fires once per game loop after startup is done.</p>

<p><strong>So does systems mean functions we want bevy to execute at a particular time, like initial setup or on every game loop update?</strong></p>

<p>Exactly. A system is just a Rust function you hand to Bevy with a sticky note that says “run me during Startup” or “execute me on every Update,” and the engine dutifully obeys.</p>

<p>Let’s run it.</p>

<p><img src="/assets/book_assets/simple-player-movement.gif" alt="Player Movement Demo" /></p>

<p><br /></p>

<h2 id="adding-sprite-graphics">Adding Sprite Graphics</h2>

<p>We’ll use the Universal LPC SpriteSheet Generator to give our character some personality. You can remix body parts, clothes, and colors at <a href="https://liberatedpixelcup.github.io/Universal-LPC-Spritesheet-Character-Generator/#?body=Body_color_light&amp;head=Human_male_light">this link</a> and export a full spritesheet.</p>

<p>For this project the spritesheet is already included in the <a href="https://github.com/jamesfebin/ImpatientProgrammerBevyRust">repo</a>. Drop the provided image files into <code class="language-plaintext highlighter-rouge">src/assets</code> so Bevy can find them when the game runs. You will need to create the <code class="language-plaintext highlighter-rouge">assets</code> directory inside the src folder.</p>

<p><img src="/assets/book_assets/universal-sprite-sheet-generator.png" alt="Universal LPC Spritesheet Generator" /></p>

<p><br /></p>

<h3 id="refactoring-code">Refactoring Code</h3>

<p>We need to add more code and adding to main.rs will make hard to read and understand.</p>

<p>Update your main.rs file to the following, also create another file player.rs</p>

<p><code class="language-plaintext highlighter-rouge">mod player;</code> pulls in the <code class="language-plaintext highlighter-rouge">player.rs</code> module, keeping our main file slim. Splitting code like this makes it easier to grow the project without one big file doing everything.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//main.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">mod</span> <span class="n">player</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.insert_resource</span><span class="p">(</span><span class="nf">ClearColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">))</span> <span class="c1">// We have updated the bg color to white</span>
        <span class="nf">.add_plugins</span><span class="p">(</span>
            <span class="n">DefaultPlugins</span><span class="nf">.set</span><span class="p">(</span><span class="n">AssetPlugin</span> <span class="p">{</span>
                <span class="c1">// Our assets live in `src/assets` for this project</span>
                <span class="n">file_path</span><span class="p">:</span> <span class="s">"src/assets"</span><span class="nf">.into</span><span class="p">(),</span>
                <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
            <span class="p">}),</span>
        <span class="p">)</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">setup_camera</span><span class="p">)</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">setup_camera</span><span class="p">(</span><span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">(</span><span class="n">Camera2d</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">.insert_resource(ClearColor(Color::WHITE))</code> drops a global setting into the app so every frame starts with a white background.</p>

<p><code class="language-plaintext highlighter-rouge">AssetPlugin</code> is Bevy’s loader for textures, audio, and other assets. We tweak its <code class="language-plaintext highlighter-rouge">file_path</code> so it looks inside <code class="language-plaintext highlighter-rouge">src/assets</code>, which is where our sprites live.</p>

<h3 id="building-the-player-module">Building the Player Module</h3>

<p>Now that we have our basic app structure in place, it’s time to bring our character to life! We’ll create a dedicated <code class="language-plaintext highlighter-rouge">player.rs</code> module to handle everything related to our little adventurer. This keeps our code organized and makes it easier to add more features later.</p>

<p>This module will hold all the constants, components, and systems that make our player move, animate, and interact with the world.</p>

<p>Add following code in player.rs</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// player.rs</span>
<span class="k">use</span> <span class="nn">bevy</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="c1">// Atlas constants</span>
<span class="k">const</span> <span class="n">TILE_SIZE</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="mi">64</span><span class="p">;</span> <span class="c1">// 64x64 tiles</span>
<span class="k">const</span> <span class="n">WALK_FRAMES</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span> <span class="c1">// 9 columns per walking row</span>
<span class="k">const</span> <span class="n">MOVE_SPEED</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">140.0</span><span class="p">;</span> <span class="c1">// pixels per second</span>
<span class="k">const</span> <span class="n">ANIM_DT</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">0.1</span><span class="p">;</span> <span class="c1">// seconds per frame (~10 FPS)</span>


<span class="nd">#[derive(Component)]</span>
<span class="k">struct</span> <span class="n">Player</span><span class="p">;</span> <span class="c1">// Moved from main.rs to here</span>
</code></pre></div></div>

<p>These constants give names to the numbers we reuse for the spritesheet and movement math: tile size, frames per row, walk speed, and how fast the animation advances. Moving the <code class="language-plaintext highlighter-rouge">Player</code> marker here keeps all player-specific types in one module.</p>

<h3 id="defining-player-directions">Defining Player Directions</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append these lines of code to player.rs</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">Copy,</span> <span class="nd">PartialEq,</span> <span class="nd">Eq)]</span>
<span class="k">enum</span> <span class="n">Facing</span> <span class="p">{</span>
    <span class="n">Up</span><span class="p">,</span>
    <span class="nb">Left</span><span class="p">,</span>
    <span class="n">Down</span><span class="p">,</span>
    <span class="nb">Right</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s an enum?</strong></p>

<p>An enum lists a handful of allowed values. Our character only ever faces four directions, so the <code class="language-plaintext highlighter-rouge">Facing</code> enum captures those options in one place.</p>

<p><strong>Why can’t I use a struct here?</strong></p>

<p>A struct would need a bunch of booleans like <code class="language-plaintext highlighter-rouge">facing_up:true</code>, <code class="language-plaintext highlighter-rouge">facing_left:false</code>, and you’d have to keep them in sync. That makes it complicated. Enum guarantees you only pick one direction.</p>

<p><strong>When to decide between using enum and struct?</strong></p>

<p>Use a struct when you need several fields, like a position with <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code>, or player stats with health and stamina. Use an enum when you choose one option from a list, like which tool the player wields (sword, bow, wand) or which menu screen is active.</p>

<p><strong>Why the purpose of adding Debug, Clone, Copy, PartialEq, Eq macros?</strong></p>

<p><code class="language-plaintext highlighter-rouge">Debug</code> lets us print the facing for logs, <code class="language-plaintext highlighter-rouge">Clone</code> and <code class="language-plaintext highlighter-rouge">Copy</code> make it trivial to duplicate the value, and <code class="language-plaintext highlighter-rouge">PartialEq</code>/<code class="language-plaintext highlighter-rouge">Eq</code> allow equality checks when we compare directions.</p>

<p><strong>Why can’t I copy through simple assignment, why should I add these macros?</strong></p>

<p>By default Rust moves values instead of copying them, so the compiler makes you opt in. Deriving <code class="language-plaintext highlighter-rouge">Copy</code> (and <code class="language-plaintext highlighter-rouge">Clone</code> as a helper) says “it’s cheap, go ahead and duplicate it.” <code class="language-plaintext highlighter-rouge">PartialEq</code> and <code class="language-plaintext highlighter-rouge">Eq</code> let us compare two facings directly, which is how we detect when the player changes direction.</p>

<p><strong>What do you mean by rust moves values, instead of copying?</strong></p>

<p>When you assign most values in Rust, the old variable stops owning it or in other words dies. Adding <code class="language-plaintext highlighter-rouge">Copy</code> keeps both variables valid.</p>

<p><strong>Why does the old variable stop owning or die when assigned?</strong></p>

<p>Rust enforces that each value has a single owner so memory can be freed safely. We’ll unpack the ownership rules and borrowing in later chapters.</p>

<p><strong>This is a bit going over my head!</strong></p>

<p>Yea, don’t worry, we have many more chapters to go and as you move through them, we will tackle this. It’s alright to not understand these concepts now.</p>

<h3 id="animation-system-components">Animation System Components</h3>

<p>When we have a spritesheet with multiple frames (like our 9-frame walking animation), we need a way to control how fast those frames play. Without timing control, our character would either be frozen on one frame or cycling through frames so fast it looks like a blur!</p>

<p>Think of it like a flipbook - if you flip the pages too slowly, the animation looks choppy. If you flip too fast, you can’t see what’s happening. The <code class="language-plaintext highlighter-rouge">AnimationTimer</code> gives us precise control over this timing, ensuring our character’s walking animation looks smooth and natural at just the right speed.</p>

<p><code class="language-plaintext highlighter-rouge">AnimationTimer</code> wraps a Bevy <code class="language-plaintext highlighter-rouge">Timer</code> so each player entity knows when to advance to the next animation frame. Each tick represents one slice of time; once the timer hits its interval we move to the next sprite in the sheet.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append these lines of code to player.rs</span>
<span class="nd">#[derive(Component,</span> <span class="nd">Deref,</span> <span class="nd">DerefMut)]</span>
<span class="k">struct</span> <span class="nf">AnimationTimer</span><span class="p">(</span><span class="n">Timer</span><span class="p">);</span>

<span class="nd">#[derive(Component)]</span>
<span class="k">struct</span> <span class="n">AnimationState</span> <span class="p">{</span>
    <span class="n">facing</span><span class="p">:</span> <span class="n">Facing</span><span class="p">,</span>
    <span class="n">moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
    <span class="n">was_moving</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>What’s Deref, DerefMut macros doing?</strong></p>

<p><code class="language-plaintext highlighter-rouge">Deref</code> lets our wrapper pretend to be the inner <code class="language-plaintext highlighter-rouge">Timer</code> when we read from it, and <code class="language-plaintext highlighter-rouge">DerefMut</code> does the same for writes. That means we can just call <code class="language-plaintext highlighter-rouge">timer.tick(time.delta())</code> on <code class="language-plaintext highlighter-rouge">AnimationTimer</code> without manually pulling out the inner value first.</p>

<p><strong>So we are renaming the Timer to AnimationTimer?</strong></p>

<p>We’re wrapping the timer, not renaming it. Think of <code class="language-plaintext highlighter-rouge">AnimationTimer</code> as a little box that holds a <code class="language-plaintext highlighter-rouge">Timer</code>, plus a label that says “this one belongs to the player animation.” When we spawn a player we create a fresh <code class="language-plaintext highlighter-rouge">Timer</code> and tuck it into that box, so each player could have its own timer if we needed multiple heroes.</p>

<p><strong>So it’s an instance of AnimationTime?</strong></p>

<p>Yes, <code class="language-plaintext highlighter-rouge">AnimationTimer</code> is a tuple struct that contains a <code class="language-plaintext highlighter-rouge">Timer</code>. We build one when we spawn the player so each entity can carry its own timer data. This pattern shows up whenever you want to attach extra meaning to an existing type without writing a brand-new API.</p>

<p><code class="language-plaintext highlighter-rouge">AnimationState</code> remembers which way the player points, whether they are moving, and whether they just started or stopped. Systems read this to choose animation rows and reset frames when movement changes.</p>

<h3 id="spawning-the-player">Spawning the Player</h3>

<p>We load the spritesheet through the <code class="language-plaintext highlighter-rouge">AssetServer</code>, create a texture atlas layout so Bevy knows the grid, and pick the starting frame for a hero facing down. Then we spawn an entity with the sprite, transform at the origin, our marker components, and the timer that will drive animation.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append these lines of code to player.rs</span>
<span class="k">fn</span> <span class="nf">spawn_player</span><span class="p">(</span>
    <span class="k">mut</span> <span class="n">commands</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>
    <span class="n">asset_server</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">AssetServer</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">atlas_layouts</span><span class="p">:</span> <span class="n">ResMut</span><span class="o">&lt;</span><span class="n">Assets</span><span class="o">&lt;</span><span class="n">TextureAtlasLayout</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Load the spritesheet and build a grid layout: 64x64 tiles, 9 columns, 12 rows</span>
    <span class="k">let</span> <span class="n">texture</span> <span class="o">=</span> <span class="n">asset_server</span><span class="nf">.load</span><span class="p">(</span><span class="s">"male_spritesheet.png"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">layout</span> <span class="o">=</span> <span class="n">atlas_layouts</span><span class="nf">.add</span><span class="p">(</span><span class="nn">TextureAtlasLayout</span><span class="p">::</span><span class="nf">from_grid</span><span class="p">(</span>
        <span class="nn">UVec2</span><span class="p">::</span><span class="nf">splat</span><span class="p">(</span><span class="n">TILE_SIZE</span><span class="p">),</span>
        <span class="n">WALK_FRAMES</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">,</span> <span class="c1">// columns used for walking frames</span>
        <span class="mi">12</span><span class="p">,</span>                  <span class="c1">// at least 12 rows available</span>
        <span class="nb">None</span><span class="p">,</span>
        <span class="nb">None</span><span class="p">,</span>
    <span class="p">));</span>

    <span class="c1">// Start facing down (towards user), idle on first frame of that row</span>
    <span class="k">let</span> <span class="n">facing</span> <span class="o">=</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">start_index</span> <span class="o">=</span> <span class="nf">atlas_index_for</span><span class="p">(</span><span class="n">facing</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

    <span class="n">commands</span><span class="nf">.spawn</span><span class="p">((</span>
        <span class="nn">Sprite</span><span class="p">::</span><span class="nf">from_atlas_image</span><span class="p">(</span>
            <span class="n">texture</span><span class="p">,</span>
            <span class="n">TextureAtlas</span> <span class="p">{</span>
                <span class="n">layout</span><span class="p">,</span>
                <span class="n">index</span><span class="p">:</span> <span class="n">start_index</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">),</span>
        <span class="nn">Transform</span><span class="p">::</span><span class="nf">from_translation</span><span class="p">(</span><span class="nn">Vec3</span><span class="p">::</span><span class="n">ZERO</span><span class="p">),</span>
        <span class="n">Player</span><span class="p">,</span>
        <span class="n">AnimationState</span> <span class="p">{</span> <span class="n">facing</span><span class="p">,</span> <span class="n">moving</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span> <span class="n">was_moving</span><span class="p">:</span> <span class="k">false</span> <span class="p">},</span>
        <span class="nf">AnimationTimer</span><span class="p">(</span><span class="nn">Timer</span><span class="p">::</span><span class="nf">from_seconds</span><span class="p">(</span><span class="n">ANIM_DT</span><span class="p">,</span> <span class="nn">TimerMode</span><span class="p">::</span><span class="n">Repeating</span><span class="p">)),</span>
    <span class="p">));</span>
<span class="p">}</span>

</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">AnimationState { facing, moving: false, was_moving: false }</code> sets the starting direction and flags that the character is idle right now and was idle last frame.</p>

<p><code class="language-plaintext highlighter-rouge">AnimationTimer(Timer::from_seconds(ANIM_DT, TimerMode::Repeating))</code> creates a repeating stopwatch that fires every <code class="language-plaintext highlighter-rouge">ANIM_DT</code> seconds to advance the spritesheet.</p>

<p><strong>What’s an <code class="language-plaintext highlighter-rouge">AssetServer</code>?</strong></p>

<p>The <code class="language-plaintext highlighter-rouge">AssetServer</code> is Bevy’s file loader and manager that handles loading and caching of game assets like images, sounds, and 3D models.</p>

<p>When you call <code class="language-plaintext highlighter-rouge">asset_server.load("path/to/sprite.png")</code>, it doesn’t immediately load the file into memory. Instead, it returns a handle that you can use later. This is called “lazy loading” - the actual file loading happens in the background, and Bevy will notify you when it’s ready.</p>

<p>This approach is efficient because:</p>
<ul>
  <li>Multiple entities can share the same sprite without loading it multiple times</li>
  <li>Assets are only loaded when actually needed</li>
  <li>Bevy can optimize and batch-load assets for better performance</li>
  <li>If an asset fails to load, it won’t crash your entire game</li>
</ul>

<p>In our case, <code class="language-plaintext highlighter-rouge">asset_server.load("sprites/player.png")</code> requests the spritesheet and returns a handle to track its loading status.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_81f36c4a3797299cde117490fc129b24.svg" alt="Comic Panel" class="comic-image" data-comic-hash="81f36c4a3797299cde117490fc129b24" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<h3 id="movement-system">Movement System</h3>

<p>Now that we have our player spawned with all the necessary components, we need to update our existing movement system to track which direction the player is facing. This is crucial for the animation system to know which row of the spritesheet to use - each direction (up, down, left, right) corresponds to a different row in our sprite atlas.</p>

<p>This updated system will detect the movement direction and update the <code class="language-plaintext highlighter-rouge">AnimationState</code> accordingly, so our animation system can pick the correct sprite row for walking animations in each direction.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append these lines of code to player.rs</span>
<span class="k">fn</span> <span class="nf">move_player</span><span class="p">(</span>
    <span class="n">input</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">ButtonInput</span><span class="o">&lt;</span><span class="n">KeyCode</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">player</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">Transform</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationState</span><span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="k">mut</span> <span class="n">transform</span><span class="p">,</span> <span class="k">mut</span> <span class="n">anim</span><span class="p">))</span> <span class="o">=</span> <span class="n">player</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">direction</span> <span class="o">=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span><span class="p">;</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowLeft</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.x</span> <span class="o">-=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowRight</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.x</span> <span class="o">+=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowUp</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.y</span> <span class="o">+=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="n">input</span><span class="nf">.pressed</span><span class="p">(</span><span class="nn">KeyCode</span><span class="p">::</span><span class="n">ArrowDown</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">direction</span><span class="py">.y</span> <span class="o">-=</span> <span class="mf">1.0</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="n">direction</span> <span class="o">!=</span> <span class="nn">Vec2</span><span class="p">::</span><span class="n">ZERO</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">direction</span><span class="nf">.normalize</span><span class="p">()</span> <span class="o">*</span> <span class="n">MOVE_SPEED</span> <span class="o">*</span> <span class="n">time</span><span class="nf">.delta_secs</span><span class="p">();</span>
        <span class="n">transform</span><span class="py">.translation.x</span> <span class="o">+=</span> <span class="n">delta</span><span class="py">.x</span><span class="p">;</span>
        <span class="n">transform</span><span class="py">.translation.y</span> <span class="o">+=</span> <span class="n">delta</span><span class="py">.y</span><span class="p">;</span>
        <span class="n">anim</span><span class="py">.moving</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>

        <span class="c1">// Update facing based on dominant direction</span>
        <span class="k">if</span> <span class="n">direction</span><span class="py">.x</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">direction</span><span class="py">.y</span><span class="nf">.abs</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">anim</span><span class="py">.facing</span> <span class="o">=</span> <span class="k">if</span> <span class="n">direction</span><span class="py">.x</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="p">};</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">anim</span><span class="py">.facing</span> <span class="o">=</span> <span class="k">if</span> <span class="n">direction</span><span class="py">.y</span> <span class="o">&gt;</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="p">};</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">anim</span><span class="py">.moving</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The query asks Bevy for the single entity tagged <code class="language-plaintext highlighter-rouge">Player</code>, giving us mutable access to its <code class="language-plaintext highlighter-rouge">Transform</code> and <code class="language-plaintext highlighter-rouge">AnimationState</code>. We build a direction vector from the pressed keys, normalize it so diagonal input isn’t faster, and move the player by speed × frame time. The facing logic compares horizontal vs vertical strength to decide which way the sprite should look. We record whether the player is moving now so later systems can detect when motion starts or stops.</p>

<h3 id="animation-implementation">Animation Implementation</h3>

<p>Now we have all the pieces in place - our player can move in different directions, and we’re tracking which way they’re facing. The final piece is the animation system that actually updates the sprite frames to create the walking animation.</p>

<p>This system takes the direction information from our movement system and uses the animation timer to cycle through the sprite frames at the right speed. It handles the complex logic of switching between different animation rows when the player changes direction, and ensures smooth transitions between walking and idle states.</p>

<div class="d2-diagram">
  <img src="/assets/generated/d2/d2_4cbe339371955fc6669e01799120d6b4.svg" alt="D2 Diagram" class="d2-svg comic-image" data-comic-hash="4cbe339371955fc6669e01799120d6b4" data-comic-settings="d2-diagram" />
</div>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append these lines of code to player.rs</span>
<span class="k">fn</span> <span class="nf">animate_player</span><span class="p">(</span>
    <span class="n">time</span><span class="p">:</span> <span class="n">Res</span><span class="o">&lt;</span><span class="n">Time</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="k">mut</span> <span class="n">query</span><span class="p">:</span> <span class="n">Query</span><span class="o">&lt;</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationState</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">AnimationTimer</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Sprite</span><span class="p">),</span> <span class="n">With</span><span class="o">&lt;</span><span class="n">Player</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nf">Ok</span><span class="p">((</span><span class="k">mut</span> <span class="n">anim</span><span class="p">,</span> <span class="k">mut</span> <span class="n">timer</span><span class="p">,</span> <span class="k">mut</span> <span class="n">sprite</span><span class="p">))</span> <span class="o">=</span> <span class="n">query</span><span class="nf">.single_mut</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="n">atlas</span> <span class="o">=</span> <span class="k">match</span> <span class="n">sprite</span><span class="py">.texture_atlas</span><span class="nf">.as_mut</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">Some</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">a</span><span class="p">,</span>
        <span class="nb">None</span> <span class="k">=&gt;</span> <span class="k">return</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="c1">// Compute the target row and current position in the atlas (column/row within the 9-column row)</span>
    <span class="k">let</span> <span class="n">target_row</span> <span class="o">=</span> <span class="nf">row_zero_based</span><span class="p">(</span><span class="n">anim</span><span class="py">.facing</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">current_col</span> <span class="o">=</span> <span class="n">atlas</span><span class="py">.index</span> <span class="o">%</span> <span class="n">WALK_FRAMES</span><span class="p">;</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">current_row</span> <span class="o">=</span> <span class="n">atlas</span><span class="py">.index</span> <span class="o">/</span> <span class="n">WALK_FRAMES</span><span class="p">;</span>

    <span class="c1">// If the facing changed (or we weren't on a walking row), snap to the first frame of the target row</span>
    <span class="k">if</span> <span class="n">current_row</span> <span class="o">!=</span> <span class="n">target_row</span> <span class="p">{</span>
        <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="nf">row_start_index</span><span class="p">(</span><span class="n">anim</span><span class="py">.facing</span><span class="p">);</span>
        <span class="n">current_col</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">current_row</span> <span class="o">=</span> <span class="n">target_row</span><span class="p">;</span>
        <span class="n">timer</span><span class="nf">.reset</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">just_started</span> <span class="o">=</span> <span class="n">anim</span><span class="py">.moving</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">anim</span><span class="py">.was_moving</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">just_stopped</span> <span class="o">=</span> <span class="o">!</span><span class="n">anim</span><span class="py">.moving</span> <span class="o">&amp;&amp;</span> <span class="n">anim</span><span class="py">.was_moving</span><span class="p">;</span>

    <span class="k">if</span> <span class="n">anim</span><span class="py">.moving</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">just_started</span> <span class="p">{</span>
            <span class="c1">// On tap or movement start, immediately advance one frame for visible feedback</span>
            <span class="k">let</span> <span class="n">row_start</span> <span class="o">=</span> <span class="nf">row_start_index</span><span class="p">(</span><span class="n">anim</span><span class="py">.facing</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">next_col</span> <span class="o">=</span> <span class="p">(</span><span class="n">current_col</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">WALK_FRAMES</span><span class="p">;</span>
            <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">row_start</span> <span class="o">+</span> <span class="n">next_col</span><span class="p">;</span>
            <span class="c1">// Restart the timer so the next advance uses a full interval</span>
            <span class="n">timer</span><span class="nf">.reset</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// Continuous movement: advance based on timer cadence</span>
            <span class="n">timer</span><span class="nf">.tick</span><span class="p">(</span><span class="n">time</span><span class="nf">.delta</span><span class="p">());</span>
            <span class="k">if</span> <span class="n">timer</span><span class="nf">.just_finished</span><span class="p">()</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">row_start</span> <span class="o">=</span> <span class="nf">row_start_index</span><span class="p">(</span><span class="n">anim</span><span class="py">.facing</span><span class="p">);</span>
                <span class="k">let</span> <span class="n">next_col</span> <span class="o">=</span> <span class="p">(</span><span class="n">current_col</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">WALK_FRAMES</span><span class="p">;</span>
                <span class="n">atlas</span><span class="py">.index</span> <span class="o">=</span> <span class="n">row_start</span> <span class="o">+</span> <span class="n">next_col</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">just_stopped</span> <span class="p">{</span>
        <span class="c1">// Not moving: keep current frame to avoid snap. Reset timer on transition to idle.</span>
        <span class="n">timer</span><span class="nf">.reset</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">// Update previous movement state</span>
    <span class="n">anim</span><span class="py">.was_moving</span> <span class="o">=</span> <span class="n">anim</span><span class="py">.moving</span><span class="p">;</span>
<span class="p">}</span>


<span class="c1">// Returns the starting atlas index for the given facing row</span>
<span class="k">fn</span> <span class="nf">row_start_index</span><span class="p">(</span><span class="n">facing</span><span class="p">:</span> <span class="n">Facing</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
    <span class="nf">row_zero_based</span><span class="p">(</span><span class="n">facing</span><span class="p">)</span> <span class="o">*</span> <span class="n">WALK_FRAMES</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">atlas_index_for</span><span class="p">(</span><span class="n">facing</span><span class="p">:</span> <span class="n">Facing</span><span class="p">,</span> <span class="n">frame_in_row</span><span class="p">:</span> <span class="nb">usize</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
    <span class="nf">row_start_index</span><span class="p">(</span><span class="n">facing</span><span class="p">)</span> <span class="o">+</span> <span class="n">frame_in_row</span><span class="nf">.min</span><span class="p">(</span><span class="n">WALK_FRAMES</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">row_zero_based</span><span class="p">(</span><span class="n">facing</span><span class="p">:</span> <span class="n">Facing</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">usize</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">facing</span> <span class="p">{</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="n">Up</span> <span class="k">=&gt;</span> <span class="mi">8</span><span class="p">,</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="nb">Left</span> <span class="k">=&gt;</span> <span class="mi">9</span><span class="p">,</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="n">Down</span> <span class="k">=&gt;</span> <span class="mi">10</span><span class="p">,</span>
        <span class="nn">Facing</span><span class="p">::</span><span class="nb">Right</span> <span class="k">=&gt;</span> <span class="mi">11</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">let Ok((mut anim, mut timer, mut sprite)) = query.single_mut() else { return; };</code> both checks the result and names the pieces we need. If the query succeeds, the code binds <code class="language-plaintext highlighter-rouge">anim</code>, <code class="language-plaintext highlighter-rouge">timer</code>, and <code class="language-plaintext highlighter-rouge">sprite</code> so we can use them later. If it fails (no player, or more than one), we hit the <code class="language-plaintext highlighter-rouge">else</code> branch and exit immediately. Rust uses the <code class="language-plaintext highlighter-rouge">Result</code> type for this: <code class="language-plaintext highlighter-rouge">Ok</code> means “query returned exactly one result,” <code class="language-plaintext highlighter-rouge">Err</code> means “something about that query didn’t match.”</p>

<p>After that we <code class="language-plaintext highlighter-rouge">match</code> on an <code class="language-plaintext highlighter-rouge">Option</code>, which is Rust’s “maybe there is a value” type. <code class="language-plaintext highlighter-rouge">Some(atlas)</code> means the texture atlas exists and we can tweak it; <code class="language-plaintext highlighter-rouge">None</code> means it hasn’t loaded yet, so we skip and let the next frame try again. It’s the same pattern you’d use when checking a map or cache: only use the value when the lookup returns something.</p>

<div class="comic-panel">
  <img src="/assets/generated/comic/comic_e9ccefef9341cc317c0f022839330025.svg" alt="Comic Panel" class="comic-image" data-comic-hash="e9ccefef9341cc317c0f022839330025" data-comic-settings="6d94351b851cc3cb428c294cfcae8153" />
</div>

<p><code class="language-plaintext highlighter-rouge">animate_player</code> pulls the animation state, timer, and sprite handle for the player. It figures out which row of the atlas matches the current facing, snaps to that row when direction changes, and uses the timer to step through columns at a steady pace. When movement stops we reset the timer so the animation rests on the last frame shown. The helper functions map a facing to the correct row and frame index so the math stays readable.</p>

<h3 id="creating-the-player-plugin">Creating the Player Plugin</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Append these lines of code to player.rs</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">PlayerPlugin</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">Plugin</span> <span class="k">for</span> <span class="n">PlayerPlugin</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">build</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">App</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">app</span><span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">spawn_player</span><span class="p">)</span>
            <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Update</span><span class="p">,</span> <span class="p">(</span><span class="n">move_player</span><span class="p">,</span> <span class="n">animate_player</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">PlayerPlugin</code> is our “player module” in plug form. In Bevy, a plugin is just a struct that knows how to register systems, resources, and assets. By implementing <code class="language-plaintext highlighter-rouge">Plugin</code> for <code class="language-plaintext highlighter-rouge">PlayerPlugin</code>, we give Bevy a checklist: whenever this plugin is added to the app, run the code inside to set up everything the player feature needs. This keeps <code class="language-plaintext highlighter-rouge">main.rs</code> from becoming a tangle of player-specific calls.</p>

<p>The <code class="language-plaintext highlighter-rouge">build</code> method is the checklist. Bevy passes us a mutable <code class="language-plaintext highlighter-rouge">App</code>, and we bolt on the systems we care about. <code class="language-plaintext highlighter-rouge">spawn_player</code> is scheduled in <code class="language-plaintext highlighter-rouge">Startup</code> so the sprite appears as soon as the game launches. <code class="language-plaintext highlighter-rouge">move_player</code> and <code class="language-plaintext highlighter-rouge">animate_player</code> go into the <code class="language-plaintext highlighter-rouge">Update</code> schedule so they execute every frame—handling input and animation in lockstep. With everything declared here, dropping <code class="language-plaintext highlighter-rouge">PlayerPlugin</code> into <code class="language-plaintext highlighter-rouge">App::new()</code> automatically wires up the entire player flow.</p>

<p><strong>So this build function is an in-built structure, which I have to write?</strong></p>

<p>Yes. The <code class="language-plaintext highlighter-rouge">Plugin</code> trait says “any plugin must provide a <code class="language-plaintext highlighter-rouge">build(&amp;self, app: &amp;mut App)</code> function.” We implement that trait, so Rust expects us to supply the body. Bevy calls this method when it loads the plugin, which is why we add all our systems inside it.</p>

<p><strong>What’s a trait?</strong></p>

<p>A trait is a contract describing what methods a type must provide. Bevy’s <code class="language-plaintext highlighter-rouge">Plugin</code> trait says “give me a <code class="language-plaintext highlighter-rouge">build</code> function so I can register your systems.” By implementing that trait for <code class="language-plaintext highlighter-rouge">PlayerPlugin</code>, we hook into Bevy’s startup process and inject our own setup code. Traits let different types share behavior, <code class="language-plaintext highlighter-rouge">PlayerPlugin</code> behaves like any other Bevy plugin, but it installs our player-specific systems.</p>

<h3 id="final-integration">Final Integration</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Update the main function in main.rs</span>

<span class="k">use</span> <span class="k">crate</span><span class="p">::</span><span class="nn">player</span><span class="p">::</span><span class="n">PlayerPlugin</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nn">App</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.insert_resource</span><span class="p">(</span><span class="nf">ClearColor</span><span class="p">(</span><span class="nn">Color</span><span class="p">::</span><span class="n">WHITE</span><span class="p">))</span>
        <span class="nf">.add_plugins</span><span class="p">(</span>
            <span class="n">DefaultPlugins</span><span class="nf">.set</span><span class="p">(</span><span class="n">AssetPlugin</span> <span class="p">{</span>
                <span class="n">file_path</span><span class="p">:</span> <span class="s">"src/assets"</span><span class="nf">.into</span><span class="p">(),</span>
                <span class="o">..</span><span class="nf">default</span><span class="p">()</span>
            <span class="p">}),</span>
        <span class="p">)</span>
        <span class="nf">.add_systems</span><span class="p">(</span><span class="n">Startup</span><span class="p">,</span> <span class="n">setup_camera</span><span class="p">)</span>
        <span class="nf">.add_plugins</span><span class="p">(</span><span class="n">PlayerPlugin</span><span class="p">)</span> <span class="c1">// Update this line</span>
        <span class="nf">.run</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s run it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p><img src="/assets/book_assets/chapter-final.gif" alt="Final Chapter Result" /></p>]]></content><author><name></name></author><category term="rust" /><summary type="html"><![CDATA[Learn to setup your game world, create a player character, and implement movement and animations.]]></summary></entry></feed>