<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>morgenthum.dev</title>
      <link>https://morgenthum.dev</link>
      <description>Developer blog about game development with Rust &amp; Bevy, and other projects.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://morgenthum.dev/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sat, 21 Mar 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>A weather tablet for quest planning</title>
          <pubDate>Sat, 21 Mar 2026 00:00:00 +0000</pubDate>
          <author>Mario Morgenthum</author>
          <link>https://morgenthum.dev/blog/weather-tablet-for-quest-planning/</link>
          <guid>https://morgenthum.dev/blog/weather-tablet-for-quest-planning/</guid>
          <description xml:base="https://morgenthum.dev/blog/weather-tablet-for-quest-planning/">&lt;p&gt;Wild Spikes has dynamic weather. Rain, sun, clouds - the conditions change over time and affect the world. Some quests are easier to complete under certain weather conditions. Until now, the player had no way to know what was coming. That changes with the weather tablet.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;weather_tablet.png&quot; alt=&quot;The weather tablet in Wild Spikes&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;&#x2F;h2&gt;
&lt;p&gt;The weather tablet shows a forecast of upcoming weather conditions. The player can pick it up, check what the next hours will look like, and plan accordingly. If a quest requires sneaking past NPCs and rain is coming - which reduces visibility - it might be worth waiting. If the sun is about to come out, it might be a good time to gather resources that only appear in clear weather.&lt;&#x2F;p&gt;
&lt;p&gt;It turns weather from a background system into something the player actively thinks about.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-is-built&quot;&gt;How it is built&lt;&#x2F;h2&gt;
&lt;p&gt;The tablet is split into two crates:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;weather_app&lt;&#x2F;strong&gt; is a pure UI application that renders the forecast. It takes weather data as input and displays it. It has no knowledge of the game world, no physics, no 3D. It can run as a standalone Bevy app, which makes it easy to develop and test in isolation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;weather_tablet&lt;&#x2F;strong&gt; handles the integration into the game. It renders the app onto a display in the 3D world, manages colliders for interaction, and bridges the weather simulation data into the app. Everything that ties the tablet to the game lives here.&lt;&#x2F;p&gt;
&lt;p&gt;This separation follows the same layered architecture I described in my &lt;a href=&quot;https:&#x2F;&#x2F;morgenthum.dev&#x2F;blog&#x2F;introduce-layers-into-bevy&#x2F;&quot;&gt;previous post&lt;&#x2F;a&gt;. The app sits in the engine layer - it is a generic, reusable UI component. The tablet crate sits in the game features layer, where it connects the app to the actual game systems.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>How to introduce layers into Bevy games</title>
          <pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate>
          <author>Mario Morgenthum</author>
          <link>https://morgenthum.dev/blog/introduce-layers-into-bevy/</link>
          <guid>https://morgenthum.dev/blog/introduce-layers-into-bevy/</guid>
          <description xml:base="https://morgenthum.dev/blog/introduce-layers-into-bevy/">&lt;p&gt;Wild Spikes is a survival and stealth game with quests, day&#x2F;night cycles, weather, crafting, and other systems you would expect in a full-fledged game. I&#x27;m building it with Rust and Bevy, targeting PC and mobile. As the project grows, keeping things organized becomes critical. In this post I want to share the architecture I use and the thinking behind it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Bevy makes it easy to add systems and plugins. I started the project as one crate, with a few plugins that all lived together, operating on one State, SubState, or ComputedState. It was great for getting something up and running quickly.&lt;&#x2F;p&gt;
&lt;p&gt;As the plugins grew, it became harder to keep track of dependencies. The moment I introduced the HUB - a separate screen for opening the inventory and crafting menu - the whole thing became messy. I couldn&#x27;t test it because everything was wired together, either through direct dependencies or through shared states. Sometimes my character controller blew up, sometimes the HUB exploded, and every time I had to fix something while keeping the whole picture in mind.&lt;&#x2F;p&gt;
&lt;p&gt;I didn&#x27;t just want Bevy plugins - I wanted real self-contained modules. So I split the codebase into multiple crates. Some were more game-specific, others more generic. After a few iterations, I introduced a separate &quot;engine&quot; layer to abstract some of the game-specific details and keep things inside the game cleaner and more focused. After further iterations, I arrived at the architecture I&#x27;m currently using. I&#x27;m sure it&#x27;s not perfect, but it has been working well for me so far.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-layers&quot;&gt;The layers&lt;&#x2F;h2&gt;
&lt;p&gt;The architecture is organized in layers, ordered from the bottom up. Each layer can only depend on layers below it - never upward.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;Product Apps        apps&amp;#x2F;*          Platform entries (desktop, mobile)
Sandboxes           sandboxes&amp;#x2F;*     Prototyping, experiments
Game Assembly       assembly&amp;#x2F;       Composition root, states, scheduling
App Bootstrap       bootstrap&amp;#x2F;      Bevy defaults, window, logging
Game Features       game&amp;#x2F;*          Survival, quests, NPCs, UI
Game Core           game&amp;#x2F;core       Shared contracts, events, tags
Engine Modules      engine&amp;#x2F;*        Generic tech, no game knowledge
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;what-lives-where&quot;&gt;What lives where&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Engine Modules&lt;&#x2F;strong&gt; are the foundation. These are reusable building blocks that know nothing about the actual game. Things like a camera controller, input actions, weather&#x2F;time simulation, navigation, animation utilities, and visual effects. They work with generic components and traits. You will never find a &lt;code&gt;Snail&lt;&#x2F;code&gt; or &lt;code&gt;Fox&lt;&#x2F;code&gt; type here.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Game Core&lt;&#x2F;strong&gt; defines the shared contracts. Data structures, traits, events, and marker components that game features need to communicate at the lowest level. It contains no gameplay logic itself - just the vocabulary that features use to talk to each other.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Game Features&lt;&#x2F;strong&gt; is where the actual game lives. Each feature is its own crate with its own plugin, covering a self-contained part of the game. The character crate handles the player controller, stealth, and survival mechanics. The NPC crate owns concrete creature logic like the owl&#x27;s behavior. Narrative contains the dialogue and quest systems. Gameplay covers crafting, inventory, and object spawning. Features depend on Game Core and Engine Modules, and can depend on each other when explicitly needed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;App Bootstrap&lt;&#x2F;strong&gt; handles the Bevy boilerplate: default plugins, window setup, logging, settings I&#x2F;O. Both the real game and sandboxes use this.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Game Assembly&lt;&#x2F;strong&gt; is the composition root. It wires everything together: which plugins to load, which states exist, how system sets are ordered, and which feature flags are active. No gameplay logic here - just configuration.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Sandboxes&lt;&#x2F;strong&gt; are throwaway apps for prototyping. They can reach into any layer, which makes them great for quick experiments without polluting the real codebase.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Product Apps&lt;&#x2F;strong&gt; are the final binaries. A desktop app, a mobile app. They do almost nothing - just connect Bootstrap and Assembly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-dependency-rules&quot;&gt;The dependency rules&lt;&#x2F;h2&gt;
&lt;p&gt;The core principle is simple: dependencies only point downward. But there are a few extra rules that matter:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Engine Modules must never reference concrete game types. No &lt;code&gt;Owl&lt;&#x2F;code&gt;, no &lt;code&gt;QuestLog&lt;&#x2F;code&gt;. Only generic components and traits.&lt;&#x2F;li&gt;
&lt;li&gt;Game Features must not use closed enums that grow with every new content type. Instead, I use composition via components. Each concrete type builds its own set of behavior components.&lt;&#x2F;li&gt;
&lt;li&gt;Game Core defines generic contracts. Concrete implementations belong in the features.&lt;&#x2F;li&gt;
&lt;li&gt;Product Apps contain no gameplay logic. They only wire Bootstrap and Assembly.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;These rules are not just documented - they are enforced. A custom &lt;code&gt;xtask&lt;&#x2F;code&gt; lint checks the dependency graph on every CI run.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;communication-between-features&quot;&gt;Communication between features&lt;&#x2F;h2&gt;
&lt;p&gt;Features do not reach into each other&#x27;s internals. Instead, they communicate through clear public APIs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Events and Messages&lt;&#x2F;strong&gt; are the main tool for cross-feature communication. One feature sends a &lt;code&gt;StartDialogue&lt;&#x2F;code&gt; or &lt;code&gt;GiveItem&lt;&#x2F;code&gt; event, another reacts to it. No direct coupling needed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Public components&lt;&#x2F;strong&gt; allow features to spawn entities that other features understand. A feature can attach a marker component that another feature&#x27;s systems pick up.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Direct queries into another feature&#x27;s private components are not allowed. If it is not part of the public API, it does not exist for other features.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;scheduling&quot;&gt;Scheduling&lt;&#x2F;h2&gt;
&lt;p&gt;One important aspect is the scheduling of systems. I thought my plugins were independent, but they were not. Even after splitting them into separate crates, they were still tightly coupled through states - which were not available in isolated examples or sandbox apps. So I introduced injectable schedules for each plugin. It looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;pub struct CharacterPlugin {
    spawn_schedule: Interned&amp;lt;dyn ScheduleLabel&amp;gt;,
    despawn_schedule: Interned&amp;lt;dyn ScheduleLabel&amp;gt;,
    input_schedule: Interned&amp;lt;dyn ScheduleLabel&amp;gt;,
    interact_schedule: Interned&amp;lt;dyn ScheduleLabel&amp;gt;,
    camera_collision_schedule: Interned&amp;lt;dyn ScheduleLabel&amp;gt;,
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;CharacterPlugin&lt;&#x2F;code&gt; combines all character-related systems, such as camera following, collision, hovering, ground adjustment, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;The initialization in the game assembly looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;app.add_plugins(
    CharacterPlugin::new(
        OnEnter(AppState::InGame),
        OnExit(AppState::InGame),
        Update,
        Update,
        PostUpdate,
    )
);
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But in examples or sandbox apps it can be initialized like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;app.add_plugins(
    CharacterPlugin::new(
        OnEnter(SandboxState::Running),
        OnExit(SandboxState::Running),
        Update,
        Update,
        PostUpdate,
    )
);
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That way, the functionality of my self-contained crates is truly self-contained. I can test them in isolation without having to worry about the rest of the game.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-it-matters-for-me&quot;&gt;Why it matters (for me)&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Every crate in every layer can have its own examples. This lets me test them in isolation.&lt;&#x2F;li&gt;
&lt;li&gt;Through isolated testing, I can clearly see when something feels messy or when the API is lacking. If I have to reach into another crate or add a bunch of unrelated plugins, it is a sign that the public API of that crate is not good enough.&lt;&#x2F;li&gt;
&lt;li&gt;Smaller crates mean faster incremental builds. When I change something in a game feature, only that crate and the layers above it need to recompile - not the entire project.&lt;&#x2F;li&gt;
&lt;li&gt;Clear boundaries make it easier to reason about changes. I can refactor the internals of a feature without worrying about breaking other features, as long as the public API stays the same.&lt;&#x2F;li&gt;
&lt;li&gt;The engine modules are not tied to Wild Spikes. I can reuse them in other projects or share them as standalone crates.&lt;&#x2F;li&gt;
&lt;li&gt;Sandboxes let me prototype new ideas quickly. I can try out a new mechanic or visual effect without touching the real game code - and throw it away if it doesn&#x27;t work out.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;a-note-on-perspective&quot;&gt;A note on perspective&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m not a professional game developer. My background is in software architecture, and I lean on the usual principles - SOLID, design patterns, clear boundaries - to keep things manageable. I&#x27;m not one of the experienced engine developers you meet on the Bevy Discord who live deep in ECS internals and rendering pipelines. This architecture is shaped by what I know, and it works for me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;by-the-numbers&quot;&gt;By the numbers&lt;&#x2F;h2&gt;
&lt;p&gt;As of today, Wild Spikes has 14 states, 97 plugins, and 71 system sets. I track these with &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;morgenthum&#x2F;bevy_xray&quot;&gt;bevy_xray&lt;&#x2F;a&gt;, a small tool I built to inspect the structure of a Bevy app.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Welcome to morgenthum.dev</title>
          <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate>
          <author>Mario Morgenthum</author>
          <link>https://morgenthum.dev/blog/welcome/</link>
          <guid>https://morgenthum.dev/blog/welcome/</guid>
          <description xml:base="https://morgenthum.dev/blog/welcome/">&lt;p&gt;It&#x27;s finally here - my developer blog is online!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-to-expect&quot;&gt;What to expect&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m currently working on &lt;strong&gt;Wild Spikes&lt;&#x2F;strong&gt;, a 3D game I&#x27;m building with &lt;a href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;&quot;&gt;Rust&lt;&#x2F;a&gt; and the &lt;a href=&quot;https:&#x2F;&#x2F;bevyengine.org&#x2F;&quot;&gt;Bevy Engine&lt;&#x2F;a&gt;. I constantly run into interesting challenges and pick up new insights that I&#x27;d like to share here.&lt;&#x2F;p&gt;
&lt;p&gt;The topics will mostly revolve around &lt;strong&gt;game development with Rust&lt;&#x2F;strong&gt;, but other projects and general software development may show up as well.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-tech-stack-of-this-blog&quot;&gt;The tech stack of this blog&lt;&#x2F;h2&gt;
&lt;p&gt;This blog itself is also a small Rust project - it&#x27;s generated with &lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt;, a blazingly fast static site generator written in Rust. I write posts in Markdown, and Zola takes care of the rest.&lt;&#x2F;p&gt;
&lt;p&gt;To be honest - if I had written and styled this blog by hand, it would look like something from the 90s. I used Claude to generate it. I gave it a few prompts about my preferences, technologies, and it came up with the design, layout, and even the color scheme. I just had to make a few tweaks to get it right.&lt;&#x2F;p&gt;
&lt;p&gt;See you soon!&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
