SURVIVE & FRY

Overview

Survive & Fry is a cooking game built for GitHub's Game Off 2023, a month-long jam running November 1st to December 1st with over 600 entries. The theme was SCALE and players assembled food orders to cure zombies rather than escape them, with a literal food scaling step baked into the preparation pipeline as our theme interpretation.

Project Details

Platform: PC

Engine: Unreal Engine

Tech: C++

Team Size: 3 People

Jam: GitHub Game Off 2023 (611 entries, theme: SCALE)

Source Code: Available on GitHub

The Preparation Pipeline

The preparation loop runs through four steps before a sandwich can be served: chop a vegetable at the chopping desk, combine the chopped ingredient with the bread, add the antidote, and scale the portion to match the current order before submitting it.

Each step is enforced through the item combination system rather than a locked sequence. The player can attempt steps in any order, but the system only accepts items that match what the bread is expecting at that point. This creates a loop where reading the current task before preparing is the natural behaviour rather than something the game has to force.

Correct serves add 5 seconds to the session timer. Wrong serves deduct 10. That asymmetry is where most of the pressure in the game comes from.

The Ingredient System

Ingredients are tracked on the bread using a TSet of FName values rather than individual boolean flags. Each item in the world carries an IngredientName property on the base AItem class. When a chopped item is combined with the bread, the system casts to AItem, reads the name, and either adds it to the owned ingredient set or handles it as an antidote.

This means no per-type casts are needed anywhere in the combination logic. Adding a new ingredient requires a new DataTable row and six lines of code, with no changes to the interaction or validation logic.

The ingredient mesh map, which links ingredient names to their corresponding visual mesh components on the bread, is populated in BeginPlay rather than the constructor. UPROPERTY TMaps are serialized into Blueprint subclasses, so anything added in the constructor risks being overridden by cached Blueprint data when new ingredients are added later. BeginPlay rebuilds the map fresh every time, which avoids that entirely.

The antidote is handled separately because it has unique visual behaviour — a particle effect and a sound — that does not fit the generic ingredient pipeline.

Order Generation

Each order is generated by PickNewOrder, which selects a random row from the IngredientDataTable and a random scale value between 1 and 3. The DataTable row defines the ingredient's internal name and its display name for the HUD.

Adding a New Ingredient — Onion

To make this concrete: adding onion as a new ingredient works like this.

In Bread.h, declare the new mesh component (2 lines):

UPROPERTY(VisibleAnywhere, meta = (AllowPrivateAccess = true))
class UStaticMeshComponent* OnionVegetableMesh;

In Bread.cpp, create and attach it in the constructor, and register it in IngredientMeshMap in BeginPlay (4 lines):

// Constructor
OnionVegetableMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Onion"));
OnionVegetableMesh->SetupAttachment(RootSceneComponent);
OnionVegetableMesh->SetMobility(EComponentMobility::Movable);

// BeginPlay
IngredientMeshMap.Add(TEXT("Onion"), OnionVegetableMesh);

Then create a whole onion Blueprint inheriting from AVegetable with CanBeChopped = true, and ChoppedItemReference pointing to a chopped onion Blueprint inheriting from AChoppedVegetable, with IngredientName "Onion". Place the whole onion on a respawning item desk in the level and connect the references. Add a new row to the DataTable with IngredientName "Onion" and DisplayName "Onion Sandwich".

That is everything. No changes to ServingDesk, CombineItems, or ValidateServe anywhere. The full process from code change to a working in-game order takes around five minutes.

Serve Validation

ValidateServe handles both success and failure outcomes in a single function. It checks three conditions: whether the bread contains the required ingredient, whether the antidote has been added, and whether the current scale matches the order. Common cleanup runs after both outcomes, clearing the desk, resetting the scale, picking a new order, and destroying the bread.

Development Under Jam Constraints

A month is not a lot of time. The architecture decisions that made the project manageable were ones that kept iteration contained. When something needed adjusting, the change was localised and did not ripple into unrelated systems. That kind of clean separation matters a lot when iteration cycles need to be fast.

The game was well received by the community, praised for being smooth and well crafted with no visible bugs, which was a result we were proud of given the timeline.

What I Learned

Jam constraints force you to make decisions you would normally postpone. Scope creep is not possible when the deadline is real, which means every feature has to justify its existence immediately. That pressure produced better scoping decisions than I would have made with unlimited time. It is a discipline I try to carry into personal projects now even without a deadline forcing it.

Gameplay Video