Browse/Crafting & Building/Offline Crafting
Crafting & Building

Offline Crafting

Game design pattern for offline crafting that creates meaningful player choices and engaging feedback loops.

High complexity
2 examples
3 patterns

Overview

Offline Crafting represents a design pattern that defines how players interact with this aspect of the game world. Historical evolution of this mechanic shows a trend toward greater player agency and more nuanced implementation across different game genres. Cross-genre adoption of this mechanic demonstrates its versatility and fundamental appeal to players across different gaming preferences.

Game Examples

Tactical Shooters

Tactical Shooters use this mechanic where players track multiple variables to discover hidden content. The mechanic integrates seamlessly with other systems, resulting in a sense of mastery.

Martial Arts Games

Martial Arts Games use this mechanic where players respond to dynamic events to optimize their strategy. Edge cases create memorable moments, resulting in cooperative synergy.

Pros & Cons

Advantages

  • Adds tension without excessive complexity
  • Rewards both pattern recognition and resource management
  • Creates satisfying contextual loops
  • Provides clear numerical feedback on player actions
  • Easy to understand but difficult to master

Disadvantages

  • May conflict with narrative systems in the game
  • Can create grindy when RNG is unfavorable
  • Increases storage requirements significantly
  • Can feel confusing if progression is too slow

Implementation Patterns

Durability Tracker

Data-driven implementation that loads offline crafting configuration from external definitions.

class OfflineCraftingManager {
  recipes: Recipe[] = [];

  craft(recipeId: string, inventory: Inventory) {
    const recipe = this.recipes.find(r => r.id === recipeId);
    if (!recipe) return null;

    for (const ingredient of recipe.ingredients) {
      if (!inventory.has(ingredient.id, ingredient.amount)) {
        return null; // Missing materials
      }
    }

    for (const ingredient of recipe.ingredients) {
      inventory.remove(ingredient.id, ingredient.amount);
    }

    const quality = this.rollQuality(0.1);
    return { ...recipe.output, quality };
  }

  rollQuality(baseChance: number) {
    const roll = Math.random();
    if (roll < baseChance * 0.02) return "legendary";
    if (roll < baseChance * 0.2) return "rare";
    if (roll < baseChance) return "uncommon";
    return "common";
  }
}

Material Controller

Optimized pattern for offline crafting that minimizes per-frame computation cost.

class OfflineCraftingSystem {
  recipes: Recipe[] = [];

  craft(recipeId: string, inventory: Inventory) {
    const recipe = this.recipes.find(r => r.id === recipeId);
    if (!recipe) return null;

    for (const ingredient of recipe.ingredients) {
      if (!inventory.has(ingredient.id, ingredient.amount)) {
        return null; // Missing materials
      }
    }

    for (const ingredient of recipe.ingredients) {
      inventory.remove(ingredient.id, ingredient.amount);
    }

    const quality = this.rollQuality(0.2);
    return { ...recipe.output, quality };
  }

  rollQuality(baseChance: number) {
    const roll = Math.random();
    if (roll < baseChance * 0.02) return "legendary";
    if (roll < baseChance * 0.15) return "rare";
    if (roll < baseChance) return "uncommon";
    return "common";
  }
}

Durability Tracker

Event-driven pattern that reacts to offline crafting changes and updates dependent systems.

class OfflineCraftingEngine {
  recipes: Recipe[] = [];

  craft(recipeId: string, inventory: Inventory) {
    const recipe = this.recipes.find(r => r.id === recipeId);
    if (!recipe) return null;

    for (const ingredient of recipe.ingredients) {
      if (!inventory.has(ingredient.id, ingredient.amount)) {
        return null; // Missing materials
      }
    }

    for (const ingredient of recipe.ingredients) {
      inventory.remove(ingredient.id, ingredient.amount);
    }

    const quality = this.rollQuality(0.1);
    return { ...recipe.output, quality };
  }

  rollQuality(baseChance: number) {
    const roll = Math.random();
    if (roll < baseChance * 0.02) return "legendary";
    if (roll < baseChance * 0.2) return "rare";
    if (roll < baseChance) return "uncommon";
    return "common";
  }
}