Lesson 30
Crafting systems: discovery, cost and payoff.
Crafting works when ingredients create interesting choices. The player should understand resources, experiment safely and feel that crafted items change the run.
1. Ingredients are simple item data
Start with ingredient definitions. Each ingredient needs an id, name, icon or color and a short use hint. The hint can be vague if recipe discovery matters, but the player still needs enough information to care about collecting it.
const INGREDIENTS = {
herb: { id: "herb", name: "Moon Herb", icon: "leaf", hint: "Restores and softens effects." },
crystal: { id: "crystal", name: "Glow Crystal", icon: "gem", hint: "Amplifies energy." },
bone: { id: "bone", name: "White Bone", icon: "bone", hint: "Hardens defensive brews." },
mushroom: { id: "mushroom", name: "Moss Cap", icon: "cap", hint: "Changes movement and perception." }
};
Source pattern: Supagames Potion Crafter stores ingredients, recipe keys, potion effects, inventory slots and discovery feedback as explicit game data.
2. Inventory slots limit decisions
If the player can carry infinite ingredients, crafting loses tension. Slots create choices: keep the rare crystal, drop the common herb, save a defensive recipe for the boss. A small inventory is easier to display on mobile too.
const inventory = {
ingredients: [null, null, null, null],
potions: []
};
function addIngredient(id) {
const slot = inventory.ingredients.findIndex(item => item === null);
if (slot < 0) return false;
inventory.ingredients[slot] = id;
updateInventoryUI();
return true;
}
Returning false lets the game show "Bag full" without losing the item. Never silently delete a pickup because the inventory is full.
3. Recipes need stable keys
For two-ingredient recipes, sort ids before looking them up. That way herb plus crystal equals crystal plus herb. If order matters in your design, keep it unsorted, but make that rule clear.
const RECIPES = {
"crystal,herb": { name: "Light Tonic", effect: "heal", value: 12 },
"bone,crystal": { name: "Crystal Shield", effect: "shield", value: 10 },
"herb,mushroom": { name: "Swift Brew", effect: "speed", value: 6 }
};
function recipeKey(a, b) {
return [a, b].sort().join(",");
}
function findRecipe(a, b) {
return RECIPES[recipeKey(a, b)] || null;
}
Stable keys also make save data easier. You can store discovered recipe keys and rebuild the recipe book after loading.
4. Discovery is feedback
Recipe discovery should feel like a reward. When the player brews a known recipe, show the result. When they discover a new recipe, add it to the recipe book, play a sound and explain the effect.
function craftSelected(a, b) {
const recipe = findRecipe(a, b);
if (!recipe) {
showCraftResult("The mixture fizzles.");
return false;
}
const key = recipeKey(a, b);
save.discoveredRecipes[key] = true;
inventory.potions.push({ ...recipe, key });
removeIngredients(a, b);
showCraftResult("Discovered " + recipe.name + ": " + recipe.effect);
persistSave();
return true;
}
Failure should still teach. "Fizzles" is fine once, but repeated failure can show hints such as "This needs something brighter."
5. Effects should alter play, not only numbers
A healing potion is useful, but crafting becomes richer when potions change movement, stealth, damage, visibility, resistance or resource gain. Effects should have duration and clear UI.
function usePotion(potion) {
if (potion.effect === "heal") player.hp = Math.min(player.maxHp, player.hp + potion.value);
if (potion.effect === "speed") effects.speedBoost = potion.value;
if (potion.effect === "shield") effects.shieldTurns = potion.value;
if (potion.effect === "invisible") effects.invisible = potion.value;
removePotion(potion.key);
updateStatusUI();
}
If a potion changes rules, show that status clearly. A hidden speed boost or invisible shield feels like a bug.
6. Crafting QA checklist
- Inventory full state is visible and does not delete pickups.
- Recipe lookup works regardless of ingredient order if order is not part of the design.
- Known and new recipes have different feedback.
- Crafted item effects are visible in the HUD.
- Save data preserves discovered recipes and current inventory.
- Mobile UI can select ingredients without tiny targets.
Crafting is strongest when it turns exploration into planning. Ingredients should not be clutter; they should be future decisions.
7. Give ingredients a world source
Crafting feels better when ingredients come from meaningful places. Herbs might grow near healing springs, crystals might appear in dangerous caves and bones might drop from skeleton guards. The source teaches the player what the ingredient means before the recipe book explains it. If every ingredient appears randomly everywhere, collection becomes noise.
Connect recipes to level goals. A swamp level can reward antidotes, a stealth level can reward smoke bombs and a boss level can reward shields. This makes crafting part of strategy instead of a separate menu. The best crafting systems let players prepare for a known problem, not just convert clutter into generic bonuses.
When saving inventory, store item ids and counts rather than copied display names. Names, icons and balance values can change later, but stable ids let old saves continue to load after an update.