Back to Projects

Anchorfall

A spell-crafting game with base building and automation, set in a world where chaos and order have been thrown out of balance.

Unity URP C# ECS

In Anchorfall, the balance between chaos and order has shattered—water flows backwards, pigs fly, and reality bends in unexpected ways. You play as a reclusive mage who wants to fix this mess and get back to peaceful solitude. Mix and match spells to create devastating combos, build and automate your sanctum, and explore a world where the rules of nature no longer apply.

Tech Showcase

Dynamic GI

Global Illumination using DDA + a heat spread algorithm.

First, a conduction mask is generated, which contains information about obstacles and their transparency.

Then, a grid-aligned DDA raycast is performed on the texture to create the direct light information. This step also gathers shadow information.

Lastly, a heat spread algorithm with a 3x3 neighborhood kernel is used to spread the direct light information and create our indirect light texture.

Blur is then applied, and the GI information gets merged with Unity's light information to create the end result.

Emissive textures follow a different path, with their light information being injected directly in between the DDA and heat spread. Since the heat-spread algorithm scales with texture size and iterations, we can add massive amounts of indirect light contributors without paying a hefty performance fee.

The system has controls for GI contribution, GI resolution, various heat spread tweaks to make light go further or contribute more and all shadows (including Unity lights) are quantized to a pixel grid.

Tileset System

It's subtle, but this system is pretty complex and involved. The game implements a variation of the BlobTileset. A 17-tile tileset is drawn containing the basic corners, inner corners, borders, and full tiles. Then, a tile generator slices these into quadrants and isolates which features each tile has. Then, we generate the full set of 47 tiles from these features and map them to the 256 possible combinations. The system also supports tile variations, so if we draw 3 border_left tiles in our initial tileset, we can use those 3 distinct features to generate more variations of the tiles that use them:

The game is actually 3D, so after performing tile selection, we dynamically generate a mesh and add the individual faces to it, performing face culling based on neighbor data. All of this is rendered directly in ECS with BRG.

The tile selection is deterministic and seeded, and the whole process is Burst-compiled.

Sprite to Mesh

To leverage ECS rendering in the game, we built a pipeline that automatically converts sprites into meshes. This has the added benefit of being able to run shaders on the sprites during the opaque phase, which simplifies lighting a fair bit.

The pipeline supports static sprites, sprite sheets, and animated sprites.

Baking systems handle registration of textures and meshes, while an accompanying runtime system handles animations.

Spell System

A robust spell system was implemented to allow for very flexible spell building. A set of behaviour modifiers is available (e.g. Bounce, Sticky, Split, Pierce, etc) that can be applied to spells. These modifiers interact with one another through a conflict resolution system. Many can co-exist, but for the ones that cannot (in the example, bounce and sticky), we have a comprehensive conflict resolution system that is able to control order of execution or suppress behaviours. This system is not complete, but was built in an extensible way to allow for change.

This system is fully Burst-compiled and uses a "pre-compilation" approach, where we calculate projectile interactions in advance and store each possible "action" the projectile may trigger in BlobAssets: a global (orderless) and a sequential one. Each projectile keeps an index (head) of where in the buffer it currently is when an event happens and increments this counter during its lifetime.

Doing it this way allows us to map complex interactions, like projectiles first bouncing and then splitting. It keeps the projectile entities lean and is deterministic by nature.

Map Generation

Forgive the spaghetti. Map generation is built on top of Map Graph and allows us to define rules and morph terrain using graph nodes. It supports various cellular automata functions, noise types, BSP trees, and more.

ECS Everywhere

The entire game is built on top of ECS. We try very hard to not use GameObjects at all, including bridge GOs from Entities.Graphics.

For VFX Graph, we use a single instance of each effect as a GameObject, and use a GraphicsBuffer to send information to it directly from ECS, with transform sync capabilities. This is surprisingly performant, since the VFX graph handles multiple entities.

I'm kinda obsessed with Burst, and I think there are more NativeCollections in this project than Lists or raw arrays. Everywhere I can Burst compile, I go out of my way to do it.