FoodieSort

A Drag-to-Match Food-Tray Puzzle — Unity Template

Version 0.1.0 | Unity 6+ | URP

🌟 This asset was made with the HyperCasual Game Engine!   Check it out — mobile puzzle templates & more. Click here to learn more ➔

Important: Template / Starting Point

This asset is designed as a template and starting point for your own game development. It provides the core systems, architecture, and tools needed to build a drag-to-match food-tray puzzle. You are expected to customize, extend, and modify this template to create your unique game. This is not a complete, ready-to-publish game — it's a foundation for developers to build upon.

Overview

FoodieSort is a drag-to-match food-tray puzzle where players slide foods from one tray's ready row to another to assemble matching trios. Each food line carries a small queue of upcoming turns; clearing a line's three ready slots animates the foods together, removes them, and promotes the next waiting turn. Win by clearing every line; lose if every ready slot fills up with no room left to drop. The game ships with three boosters, per-level progression via PlayerPrefs, a visual level editor, an automatic level generator, an in-game developer mode panel, and a one-click setup wizard.

Drag-to-Match Puzzles

Touch or click a food in any line's ready row and drag it to another line. When all three ready slots on a line share the same food type, the line auto-completes with a star burst.

Level Editor

A full Unity Editor window with a visual grid painter, per-line turn editor, paint-mode toggles (None / Place / Erase), and per-level booster counts + time-limit fields.

Auto Level Generator

Batch-generate hundreds of fresh levels with a concave difficulty curve. Append-only — never overwrites hand-tuned levels. Every generated turn carries exactly one gap slot so the player can always make a move.

Three Boosters

Shuffle (randomizes the next queued turn on a random line), Plate Clear (removes a stuck line's ready row), and Extra Slot (forces a free refill turn on a random line). Each booster has a one-time tutorial popup.

One-Click Setup Wizard

Run Tools > FoodieSort > Setup Wizard once and the project builds the canonical HUD from the shipped prefabs, wires every audio clip in Audio/, and applies every required Player + Quality Setting in one pass.

JSON Levels

Levels ship as plain JSON under Resources/Levels/. Regenerate or extend at any time with the Auto Level Generator; hand-tune the first five with the Level Editor for the best first-run experience.

Getting Started

Requirements

Installation

  1. Import the FoodieSort package into your Unity project
  2. Ensure the project is set to use the Universal Render Pipeline (check Edit > Project Settings > Graphics)
  3. Open the game scene at Assets/FoodieSort/Scenes/GameNew.unity
  4. Run Tools > FoodieSort > Setup Wizard once — click ▶ Run All Steps. It builds the HUD from the canonical prefabs in Prefabs/, wires every AudioClip in Audio/, and applies every Player + Quality Setting
  5. Press Play to test the first level

Quick Test

Once in Play mode, drag any food from a line's ready row onto another line's ready row. When all three slots on a line share the same food, the line bursts and clears. Clear every line to win — the next level loads automatically after a short delay. Press the Right Arrow key during gameplay to skip ahead and inspect later levels; press the gear-icon Developer Mode button (top-left) to open the dev panel.

Folder Structure

FoodieSort/ │ ├── Art/ — Materials, textures (subsumed shared package textures land here) │ └── Textures/ │ ├── Audio/ — 60+ WAV SFX auto-wired by the Setup Wizard │ ├── Datas/ — ScriptableObject configuration (FoodData + per-food sprite sets) │ ├── Documentation/ — This documentation file │ ├── Editor/ │ ├── LevelEditorWindow.cs — Visual level designer │ ├── AutoLevelGeneratorWindow.cs — Automatic batch level generator │ ├── FoodieSortSetupWizard.cs — Idempotent scene + settings bring-up │ ├── FoodieSortWelcomePopup.cs — First-open welcome + review CTA │ └── InputSystemCheck.cs — Warns / blocks Play mode if Input Handling is "New only" │ ├── PostProcessing/ — HC URP volume profile (per-game GUID) │ ├── Prefabs/ — Gameplay prefabs + canonical UI prefabs │ ├── Food.prefab, TrayTemp.prefab, Trigger.prefab, Partical.prefab │ ├── GameHUD_Panel.prefab — Bottom holder + 3 booster buttons + level text │ ├── LevelClearedPanel.prefab — Win screen (trophy + Continue) │ ├── GameOverPanel.prefab — Lose screen (skull + Retry) │ ├── Popup_Powerup_Picker.prefab — First-use booster tutorial popup │ └── DeveloperModeButton.prefab — Dev panel toggle button + DeveloperSettingsPanel child │ ├── Resources/ │ └── Levels/ — JSON levels (Level_1.json, Level_2.json, …) │ ├── Scenes/ │ └── GameNew.unity — Main game scene (single playable scene) │ └── Scripts/ — All runtime scripts ├── FoodieSortGameManager.cs — Scene orchestrator + arrow-key level nav ├── GameLevel.cs — Bootstrap that pulls the current level from LevelManager ├── GridManager.cs — Line layout, drag handling, match detection, win/lose ├── InputManager.cs — Touch / mouse drag handling for foods ├── Food.cs, FoodData.cs — Per-food component + sprite-set ScriptableObject ├── LevelData.cs — Runtime level descriptor (POCO; serialized to JSON) ├── LevelManager.cs — Static JSON loader + level progression ├── SoundManager.cs — Singleton audio (HypercasualGameEngine namespace) ├── BoosterManager.cs — Shuffle, Plate Clear, Extra Slot (HypercasualGameEngine namespace) ├── LoseWinPanelManager.cs — Win/Lose orchestrator + Next Level wiring ├── DeveloperModeButton.cs — Toggle the DeveloperSettingsPanel └── DeveloperSettingsController.cs — Dev panel actions + Shift+combo hotkeys

Single-folder publishing: the entire game ships from Assets/FoodieSort/. There is no sibling Boosters-UI-DevSettings/ or Hypercasual Game Engine/ folder — both legacy folders have been subsumed into FoodieSort/ with all prefab and script GUIDs preserved.

Gameplay

Core Mechanics

Each level lays out a set of food lines on a small grid. Every line owns three ready slots and a queue of turns waiting in the wings. Each turn delivers up to three foods into the ready row when the previous turn clears. The player drags foods between lines to assemble matching trios. When a line's three ready slots all share the same TypeFood, the line plays a satisfaction animation, fires PlayFoodMatch, and the next turn promotes into ready.

Food Palette

23 food types are defined in TypeFood (in LevelData.cs) with a matching color per food in FoodColors. A small representative sample:

TypeColorApprox. RGB
Meet Reddish brown(200, 80, 60)
Fish Sea blue(100, 180, 220)
Cake Pink(255, 180, 200)
Avocado Green(80, 160, 80)
Cheese Yellow(255, 210, 80)
Grapes Purple(130, 60, 160)

Win & Lose Conditions

Animations & Feedback

Foods scale up briefly when picked up (drives PlayFoodTap), fly along an arc when placed (PlayFoodPlace), and burst together with a particle effect when a line completes (PlayFoodMatch). The Partical.prefab (intentional spelling kept for backwards-compat with existing references) handles the particle burst.

Scripts Reference

Runtime scripts use the FoodieSort.Scripts namespace. Editor scripts use FoodieSort.Editor. Shared engine helpers (SoundManager, BoosterManager, LoseWinPanelManager, DeveloperSettingsController, DeveloperModeButton) live under HypercasualGameEngine.

Core Scripts

ScriptDescription
FoodieSortGameManager Scene orchestrator. [DefaultExecutionOrder(-100)] so Awake runs before every other MonoBehaviour. Auto-creates missing infrastructure (EventSystem, SoundManager, BoosterManager) using FindFirstObjectByType<T>() — never T.Instance == null guards (pitfall #15). Routes TriggerWin / TriggerLose through SoundManager + LoseWinPanelManager. Hosts the ←/→ arrow-key level nav (pitfall #12 — lives here, not on DeveloperSettingsController).
GameLevel Bootstrap MonoBehaviour. Pulls LevelManager.GetCurrentLevel() in Awake and hands it to GridManager.Initialize. Fails loudly with a Debug.LogError when the manager returns null — never silently falls back to an Inspector-assigned asset (pitfall #13).
GridManager Owns the live food-line layout, drag-and-drop placement, match detection, and win/lose detection. Routes win/lose through FoodieSortGameManager.TriggerWin/Lose — never directly through LoseWinPanelManager (pitfall #10).
InputManager Tap/drag handling. Raycasts to pick a food, tracks the drag, snaps to the destination line on release. Fires PlayFoodTap on pick-up.
Food / FoodData Per-food MonoBehaviour and the sprite-set ScriptableObject lookup.
LevelData [Serializable] POCO — not a ScriptableObject — serialized to JSON. Contains gridWidth, gridHeight, timeLimit, a list of FoodLineData (each with a grid position + list of three-slot turns), and the three booster *Count + *UnlockLevel fields. Because it's a POCO, Resources.UnloadUnusedAssets doesn't sweep it — no HideFlags needed (the pitfall #1 trap only applies to runtime ScriptableObjects).
LevelManager Static JSON loader. Editor reads from the hard-coded path Assets/FoodieSort/Resources/Levels/; built players use a strict sequential Resources.Load<TextAsset>("Levels/Level_" + i) loop. PlayerPrefs key: FoodieSort_CurrentLevel (game-prefixed per pitfall #16). Exposes GetCurrentLevel, AdvanceLevel, GoToLevel, GoToPreviousLevel, ForceReload, plus editor-only SaveLevel / LoadAllLevelsFromDisk / GetExistingLevelCount.
DeveloperModeButton Toggles the DeveloperSettingsPanel child visible/invisible. Auto-finds the panel child if no Inspector reference is assigned. Field name is developerSettingsPanel (pitfall #28: the SetupWizard wires this via SerializedObject.FindProperty("developerSettingsPanel")).

Engine Scripts (HypercasualGameEngine namespace)

ScriptDescription
SoundManager Singleton with one AudioSource. Master-volume slider plus one PlayX() per gameplay event: PlayLevelClear (alias PlayLevelWin), PlayLevelFail, PlayBlocked, PlayFoodTap/Place/Match, PlayShuffle/PlateClear/ExtraSlot, PlayBoosterUse, PlayButtonClick. Four random-pick variant pools (successClips, failClips, popClips, switchClips) seed the moment-to-moment SFX so the same clip never plays twice in a row.
BoosterManager Single source of truth for the three booster counts (pitfall #23). Finds Booster-Button-1/2/3 + Booster-Popup-1/2/3 via an inactive-aware scan (Resources.FindObjectsOfTypeAll<Transform> filtered by scene.IsValid() + hideFlags == None) so initially-inactive popups are still discoverable (pitfall #19). TryShowBoosterPopup fires before any can-run gate so the tutorial popup always reaches the player on first click (pitfall #31), and wires MainPanel/ButtonContinue.onClick to dismiss the popup since the shared prefab ships with an empty listener list. U hotkey reads both legacy Input and the new Keyboard.current (pitfall #26) and is self-healing (re-runs HookUpExistingBoosters / FindBoosterPopups if refs are stale).
LoseWinPanelManager Lives on its own GameObject (separate from either panel) so deactivating a panel doesn't hide the manager. winPanel → LevelClearedPanel scene instance, losePanel → GameOverPanel — two SEPARATE prefabs, not nested children of a monolithic container. ShowWin / ShowLose re-attach button listeners on every show (pitfall #30 listener variant); both click handlers are public so Inspector-wired persistent OnClicks can also reach them, and each emits a Debug.Log. LoadNextLevel() calls LevelManager.AdvanceLevel() before SceneManager.LoadScene (pitfall #2 fix).
DeveloperSettingsController Dev-panel actions: next/previous/reload level, trigger win/lose, unlock + refill boosters, reset popup-shown PlayerPrefs, ad-panel toggles. Shift+U/W/L/R hotkeys handled in Update() (Shift combos only — bare arrow keys live on FoodieSortGameManager per pitfall #12). Cross-namespace calls (e.g. LevelManager.AdvanceLevel) are reached via the using FoodieSort.Scripts; at the top of the file (pitfall #13).
DeveloperModeButton Same script as listed under Core — lives in HypercasualGameEngine namespace post-subsume.

Editor Scripts

ScriptDescription
FoodieSortSetupWizard Tools > FoodieSort > Setup Wizard. Idempotent end-to-end project bring-up. Step 3 instantiates the canonical UI prefabs from Assets/FoodieSort/Prefabs/ via PrefabUtility.InstantiatePrefab — never new GameObject(...) an equivalent tree (pitfalls #25/#30). Detects procedural leftovers via PrefabUtility.IsPartOfPrefabInstance and rebuilds them from the prefab. If any canonical prefab or the Audio/ folder is missing, logs ERR and refuses to mark the step OK — never silently falls back. Win/Lose Canvas sort orders are Inspector-authored; the wizard touches popup (sort 20) and dev-panel (sort 30) sort orders only.
FoodieSortWelcomePopup [InitializeOnLoad]. Welcome popup shown once per Unity session on project open with two CTAs: “Run Setup Wizard” and “Open Documentation”. Permanently dismissable via EditorPrefs. Reopenable at Tools > FoodieSort > Welcome.
LevelEditorWindow Tools > FoodieSort > Level Editor. Two-pane visual editor — see Level Editor section below.
AutoLevelGeneratorWindow Tools > FoodieSort > Auto Level Generator. Batch generator — see Auto Level Generator section below.
InputSystemCheck [InitializeOnLoad] guard. Warns — and blocks Play mode — when Active Input Handling is set to "New only". Offers a one-click "Fix & Restart" that flips the setting to "Both" and reopens the project. Verbatim copy of the canonical InputSystemCheck.cs from the HypercasualGameEngine — do not rewrite.

Level Editor

Opening the Level Editor

In the Unity menu bar, go to Tools > FoodieSort > Level Editor. This opens a dedicated Editor window — no need to enter Play mode.

Features

Paint Modes

ModeLeft Click
NoneNo painting — click cells to inspect
PlacePlace a new food line at the clicked cell. Click an occupied cell to select that line for editing in the right panel.
EraseRemove the food line at the clicked cell.

Level Storage

All levels are stored as individual JSON files in Assets/FoodieSort/Resources/Levels/ with the naming convention Level_1.json, Level_2.json, … The runtime loader reads from this hard-coded path in the Editor and via strict sequential Resources.Load<TextAsset>("Levels/Level_" + i) in built players (pitfalls #9 / #11 — never let multiple Resources/Levels folders silently merge).

Automatic Level Generator

The Automatic Level Generator is an Editor tool that batch-generates playable food-line levels. It controls grid size, food-line count, turn count, food-type variety, time budget, and per-level booster counts. Generated levels respect the per-turn gap invariant (pitfall #29) so the player can always make a move at level start.

Opening the Tool

In the Unity menu bar, go to Tools > FoodieSort > Auto Level Generator. This opens a settings window where you can configure all parameters before generating.

Settings

SettingDescription
Levels to GenerateHow many new level files to create. New levels are appended after existing ones (never overwrites).
Grid Size (Min & Max)Range for the grid columns / rows. Early levels lean toward the minimum, harder levels toward the maximum.
Food Lines (Min & Max)Range for the number of food lines per level.
Turns per Line (Min & Max)Range for how many turns each line carries in its waiting queue.
Food Types Used (Min & Max)Range for how many of the 23 food types are sampled per level.
Time Limit (Easy / Hard)Seconds of countdown budget; lerps from the easy value at level 0 to the hard value at the last generated level.
Easy Match Chance (Easy / Hard)Probability that a non-anchor turn ships as a matching pair. Drops as difficulty rises so harder levels feel more random.
Boosters per LevelPer-booster count + unlock level written into every generated level's JSON.

How It Works

  1. Configure settings — adjust all parameters in the Editor window
  2. Click “Generate Levels” — the tool detects existing levels and appends new ones
  3. For each level, the generator:
    • Lerps grid size, line count, turn count, and food-type count along the difficulty curve with ±7% jitter
    • Pre-assigns one distinct “home type” per line so different lines have different turn-0 anchor pairs
    • Pre-assigns distinct gap indices per line via round-robin so the gap isn't always in the same slot
    • Builds turn 0 and turn 1 of each line as [home, home, gap] so the player can read each line's intent at a glance
    • For turns 2+, picks each turn's matching probability from the difficulty curve and ensures the non-gap slots either form a matching pair or two distinct types
    • Writes the JSON level file to Resources/Levels/Level_N.json
    • Forces GC.Collect() every 50 levels to keep the editor responsive

Initial-State Playability Invariant (pitfall #29)

Every generated turn carries exactly one None / gap slot. There is no “force one fully-matching turn per line” post-process — that pattern auto-solves the first row on the first drop, ending the level before the player has played it. With the gap invariant the player creates matches organically by dragging into the gap.

Non-Destructive

The generator never overwrites existing level files. It always starts numbering from the next available index (e.g., if Level_30 exists, new levels start from Level_31). Use the red “Delete All Levels” button at the bottom of the window if you want to start fresh.

Boosters

The game includes three booster abilities that players can activate during gameplay. Each booster has a configurable number of uses per level (set in the level JSON) and a one-time tutorial popup keyed in PlayerPrefs.

BoosterEffectActivation
Shuffle Fisher-Yates shuffles the three slots of the next queued turn on a random food line. Useful when the next turn would over-fill a line. Tap the Shuffle button (Booster-Button-1). Default unlock: level 1. Default count: 3.
Plate Clear Finds the first line with any filled ready slots, destroys those foods, and promotes the next waiting turn so the line can keep playing. Tap the Plate Clear button (Booster-Button-2). Default unlock: level 2. Default count: 3.
Extra Slot Forces an immediate refill (ApplyFoodIntoLine) on a random line — effectively a free extra turn. Tap the Extra Slot button (Booster-Button-3). Default unlock: level 3. Default count: 3.

Booster Buttons

Booster buttons live in the scene under GameHUD_Panel/BottomHolder. Each button has:

Tutorial-Popup Visibility Rule (pitfall #31)

TryShowBoosterPopup fires before any can-run gate — the popup is educational content and must reach the player on first click regardless of board state. The MainPanel/ButtonContinue child of the popup ships with an empty onClick list, so BoosterManager wires it to popup.SetActive(false) at show-time; without that, the popup would block input forever.

Cheat Mode

Press U during gameplay to unlock and refill all boosters — the handler reads both legacy Input.GetKeyDown and the new Keyboard.current.uKey.wasPressedThisFrame (pitfall #26) and is self-healing (re-runs HookUpExistingBoosters / FindBoosterPopups if refs are stale). Press Right Arrow / Left Arrow to jump to the next / previous level. Both shortcuts live on FoodieSortGameManager.Update() (not DeveloperSettingsController) so they fire even when the dev panel is closed (pitfall #12).

Customization Guide

Adding New Food Types

To add a new food type:

  1. Open LevelData.cs and add a new entry to the TypeFood enum:
    public enum TypeFood { None, Meet, Fish, // … existing entries … // Add your custom food: Sushi }
  2. In the same file, add the food's color to the FoodColors dictionary:
    { TypeFood.Sushi, new Color32(220, 50, 80, 255) },
  3. Add the food's sprite to the FoodData ScriptableObject in Datas/Food/
  4. Regenerate or hand-author a level that uses the new type

New foods are automatically available in the Level Editor, the Auto Level Generator, and at runtime.

Adjusting Grid Visuals

The grid layout is controlled by GridManager.cs. Key Inspector properties:

Audio

Audio lives on a single SoundManager singleton. The Setup Wizard auto-wires every clip in Assets/FoodieSort/Audio/ by canonical filename (LevelClear, LevelFail, Blocked, Switch24/25/38, Pop26/30, Success5, etc.) and seeds the four random-pick variant pools by prefix. Replace any clip on the SoundManager GameObject in the scene to retheme a specific event. Add a new clip field + matching PlayX() method if you introduce a new gameplay event.

Adding New Boosters

New boosters can be added by following the existing pattern in BoosterManager.cs:

  1. Add a count field to LevelData (e.g. public int myBoosterCount = 3;) and an unlock-level field
  2. Add a fourth named child Booster-Button-4 to the GameHUD_Panel/BottomHolder hierarchy with the same Image + Bg + PowerUpCountText children as the existing buttons
  3. Wire it up in HookUpExistingBoosters() with the same FindInactiveByName + button-listener pattern
  4. Implement the booster logic in a new OnBooster4Clicked() with TryShowBoosterPopup as the FIRST line (pitfall #31)
  5. Add a fourth Booster-Popup-4 popup with Canvas overrideSorting=true sortOrder=20
  6. Update UpdateBoosterUI() to draw the new button's lock / unlock / count display

JSON Level Format

Each level is a JSON file with the following structure:

{ "gridWidth": 3, "gridHeight": 3, "timeLimit": 120.0, "foodLines": [ { "gridPosition": { "x": 0, "y": 0 }, "turns": [ { "typeFoods": [1, 1, 0] }, // Meet, Meet, gap { "typeFoods": [0, 1, 1] } // gap, Meet, Meet ] } ], "shuffleBoosterCount": 3, "plateClearBoosterCount": 3, "extraSlotBoosterCount": 3, "shuffleUnlockLevel": 0, "plateClearUnlockLevel": 1, "extraSlotUnlockLevel": 2 }

The foodLines array describes every food line on the grid. Each line has a gridPosition (where it sits) and a list of turns; each turn has three typeFoods (int values from the TypeFood enum — 0 = None = the gap). Per the pitfall #29 invariant, every turn must contain exactly one 0.

Support

Common Issues

IssueSolution
No levels load / empty scene at Play Ensure JSON level files exist in Assets/FoodieSort/Resources/Levels/ with the naming convention Level_1.json, Level_2.json, etc. Use the Auto Level Generator to create levels if the folder is empty.
Pink / magenta materials on objects The project requires URP. Go to Edit > Project Settings > Graphics and ensure a URP Render Pipeline Asset is assigned. Check that the Settings/ folder contains valid URP assets.
HUD looks wrong / missing booster buttons The wizard's HUD step refuses to procedurally fall back when the canonical prefabs are missing (pitfall #30). Check the Console for ERR <prefab> missing at Assets/FoodieSort/Prefabs/…. Re-import the canonical prefab pack and re-run Tools > FoodieSort > Setup Wizard step 3.
SoundManager has empty clip fields The wizard's audio step also refuses to silently pass when the Audio/ folder is empty (pitfall #30 audio variant). Re-import the audio library, then re-run step 4.
Booster buttons don't respond Ensure the GameHUD_Panel/BottomHolder hierarchy contains children named Booster-Button-1, Booster-Button-2, and Booster-Button-3. Each needs a Button component. If you added a Canvas for sorting order, you must also add a GraphicRaycaster.
First-use booster popups never show The popups Booster-Popup-1/2/3 must exist in the scene with m_IsActive: 0. GameObject.Find skips inactive objects, so BoosterManager uses an inactive-aware scan (pitfall #19) — if you renamed the popups, restore the literal names. To replay the tutorial, click “Reset Booster Popups” on the Developer Settings panel.
Continue / Retry button visible but does nothing LoseWinPanelManager.ShowWin/ShowLose re-attach the button listeners on every show (pitfall #30 listener variant). If the click still doesn't fire, check the Console for the [LoseWinPanelManager] LoadNextLevel / OnRetryClicked log lines — absence means the click never reached the handler. Verify the Inspector-wired winPanel / losePanel fields point at LevelClearedPanel / GameOverPanel and not at the legacy monolithic LoseWinPanel container.
Win panel renders behind the HUD The win/lose Canvas sortingOrder is Inspector-authored, not wizard-set. Select LevelClearedPanel and GameOverPanel in the scene, set their Canvas.overrideSorting = true and sortingOrder = 10 (or higher than the root Canvas).
Level Editor shows empty grid Click “Reload from Disk” in the Level Editor window, or ensure the Resources/Levels/ folder exists and contains valid JSON files.
Want ad-test buttons on the dev panel The shipped DeveloperSettingsController matches the canonical from 12-developer-mode.md and does not include ad-test handlers (which would require the Ragendom Monetization module). To add them: import the Ragendom Monetization package, then re-add the showBannerButton / showInterstitialButton / showRewardedButton field declarations and the matching Ragendom.AdsManager.* handlers per the richer shared variant documented in 12-developer-mode.md ยง Pre-existing shared package.

Tips

Contact Us

If you get stuck or have any issues, feel free to reach out to us at ragendom@gmail.com. We are happy to help!