Stalberg Adapts Wave Function Collapse to Assemble 3D Bad North Levels

EPC2018 | Wave Function Collapse in Bad North | Oskar Stalberg — BUas Games

BUas Games

Eliminating tile candidates through local boundary constraints builds coherent 3D dioramas from small modular sets, provided pathfinding heuristics guide placement and generation contradictions trigger immediate restarts rather than backtracking.

AI-assisted summary of EPC2018 | Wave Function Collapse in Bad North | Oskar Stalberg — original source. Check the original for full context.

Source published:

Key ideas

  • Oskar Stålberg adapts Maxim Gumin's 2D Wave Function Collapse algorithm into 3D, treating level generation as an act of candidate elimination over grid slots rather than constructive placement. (1:33)
  • Bad North scales from an initial prototype of 16 modules to roughly 300 modular pieces, initializing around 100,000 candidate module states per 3D island generation pass. (14:50)
  • When contradictions or gameplay validation failures occur, the engine avoids step-by-step backtracking and instead snapshots the post-boundary state to execute rapid full re-collapses. (17:53)
  • A custom Unity asset pipeline parses raw geometry from a single imported FBX, automatically grouping triangles that cross tile boundaries into multi-tile modules to eliminate visible grid seams. (24:36)

Detailed notes

## Background This talk was published on YouTube by BUas Games on July 11, 2018, recorded at the Everything Procedural Conference (EPC2018) hosted by Breda University of Applied Sciences. In the presentation, developer Oskar Stålberg explains how he adapted the Wave Function Collapse (WFC) algorithm to procedurally assemble stylized 3D island dioramas for his strategy game Bad North, detailing the underlying theory, custom Unity pipelines, and artistic philosophies behind his workflow.

## TL;DR Oskar Stålberg outlines his journey from AAA technical art to indie game development, detailing how he translated Maxim Gumin's 2D Wave Function Collapse algorithm into a 3D level-generation engine for Bad North. He demonstrates how WFC operates as an elimination process over possibility space, resolving strict adjacency constraints extracted from modular geometry rather than following traditional combinatorial placement. To make generated spaces playable, he embeds navigability heuristics directly into tiles, manages multi-tile groupings and deliberate asymmetry, and handles contradictions through efficient full-state reloads. Finally, he explains how minimalist, high-contrast visual styling turns the procedural dioramas into clean, readable tactical canvases while combating procedural sameness through themed sets and multi-stage navmesh validation.

## Atmosphere & style The presentation feels intimate, candid, and refreshingly craft-focused, delivered with Stålberg's modest, dry humor and pragmatic engineering mindset. Working through live visual debuggers and wireframe collapse passes—some built just the day prior—he openly shares both elegant mathematical breakthroughs and scrappy real-world compromises, such as abandoning backtracking in favor of full retries. The room carries the attentive, appreciative quiet of a technical conference audience, punctuated by chuckles at his understated self-deprecation and honest reflections on art direction.

## Bulletin - [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Oskar Stålberg adapts Maxim Gumin's 2D Wave Function Collapse algorithm into 3D, treating level generation as an act of candidate elimination over grid slots rather than constructive placement. - [14:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=890s) Bad North scales from an initial prototype of 16 modules to roughly 300 modular pieces, initializing around 100,000 candidate module states per 3D island generation pass. - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) When contradictions or gameplay validation failures occur, the engine avoids step-by-step backtracking and instead snapshots the post-boundary state to execute rapid full re-collapses. - [24:36](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1476s) A custom Unity asset pipeline parses raw geometry from a single imported FBX, automatically grouping triangles that cross tile boundaries into multi-tile modules to eliminate visible grid seams.

## Key takeaways - [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Wave Function Collapse operates as a content-agnostic solver that deduces spatial arrangements purely from boundary socket constraints rather than hardcoded architectural logic. - [05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) Slot terminology distinguishes the coordinate spaces on the grid from the modules placed within them, resolving an ambiguous use of the word 'tile'. - [08:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=530s) Placement in WFC functions through candidate subtraction; removing invalid modules cascades recursively across neighboring cells via constraint propagation. - [10:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=637s) Adjacency rules can be extracted programmatically by sampling and hashing border color points along tile edges instead of hand-authoring connection tables. - [14:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=890s) Island generation in Bad North starts by fixing external boundary conditions—sky on the perimeter and water at the base—which instantly prunes a vast portion of the initial possibility space. - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) WFC lacks inherent awareness of high-level gameplay concepts like path traversability, requiring metadata-driven heuristics to bias module selection along walkable graphs. - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) Exponential compute scaling with larger grid volumes drove Bad North's core aesthetic and design choice toward compact, isolated diorama islands. - [23:05](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1385s) Bottom-up generation lets developers evaluate foundational silhouettes and slope profiles with a minimal tile set before introducing specialized edge cases. - [24:36](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1476s) Unity import tooling parses combined FBX geometry into multi-tile modules without slicing triangles, preventing repetitive grid seams and enabling smooth vertical stairs. - [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) Deliberate asymmetry and transitional adapter details break modular repetition and allow decorative seams to resolve geometric width discrepancies. - [30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) Restrained environmental detailing acts as a functional canvas for tactical clarity and permanent battle stains, reserving visual density for structural gameplay transitions. - [30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) WFC is grid-agnostic and can be applied to distorted cubic lattices, spherical triangles, or arbitrary topological graphs to produce organic architectural layouts. - [33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) Generation applies an explicit anti-symmetry constraint to prevent generic modules from pairing with identical or mirrored duplicates along matching sockets. - [36:14](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2174s) Validation operates across multiple tiers: mid-solve bailouts trigger if paths seal before reaching sea level, followed by post-collapse navmesh checks for Viking landing spots.

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=0s) Introduction, Background, and the Power of Playable Demos

Oskar Stalberg introduces himself to the audience at Breda University of Applied Sciences, describing his focus: “My name is Oskar Stalberg. I make procedural planets and pretty things.” He originally comes from an art background, having studied at The Game Assembly in Malmö. Over time, he found himself drawn deeper into technical disciplines, shaders, and procedural systems. He spent several years in AAA at Massive Entertainment working on *Tom Clancy's The Division*, where he created the game's iconic holographic map. Beside his studio work, he developed a habit of releasing experimental side projects as interactive web browser demos rather than simple screenshots or gameplay videos. Two prominent examples include his procedural city tool *Brick Block* and an interactive miniature planet generator.

Stalberg offers this approach as key advice to students in the audience: “That's a very good way to do projects as student too, by the way, just like make things that you can publish, that you don't just publish screenshots or videos, that publish like interactive things, because they're so much more likely to be shared and it's so much more impressive, so do that.” After working at Massive and spending time at Ubisoft Montreal, he transitioned to independent development to work on his own game, *Bad North*, a stylish procedural strategy game about defending islands against invading Vikings.

- Studied game art at The Game Assembly (Malmö, Sweden). - AAA experience: Massive Entertainment (*The Division* holographic map) and Ubisoft Montreal. - Published interactive web toys: *Brick Block* and procedural planet generators. - Current project: *Bad North*, an island-defense strategy game powered by procedural generation.

---

[01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Discovering Wave Function Collapse and Early 3D Experiments

The core level generation in *Bad North* relies on an algorithm known as Wave Function Collapse (WFC). Stalberg clarifies that he did not invent the name and jokingly warns the audience: “I didn't name it Wave Function Collapse, so you know, don't ask me about the connection to quantum mechanics.” The algorithm was originally developed by Maxim Gumin, who designed it to take a tiny example bitmap, analyze which pixels or patterns are permitted to sit adjacent to one another, and synthesize a much larger bitmap that strictly respects those derived adjacency rules.

Upon seeing Gumin's 2D texture synthesis results, Stalberg immediately realized the concept could be generalized into three dimensions to solve procedural level architecture: “Obviously my first idea when I saw this one was like, I bet you can use this for 3D. I bet you can use this for other things than just textures to build little larger works.” The primary appeal was that the algorithm is inherently content-agnostic; it does not need to understand architectural logic, architecture styles, or geography. Instead, an artist creates a modular tile set, and the algorithm automatically deduces how pieces fit together based purely on connection constraints.

Stalberg’s earliest prototype implemented WFC over a spherical triangular tile mesh. While reminiscent of his earlier planet project, this new setup allowed the system to solve complex continuous networks, such as roads and villages, that were previously impossible without hardcoded rules. Another experiment involved generating organic cliffside platforms using just a handful of handcrafted modules. Because the system is agnostic, an artist can introduce a chimney, an overhang, or a vertical divider tile into the palette, and the solver weaves them into coherent structures whenever the boundaries match.

Stalberg also notes an aesthetic design lesson learned from an abandoned side project involving a jumping cat: vibrant, busy environments make games visually unreadable. If the procedural background is heavily saturated with color, the gameplay units and UI elements struggle to stand out, forcing him toward the cleaner, restrained art direction that defines *Bad North*.

- Concept origin: Maxim Gumin's 2D Wave Function Collapse algorithm for pattern-based bitmap synthesis. - Core philosophy: Content-agnostic constraint solving—the algorithm does not know what an object is, only how its boundaries connect. - Early prototypes: Triangular spherical tiling for roads/villages and small modular cliff dioramas. - Design takeaway: High color saturation in procedural geometry interferes with gameplay clarity and UI hierarchy.

---

[05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) The Mechanics of WFC: Slots, Modules, and Possibility Space

To clarify the inner workings of WFC, Stalberg walks through a custom 2D visual demonstration built for the talk. He highlights a fundamental ambiguity in procedural level design: the word "tile" can mean either the coordinate space where an object sits or the mesh piece placed inside it. To eliminate confusion, he establishes strict terminology:

> “I split up the terminology into modules and slots, so like each slot picks a module, that's the terminology.”

At the start of generation, the system initializes a grid of empty slots. Each slot begins in a state of maximum entropy, containing a possibility space where every single module in the tileset is considered a valid candidate. Stalberg demonstrates how boundary constraints dictate the outcome by feeding the solver an extremely restricted two-module set: pure sky and pure dirt, with no transition piece connecting them. Because the set lacks a transition tile that has sky on one side and dirt on the other, selecting dirt in a single slot forces every other slot to collapse into dirt, while selecting sky forces the entire board to turn into sky.

When a connector tile (dirt on the bottom, sky on the top) is introduced into the set, the solver gains flexibility. Placing a sky module forces the slot directly below it to be compatible (either another sky module or the transition module), but forbids solid dirt from touching sky directly. If an invalid module configuration is given—such as providing only corner modules without any straight edges or interior fillers—the solver immediately errors out because a continuous, self-consistent loop cannot be resolved.

```mermaid flowchart TD accTitle: Wave Function Collapse Slot Resolution accDescr: Diagram illustrating how slot states transition from an initial superstate of all modules to a single collapsed module.

subgraph PossibilitySpace["1. Initial Possibility Space"] S1["Slot [x, y]<br/>Contains all modules:<br/>{Sky, Dirt, Transition, Corner}"] end

subgraph Selection["2. Observation / Selection"] S2["Slot [x, y]<br/>Selects module: [Transition]<br/>(Removes all other candidates)"] end

subgraph ConstraintPropagation["3. Neighbor Constraints"] Top["Slot [x, y+1]<br/>Above Transition:<br/>Must match Sky border"] Bottom["Slot [x, y-1]<br/>Below Transition:<br/>Must match Dirt border"] end

PossibilitySpace --> Selection Selection --> ConstraintPropagation ```

---

[08:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=530s) Collapse as Elimination and Constraint Propagation

Stalberg emphasizes that collapse is fundamentally an act of subtraction rather than placement: “The core idea is that, yeah, you have this huge possibility space where everything is possible everywhere, and then as soon as you place something, what you're doing is not really placing something, but what you're doing is you're removing all other possibilities from that tile until there's only one remaining.”

When an alternative is removed from a slot, it triggers a chain reaction across neighboring slots:

1. A module is removed from slot $A$. 2. The solver checks whether that removed module was the last remaining candidate in slot $A$ that offered a particular boundary interface (for example, a downward-facing sky edge). 3. If no remaining modules in slot $A$ provide that interface, the adjacent slot directly below ($B$) cannot legally present an upward-facing sky edge. 4. Slot $B$ must immediately prune every module in its own local possibility set that required an upward-facing sky edge. 5. Pruning those modules from slot $B$ may eliminate the last instance of another boundary type facing its own neighbors, continuing the cycle recursively.

In a tileset without overhangs, placing a dirt block forces every slot directly beneath it to resolve to dirt as well, organically generating pillars down to the bottom of the map. Stalberg notes that when coding WFC for the first time, a brute-force approach is acceptable: after every modification, scan every single slot in the grid to verify whether its remaining modules still have valid neighbors. However, a production-ready solver optimizes this process into an event-driven propagation model. Each slot tracks the counts of its available socket types; as soon as a candidate module is deleted, it checks if it was the last of its type, and only notifies its immediate neighbors when an edge possibility has genuinely dropped to zero.

- "Placing" a tile actually means eliminating all competing candidates until only one choice remains. - Incompatible candidate elimination cascades outward recursively across adjacent slots. - Basic implementation strategy: Check full grid consistency after every collapse. - Optimized implementation strategy: Event-driven notification where slots only alert neighbors when an edge connection type is completely exhausted.

[10:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=637s) Deriving Constraints from Texture Sampling and Constraint-Based Programming

Oskar Stalberg demonstrates how adjacency constraints can be extracted automatically from 2D pixel art tiles rather than being manually authored. For a simple 2D tile image, the system inspects the edges to determine which modules can legally sit next to one another. Stalberg explains that he derives these rules by sampling colors at three distinct points along each border: top, bottom, and sides. These three sampled colors are concatenated and hashed. Any tile sharing identical color values at those sample locations receives the matching hash, establishing an immediate index for valid neighbors. On screen, the debug numbers displayed correspond directly to the hex values of the sampled pixels, which Stalberg notes can be helpful for debugging when two modules unexpectedly refuse to connect because of a slight accidental color mismatch. He adds that one could simply round off color values before hashing to avoid being overly tedious.

```mermaid flowchart LR accTitle: Constraint Extraction Pipeline accDescr: Pipeline showing how raw pixel tiles are sampled at edge points, hashed, and matched into adjacency constraints. subgraph Sampling["Edge Sampling"] A["2D Tile Edge"] --> B["Sample 3 Points"] end subgraph Hashing["Hashing & Rules"] B --> C["Hash Color Values"] C --> D["Adjacency Constraint Rules"] end ```

Stalberg connects this mechanic to the broader discipline of constraint-based programming, which he admits he had not encountered formally when he first started the project. In this paradigm, rather than scripting step-by-step procedural placement rules, the developer simply declares what states are permissible alongside one another. The engine's job is to discover an arrangement that satisfies all conditions. Stalberg emphasizes the philosophical shift of this approach: “You're starting with just declaring what are the constraints here like what things are allowed together with which things in this case of course is strictly adjacency constraints so it's just what things are allowed to what things and then like in all constraint based programming you just like ask how are we going to give you a solution that satisfies those constraints.” Hard constraints enforce strict rules, while heuristics—such as probability weights or Perlin noise randomness—guide selection whenever multiple valid possibilities remain.

- Color sampling takes three sample points per tile edge to form hash keys. - Debug views expose edge hex colors to quickly spot minute authoring mismatches. - Hard constraints define strict legal neighbors; heuristics guide choices among multiple valid candidates.

---

[13:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=817s) A Minimal Wave Function Collapse Schematic

To make the mechanics of propagation concrete, Stalberg presents a schematic diagram illustrating the simplest possible system. It features only two adjacent slots and two boundary types: `0` representing grass and `1` representing road. Each slot contains four candidate modules that rely on the neighboring slot's available options to stay valid.

```mermaid flowchart TD accTitle: Constraint Propagation Dependency accDescr: Diagram illustrating how removing an option in Slot A forces dependent candidate modules in Slot B to collapse and be eliminated. subgraph SlotA["Slot A"] A1["Candidate Module"] end subgraph SlotB["Slot B"] B1["Dependent Module 1"] B2["Dependent Module 2"] end A1 -.->|"relies on"| B1 A1 -.->|"relies on"| B2 ```

Stalberg walks through what happens when an option is pruned: if a road tile in the first slot is eliminated, the connection feeding into dependent pieces in the second slot is broken. Deprived of a matching neighbor, those dependent modules in the adjacent slot must also be removed. The system resolves these implications transitively across the entire grid. Stalberg summarizes this core lifecycle as establishing a “big possibility space and then collapse down and resolving implications of your constraints.”

- Minimal demonstration uses two slots and two edge types: `0` (grass) and `1` (road). - Four potential modules populate each slot initially. - Removing a single candidate severs dependencies and cascades into removals in adjacent cells.

---

[14:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=890s) Visualizing 3D Island Collapse in *Bad North*

Transitioning to *Bad North*, Stalberg pulls up the game's campaign map and activates a live debug visualization to show 3D level generation in real time. He notes that the generation is running far slower than normal because drawing the wireframes of all candidates within each slot introduces heavy rendering overhead. Across the grid, a single tile can harbor hundreds of module variants once rotations and mirrors are factored in, yielding roughly 100,000 total possibilities initially.

```mermaid flowchart TD accTitle: Level Generation Lifecycle accDescr: Step-by-step generation flow from initializing boundary constraints to final tile collapse in Bad North. subgraph Setup["Initialization"] I1["Initialize 3D Grid"] --> I2["Apply Boundaries: Sky Top/Sides & Water Base"] I2 --> I3["Propagate Boundary Constraints"] end subgraph Collapse["Interactive Collapse"] I3 --> C1["Place Key Features: House Foundations"] C1 --> C2["Wave Propagation Prunes Invalid Candidates"] C2 --> C3["Cascade Accelerates to Final Mesh"] end ```

Unlike the unconstrained 2D sandbox, an island level begins with mandatory external boundary conditions: the space outside must be sky, the bottom slice must be water, and the core cannot be pure solid rock throughout. Propagating these initial restrictions instantly prunes a vast swath of candidates, causing ghostly wireframe silhouettes of the island to emerge before any single module is definitively locked in. The first actual concrete placement is typically a foundation hole reserved for a house (the houses themselves are dynamic props placed later). As the house foundation settles, surrounding options prune immediately. Stalberg observes that while the system starts slowly while resolving initial implications across thousands of possibilities, it accelerates rapidly toward the end because the vast majority of conflicts have already been eliminated.

- Wireframe visualization exposes approximately 100,000 candidate module states during initialization. - Boundary rules mandate open sky along external boundaries and sea level at the base. - Island houses are placed as empty footprint sockets; actual building meshes are dynamically added post-generation.

---

[17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) Scaling Limits, Navigability Heuristics, and Retry Logic

As Stalberg attempts to generate larger islands, the computational cost escalates noticeably. Stalberg points out: “Of course that's with a lot of procedural things the generation time increases exponentially with size so the bigger the map the slower it is though we always resolve that by playing on small islands.” Beyond pure generation speed, he tackles a fundamental limitation of Wave Function Collapse: the algorithm possesses no inherent understanding of high-level gameplay concepts such as whether an island is traversable. While one can validate navigability post-generation by running pathfinding checks from the beach up to the houses, doing so naively can result in generating many unusable maps that must simply be discarded.

```mermaid flowchart TD accTitle: Island Generation with Navigability and Retries accDescr: Flowchart illustrating the navigability heuristics and contradiction reload loop in Bad North. subgraph Generation["Step 1: Expand Navigability"] G1["Seed Navigable Root at House"] --> G2["Heuristic Prefers Tiles Expanding Navigable Paths"] G2 --> G3["Propagate Connectivity Outwards via Green Links"] end subgraph Validation["Step 2: Check & Retry"] G3 --> V1{"Valid Level? (e.g. Sufficient Beach)"} V1 -->|"Contradiction or Validation Fail"| V2["Discard & Reload Initial Resolved State"] V2 --> G1 V1 -->|"Pass"| V3["Final Island Diorama"] end ```

To solve this, Stalberg embeds navigability data directly into each module. Modules contain simplified connectivity data—visualized in the debug tool as green lines—that indicate whether units can walk across them to adjacent cells. When the generator has free choices rather than forced deductions, it uses a navigability heuristic: it anchors generation at a house known to be walkable, and then biases module selection outward along those green navigation lines to ensure continuous paths down to the beaches.

When the generator hits an impossible contradiction or fails gameplay checks (such as not generating enough beach landing area for Viking longships), it must recover. Stalberg explores the dilemma of backtracking: > “That's also an interesting question of like backtracking in generation in general like would you when you make an error when you run into a contradiction do you go back and then try something else or do you... I tried that a bunch of times but the thing is that you don't know how far back you have to go you don't know if you like maybe the error was implied in the first tiles you placed so in my case I solved it by just I save the initial state when it's resolved all the initial contradictions and then we just if something fails if it runs into a contradiction it just reloads that and collapses again this way faster than symmetric.”

Stalberg concludes by noting that he had only built this wireframe collapse visualization the previous day. He points out how the system naturally constrains edges early: outer ocean corners exhibit very low entropy from the start, as their possibilities are immediately restricted to water, submerged reefs, or corner beach pieces.

- Procedural generation time scales exponentially with grid volume, motivating *Bad North*'s design choice of compact diorama islands. - Modules carry simplified topological metadata (displayed as green lines) to ensure pathfinding continuity. - Backtracking step-by-step proved ineffective; the engine instead snapshots the post-boundary state and restarts the collapse entirely whenever a contradiction occurs.

[22:20](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1340s) Navigability Heuristics and Expanding the Walkable Space

Observing a taller procedural generation pass, Oskar demonstrates how the algorithm resolves island geometry through initial contradictions and connectivity rules. A visible staircase emerges, showing the underlying navigable network snaking up across elevation changes and looping back down. To keep the play space coherent, the generation logic tracks bounding boxes that encapsulate the navigable graph.

```mermaid flowchart TD accTitle: Navigability Heuristic Tile Evaluation accDescr: Flowchart illustrating how candidate tiles are evaluated and scored based on their expansion of navigable space. subgraph TileEvaluation["Candidate Tile Scoring"] A["Candidate Tile Proposed"] --> B{"Does tile connect to network?"} B -->|"Yes"| C{"Expands navigable bounding box?"} B -->|"No"| D["Standard / Low Score"] C -->|"Yes"| E["High Heuristic Score"] C -->|"No"| D end ```

The algorithm actively prioritizes island connectivity by scoring potential tile placements against their contribution to the player's movement network. When a candidate tile would increase the size of an existing navigable bounding box, the system awards it a high score, steering the collapse toward broad, walkable multi-tiered dioramas rather than pinched, dead-end landmasses.

[23:05](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1385s) Bottom-Up Tilesets and Component-Driven Generation

Switching to an unshaded wireframe view, Oskar reveals the complete collection of modular building blocks used for *Bad North*. While the project eventually grew to include around 300 individual tiles, it started with a tiny subset of foundational pieces. Traditional combinatorial generation usually forces artists to hand-craft every conceivable permutation up front to satisfy high-level abstractions like voxels. By contrast, Wave Function Collapse works inversely from the ground up:

> “Because this algorithm, as I said, since it solves the constraints, because with a lot of combinatorial things you need to create a lot of different permutations, which is a pain, but since this one is driven from the components instead of the content being placed to sort of satisfy the higher-level abstraction... you can get it going with very, very few things.”

By starting with a minimalist core, Oskar could immediately evaluate key aesthetic baselines: - Defining the precise profile and silhouette of ground edges. - Determining the slope angles and corner treatments. - Dialing in the global target density for geometric detail.

Once those basics function, adding content becomes trivial. Highly specific elements like staircases inevitably demand numerous permutations to handle diverse elevations and alignments, whereas specialized environmental accents—such as isolated sea rocks that only border water—require only a single, specialized one-off tile.

[24:36](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1476s) In-Editor Adjacency Visualizers and Mesh Slicing

To verify edge compatibility across the expanding library without wrestling with strict organizational hierarchies in Maya, Oskar created custom tooling in Unity. Selecting an edge of a tile automatically highlights all compatible mating surfaces across the entire library. This allows instant visual confirmation that newly authored assets will mesh seamlessly with existing components.

```mermaid flowchart LR accTitle: Tile Asset Import and Slicing Pipeline accDescr: Flowchart showing the pipeline from full FBX import in Unity to analyzed, multi-tile modules. subgraph UnityImport["Unity FBX Processing"] direction TB M["Single Merged FBX Mesh"] --> P["Parse Polygons into Tile Grid"] P --> S{"Triangle spans across tile boundary?"} S -->|"Yes"| B["Merge into Multi-Tile Module"] S -->|"No"| A["Keep as Single-Tile Element"] B --> E["Analyze Edges & Adjacency Sockets"] A --> E end ```

Rather than meticulously cutting, positioning, and exporting individual tile meshes in 3D modeling software, Oskar imports everything as one combined FBX. The Unity import pipeline parses the raw geometry, sorts the triangles into distinct grid cells, and evaluates which parts belong together:

- The importer inspects the mesh without slicing triangles: if a triangle spans across adjacent tile coordinates, the pipeline groups those units into a single multi-tile piece. - Multi-tile blocks break up obvious grid repetition, allowing flowing terrain, sweeping corners, and connected staircases that climb smoothly between vertical tiers. - Subtle vertex displacements—such as nudging an internal vertex off-center—trick the eye into reading multi-part assemblies as a continuous, organic surface rather than segmented blocks. - Baked green line markers define embedded paths, feeding directly into the navigability heuristic during island generation.

[27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) Purposeful Asymmetry and the Philosophy of Detail

Oskar details how asymmetric modular assets reduce visual monotony. Modeling a corner column with one side subtly narrower than the other creates four distinct visual permutations simply by rotating and mirroring the piece across an elevated pillar.

This asymmetry also enables specialized transitional "adapters." Where mismatched edges meet, Oskar inserts a small crack or seam. The detail is decorative, but it serves a direct architectural function: it flips the mirror orientation from thick-to-thin, resolving the geometric discrepancy.

> “And that, I think that's a nice way of thinking about detail in general: like making detail that's meaningful, and that is changing things.”

Examining a fully generated island, Oskar argues that blank surfaces do not need heavy textural noise. An obvious tiling grass texture provides no real gameplay value. Instead, visual storytelling lives at structural boundaries:

- Clear grass boundaries immediately communicate flat, playable tactical plateaus. - Transition edges define the drop-off from grass to sheer cliff faces. - Procedurally scattered convex edge tufts break up hard mathematical boundaries. - Adapter seams and deliberate cracks transform simple alignments into lived-in, believable stone architecture.

> “You don't need detail everywhere; you need detail to tell small stories, and usually making transitions between things—that's what detail should do, typically.”

[30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) Environment as a Canvas and Q&A on Alternative Grids

The diorama's minimalist styling directly serves gameplay clarity. The environment acts as an uncluttered stage for tactical combat, leaving the focal weight on unit movement. Units are commanded as squads rather than individuals, so individual soldiers sport simple geometric silhouettes that emphasize collective group cohesion. The island dioramas function as literal canvases: during battles, blood from clashes permanently stains the white cliffs and green grass. Oskar briefly jokes about the game's physics not being fully frame-rate independent due to earlier technical oversights before concluding his presentation to applause.

Opening the floor to questions, an attendee asks about applying Wave Function Collapse to alternative grids. Oskar notes that while he has experimented with spherical triangular grids, his primary ambition is exploring distorted cubic modules: - Cubes can be warped to meet at irregular vertex junctions, creating intersections where five or three cubes converge at a single vertex. - Distorting the interior geometry of those irregular cells would naturally produce curving, organic layouts ideal for medieval streets and alleyways. - Triangular and hexagonal lattices naturally favor sprawling planetary and continental silhouettes, whereas cubic grids provide the structured, orthogonal support needed for architectural interiors and small buildings. - The algorithm remains grid-agnostic: developers can generate across arbitrary topological graphs, mixing 2D planes, triangles, and 3D cubes within the same framework.

A following question focuses on the demo's interactive placement mode, prompting Oskar to outline the concept of "mixed-initiative" procedural generation, where user input establishes high-level intent while the system algorithmically handles constraint propagation.

[33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) Module Selection Rules, Seeding, and Mirror Constraints

Addressing an audience question about whether content is placed manually or if the algorithm operates with full initiative, Oskar Stålberg explains that the game itself makes all module selections procedurally rather than through manual authoring. While a simple demo might feature only sixteen possible module options, *Bad North* contains roughly 300 modules, making an interactive manual picker impractical to present without dedicated tools: “I'm sure there's a really good proper interactive tool in this but I haven't built it so no the game the game just like picks them according to different rules.”

The generation sequence begins by placing essential gameplay elements, starting with houses because the core defense mechanics fundamentally rely on their presence. From there, selection is driven by metadata, thematic groupings, and bounding boxes that enforce specific placement restrictions. One critical constraint prevents certain tiles from appearing adjacent to copies of themselves or direct mirrored duplicates. Because identical or mirrored sockets fit against each other with mathematical perfection, the algorithm naturally tends to pair them up, producing jarring, unnatural symmetry across generic pieces: “Quite an important rule is for modules I don't want to be next to a mirror of themselves because that usually looks quite bad when you have generic pieces next to each other and how comes quite often because of course a piece fits perfectly next to the mirror of itself.”

- **Module Count:** Scales from roughly 16 tiles in simple toy demonstrations up to around 300 modular pieces in the full game. - **Initial Anchor:** Generation starts by placing houses first, ensuring vital strategic targets exist before surrounding terrain settles. - **Anti-Symmetry Safeguard:** Explicit rules block tiles from neighboring identical or mirrored instances of themselves to avoid repetitive visual artifacts.

---

[34:39](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2079s) Thematic Variety and Combating Procedural Sameness

To ensure that generated levels feel distinct across an entire campaign, Stålberg organizes modules into thematic sets with customized rules. Navigating his project—noting wryly that he is presenting from an unfamiliar workstation—he demonstrates several tile categories, including dark cave modules reserved exclusively for specific islands, ruined structures, and rugged cliff sections with massive boulders. In one active level preview demonstrating the rock theme, the system resolves a dramatic, cliffside overhang acting as a natural bridge across the terrain.

These thematic groupings directly address one of procedural generation's most persistent pitfalls: aesthetic monotony. Swapping out geometry sets, adjusting color palettes, and modifying surface foliage allow the engine to produce dramatically different moods while relying on the same underlying solver logic. Answering an aside about mesh complexity, Stålberg remarks that because everything in the game is stylized low-poly, he models the assets himself with just a handful of triangles, keeping geometric overhead minimal.

> “That's also always a challenge with procedural generation that think things can look to seemy and this is one of the ways I approach that.” — Oskar Stålberg

---

[36:14](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2174s) Validation Stages, Navmesh Checks, and Failure Recovery

An audience member asks about handling failed generation runs—specifically referencing cases where the algorithm must restart because it failed to generate enough valid landing beaches—and whether the engine shifts tile weights between attempts or merely rerolls the seed. Stålberg clarifies that the solver operates under two distinct layers: hard adjacency constraints that must always be satisfied, and soft probability weights that bias how frequently certain tiles appear within specific spatial volumes. When a failure occurs, the engine does not necessarily need to reweight the tiles; rather, the process is governed by a series of mid-generation and post-generation validation checks.

```mermaid flowchart TD accTitle: Island Generation and Validation Pipeline accDescr: Flowchart illustrating the step-by-step validation of island generation from initial constraints to navmesh beach verification.

subgraph Generation ["Wave Function Collapse"] A["Seed Anchors & Themes\n(Houses, Spatial Weights)"] --> B["Propagate Tile Adjacency\n(Hard Constraints)"] B --> C{"Check In-Progress\nNavigable Island"} end

subgraph Validation ["Island Validation Checks"] C -->|"Closed before reaching bottom"| D["Discard & Restart Generation"] C -->|"Pathways Open"| E["Construct Navmesh"] E --> F{"Sufficient Landing Beaches\nfor Viking Boats?"} F -->|"Too Few Beaches"| D F -->|"Requirements Met"| G["Final Island Diarama Accepted"] end ```

The beach requirement is verified after the primary Wave Function Collapse pass completes. Once the geometric island is fully resolved, the engine generates the navigation mesh and measures whether there are sufficient shoreline entry points where incoming Viking longships can land. If the beach count is insufficient, the level is discarded and restarted. However, Stålberg also implements early-exit validations during the wave propagation itself: if the system detects that the navigable island graph has closed off prematurely before reaching the bottom of the grid, it abandons the attempt immediately, saving valuable compute time by avoiding downstream navigation mesh calculations.

- **Constraint Hierarchy:** Strict adjacency rules act as absolute, inviolable constraints, while regional probabilities nudge aesthetic variety without breaking connectivity. - **Mid-Generation Bailout:** If the traversable network closes off before expanding to the base level, the generator aborts mid-solve. - **Post-Generation Navmesh Check:** Viking landing beaches are validated on the generated navmesh; failing to meet landing quota triggers an automatic restart.

## Notable quotes > [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) You don't need detail everywhere; you need detail to tell small stories, and usually making transitions between things—that's what detail should do, typically. — Oskar Stalberg

## Mentioned Oskar Stalberg, Bad North, Wave Function Collapse, Maxim Gumin, Massive Entertainment, Ubisoft Montreal, The Game Assembly, Tom Clancy's The Division, Brick Block, Breda University of Applied Sciences, Unity, Maya

## Video description Bad North is a procedurally generated strategy game about fighting Vikings. Oskar Stalberg will talk about how the Wave Function Collapse algorithm is employed to magically assemble idyllic island dioramas from handcrafted tilesets. You'll learn about the theory behind WFC, its implementation and further development in Bad North, as well as the tools and pipeline surrounding it. Lastly, Oskar will cover how he approaches the integration of art and tech aesthetically.

Everything Procedural is a yearly conference organised by the Games programmes of Breda University of Applied Sciences (BUas). The conference brings together experts and developers in the field of procedural generation and focuses on procedural game content and its application in AAA or innovative games.

https://www.everythingprocedural.com/

https://www.buas.nl/en/programmes/creative-media-and-game-technologies https://www.buas.nl/en/programmes/master-game-technology

[Source](https://www.youtube.com/watch?v=0bcZb-SsnrA)

中文简报

Stålberg改造波函数坍缩算法以构建《Bad North》3D关卡 通过局部边界约束消除候选图块,能够利用小型模块化组件构建连贯的3D微缩场景,前提是用寻路启发式算法引导放置,并在生成冲突时立即重启而非回溯。 Oskar Stålberg概述了他从3A技术美术转向独立游戏开发的历程,详细介绍了他是如何将Maxim Gumin的2D波函数坍缩(WFC)算法转化为《Bad North》的3D关卡生成引擎的。他展示了WFC如何作为一种在可能性空间上的消除过程运行,通过解析从模块化几何体中提取的严格相邻约束来工作,而非遵循传统的组合放置方式。为了使生成的空间具备可玩性,他将可通行性启发式算法直接嵌入图块中,处理多图块分组与刻意的不对称设计,并通过高效的全状态重载来应对冲突。最后,他解释了极简、高对比度的视觉风格如何将程序化生成的微缩场景转化为清晰易读的战术画布,同时通过主题化组件和多阶段导航网格验证来克服程序化生成容易千篇一律的问题。 Oskar Stålberg将Maxim Gumin的2D波函数坍缩算法适配至3D环境,将关卡生成视为在网格槽位上消除候选模块的过程,而非构造性的放置。 (1:33) 《Bad North》从最初包含16个模块的原型扩展到约300个模块化部件,在每次3D岛屿生成过程中会初始化大约100,000个候选模块状态。 (14:50) 当出现冲突或玩法验证失败时,该引擎不会进行逐步回溯,而是快照记录确定边界后的状态,以执行快速的整体重新坍缩。 (17:53) 定制的Unity资产管线从单个导入的FBX中解析原始几何体,自动将跨越图块边界的三角形归入多图块模块中,从而消除可见的网格接缝。 (24:36)

Reading summary

Oskar Stålberg explains how Wave Function Collapse, adapted from 2D texture synthesis into 3D level architecture for Bad North, generates coherent tactical dioramas through bottom-up constraint elimination rather than top-down scripting. By pairing content-agnostic adjacency rules with pathfinding heuristics, automated asset parsing, and early-exit validation, the system reliably constructs playable islands while avoiding geometric monotony and computational stalls. Constraint Solving Through Subtraction and Propagation (5:50) Wave Function Collapse (WFC) divides level generation into slots (spatial coordinates) and modules (mesh candidates). Each slot initializes in a state of maximum entropy containing all candidate pieces, which can exceed 100,000 potential variations when rotations and mirrors are factored in. Stålberg emphasizes that placing an asset is fundamentally an act of subtraction: choosing a module eliminates all competing alternatives in that slot, which in turn invalidates incompatible connection sockets in adjacent cells and cascades recursively outward across the grid. Rather than hand-authoring rules for every permutation, adjacency constraints are extracted automatically from assets by sampling and hashing border colors at three distinct edge points. To avoid performance degradation from repeated full-grid scans, a production solver tracks available socket type counts per slot, switching to an event-driven notification model that alerts neighboring cells only when an edge connection type is completely exhausted. Embedding Navigability into a Content-Agnostic Solver (17:53) Because WFC operates purely on local boundary interfaces, it lacks high-level awareness of topological gameplay requirements such as whether an island is traversable. Naively validating pathfinding post-generation often causes unusable maps that must be discarded. To guide generation constructively, Stålberg embeds simplified connectivity metadata—visualized as navigation lines—directly into the modular assets. Generation begins by anchoring gameplay essentials, specifically house foundations, which are vital targets for the game's defensive mechanics. When unconstrained slots present multiple valid candidates, a navigability heuristic scores and prioritizes modules that actively expand the bounding box of the walkable graph. This biases wave collapse outward from the houses, guaranteeing continuous, multi-tiered paths down to the water. Bottom-Up Asset Pipelines and Intentional Asymmetry (23:05) Traditional combinatorial generation typically requires artists to hand-craft every conceivable permutation up front to satisfy high-level abstractions like voxels. By contrast, WFC operates bottom-up, allowing an artist to establish silhouettes, slope profiles, and detail density with a small foundational tileset before expanding to the game's roughly 300 pieces. To streamline authoring, Stålberg imports combined FBX files into Unity, where an automated importer parses geometry across grid boundaries without slicing triangles, grouping crossing geometry into multi-tile modules that mask underlying grid rigidity. To prevent repetitive visual artifacts, Stålberg incorporates subtle vertex displacements and deliberate asymmetry. Modeling corner pillars with unequal sides yields four distinct visual appearances through mirroring and rotation. Where asymmetrical edges meet, small decorative transition cracks function as architectural adapters that flip piece alignment. Detail is reserved strictly for transitions, such as grass boundaries and sheer cliff drop-offs, keeping surfaces clean so the environment functions as an uncluttered combat stage. Failure Recovery, Validation Checks, and Aesthetic Control (36:14) Because procedural generation time scales exponentially with grid volume, Bad North limits island sizes to compact dioramas. When the solver encounters an impossible contradiction, traditional step-by-step backtracking is abandoned because the root error often lies in early choices; instead, the engine snapshots the state immediately following boundary setup (sky and sea level) and restarts the collapse from that baseline. The generation pipeline enforces a strict hierarchy: inviolable hard adjacency constraints guarantee physical assembly, while soft regional weights and thematic module pools (such as caves or rocky cliff overhangs) preserve visual diversity across a campaign. Stålberg incorporates an anti-symmetry rule preventing identical or mirrored generic modules from sitting adjacently. To avoid wasted compute time, the system triggers early bailouts mid-solve if the navigable network closes off prematurely, followed by a post-solve navmesh check that discards islands lacking sufficient beach landing spots for Viking longships. Stålberg demonstrates that while Wave Function Collapse is inherently grid- and content-agnostic—adaptable to spherical triangular graphs or warped cubic grids for organic streets—making it viable for shipped games requires augmenting pure mathematical deduction with targeted domain heuristics, asset-side metadata, and rapid failure-recovery systems.

精炼阅读摘要

独立游戏开发者 Oskar Stalberg 结合自身在《Bad North》中的实践,解析了如何将二维波函数坍缩(Wave Function Collapse,简称 WFC)算法扩展至三维程序化关卡生成,并指出程序化生成的核心并非放置物体,而是在严格的边界硬约束下持续消除不可能的候选状态。 WFC 的核心机制与消除式推导 (1:33) Stalberg 解释,波函数坍缩算法最初由 Maxim Gumin 开发用于二维位图合成,其本质是一种与具体内容无关的约束求解方法。算法将关卡划分为各个“插槽(slot)”,并将放入其中的网格碎片称为“模块(module)”。生成初始阶段,每个插槽都处于最大熵状态,包含图集中的所有潜在模块(在三维初始化并计入旋转与镜像时可达约 10 万种可能)。所谓“放置”图块,本质上是消除该插槽内除选定项之外的所有其他候选可能。 一旦某个模块被移除,若该移除导致某个方向的特定边界接口数量归零,相邻插槽便会立即剔除所有依赖该接口的候选模块,从而在网格中引发级联消除。在工程实现上,初学者可采用修改后全网格扫描的暴力解法,但进入实际生产环境时,必须优化为事件驱动的传播模型:仅当插槽内某种边界连接类型的可用计数彻底归零时,才向相邻插槽发送通知并触发连锁消除。 边界提取与由底向上的组件驱动生成 (10:37) 在资产制作环节,相邻连接规则无需手动繁琐配置,而是可以通过采样二维图块边缘顶、底、侧三点的像素颜色并进行哈希计算来自动提取。在三维资产管线中,Stalberg 避免在建模软件中逐一切割导出图块,而是将整个场景作为单一 FBX 导入 Unity,由引擎解析几何体并根据网格跨度自动合并为单格或多格模块。多格模块能够有效打破传统网格的机械重复感,形成平滑的斜坡、转角与楼梯。 与传统自顶向下为满足高层抽象而穷举所有排列组合的方法不同,WFC 采用由底向上的组件驱动逻辑。开发者可以从极少数核心模块起步,快速验证边缘剪影、坡度转角和几何密度等美学基线。全作模块规模最终从简单示例的约 16 块扩展到正式游戏中的约 300 块。为避免视觉单调,Stalberg 引入了不对称柱体设计,并通过规则明确禁止通用模块与自身的镜像直接相邻,防止完全吻合的接口产生机械刻板的对称感。 可达性启发式控制与多阶段校验回滚 (17:53) 纯粹的 WFC 缺乏对整体游戏玩法的宏观理解,随着生成尺寸扩大,耗时呈指数级上升,且容易生成无法通行的死路。为解决这一局限,《Bad North》将关卡限制在紧凑的微缩岛屿尺度,并在模块内部直接烘焙绿色的拓扑连通数据。在存在自由选择余地时,算法会以必须防守的房屋地基为初始锚点,优先选取能够扩大可通达包围盒面积的模块,引导坍缩过程自发向外延伸出通向海滩的完整步行网络。 当生成过程遭遇不可调和的逻辑矛盾或玩法校验失败时,逐步回溯(backtracking)往往难以定位深层根源。Stalberg 的解决方案是在解算完初始边界条件(顶部为天空、底层为海水)后对状态进行快照。一旦发生死锁,引擎直接重新加载该快照并整体重启坍缩。此外,系统设置了双层校验机制:在生成过程中,若检测到可通行图集在触及底层前过早封闭,系统会立即中途放弃;在三维实体生成完毕后,引擎会进一步生成导航网格,若维京长船的登陆海滩数量不足,则直接废弃关卡并重新投掷随机种子重算。 克制的美学哲学与网格拓扑拓展 (27:19) 在视觉艺术表现上,Stalberg 强调环境应当作为凸显战斗玩法的克制画布,避免高饱和度色彩与无意义的满铺纹理干扰 UI 及兵团轮廓。细节应当服务于结构边界与叙事过渡,例如利用草地与峭壁的交界清晰传达可站立区域,利用接缝裂纹化解几何厚度不一致的错位,而战斗中维京人留下的血迹则能永久浸染岛屿表面。 针对 WFC 在不同网格形态下的应用潜力,Stalberg 指出该算法本质上与网格类型无关,可在任意拓扑图上运行。相比于适合表现星球与大陆轮廓的三角形或六边形网格,正方体网格更适合室内与规则建筑;而通过扭曲立方体模块使三到五个立方体汇聚于单个不规则顶点,则能够天然生成蜿蜒曲折、具有有机流动感的中世纪街巷。 Stalberg 证明了底层消除式约束与高层玩法启发式结合的可行性,但纯粹的 WFC 依然无法自主理解高阶玩法逻辑,且在大尺寸网格下算力消耗呈指数级增长;开发团队必须依赖紧凑的关卡体量、强化的早期终止检查以及针对特定网格拓扑的工程适配来维持运行效率。

Detailed notes — zh

## Background 该视频由 YouTube 频道 BUas Games 于 2018 年 7 月 11 日发布,内容为 Everything Procedural 2018(EPC2018)大会上的技术演讲。独立游戏开发者 Oskar Stålberg 围绕维京题材策略游戏《绝境北方》(Bad North),分享了如何利用波函数坍缩(Wave Function Collapse,简称 WFC)算法结合手工制作的图块集,程序化拼装出兼具美感与可玩性的岛屿微缩关卡,并深入探讨了艺术与技术融合的设计管线。 ## TL;DR Oskar Stålberg 详细拆解了 WFC 算法从 2D 像素位图向 3D 游戏关卡扩展的完整工程实现,包括插槽、模块、可能性空间以及事件驱动的邻接约束消除传播机制。针对完全由几何规则驱动的 WFC 对游戏玩法“一无所知”的痛点,他设计了由核心建筑出发、以可通行边界框为导向的启发式策略,并结合“快照与重置”和阶段性寻路网格验证来解决死局与规模爆炸问题。在美术与工程管道上,他分享了基于边缘颜色哈希的自动邻接提取、FBX 跨图块网格拼接欺骗人眼、非对称拐角过渡以及环境作为玩法画布等极具启发性的低多边形美学理念。 ## Atmosphere & style 整场演讲呈现出一种轻松、极具极客浪漫且脚踏实地的技术分享氛围。主讲人幽默自嘲自己是个“制作好看星球的美术”,用生动的大白话和前一天赶工出来的线框可视化 Demo 拆解硬核的约束满足编程,现场互动松弛而真诚。听众能直观感受到一位跨界开发者在直面工程瓶颈时的务实与机敏——不迷信高大上的复杂回溯,而是用巧妙的几何欺骗和极简管线优雅地平衡算力与美感,既充满启发性又极具工匠趣味。 ## Bulletin - [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Oskar Stålberg 分享了将二维波函数坍缩算法(WFC)拓展至三维空间的实践,通过纯内容无关的邻接约束计算,自底向上拼装出《绝境北方》的微缩岛屿关卡。 - [20:44](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1244s) 在约束求解中,相比逐级回溯死胡同的高昂代价,项目采用保存初始硬约束快照并在冲突时彻底重置的策略,大幅降低了运算开销。 - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) 为克服 WFC 对玩法可达性缺乏认知的缺陷,系统结合图块内置寻路标记与包围盒启发式算法,优先保证核心建筑到沙滩的立体通路联通。 - [25:06](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1506s) 美术设计上提出非对称转角与跨图块复合网格方案,打破网格化棋盘感,并将细节重心放在不同材质交界的过渡结构上以讲述视觉故事。 ## Key takeaways - [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) WFC 本质上是内容无关的约束满足系统,算法底层不关心具体生成的是建筑还是地形,只负责消除违反邻接规则的可能性。 - [05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) WFC 的核心思维并非在空地放置物体,而是先建立包含所有可能性的插槽空间,通过放置行为不断剔除非法选项直至每个插槽仅剩单一确定模块。 - [08:52](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=532s) 约束传播采用事件驱动的局部递归优于全局逐块检测,仅当某类接口特征彻底归零时才向邻居广播,能大幅缩减计算开销。 - [10:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=637s) 图块边缘邻接规则可通过采样关键点像素颜色并计算 Hash 自动生成,省去了手动配置数百种图块对应关系的繁琐工作。 - [14:41](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=881s) 三维岛屿生成优先解析外围纯天空和底层海水等初始硬约束,在极短时间内迅速勾勒出关卡轮廓并压缩可能性空间。 - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) 算法缺乏对可玩性的理解,因此在每个模块内部预置连通线,并自内向外沿可通行边界框进行启发式坍缩,主动引导立体通路形成。 - [20:44](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1244s) 在遇到不可解死局时,逐步回溯难以定位早期错误决策,直接基于初始边界快照执行整岛重置反而性能更优。 - [23:05](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1385s) WFC 采用构件驱动而非体素顶层抽象填充,因此只需极少的核心图块就能验证拓扑原型,极大降低了初期排列组合的美术成本。 - [25:06](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1506s) 通过导入时合并模型并根据三角形跨度重新切分,系统能提取跨越两个格子的复合大构件与微小倒角,有效消除场景的机械网格感。 - [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) 拐角设计引入微弱的非对称厚度,结合旋转、镜像与功能性过渡缝隙构件,用最少资产换取多样的视觉变体。 - [30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) 场景环境应充当玩法的留白画布,视觉细节应当集中在材质碰撞与交界处以讲故事,而非在平面区域做无意义的细节堆砌。 - [31:41](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1901s) WFC 算法不仅能在立方体网格上运行,还能适配球面三角剖分、扭曲立方体或任意拓扑图系统,支持多图块形状混合。 - [33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) 为避免相同通用模块在物理接口完全吻合时产生单调的视觉自咬合,系统设置了禁止模块与自身镜像相邻的硬性约束规则。 - [36:14](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2174s) 关卡验证引入早期剪枝与全岛 Navmesh 事后校验:若通行网络提前闭合则立即熔断,生成完毕后若缺乏足量登陆滩涂则废弃重抽。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=0s) 从美术到程序化生成的个人背景与互动 Demo 经验 Oskar Stålberg 登台向现场观众问好,幽默地将自己定位为“制作程序化星球和好看事物的人”(“procedural planets and pretty things”)。他本身拥有纯正的美术背景,早年毕业于瑞典马尔默的 The Game Assembly。随着创作的深入,他逐渐跨足技术领域,热衷于用程序化手段来构建世界。在职业生涯早期,他曾任职于育碧旗下 Massive 工作室,参与了《全境封锁》(Tom Clancy's The Division)的开发,并主导制作了广为人知的酷炫全息投影地图系统。离开 Massive 后,他也曾在 UsTwo 工作室短暂停留,而目前则全职投入独立游戏研发,正在制作一款名为《绝境北方》(Bad North)的维京题材策略游戏。 在此期间,Oskar 积累了大量用于自我探索的程序化互动演示 Demo,例如可以在浏览器中实时操控建筑形变的“Brick Block”以及程序化星球项目。他特别针对在场的游戏技术专业学生分享了一条重要经验: > “以学生身份做项目,这也是一种非常棒的方式,不要只发布截图或视频,而是发布互动的作品,因为这类内容更容易被传播,给人留下的印象也深刻得多,所以一定要去试试。” ### [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) 波函数坍缩算法(WFC)的起源与 3D 化构想 本次演讲的核心是《绝境北方》关卡生成所依赖的程序化算法——波函数坍缩(Wave Function Collapse,简称 WFC)。Oskar 笑着澄清自己并未发明这一名称,因此“别问我它跟量子力学的关系”。该算法的开拓者是 Maxim Gumin,其最初原型主要基于一个小尺寸输入位图,通过提取像素之间的邻接约束关系(哪些像素可以和哪些像素并置),进而合成出一张在局部规律上与样本高度一致的大尺寸位图。 看到 Maxim Gumin 的二维成果后,Oskar 的第一直觉是该逻辑绝不局限于纹理贴图,完全可以拓展到三维空间,用来构建规模更宏大的程序化世界。这与他长期思考的问题不谋而合:如何打造一个“与内容无关”(content-agnostic)的优雅程序化算法。他随后在推特上展示了早期实验,将 WFC 应用于球面三角面片网格以生成包含道路与村落的星球模型。WFC 的根本优势在于:只要开发者给出一套预制图块集(tile set),算法就会自动根据图块拼接所蕴含的规则去求解,进而拼装出整体连贯的环境或模型。 ```mermaid flowchart LR accTitle: WFC 核心流程 accDescr: 展示从输入图块集提取约束,经由求解器生成完整环境的流动图。 subgraph Input["输入阶段"] A["图块集 / Modules"] end subgraph Engine["WFC 算法核心"] B["推导邻接约束条件"] C["求解并消除可能性"] end subgraph Output["生成结果"] D["连贯 3D 环境 / 建筑"] end A --> B B --> C C --> D ``` - 该算法与内容无关(content-agnostic),底层逻辑完全不知道自己是在造房子、造陆地还是画贴图,它只负责计算模块之间的邻接合法性。 - 美术人员只要设计好过渡构件(例如烟囱、隔断墙或平台交界面),算法就能利用这些组件拼装出复杂的宏观结构。 - Oskar 早期还曾尝试过一款让猫咪在彩色方块世界跳跃的 Demo,但他得出了一个深刻的视觉教训:场景过于花哨会导致玩法和 UI 无法突出,“在色彩极度丰富的环境上搭建玩法是非常困难的,因为那会逼得玩法必须更加花俏,而无法吸引玩家的视线”。 ### [05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) 核心概念拆解:模块(Modules)、插槽(Slots)与坍缩逻辑 为了消除游戏开发中对“Tile”一词既指代位置又指代物件的双重歧义,Oskar 在术语上做出了明确区分:用于安放物件的空间位置被称为**插槽(Slots)**,而可以被摆放进去的具体资产预制件则被称为**模块(Modules)**。WFC 的基础运作环境是一个巨大的可能性空间(possibility space),在初始状态下,地图上的每一个插槽内部都保留着所有模块放置的可能。 演示中,Oskar 构建了一个高度简化的教学演示用例,直观地呈现约束条件如何工作: - 如果输入模块只有纯天空(full sky)和纯泥土(full dirt),且二者之间没有任何过渡模块,那么只要在一个插槽中确定了天空,整个世界的所有插槽都会因为无法邻接泥土而瞬间全部坍缩为纯天空;反之亦然。 - 一旦引入了中间带过渡的地块(例如上天下土的草地块),空间中就形成了结构:天空模块下方不能直接接泥土,泥土上方也不能悬空接纯天空,由此构建起稳定的分层规则。 - 如果输入的模块组合根本无法满足互相闭合(例如只有转角块),算法在求解时就会直接报错中断(fail),因为找不到任何满足约束的摆放组合。 Oskar 强调了 WFC 的本质思考逻辑: > “核心思路是,你拥有一个巨大的可能性空间,在这个空间里任何地方都有可能发生任何事情,而一旦你放置了某个东西,你实际上并不是在放置它,而是把那个图块中所有其他的可能性全部剔除,直到只剩下一个。” ### [08:52](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=532s) 约束传播的实现方式:全局检测与局部递归 当一个插槽的某种模块可能性被剔除后,这种变化会立即引发连锁反应。举例来说,如果一个带有“向下开口为天空”属性的模块被移除,且它是该插槽中最后一个满足该朝向条件的模块,那么它正下方的邻接插槽就必须随之剔除所有“向上开口为天空”的备选模块。这又会继续触发更外层插槽的筛除,形成向外扩散的波浪式级联。 针对这种波函数的传播消除(propagation),Oskar 梳理了两种工程实现思路: ```mermaid flowchart TD accTitle: 约束传播机制对比 accDescr: 对比全局暴力检索与事件驱动递归消除两种逻辑实现。 subgraph Naive["基础实现:全局一致性检查"] N1["插槽状态发生改变"] --> N2["遍历全局每一个插槽"] N2 --> N3["验证周围相邻可能是否仍合法"] end subgraph Optimized["优化实现:事件驱动递归传播"] O1["移除插槽中的某一模块"] --> O2{"该模块是否为其特征类型的最后一个?"} O2 -- "否" --> O3["无需广播,结束"] O2 -- "是" --> O4["向相邻插槽广播失效消息"] O4 --> O5["邻居插槽移除不兼容模块并继续递归"] end ``` - **初学者方案(全局检查)**:每当任意插槽发生改动,立刻遍历整个网格中的每一个图块,重新检查其相邻模块是否仍能满足当前的边缘匹配。Oskar 认为作为起步,这种写法完全可行且符合直觉。 - **高效传播方案(事件驱动递归)**:在模块被剔除的瞬间,插槽立即判断:“被移除的是否是此类特征中在本科插槽保留的最后一个模块?”如果是,则专门向对应的相邻插槽发送通知,告知该方向已无匹配项;相邻插槽收到通知后移除自身对应的不兼容模块,并继续向下递归传播。这种针对性广播大幅减少了无谓的计算开销。 ### [10:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=637s) 基于像素采样与哈希的相邻约束自动推导 Oskar Stalberg 演示了一个利用 2D 图像进行波函数坍缩(Wave Function Collapse,简称 WFC)的简化示例。在这套系统中,图块之间的邻接规则(Adjacency Constraints)并非手动逐条编写,而是直接从图块图像的像素数据中推导而来。以一个包含天空与地面的简易像素图为例,系统只需沿着图块的边缘(如顶部边缘或侧面边缘)采样三个特定点的颜色值,并将这三个点的颜色转换成哈希值(Hash)。凡是在对应边缘三个采样点具有相同哈希值的图块边缘,就被判定为可以无缝拼接。 在界面上,用户可以看到各个图块边缘标注了十六进制的颜色数值。这些数值不仅便于调试,让开发者能一眼看清“原本以为能拼在一起的两块瓦片其实颜色有微小偏差”,也可以在存入哈希表之前进行四舍五入或容差处理,避免因轻微色差导致匹配失败。这套机制将几何或图像的兼容性判定完全简化成了哈希比较: ```mermaid flowchart LR accTitle: 边缘哈希匹配流程 accDescr: 展示从图块边缘采样像素颜色生成哈希,并通过比对哈希值确定图块邻接规则的过程。 subgraph Sampling["像素采样"] A["图块边缘采样<br/>(采集3个关键点像素)"] B["十六进制颜色值<br/>(可做容差取整)"] end subgraph Hashing["哈希与匹配"] C["生成边缘 Hash"] D{"Hash 是否一致?"} E["判定为合法邻接图块"] F["判定为冲突并剔除"] end A --> B B --> C C --> D D -->|"一致"| E D -->|"不一致"| F ``` - **三点采样机制**:通过在图块边缘取三个点的颜色值,系统可以区分“纯天空”、“纯泥土”或“天空与泥土交界”的边缘类型。 - **自动化邻接提取**:只要在同侧具备相同的哈希值,算法就会自动将其注册为合法的相邻选项,免去了繁琐的手动配对。 --- ### [12:31](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=751s) 约束满足编程思想与硬约束/启发式规则 Oskar 谈到,这种算法背后其实对应着计算机科学中一个更宏大的领域——基于约束的编程(Constraint-Based Programming)。他坦言自己在刚接触并开发该技术时并不知道这个概念。关于这种编程范式,Oskar 强调:“It's sort of the idea that instead of you're writing an algorithm by it, you're starting with just declaring what are the constraints here like what things are allowed together with which things.” 在约束满足编程中,主要包含两类逻辑: 1. **硬约束(Hard Constraints)**:绝对不可逾越的规则,例如严格的邻接约束——哪些模块物理上允许拼接在一起,哪些绝对禁止。 2. **启发式规则(Heuristics)**:当硬约束筛选后依然存在多个可行解时(例如面对全空的画板,任何东西都可以合法放置),用于引导选择的策略,比如基于预设概率、噪声采样或权重偏好来随机挑选下一个坍缩的目标。 为了直观展示约束传播的原理,Oskar 展示了一张包含草地(代号 0)和道路(代号 1)的极简示意图。系统中仅有两个插槽,每个插槽各自有 4 个潜在模块。当两个插槽的选项相互依赖时,如果移除了插槽 A 中某一小段道路模块,导致插槽 B 依赖该接口的对应模块失去了连接来源,这些受影响的连线就会被切断;插槽 B 随之必须移除自身对应的两个模块,从而触发连锁反应。整个过程就是不断在一个庞大的可能性空间中,通过传播和消除矛盾来逐步坍缩。 --- ### [14:41](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=881s) 《Bad North》中的 3D 岛屿生成与初始约束解析 随后,Oskar 将话题转向《Bad North》的实机工程,在游戏的战役地图中实时演示 3D 岛屿关卡的程序化生成。为了让观众看清算法的内部运作,他开启了线框可视化模式(由于每一帧都需要渲染所有插槽中未被排除的候选模块,此时的运行速度比正常无可视化状态要慢得多)。 生成的第一步并非盲目放置图块,而是先构建并初始化整个可能性空间(Possibility Space)。一个小小的岛屿空间内包含几百个基础模块,算上旋转和镜像,每个插槽都蕴含着海量组合,初始状态下往往存在约十万种潜在可能。紧接着,系统必须先解析“初始硬约束”: - **外部包围**:关卡的最外围必须是纯净的天空,不可能延伸出地下泥土。 - **底层基础**:既然是岛屿,最底层边缘必须是海水。 当外围天空和底部水面的硬约束施加后,算法在空间内快速传播排除逻辑,大量的非法模块被即刻剔除。此时画面上会浮现出许多半透明的线框“残影”,岛屿的轮廓阴影在放置具体图块之前就已经初具雏形。随后,系统才开始在关键位置放入第一个确定模块(例如给房屋预留的地基空洞),周边不兼容的选项立刻随之消失,生成过程随着约束的层层递减而越跑越快。 --- ### [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) 规模爆炸与关卡可达性(Navigability)挑战 随着展示岛屿尺寸的增加,程序化生成的通病开始显现:生成耗时随地图尺寸呈指数级增长(Exponential Increase)。不过 Oskar 幽默地指出,解决这个问题的最简单办法就是“把游戏设计成只在小岛上玩”。 除了性能瓶颈,更大的技术难题在于玩法可行性。波函数坍缩算法的核心逻辑只关心几何图块能否合法拼接,对“关卡是否可玩”毫无概念。Oskar 表示:“Since the technique the core of the tech doesn't know what we are generating, but I need to generate playable levels right like where you can actually go all the places where you have to go and that actually took me a while to figure out how to do that in a good way.” 如果在生成完毕后再去校验岛屿是否可通达(例如士兵能否从海滩一路走到山顶的房屋),一旦发现死路就必须彻底丢弃关卡重新生成,这会导致大量的计算浪费。为此,Oskar 设计了一套基于连通性的启发式生成策略: - **微观通路数据**:每个模块内部预先标记了非常简化的寻路抽象(在可视化中表现为绿色线条),平坦地面模块会标明自己可以连通相邻的平地。 - **由内向外的启发式引导**:算法在自主选择坍缩位置时,优先从已知可通达的核心点(如房屋)开始,沿着绿色导航线不断向外选择能够扩展通达区域的模块,主动保证路径的连通。 ```mermaid flowchart TD accTitle: 连通性启发式坍缩策略 accDescr: 说明从核心建筑出发,依据模块内部导航数据向外扩展连通区域的生成逻辑。 subgraph ConstraintSetup["初始阶段"] A["边界天空/海水约束"] --> B["解析初始冲突,生成岛屿剪影"] end subgraph GuidedCollapse["连通性引导坍缩"] C["放置核心起点(房屋地基)"] D["读取模块连通数据(绿色导航线)"] E["启发式选择:优先坍缩可向外连通的模块"] F{"是否满足关卡玩法要求?<br/>(如足够的沙滩、全图可达)"} end B --> C C --> D D --> E E --> F ``` --- ### [20:44](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1244s) 回溯机制的反思与快速重置策略 在演示中,生成器偶尔会提示“沙滩面积不足”并将生成的关卡丢弃重来。由此 Oskar 探讨了程序化生成领域中经典的回溯(Backtracking)难题:当算法遇到死胡同或矛盾时,究竟应该回退几步重新尝试,还是彻底重来? Oskar 曾多次尝试实现局部回溯,但很快发现其中的根本缺陷:开发者根本无法预知导致当前死局的错误决策究竟发生在多少步之前——那个致命的冲突模块很可能是在最开始放置的前几块瓦片时就埋下了隐患。在经过多次实验后,Oskar 放弃了复杂的逐步回溯算法,采用了更高效的“快照与重置”策略: - **保存干净基态**:在所有边界初始硬约束(天空、水面等)完成解析传播后,立即将此时的数据状态保存下来作为起点快照。 - **矛盾直接重置**:生成过程中一旦遇到无法解决的硬性矛盾或不符合游戏规则(如海滩数量不足),系统不进行局部微调,而是直接重载该初始快照,重新执行一遍坍缩。Oskar 强调这种重置方式比步步回溯要快得多。 最后,Oskar 看着屏幕上刚刚完工的可视化系统感叹,自己前一天才把这套线框阴影渲染赶工做出来。借助阴影能够清晰看到,边缘和角落由于受到水面与边界的多重挤压,其初始的可能性空间本身就极小(只能是角落海滩、水中礁石或纯水面),这也正是边界处能被迅速确定下来的原因。 ### [22:20](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1340s) 可寻路网络与边界框启发式 在展示较高岛屿的生成时,Oskar Stalberg 演示了内部可寻路网络(navigable network)是如何变得丰富且有趣的。岛屿生成过程中会形成楼梯,寻路网络随之向上爬升、随后下降。算法通过类似包围盒(bounding box)的结构来封装这套网络。在启发式算法的设计上,系统会主动评估每一个候选图块:如果放置某个新图块能够扩展可通行区域边界框的尺寸,它就会获得更高的评分。 ```mermaid flowchart LR accTitle: Navigability Heuristic Evaluation accDescr: Diagram showing how a candidate tile is evaluated based on whether it expands the walkable bounding box. subgraph Eval["图块评估阶段"] A["候选图块"] --> B{"是否扩大可寻路<br/>网络边界框?"} end subgraph Result["启发式决策"] B -- "是" --> C["赋予高优先级分数"] B -- "否" --> D["基础分数 / 低优先级"] C --> E["挑选最优图块放置"] end ``` - 该启发式方法能够自发驱使算法生成更完整、联通度更高的立体通行网络。 - 这一套边界框机制确保了即使岛屿地势变高,AI 寻路也不会断裂或陷入死胡同。 --- ### [23:05](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1385s) 图块集组合与自底向上的设计流程 Oskar 切换到无着色加线框(unshaded with wireframe)的视图,展示了游戏中使用的全部基础图块。虽然随着开发进行,图块总数已经膨胀到了大约 300 个,但在项目最初启动时,仅仅依赖极少数基础块就跑通了。一般的组合生成技术需要美术手动制作海量的排列组合(permutations),这个过程非常痛苦。但波动函数坍缩(Wave Function Collapse, WFC)是由底层构件自身驱动的,而不是为了迎合体素等高层抽象强行去填补内容,因此从极少的几个构件开始就能让系统运转起来,算法会自动弄明白如何用它们搭建结构。 “因为这个算法就像我说的,它是在解决约束条件,而很多这类组合式的东西需要你制作大量的不同排列组合,这非常痛苦;但由于这个算法是由构件本身驱动的,而不是像体素那样为了满足高层抽象去硬塞内容,所以只要非常少的内容就能把它跑起来,它自己会弄明白如何用这些东西去构建结构。”Oskar Stalberg 解释道。最开始他只做了几个图块来确定最核心的视觉基调:边缘是什么样的、斜坡是什么样的、拐角该如何收拢、细节量应该保持在什么水准。一旦这套基础确立,后续就可以根据需求随意扩充;像楼梯由于需要应对各种接合情况,数量就极其庞大,而诸如散落在水中的孤立礁石,则属于出现频率极低的单点构件。为了辅助内容创作,Oskar 还专门开发了一个邻接关系检查工具(adjacency tool):在工具中高亮某个图块的一侧,所有能与该边接合的其他图块边缘都会被同步高亮,这让他在修改资产时能迅速排查哪些图块能匹配上、哪些图块失效了。 - 初始图块仅需关注基本拓扑元素:直边、斜坡、外角、转角等。 - 复杂图块(如各类楼梯)占据了图块集数量的大头,用以解决不同高度和朝向的连通。 - 专用邻接检查工具解决了手工维护数百个图块边缘兼容性的难题。 --- ### [25:06](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1506s) 跨图块网格拼接与图块边缘欺骗技巧 在资产导入管道上,Oskar 坦言自己极其讨厌在制作资产时保持严谨的文件层级和组织结构。因此他的做法十分直接粗暴:把整个 FBX 模型导入 Unity 时,先将其完全合并为一个整体,然后再通过代码将其切开并分析各个图块边缘。通过这种方式,系统能够识别出跨越两个图块的“大构件”。这种跨图块设计至关重要,它能打破网格系统原本一目了然的图块感。虽然《Bad North》并不刻意隐瞒其基于图块的本质,但像拐角处平滑扫过的线条或跨格的长楼梯,能够让玩家难以一眼看穿背后的网格规则,从而保持画面的灵动。 “这块构件延伸跨越了两个图块,这是用来打破显而易见的图块感的一个超级有用的手段。”Oskar 说道。这套切分与合并并非在网格表面进行物理布尔切割,而是根据建模时面片的分布:脚本会分析三角面的跨度,如果一个三角形跨越到了相邻图块,系统就会判断这些部分属于同一个组合体并将它们合并回大构件中。除了跨格大件,他还使用了一些微小的几何欺骗手段:例如在边缘处做一点倒角将硬边磨圆,或者将平面中间的顶点稍稍往侧面挪移一点,视觉上就会欺骗人眼,使其看起来像一个完整的整体大平面,而不是由若干碎片拼合而成的地块。 ```mermaid flowchart TD accTitle: FBX Mesh Import and Slicing Pipeline accDescr: Pipeline showing FBX import, merging, triangle analysis, and dynamic tile construction. subgraph Import["模型导入与预处理"] FBX["导入完整 FBX 模型"] --> Merge["合并为单一网格"] end subgraph Analysis["网格分析与归类"] Merge --> Analyze["遍历并分析各个三角形分布"] Analyze --> Span{"三角面是否<br/>横跨相邻图块?"} Span -- "是" --> BigPiece["标记为多图块复合大构件"] Span -- "否" --> SinglePiece["归入单一图块网格"] end subgraph Runtime["运行时图块组装"] BigPiece --> Out["生成无缝大构件(如平滑转角、长楼梯)"] SinglePiece --> Out end ``` --- ### [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) 非对称拐角与有意义的细节过渡 在艺术表现层面,Oskar 详细剖析了拐角设计的巧思。他将拐角做成了微弱的非对称结构:一侧的厚度比另一侧稍微薄一点。这个简单的改动带来了一个极大的好处——单单是一根柱子顶端的四个外角,通过旋转与镜像组合,就能产生极其丰富的视觉变体。此外,这也允许他插入各种起到“转接器”(adapters)作用的构件,比如在两段薄厚不一的交界处留出一道裂隙。这道裂缝不仅是一处细节装饰,更是具有功能性意义的,因为它顺理成章地将边缘的粗细朝向颠倒了过来。 在展示生成的完整关卡时,Oskar 阐明了他对游戏美术细节的核心哲学:画面的趣味性从来不在于表面本身,而在于表面与表面碰撞接合的地方。“你不需要到处堆砌细节,你需要细节来讲述小故事,而细节通常的作用就是做好事物之间的过渡转换。”在关卡中,大片平铺的草地纹理并不能提供有价值的信息;相反,真正的故事发生在地块边缘——草地与悬崖碰撞处的边缘线、悬崖凸角处程序化生成的草针、以及利用转接器产生的缝隙与小突起。这些分布在接缝处的细节打破了机械感,为整个模型注入了生机。 - 拐角边缘的微小非对称性,使基础图块在旋转和镜像中能够自然组合出多样外观。 - 表面交界处(草地与岩壁、岩壁凸角)采用程序化边缘模型与独立草针进行收口修饰。 - 细节的真正职能在于提供转换过渡,讲述结构交界处的故事,而非无效的均匀铺满。 --- ### [30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) 环境作为玩法的画布与低保真美学 从整体美术风格来看,岛屿环境在设计上扮演的是“游戏玩法的画布”这一角色。环境固然需要优美,但绝不能塞满喧宾夺主的细节,因为真正的视觉焦点和细节应当来自瞬息万变的游戏动态。这个理念同样延伸到了单位角色设计上:由于玩家在游戏中指挥的是小队而非单兵,因此每个士兵个体的美术设计都极为极简抽象,视觉重心被完全放在小队整体的移动态势上。 更字面意义的是,这些岛屿确实是玩法的“实体画布”——随着战斗打响,维京敌人与守军交战溅出的血液会直接涂抹在干净的岛屿地形上,将原本静止的地貌染上战火的痕迹。在演示运行效果时,Oskar 略带自嘲地提及,由于当初技术积累不足,游戏在逻辑上并没有做到完全的帧率无关(framerate independent),所以在低帧率下运行会有轻微的不平滑。随后他结束了演讲主体内容,现场响起了掌声。 --- ### [31:41](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1901s) 问答环节:非立方网格拓展与混合主动性 在演讲后的问答环节中,现场观众就网格拓扑的形态提出了疑问。Oskar 回顾道,自己之前尝试过球体上的三角形剖分。但他一直极度渴望尝试的一套方案是“变形立方体”(distorted cubes):底层虽然是立方体模块,但通过扭曲网格让它们契合在一起,在某些拐角处由 5 个立方体交汇,某些地方由 3 个交汇。 “我认为你可以用这样一个系统,通过弯曲的道路做出非常美丽的类中世纪街景。”Oskar 解释说,不同的图块结构适用于不同的场景题材。三角形或六边形系统(两者在拓扑上互相对偶)非常适合大尺度的地形走势,比如他展示的星球表面;但当需要搭建房屋或细致人造物时,三角形就会变得非常棘手,此时立方体网格则自然得多。实际上,WFC 算法并不强制依赖正交网格,它可以运行在完全任意的拓扑图(arbitrary graph)之上,甚至可以将三角形、立方体等多种形状混用,或者直接运行在二维平面中。 紧接着,观众针对地图生成过程中玩家介入的逻辑进行了提问。Oskar 确认了这一机制,并指出这种开发范式在学术界和行业内通常被称为“混合主动性方法”(mixed-initiative approach):即系统向人类用户征求一部分输入与控制(用户主动),随后算法再依据这些输入继续进行约束求解与自动填充(系统主动)。 - 三角形与六边形网格擅长表现连续、宏观的自然大地形。 - 变形立方体网格擅长表现蜿蜒道路、中世纪街巷等规则与有机交错的人工建筑。 - 波动函数坍缩不仅局限于三维体素网格,同样完全兼容任意拓扑图(Arbitrary Graph)及多图块混合系统。 ### [33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) 规则驱动的模块选择与防重复放置机制 在谈及关卡生成器是否由人工交互介入时,Oskar Stalberg 解释道,在实际游戏中算法完全是自主运行的,并不存在半成品状态下的人工挑选。“在示例里可能只有大概 16 个备选切片,但在实际游戏中存在大约 300 个选项,你怎么可能把这些全摆出来供人交互选择呢?”他坦言这套机制完全有潜力做成非常强大的交互式关卡编辑工具,但他目前并未花时间去搭建那样的前端工具,因此整座岛屿的模块拼接纯粹是依据各种程序规则与随机性驱动的。 生成的第一步往往是从房屋开始切入,因为整个塔防玩法的核心就依赖于必须存在可供防守的房屋。为了更好地引导生成方向,Oskar Stalberg 设计了带有标签名称的逻辑选框与专用规则。例如,系统明确规定某些模块绝对不允许与自身的复制体相邻放置,尤其严谨的是禁止与自身的镜像模块相邻。正如 Oskar Stalberg 所指出的:“另一个非常重要的规则是,我不希望模块与自身的镜像相邻,因为当通用构件挨着摆放时通常看起来相当糟糕,而且这种情况很容易出现,毕竟构件与自身的镜像在接口上往往能够完美拼合。” - **候选池规模**:玩具示例中可能仅有 16 种图块,但《Bad North》实机运行中需在约 300 种模块状态间做裁决。 - **玩法前置**:算法优先锁定并生成村落房屋,确保生成的岛屿天然符合战斗与防守的核心玩法前提。 - **视觉镜像约束**:硬性阻断对称模块的自咬合,避免相同地貌在物理接缝吻合的前提下产生枯燥、不自然的机械重复感。 ### [34:39](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2079s) 规避程序化同质感的主题与结构变化 为了解决程序化生成中极易出现的千篇一律感,Oskar Stalberg 在架构中引入了“关卡主题(Themes)”概念。在展示控制台时,他一边翻找预制件一边调侃这并非他平时习惯的工作站,随后展示了洞穴模块、残垣断壁般的废墟构件以及大型巨石。在演示的岩石主题关卡中,算法巧妙地生成了一座凌空伸出、宛如天然石桥的悬挑结构。针对程序化内容创作的通病,Oskar Stalberg 强调:“程序化生成始终面临一个挑战,那就是所有东西看起来可能会太雷同,而这也是我应对这一问题的方法之一。”除了不同地貌组件的切换,动态更换调色板与植被也是打破视觉审美疲劳的必备手段。 在谈到网格细分与性能开销时,他提到模型本身由于极简风格的限制,多边形面数极为精简,基本全由少数几个低多边形三角形构成。这种轻量化的资产风格赋予了模块极大的组合灵活性,使得算法即使在有限的运算资源下,也能迅速通过少量多边形拼凑出风格鲜明的自然风貌。 ```mermaid flowchart TD accTitle: Island Theme and Aesthetics Pipeline accDescr: Pipeline showing how level themes, special modules, and visual styling prevent procedural sameness. subgraph Themes["主题控制 (Theme System)"] direction TB T1["洞穴部件 (Caves)"] T2["废墟遗迹 (Ruins)"] T3["巨石悬崖 (Boulders & Bridges)"] end subgraph Modifiers["视觉变化 (Visual Styling)"] direction TB M1["调色板切换 (Palette Swapping)"] M2["植被覆盖调整 (Foliage Variation)"] M3["低多边形资产组合 (Low-poly Modules)"] end Themes --> Modifiers Modifiers --> Result["独特的岛屿微缩景观 (Unique Island Diorama)"] ``` ### [36:14](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2174s) 概率权重与导航网格的多阶段验证 现场观众针对海滩生成失败后的重试机制提出了疑问:此前 Oskar Stalberg 提到过某个岛屿因为找不到足够的海滩而连续重启了两次生成流程,观众想知道在重新生成时,算法是会动态调整图块的概率权重,还是仅仅更换随机种子重新跑一遍。Oskar Stalberg 回答表示两者皆有应用:邻接约束始终是不可逾越的硬性限制,但在硬性规则之外存在概率机制,他可以在特定空间区域内划定边界框,从而主动调高或调低局部图块的出现概率。 不过,海滩数量不足的判定是在波函数坍缩(WFC)算法完全跑完之后才触发的。此时系统会构建全岛的寻路网格(Navmesh),并检查是否有足够数量可供维京战船靠泊的海滩;如果判定可用滩涂不足,便会彻底废弃该岛屿并重启流程。除了这种事后校验,算法在生成过程中也布置了早期的即时验证逻辑。例如,算法会实时监控正在构建的可通行岛屿网络,一旦发现可通行区域在尚未延伸至底层水面之前就已经被地形彻底封死,系统便会判定当前分支已无可挽回,从而在执行后续重型计算之前提前中止并丢弃该状态。 ```mermaid flowchart TD accTitle: Island Generation and Validation Loop accDescr: Step-by-step validation loop for island generation including early failure checks and navmesh validation. Start["启动波函数坍缩 (WFC)"] --> LocalChecks{"生成过程中验证<br/>通行网络是否提前闭合?"} LocalChecks -- "是 (死局)" --> Abort["提前放弃并重置"] LocalChecks -- "否" --> FinishWFC["完成地貌拼接"] FinishWFC --> BuildNav["构建导航网格 (Navmesh)"] BuildNav --> BeachCheck{"滩涂验证<br/>是否有足够船只着陆点?"} BeachCheck -- "不足" --> Discard["废弃整岛并重抽种子"] BeachCheck -- "充足" --> Pass["成功输出可用关卡"] Abort --> Start Discard --> Start ``` - **硬性约束与软性概率**:邻接限制(Adjacency Constraints)属于绝对不允许打破的硬规则;而区域概率权重属于软调节,支持在特定三维选框内人为干预地块出现的概率。 - **事后全局验证**:地貌坍缩完成后,引擎生成寻路导航网格,对滩涂登陆点进行连通性与数量双重测试,不合规即直接推倒重来。 - **运行期早期剪枝**:如果通往水面的通路网络在中途闭合,算法无需等待全岛生成完成即可当场报错熔断,避免无效算力浪费。 ## Notable quotes > [05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) 一旦你放置了某个东西,你实际上并不是在放置它,而是把那个图块中所有其他的可能性全部剔除,直到只剩下一个。 — Oskar Stålberg > [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) 你不需要到处堆砌细节,你需要细节来讲述小故事,而细节通常的作用就是做好事物之间的过渡转换。 — Oskar Stålberg > [33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) 我不希望模块与自身的镜像相邻,因为当通用构件挨着摆放时通常看起来相当糟糕,而且这种情况很容易出现,毕竟构件与自身的镜像在接口上往往能够完美拼合。 — Oskar Stålberg > [00:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=0s) 不要只发布截图或视频,而是发布互动的作品,因为这类内容更容易被传播,给人留下的印象也深刻得多,所以一定要去试试。 — Oskar Stålberg ## Mentioned Oskar Stålberg, Bad North, Wave Function Collapse, Maxim Gumin, Everything Procedural Conference, Breda University of Applied Sciences, The Game Assembly, Massive Entertainment, Tom Clancy's The Division, UsTwo, Brick Block, Unity ## Video description Bad North is a procedurally generated strategy game about fighting Vikings. Oskar Stalberg will talk about how the Wave Function Collapse algorithm is employed to magically assemble idyllic island dioramas from handcrafted tilesets. You'll learn about the theory behind WFC, its implementation and further development in Bad North, as well as the tools and pipeline surrounding it. Lastly, Oskar will cover how he approaches the integration of art and tech aesthetically. Everything Procedural is a yearly conference organised by the Games programmes of Breda University of Applied Sciences (BUas). The conference brings together experts and developers in the field of procedural generation and focuses on procedural game content and its application in AAA or innovative games. https://www.everythingprocedural.com/ https://www.buas.nl/en/programmes/creative-media-and-game-technologies https://www.buas.nl/en/programmes/master-game-technology [Source](https://www.youtube.com/watch?v=0bcZb-SsnrA)

Source quotes

[5:50] Oskar Stålberg: 一旦你放置了某个东西,你实际上并不是在放置它,而是把那个图块中所有其他的可能性全部剔除,直到只剩下一个。 [27:19] Oskar Stålberg: 你不需要到处堆砌细节,你需要细节来讲述小故事,而细节通常的作用就是做好事物之间的过渡转换。 [33:33] Oskar Stålberg: 我不希望模块与自身的镜像相邻,因为当通用构件挨着摆放时通常看起来相当糟糕,而且这种情况很容易出现,毕竟构件与自身的镜像在接口上往往能够完美拼合。 [0:00] Oskar Stålberg: 不要只发布截图或视频,而是发布互动的作品,因为这类内容更容易被传播,给人留下的印象也深刻得多,所以一定要去试试。

Source quotes

[27:19] Oskar Stalberg: You don't need detail everywhere; you need detail to tell small stories, and usually making transitions between things—that's what detail should do, typically.

Original transcript

[0:00] all right hello everyone nice to be here [0:03] yeah my name is Oscar solver I make [0:07] procedural planets and pretty things I [0:10] come from like an artist's background I [0:13] studied at the game assembly mama which [0:15] is a single place similar to this one I [0:17] studied art there but then I should have [0:19] ventured more and more into technical [0:21] science and making more procedural [0:23] things I used to work at massive I [0:26] worked on the division I made that cool [0:29] like holographic map maybe you've seen [0:30] it I was good fun and side projects I've [0:36] got a bunch of other like procedural [0:38] interactive demos just to explore things [0:41] myself but and I mean they habit of [0:42] putting them out this interactive demos [0:44] online too so both that little house [0:46] there called brick block and not planet [0:49] you can find on my web page and you can [0:50] open them in the browser and it's just [0:52] like tools to manipulate a house and [0:54] manipulate a planet that's a very good [0:59] way to do projects as student too by the [1:01] way [1:01] just like make things that you can [1:03] publish that you don't just publish [1:04] screenshots or videos that publish like [1:06] interactive things because there's so [1:08] much more likely to be shared and it's [1:10] so much more impressive so do that [1:13] yeah I said I worked with NASA for a few [1:16] years I worked at us to model and say [1:19] for a bit too but now I'm indeed making [1:23] my own games well trying to make my own [1:26] game one game called bad north looks [1:29] like this right now but today I am going [1:33] to talk to you about a procedural [1:38] algorithm that we are using to generate [1:40] these levels it's called weight function [1:42] collapse I didn't name it wave function [1:44] collapse so you know don't ask me about [1:47] the connection to quantum mechanics but [1:49] this is like a gift I came across this [1:51] guy Maxim goomy is the guy who invented [1:54] the concept or I mean he based it off of [1:57] something else too but I think he did [1:59] the bulk of it you mentioned so as you [2:02] can see it's well the way he did it is a [2:05] way to use like a small example bitmap [2:08] and they create a larger bitmap that's [2:11] consistent with that so what it really [2:13] does it's like deriving adjacency [2:16] constraints like which pixels are [2:17] allowed to be next to which pixels from [2:20] the example and then it can create [2:21] something bigger out of that so [2:23] obviously my first idea when I saw this [2:25] one was like I bet you can use this for [2:27] 3d I bet you can use this for other [2:28] things than just textures to build [2:33] little larger works because it tied him [2:35] very neatly to like some problems I had [2:37] looked at before [2:38] like how to make nicely very sort of [2:41] Compton's agnostic procedural algorithms [2:47] so yeah this is the first little thing [2:53] all right yeah there we go yeah he's had [2:58] to download these is like half of these [3:00] are just my old tweets that I downloaded [3:02] again into the you should follow me on [3:05] Twitter to a post about these kind of [3:06] things but this is like my first [3:08] implementation I hope the wave function [3:10] cooks that I built and as you see it [3:12] what it does it it's applied on like a [3:15] triangular tiles model on spheres one in [3:19] the middle there is like villages roads [3:24] quite similar to the planet I did before [3:26] but it can also it can do some things [3:28] that that planet couldn't do like some [3:31] ways of making roads and because that [3:32] pipe didn't have worlds for examples [3:34] they like are a bit different [3:35] otherwise but you can also make you do [3:37] like weird things you see this guy's [3:39] just like building sort of also just to [3:43] yeah I don't like UT I don't want the [3:45] triangular tiles for that would look [3:46] like this video doesn't seem too long [3:49] but that's just another night concert so [3:51] the core idea is that you just you just [3:53] give it a title set and then it figures [3:56] out how to assemble that tile set into [3:59] one coherent model or environments [4:02] people like whatever constraints are [4:04] implied by how your tile set fits [4:07] together it will solve for that so if [4:10] you give it like if you if you if you [4:14] haven't build a ton of transitions it [4:16] will just figure out a way to build the [4:19] environment with those transitions that [4:21] you have did you have that build so this [4:24] is another early example of what I did [4:26] there so you see it's like building a [4:27] little sort of safe walk something like [4:28] that and these like to the left third [4:31] that's actually all the inputs modules [4:33] been used so it's not today it's quite [4:35] quick to get started with it and then [4:37] you figures out how to put it together [4:39] into different things and this I think [4:45] this is the album is algorithm is my [4:47] contents agnostic fundamentally like it [4:49] doesn't know that it's building a house [4:50] it just knows that it needs to fit [4:52] things together it makes it like a Civ [4:55] artist you can build all these nice the [4:57] whole sort of transitions between plates [4:59] and you can build like extra little [5:01] things use things like you can build a [5:03] chimney and you can build like I have [5:06] some larger peach some sort of extra [5:08] features of my been going up here these [5:12] little sort of divider here so yeah you [5:15] can build things like that and then it [5:17] will like three larger structures form [5:18] it so now I'm going and this is another [5:21] thing I tried to build this is actually [5:23] invisible became I had worth a long time [5:25] ago it looks like this and you played a [5:26] little cat jumped around but then but [5:29] that all changed it turned up then this [5:31] is an important lesson it was too [5:32] colorful it's very hard to put gameplay [5:35] on top of an environment that's very [5:36] colorful because then the gameplay is [5:38] even more colorful [5:39] unlike the UI [5:41] the attention but it's also just an [5:44] example of the kind of variation you can [5:45] get from from the algorithm so now we're [5:50] going to get into sort of the specifics [5:52] of the algorithm how it actually works [5:55] and as any of you looked at this other [5:58] working before few people have that's [6:02] good so my hope is here that actually [6:04] has any of you built one yourself for [6:06] trying to a few people that's really [6:10] cool so my hope is that actually like if [6:11] you've been interested in it you should [6:14] from this presentation be able to get [6:16] going and start sort of understanding [6:19] the core ok so this is a very simplified [6:21] version of it that's made specifically [6:24] for presentations so you see here [6:27] there's like the input modules and here [6:30] is like a big possibility space and call [6:32] cuz there's a bit and we do it too when [6:33] you talk about tiles like it's a title [6:35] where things go or is a tile what you [6:38] place there so I split up the [6:39] terminology into modules and slots so [6:42] like come on each slot picks a module [6:44] that's the terminology so right now you [6:47] have everything being possible every [6:50] single module here being possible inside [6:52] each slot and I can just quick solve [6:55] what so you see it like constricts [6:59] different environments there our [7:02] therapist is like how the particles [7:03] input modules and this like this [7:05] particular set of module because the [7:07] algorithm can fail in some cases but [7:09] with this input this input module it [7:12] can't actually sell because it's too [7:13] simple there are no what we're [7:14] dependence is built into it and the way [7:18] it works [7:18] let's start by like these selecting so [7:23] now I will only feed it to modules only [7:26] too much was possible full sky or full [7:28] dark so obviously since they don't have [7:30] any transition between those modules I [7:32] don't have any taught us that I'd like [7:33] both sky on both dirt as soon as I [7:35] select one of them everything turns into [7:38] sky for example or everything turns into [7:41] dirt because there's nothing like [7:43] there's nothing to connect [7:44] but if I had like this little connector [7:47] here right so it's dirt at the bottom [7:49] this kind of top if I slip sky here yeah [7:52] everything above it I'm inviting that it [7:54] has to be sky [7:55] if I select dirt here yeah everything [7:57] along with that and below that has to be [7:59] dirt but here from still a bit ambiguous [8:00] but as soon as I place the like the top [8:05] line part there then like the entire [8:07] class is done so the way and of course [8:15] yeah you can has to see there like here [8:17] it it errors because it can't solve it [8:18] because it doesn't have any modules [8:19] whatsoever I just give it like a coroner [8:22] module it can't solve it either because [8:23] there's no way to build a Korean system [8:25] with just a quarter module if I gave it [8:27] corners I'm sky then yeah it kind of go [8:29] the corner of course guy he's there but [8:31] that's it so the way it sort of cuz I [8:37] feel like it you can see the solving [8:39] happening in a little tiny slider here [8:42] and this is also available their own [8:44] lines you can you can open this as I [8:50] click a peak so the core idea is that [8:52] yeah you have this huge possibility [8:54] space where everything is possible [8:55] everywhere and then as soon as you place [8:57] something what you're doing is not [8:58] really placing something of what you're [9:00] doing is you're removing all other [9:02] possibilities from that tile until [9:05] there's only one remaining and as those [9:07] are removing though they look like if [9:10] you have one that has like sky downward [9:13] pointing if then you remove that one and [9:16] that was the last one that had sky [9:17] downward pointing then in the slot below [9:21] it it has to remove everything that [9:24] relied on having sky downward so [9:26] everything that has sky upward and then [9:28] you know that might have further [9:29] implications and has to remove some [9:31] other things that rely on those things [9:32] so since this is like I don't have any [9:35] overhangs so as soon as I play something [9:38] with dirt it knows that everything below [9:41] has to be dirt right you see how they [9:42] created this big dirt pillar here and I [9:44] was gonna make a similar Twitter I'm in [9:47] the tweet because they don't have [9:48] anything but with in between there so [9:50] that's a that's a recursive like I don't [9:54] have to because obviously the first name [9:56] approaching that's a completely fine way [9:58] to start with it it's just like when you [9:59] have made any change just check the [10:01] consistency of every single tire like [10:03] are there still adjacent possible [10:06] modules that would satisfy like these [10:10] kiss drinks that's perfectly fine way to [10:12] start but then you can then you can [10:15] build it in like a smarter way word like [10:16] as soon as you remove something it [10:18] checks [10:19] was I had the last of my type that was [10:21] removed and if I was then might let the [10:24] slope next to me know that hey there's [10:26] nothing matching this anymore [10:27] and that goes okay that means I have to [10:29] remove this one and they're like that [10:31] propagates too so if we if we for [10:37] example we go for [10:48] so you see how I can paste this little [10:50] so this all anchorperson so it's all [10:52] implied from one of them and it just [10:54] picks the first one so in this [11:01] particular case the the because this is [11:04] just a 2d picture so you can derive your [11:06] adjacency constraints depending on you [11:10] know what kind of tiles and modules [11:12] you're working with in this case I'm [11:13] just working with a picture picks a [11:15] picture and it's not a very complex [11:17] picture [11:18] so I've derived that by just like you [11:20] see if I click this is here some other [11:22] things like up to so like if I click [11:25] this one see this is like siding sky [11:27] this is like top and bottom sky this is [11:32] like sighing dirt top and bottom dirt [11:36] and it derives those in this case just [11:40] by sampling the texture on three points [11:42] just getting what's the color here here [11:44] here like along the top explicit color [11:46] there there there getting the color just [11:49] turn that into a hash and then you know [11:51] everything that has the same color in [11:53] those three spots will get the same hash [11:55] and then you can index that too and then [11:57] it's all very simple so so this the [12:00] little numbers you see here it's [12:01] actually better that color on that point [12:04] in like hex values but that doesn't [12:06] really matter [12:07] I mean it's helpful if it's somewhat [12:08] meetable to you is that you can compare [12:10] oh I thought these pieces would fit [12:11] together but then you can look all know [12:13] the color is actually slightly different [12:15] because they accidentally did something [12:16] wrong of course you can round off those [12:18] things before you put the diamond hashes [12:20] to if you don't want to be too tedious [12:25] yes yes so yeah [12:31] the court and this is so this is an [12:33] example of there's a bigger field of [12:35] programming that I didn't know when I [12:36] got started with this called [12:38] constraints based programming and it's [12:42] sort of the idea that instead of you're [12:45] writing an algorithm by it you're [12:47] starting with just declaring what are [12:49] the constraints here like what things [12:51] are allowed together with which things [12:53] in this case of course is strictly [12:56] adjacency constraints so it's just what [12:58] things aren't allowed to what things and [13:00] then like in all constraint based [13:02] programming you just like asked how [13:04] we're going to give you a solution that [13:05] satisfies those constraints you have two [13:07] constraints which like hard rules and [13:09] then you can also have heuristics which [13:11] I like ok but when there are multiple [13:13] options right like here it doesn't know [13:15] what to do because obviously like it can [13:16] put anything anywhere but then you can [13:19] have heuristics that help like oh you [13:21] know high probabilities you can have [13:22] this random picking with noise and [13:23] things like that too but yeah so this is [13:26] one example of constraint based [13:28] programming which is a very useful way [13:32] of writing code and migrating pools in [13:34] in general so that's the simplified [13:37] version I also made a little go there we [13:41] go I made a little just schematic to [13:44] show like an even simpler version this [13:46] one doesn't move unfortunately it's just [13:47] a picture but you can see how like here [13:50] I just have two types of sites I have 0 [13:53] & 1 & 0 is grass and one is Road so I [13:58] have two slots with a very very small [14:00] system and each in each of these slots [14:02] four modules are possible and here we [14:04] can see how they rely on each other [14:06] right so how like the this road piece [14:09] this piece here and this piece here both [14:11] rely on this little piece here right so [14:15] if I would remove that piece then like [14:18] this mine gets cut and there's nothing [14:21] feeding into this anymore so then it [14:23] tells the next one okay you now need to [14:25] remove this one and this one and I'm [14:27] like those two [14:29] that it would collapse that so yeah big [14:32] possibility space and then collapse down [14:34] and resolving implications of your [14:37] constraints and then I'll show you how [14:41] this actually works in our game no [14:46] that's something else there we go [14:50] so this is five mark again we're making [14:52] and I'll show you I'll go ahead and [14:55] generate some new levels it's like a [14:59] campaign map that now I have turned on [15:00] to you so I can just like also generate [15:02] whatever I want here so I'll pick a [15:04] level and the generation is going to [15:08] happen so here you can see he's just [15:10] like the wireframes of all the modules [15:12] that would be possible on all these in [15:16] all these slots like it's much it's much [15:19] slower now by the way because this [15:20] visualizing it has its within so you see [15:26] it's sort of building a possibility [15:27] spaces first right because it needs to [15:28] do that needs to propagate everything [15:30] everywhere and then it needs to resolve [15:32] initial initial constraints cuz the [15:36] previous one we saw it didn't have any [15:37] additional constraints anything can go [15:38] anywhere but here like I need to have [15:40] sky on the outside the level can all be [15:43] inside dirt right and I also it's island [15:46] so I need to have water at the bottom so [15:48] that like initially implies the bunch of [15:51] constraints you have a bunch of things [15:52] being removed right here somewhere [15:55] around there yeah now we've resolved the [15:57] basics the basic initial constraint so [16:00] you can already like start to see like [16:02] the shadow of the shape of the island [16:06] here you have the first thing actually [16:08] being placed it's it's it's a house it's [16:11] a hole for a house because the house is [16:13] did I've added later because they're not [16:14] part of the Train and you see how [16:17] already like some options around the [16:19] south side of the house started [16:20] disappearing because obviously there's [16:22] already [16:24] well they did but a little bit further [16:26] and he seems like having youth placing [16:29] new things and resolving constraints so [16:32] like here it started adding a niche and [16:34] there we seem like the shadows like now [16:36] there's a gap here because there's [16:37] nothing like that would be possible to [16:38] place today that would but wouldn't [16:42] conflict with this edge here so you see [16:44] these sort of this sort of ghost to [16:46] represent yeah and they're now it's all [16:47] generator so it started like acid gets [16:50] going into its faster and faster and [16:52] faster because most of the implications [16:53] are already been resolved nobody never [16:59] look oh I think that's a different yeah [17:01] but this is like what the result yeah [17:12] see seeds getting constructed so [17:14] building spaces spending that's what in [17:18] slows parts and I was getting really [17:21] slow like placing all these cuz there's [17:23] no need to be like them I think around [17:24] like a hundred thousand possibilities [17:26] just this light guy I don't there's a [17:28] few hundred modules and then they place [17:31] one on each tile on might rotate to the [17:34] mirror to [17:53] now it's starting to collect it it's a [17:55] bigger level yeah and of course that's [17:57] with a lot of procedural things the [17:59] generation time increases exponentially [18:02] with sizes are the bigger the bigger [18:04] than that the slower it is though we [18:08] always be solved that if I play on small [18:09] islands so yeah it's starting to shrink [18:20] it like you see some shadows disappear [18:22] here that my Chronicle ologist by the [18:23] initial initial implications [18:38] holies yeah and now it starts like some [18:41] of the initial ones and start placing [18:43] placing tiles and this is also really [18:45] like one thing that's interesting a big [18:48] problem with procedural generation in [18:50] general and with this technique in [18:52] particular this like sense the technique [18:54] the core of the tech doesn't know what [18:56] we are generating but I need to generate [18:59] playable levels right like where you can [19:01] actually go all the places where you [19:02] have to go and that actually took me a [19:04] while to figure out how to do that in a [19:06] good way obviously I can after i [19:07] generated a level i can go through it [19:10] and like validate mike has like can you [19:12] go from the beach up to the house [19:14] everywhere where you're supposed to that [19:16] works but obviously you can end up [19:18] generating a bunch of levels that you [19:20] just sort of have to throw away oh look [19:23] at that [19:26] no no no it's a whole there's a cave [19:29] inside but we won't be able to see it [19:31] okay yes another level for us it hides [19:41] them when they generate them because [19:42] this is like I suppose happened sort of [19:44] offline [19:48] but yeah but so the way we did with [19:50] navigability is you see these green [19:52] lines here like the modules have a very [19:54] simplified idea of how you can navigate [19:59] from them so this is like a flat area so [20:01] it knows that it can go to adjacent [20:03] modules so when when I generate I this [20:08] is like the heuristics I was talking [20:10] about before so when I get to because [20:12] sometimes it's just implied and you just [20:14] need to place the module somewhere [20:15] because nothing else is low but [20:16] sometimes you you get to pick like [20:18] especially when you start with an empty [20:20] canvas you get to pick which modules [20:21] you're supposed to place where and that [20:24] then I drive this with like a [20:25] navigability heuristic so I start with [20:27] one place that I know it's navigable I [20:29] start with a house and then it tries it [20:33] tries to pick tiles that expand that [20:36] navigable island so you see how it keeps [20:40] like building with these sort of these [20:42] green lines outwards [20:44] there we go and here actually it starts [20:47] here we can see it is complaining that [20:49] these levels don't have enough beach so [20:52] we've discarded them and tried again so [20:53] we have to do that few times that's also [20:56] an interesting question of like [20:56] backtracking in generation in general [20:58] like would you when you make an error [21:00] when you run into a contradiction do you [21:02] go back and then try something else or [21:04] do you I tried that a bunch of times but [21:06] the thing is that you don't know how far [21:07] back you have to go you don't know if [21:09] you like maybe the error was implied in [21:12] the first pylons you placed so in my [21:15] case I solved it by just I say the [21:17] initial state when it's resolved all the [21:19] initial contradictions and then we just [21:21] if something fails if it runs into a [21:23] contradiction it just reload reload [21:25] stack and collapse again this way faster [21:27] than symmetric [21:46] they should built this visualization [21:48] like yesterday so quite happy with how I [21:52] can get sort of shadowy view of so you [21:55] sort of see how the way they imply [22:00] possibilities and you see you can tell [22:04] also that of course like keep hearing [22:06] the corners there are way fewer [22:08] possibilities even in the beginning [22:10] because there can like there can be a [22:11] corner corner beach there can be a [22:13] little rough in the water [22:14] and it can be water but that's basically [22:16] it and that's all like that gets implied [22:20] by by just by the initial contradictions [22:25] this is a taller one so I guess yeah [22:27] here we can see the these sort of the [22:30] navigable network being a little bit [22:32] more interesting we should be able to [22:33] once you start building that work yeah [22:36] you see like here it's sort of a little [22:38] stair going on here so there's not a [22:40] bubble network goes up like this and [22:42] then it goes down right so it keeps and [22:44] this reading books like encapsulate the [22:48] navigable network so the way the way I [22:51] do this time - both heuristic is like if [22:53] the new tile would increase the size of [22:56] a navigable box then that gets high [22:58] score and I think that one [23:05] oh yeah and we can have a look at all [23:11] the pieces I'm using for this so this is [23:13] like all the tires I'm using unshaded [23:18] some wireframe on them so it actually [23:21] spent like now there's a ton of them I [23:22] concur just about like 300 so that's a [23:24] lot but it just started out with like [23:27] maybe these ones something like that [23:29] because this algorithm as I said says it [23:31] solves the constraints it because with a [23:34] lot of companies kind of combinatorial [23:36] things you need to create a lot of [23:37] different permutations which is the pain [23:40] but since this one is driven from the [23:42] competence instead of the content being [23:45] placed to sort of satisfy the the sort [23:50] of higher level abstraction so for [23:51] example voxels or something like that [23:53] you can get it going with very very few [23:55] things it will just figure out how to [23:56] build structures from that so I started [23:59] with something like these ones just to [24:01] get a sense of right if they're really [24:02] important things like what's the like [24:04] what do my hedges look like what do my [24:06] slopes look like what's the corner like [24:08] how much detail do I want right because [24:10] it's a really important part but then [24:13] you know it's really easy to just keep [24:15] adding new content and you can choose [24:17] yourself how many like how much [24:19] permutations you want to add like these [24:21] are all of my sort of little stair cases [24:23] which like obviously you need a ton of [24:25] those for all kinds of situations but [24:28] there are some other things that became [24:29] sort of occur on just sort of one-off [24:31] locations like this little rock here it [24:33] just goes in the water it just goes into [24:36] work I also added a little tool so you [24:39] can check adjacency so here is if I just [24:43] highlight the sites of things other [24:45] things get highlighted that fit with [24:47] that thing so this will type it here of [24:50] course fits with all the other little [24:52] side bits that's really useful when you [24:55] alter you Compton's and you need to [24:56] check the hilts and you think this [24:57] actually fit with the lesson thanks I be [25:00] able do they actually function burkas in [25:02] maya and not like it's also a little bit [25:04] in the fours [25:06] yeah yeah this is one maybe yeah that's [25:09] uh that's cuz I really detest like [25:11] keeping a good structure in my mind [25:14] since things like that so what I do is [25:17] like on when I import the FBX to unity [25:20] I just merge it all into one and then I [25:22] go through and cut it up into little [25:23] pieces and analyze the edges of it and [25:26] and it can also make bigger pieces this [25:29] is quite an important aspect that you [25:33] can make right so this piece here [25:35] stretches across two tiles which is [25:37] that's a super useful thing to use just [25:40] to sort of break up the obvious tiling [25:42] of course so I still like I don't hide [25:44] the time and you can see in the game [25:45] it's clearly a Titleist game but you can [25:48] still like just like sweep around [25:49] corners a little bit more like those [25:51] things are really nice so that you keep [25:53] them on their toes they don't know [25:54] exactly what's going on it's really good [25:57] for those things and it's also written [25:58] of course crucial for making visible [26:01] staircases because they have to make it [26:04] have to go from one level to another one [26:06] so almost beginning back here you can [26:09] see also these little green lines here [26:11] is the sort of pieces of navigable [26:14] network that it's using for that with [26:18] the navigability heuristic that I used [26:21] to explain it and the weight the weight [26:23] just two minutes when I import them to [26:26] cut it up into pieces the weight figure [26:27] self which one are supposed to go into [26:29] small pieces and which ones are supposed [26:31] to go into big pieces and actually check [26:33] the plate does this have a triangle that [26:35] spans over to another tile and then it [26:39] just merges those into what so it's all [26:42] in how I model them so I need to place [26:44] all the mic it doesn't actually cut the [26:47] mesh it just takes all the triangles and [26:49] then puts them into different titles and [26:51] then like figures out if some of them go [26:52] together and then they get merged which [26:56] is super I have some more examples so [26:57] just like those sort of grounding right [27:00] like this a little bit here just past [27:03] like just rounds all finacial Oh [27:05] which makes this a bit nicer and this [27:08] little playing here actually move the [27:11] together Middleburg picks it's just [27:12] moved to the side a little bit good this [27:14] is your trick the thinking - thinking [27:15] that this is just one big plane instead [27:17] of kiting up in the little place but [27:19] yeah and so one thing artistically I'm [27:22] quite happy with you if you look at a [27:24] corner it's slightly asymmetrical it's [27:28] like this side here is a little bit [27:30] smaller than this side here that's a [27:33] very easy way to get a tiny bit of [27:36] variation so like on top of like a [27:38] pillar that if you take four different [27:39] corners of some of them are mirror then [27:41] like there are a bunch of different ways [27:43] that those could look like and also it [27:46] means I can add these little nice pieces [27:49] which are sort of adapters but where I [27:51] can have like a bit of a crack which is [27:53] like it is a detail but it's also sort [27:55] of meaningful in the way that it it [27:57] flips the big mirrors orientation right [28:00] so it's thick here to thin there it's in [28:02] there but thank you and that's I think [28:04] that's a nice way of thinking about [28:06] detail in general like making detail [28:08] this that's meaningful and that DS [28:10] changing thing have a look at a look at [28:16] generated level here [28:19] yeah I'm dimension also something about [28:21] the history speed of oh I mean there's a [28:26] lot of technical things going on here [28:28] but there's also a bunch of the stage [28:29] things going on here [28:30] and you can tell that the like the [28:34] interesting thing is not what happens [28:38] with like on surfaces the interesting [28:41] thing is what happens where surfaces [28:43] meet each other right like if I would [28:45] just kind of like a super obvious tiling [28:48] grass texture here that wouldn't like [28:50] that doesn't really provide information [28:51] right we sort of yeah we get it's grass [28:53] but like so the stories instead [28:56] additional stories are being told where [28:58] surfaces meet each other right so the [29:01] first weeding obviously if it's like the [29:03] grass patches like where are those [29:04] levels and then we have the little edge [29:06] you got here between the grass and the [29:09] and the clips and then you have another [29:12] little edge word about the [29:13] convex where you put these little brass [29:16] pins which also might break up the [29:18] timing know that those are procedurally [29:19] in place of course the grass patches and [29:24] then you get these little sort of yeah [29:27] here you can see one of these still we [29:28] were adapters being used just like so [29:31] you have a yellow pine like normal [29:32] normally the corners just align but then [29:34] sometimes it shifts and there's a little [29:36] bit of a crack in it so that's a good [29:38] gear to like soft line but a little bump [29:41] in it listen I think that's a nice [29:43] approach to detail in general you don't [29:45] eat detail everywhere you need detail to [29:47] smell to tell small stories and usually [29:50] making transitions between things that's [29:52] what details should do typically yeah in [30:00] fact sort of most of the anti of course [30:04] keep spying in too because if we've put [30:07] some units on here it's also the idea of [30:13] right where the environment is it is a [30:15] cannabis for the gameplay so the [30:17] environment like the emergency be [30:19] beautiful but it shouldn't have that [30:21] many details because the details should [30:24] come from the game terrain and it's the [30:27] same it's also but these units because [30:28] in this particular game of course you [30:30] control small armies instead of [30:32] individual units so each of the little [30:35] each of an individual units is quite [30:38] simple in its aesthetics to sort of and [30:41] the focus is being put on how they move [30:43] as a unit in stacks [30:47] and of course the the items are also [30:51] quite literally a canvas for the game [30:53] played in that you do if they do get [30:54] painted might have a little blood yep [31:01] that sort of it's about yeah it the game [31:08] isn't completely frame rates independent [31:10] unfortunately because I didn't know that [31:11] much when I built it certainly so when [31:14] it's a bit of a lower frame rate yeah so [31:20] that was it for my presentations [31:26] [Applause] [31:33] [Applause] [31:41] like once they showed through the [31:44] beginning I've tried triangles on the [31:45] sphere and that's what I've tried [31:51] something that I've been very that I've [31:54] really been wanting to try is doing sort [31:58] of sort of cubish so like X cubes and [32:03] the modules are cubes but you distort [32:05] them to fit together so sometimes you [32:07] have a 5 quart like 5 cubes meeting more [32:10] corners sometimes you have three keeps [32:11] meeting in one corner I think you could [32:12] and then you like just distort the tiles [32:14] to fit them in there I think you could [32:16] make really beautiful sort of medieval [32:18] seats and things like that with my [32:19] curving roads like that with the system [32:22] like that just I I find when you build [32:26] different tile structures sort of work [32:29] for different things right the triangles [32:31] so that's hexa bombs right because [32:33] triangles and hexagons are related are [32:34] really good when you make like for like [32:38] the planet I showed first for example [32:40] when you have bigger terrain shapes and [32:42] then like face they're decent or two but [32:45] four houses in smaller shape so it's [32:47] quite hard to do with triangles it's [32:48] much easier to do with with cuse because [32:51] that's sort of good structure to shape [32:52] yeah but yeah you can do it with [32:54] whatever rigid one you don't even have [32:56] to do with a grave you can do it with my [32:58] completely arbitrary graph and you can [33:02] mix it right you can use both triangular [33:03] tiles on just like tiles some cute eyes [33:06] you've two dimensional of course [33:13] the example you showed that you choose [33:21] yes how did the game choose which [33:24] direction to go yep so that's that's [33:28] approaching general is called the mixed [33:29] initiative approach we're like you asked [33:32] user of some initiative and then the [33:33] algorithm has some initiative [33:34] the game isint makes finished it at all [33:36] like that's all just being picked [33:39] randomly both because of course as you [33:41] saw in the example there were like [33:43] sixteen options in the game there's like [33:45] 300 options so like how would you [33:47] present that I'm sure there is a good [33:49] way to make the proper intro having I'm [33:51] sure there's a really good proper [33:53] interactive tool in this but I haven't [33:54] built it so no the game the game just [33:59] like picks them according to different [34:01] rules [34:02] it starts with houses for example [34:04] because the game the game relies on [34:06] there being houses and also I have a [34:10] bunch of things to drive it so I have [34:11] like fiends and some boxes around these [34:15] which like have names and they have [34:17] different rules so some things for [34:19] example have rules that they're not [34:21] allowed to be next to themselves like [34:23] copies of themselves quite an important [34:25] rule is for modules I don't want to be [34:28] next to a mirror of themselves because [34:30] that usually looks quite bad when you [34:31] have generic pieces next to each other [34:33] and how comes quite often because of [34:34] course a piece fits perfectly next to [34:36] the mirror of itself but I also use [34:39] these four themes right so I have some [34:41] like have some cave parts here this is [34:44] like the case which I only use for some [34:47] levels and then I have some like so my [34:51] ruining structures kind of [34:59] there we go yeah it's this is not my [35:05] normal work station but yeah I have some [35:09] like Ruben type shapes too that I use [35:11] for different than I then I have some [35:12] with like some big boulders and rocks [35:14] and things like that this is actually [35:15] this level here's is a rock theme right [35:18] so you see I have this little nice [35:20] little overhang here it's just like a [35:24] bridge so I had some themes like that [35:29] just to keep you I mean that's also [35:30] always a challenge with procedural [35:31] generation that think things can look to [35:33] see me and this is one of the ways I [35:36] approach that I mean obviously also [35:37] switching colors is searching foliage [35:39] things like that also yeah [35:54] no I'm sure that's good [35:58] I'm sure there's a lot to be gained [35:59] there it's everything so little pull [36:01] here though so modulated myself this I [36:04] mean like it's just a few triangles yeah [36:14] actually you said like for one of the [36:16] island the algorithm has to restart [36:18] twice because the you find enough yeah [36:21] but when do generic can can you like do [36:24] you do you shift the weights of the [36:26] different times you're gonna put or are [36:28] you are doing this the same just they're [36:30] just you're straying from the other I [36:33] mean there's both forming the observers [36:35] the adjacency constraints that have hard [36:38] constraint right always but then there's [36:40] also probabilities and I can put boxes [36:42] around things and see like hey the tiles [36:44] within this area nation and I was like [36:46] the higher or lower probability so yeah [36:50] but the beach thing that happens [36:53] afterwards when it's like it [36:55] this algorithm is done and then it's [36:57] constructing the navmesh and then it's [36:59] checking like is there enough beaches [37:01] where ships good land if there's not [37:03] enough than described gave in this time [37:05] but it kind of fail very earlier in the [37:06] back to and it can fail like there are a [37:09] few validation sets after generation and [37:11] there are a few during so if it closes [37:14] the navigable island so the the network [37:17] its building if that's if that gets [37:20] closed before it reaches the bottom for [37:22] example then you know there's no way [37:24] this [37:25] and then I can discard it before I do [37:32] you