Guide 17 — Store Simulator
Sanad Ammari
What you're building
A first-person shop management sim in the tradition of Supermarket Simulator and Gas Station Simulator. Order stock, carry boxes, fill shelves, serve customers at the register, pay the rent, and try to still be in business tomorrow.
Yours is the only fully 3D game in the class, and the only one that needs a baked NavMesh — which is worth knowing up front because it's the one step where nothing works at all until it's done.
Time: about four hours. The NavMesh and the first-person setup are front-loaded; once customers walk, the rest goes quickly.
1. Before you start
- Finish Guide 0.
- Install one extra package: AI Navigation (Window → Package Manager → Unity Registry).
- One new input action:
Pickup. - You do not need Guide B §B1 (the 2D player) or §B3 (the 2D camera). Your setup is different — sections 3 and 4 replace them.
2. Scripts you'll use
The store (Tier 7)
FirstPersonController · ProductType · Carryable · PickupCarry · ShelfSlot · StoreEconomy · CustomerAI · Register · CustomerSpawner
Core
GameManager · SceneLoader · AudioManager · HUDController · ChoiceFlagManager
Saving (Tier 7)
SaveData · SaveSystem
Interaction (Tier 6)
Interactable · InteractionPrompt — optional, for a shop terminal
You will not need: anything 2D. No PlayerController2D, no Health, no combat, no hazards.
3. Build the store and bake the NavMesh
Do this first. Customers are your entire game and they cannot move one centimetre without it.
The room
- GameObject → 3D Object → Plane, name it
Floor. Scale it to about (3, 1, 3) — that's a 30×30 metre shop. - Walls: 3D Object → Cube, scaled thin and tall, around the edges. Leave a gap for the door.
- Shelves: cubes for now. Grey boxes are fine — you're testing whether customers can walk, not how it looks.
Bake it
- Window → AI → Navigation. (Unity 6 with the AI Navigation package: instead select the
Floorand Add Component → NavMeshSurface.) - With
NavMeshSurfaceon the floor, click Bake. - You should see a blue overlay on the walkable area of your floor. Walls and shelves should be cut out of it.
If there's no blue
| Problem | Fix |
|---|---|
| Nothing bakes at all | The floor has no NavMeshSurface, or you clicked Bake on the wrong object |
| Blue covers the shelves | Shelves need colliders — a Cube has one by default; check they're not disabled |
| Blue doesn't reach the corners | Agent Radius is too large in the bake settings — lower it to 0.3 |
| Blue is patchy | Your floor is made of many small objects with gaps between them |
Do not proceed until you see blue. Everything after this depends on it.
4. Build the player
3D, so this is different from every other guide.
- GameObject → Create Empty, name it
Player. Tag →Player. - Add a CharacterController:
| Setting | Value |
|---|---|
| Height | 1.8 |
| Radius | 0.3 |
| Center | (0, 0.9, 0) |
- Add Player Input — Behavior: Send Messages, Default Map Player.
- Add FirstPersonController:
| Field | Value |
|---|---|
| Walk Speed | 3.5 |
| Sprint Speed | 6 |
| Crouch Speed | 1.8 |
| Gravity | −20 |
| Jump Height | 1.1 |
| Camera Pivot | see step 5 |
| Mouse Sensitivity | 0.12 |
| Stick Sensitivity | 120 |
| Min / Max Pitch | −80 / 80 |
| Stand Height | 1.8 |
| Crouch Height | 1.1 |
| Lock Cursor | ✓ |
- Right-click
Player→ Create Empty, name itCameraHolder, position (0, 1.6, 0). Drag your Main Camera inside it, at local position zero. DragCameraHolderinto Camera Pivot. - Right-click
CameraHolder→ Create Empty, name itCarryPoint, position (0, −0.2, 0.8) — in front of and slightly below the camera.
Escape releases the cursor. Useful in the editor. If you add a pause menu, wire it to
FirstPersonController.UnlockCursor.
Press Play now. You should be able to walk and look around.
5. Create your products
Right-click Project → Create → GAME1303 → Product, once per item you'll sell.
Example: Milk
| Field | Value |
|---|---|
| Display Name | Milk |
| Icon | a sprite, optional |
| World Prefab | see below |
| Wholesale Price | 1.00 |
| Retail Price | 2.00 |
| Units Per Box | 12 |
| Popularity | 2 (popular) |
| Price Tolerance Multiplier | 1.5 |
A starting range
| Product | Wholesale | Retail | Per Box | Popularity |
|---|---|---|---|---|
| Milk | 1.00 | 2.00 | 12 | 2 |
| Bread | 0.80 | 1.75 | 10 | 2 |
| Batteries | 2.50 | 5.00 | 8 | 1 |
| Cleaning spray | 1.50 | 3.25 | 6 | 1 |
The gap between wholesale and retail is your entire economy. A box of milk costs 12 units × £1 = £12 and sells for £24. That £12 margin has to cover the rent.
The product prefab
- GameObject → 3D Object → Cube, scale it to roughly the item's size (milk: 0.1 × 0.2 × 0.1).
- Add a Rigidbody and keep its Box Collider.
- Layer →
Carryable. - Add Carryable:
| Field | Value |
|---|---|
| Product | the ProductType asset |
| Units | −1 (means "use the product's Units Per Box") |
| Can Be Picked Up | ✓ |
| Destroy When Empty | ✓ |
- Save as a prefab and drag it into the ProductType's World Prefab slot.
One prefab does double duty: it's the box you carry and the item that appears on the shelf.
ShelfSlotdisables physics on the display copies.
6. Carrying
On Player, add PickupCarry:
| Field | Value |
|---|---|
| Carry Point | the CarryPoint child |
| View Camera | your Main Camera |
| Reach | 2.5 |
| Carryable Layers | Carryable |
| Can Throw | ✓ |
| Throw Force | 6 |
| Place Distance | 1.2 |
| Pickup Sfx | pickup |
| Drop Sfx | drop |
Add the Pickup input action (bind E). Throwing uses the existing Attack action (left mouse).
How it plays: look at a box within 2.5m, press E to pick it up, press E again to put it down, left-click to throw it.
7. Shelves
- Empty GameObject
ShelfSlot_Milkpositioned on a shelf. - Add a Box Collider roughly the size of the shelf space (not a trigger — the player's ray needs to hit it).
- Right-click it → Create Empty, name it
ItemAnchor, at the back-left of the slot. - Add ShelfSlot:
| Field | Value |
|---|---|
| Assigned Product | Milk (or leave empty for "any") |
| Capacity | 12 |
| Units Per Press | 3 |
| Item Anchor | the child |
| Columns / Rows | 3 / 2 |
| Spacing | (0.25, 0.25, 0.25) — tune to your item size |
| Look Range | 3 |
| Stock Sfx | stock |
- Save as a prefab and place several, assigning a different product to each.
How it plays: carry a matching box, look at the shelf, press E. Three units transfer per press and appear as visible objects. Keep pressing until the box is empty or the shelf is full.
Units Per Press 3 is a pacing decision. At 1 it's tedious; at 12 it's instant and unsatisfying. Three presses to empty a twelve-unit box feels like work without being a chore — which is exactly the tone of the genre.
8. The register
- A cube or model for the counter, named
Register. - Add a Box Collider.
- Right-click → Create Empty, name it
QueueStart, positioned in front of the counter where the first customer stands. - Another empty called
PlayerStandZonebehind the counter, with a Box Collider, Is Trigger ✓, big enough to stand in. - Add Register:
| Field | Value |
|---|---|
| Queue Start | the QueueStart child |
| Queue Spacing | 0.8 |
| Queue Direction | (0, 0, −1) — the way the queue extends back |
| Max Queue | 6 |
| Player Stand Zone | the trigger collider |
| Scan Time | 0.4 |
| Auto Serve | ✓ while testing, ✗ later |
| Auto Serve Delay | 2 |
| Scan Sfx | beep |
| Cash Sfx | cash |
How it plays: stand inside the zone behind the counter, press E once per item in the customer's basket. When you've scanned everything, they pay and leave.
Auto Serve ✓ while testing means sales happen without you, so you can watch the economy work before building the interaction. Turn it off once customers are queuing properly.
The cyan gizmo spheres show where the queue will form. Check they aren't inside a wall.
9. Customers
The prefab
- GameObject → 3D Object → Capsule, name it
Customer. - Add a NavMeshAgent:
| Setting | Value |
|---|---|
| Radius | 0.3 |
| Height | 1.8 |
| Speed | 2 |
| Angular Speed | 200 |
| Acceleration | 8 |
| Stopping Distance | 0.5 |
- Add CustomerAI:
| Field | Value |
|---|---|
| Shopping List Size | 2 |
| Patience Seconds | 25 |
| Pickup Time | 1.5 |
| Register | leave empty — it finds it |
| Store Exit | assigned by the spawner |
- Save as a prefab. Make two or three with different capsule colours so the shop doesn't look cloned.
The spawner
- Empty GameObject
Spawnerat the shop door, on the NavMesh. - Empty GameObject
StoreExitat the same door. - Add CustomerSpawner:
| Field | Value |
|---|---|
| Customer Prefabs | your customer prefabs |
| Register | the Register |
| Store Exit | the StoreExit |
| Seconds Between Customers | 12 |
| Variance | 4 |
| Max In Store | 6 |
| Start Delay | 5 |
| Scale With Day | ✓ |
| Rate Improvement Per Day | 0.8 |
| Minimum Interval | 3 |
What a customer does
Walks in → picks 2 products (weighted by Popularity) → walks to a stocked shelf → takes one → repeats → queues at the register → waits for you → pays → leaves. If a shelf is empty and stays empty past their patience, they leave unhappy.
OnLeftUnhappy is your most important feedback. Wire it to a sound — it's how the player learns to restock without being told.
10. The economy
- Empty GameObject
StoreEconomy+ the component. - Empty GameObject
DeliveryPointnear the back door, on the NavMesh.
| Field | Value |
|---|---|
| Starting Cash | 200 |
| Daily Rent | 50 |
| Use Day Cycle | ✓ |
| Day Length Seconds | 300 (5 minutes) |
| Delivery Point | the DeliveryPoint |
| Delivery Delay | 5 |
Ordering stock
StoreEconomy.OrderBox(product) spends wholesale × unitsPerBox and delivers a box after 5 seconds. To let the player order, make a simple shop UI:
- Canvas → Panel
ShopPanel, hidden by default. - One Button per product, labelled
Milk — £12. - Each Button's On Click →
StoreEconomy.OrderBox, then drag the ProductType asset into the parameter slot. - A Button somewhere to toggle the panel.
Or: put an Interactable on a back-room computer that opens the panel. More diegetic, same wiring.
Tuning the difficulty
Starting Cash 200, rent 50 per day, a milk box costing 12 and returning 24. That means roughly five boxes sold per day just to cover rent. Tight — deliberately.
The reference games work because money is tight at the start. If your first playtest feels comfortable, lower Starting Cash rather than raising rent; running out of runway is more interesting than a big bill.
11. The HUD
- Canvas →
CashText(£200.00), top-right. DayText(Day 1), below it.DayProgressBar— a Slider, Min 0 / Max 1.CarryText— centre-bottom, shows what you're holding.- A crosshair — a small dot Image in the centre.
- Empty child
HUDwith HUDController (most of its fields stay empty; your game has no health).
Text wiring note:
OnCashChangedsends afloatandOnDayChangedanint; TMP'stextwants astring, so they don't connect directly. Ask your instructor for a small display script — one that takes a float and formats it as currency. It's about four lines and you'll use it for cash, day, and the carry readout.The day progress bar works with no script:
OnDayTimeChangedsends a 0–1 float straight intoSlider.value.
12. Wire it up
| On this object | Event | Drag in | Choose |
|---|---|---|---|
StoreEconomy | On Day Time Changed | DayProgressBar | Slider.value (Dynamic float) |
StoreEconomy | On Day Ended | AudioManager | PlaySFX (string) → day_end |
StoreEconomy | On Went Broke | GameManager | GameManager.GameOver |
StoreEconomy | On Delivered | AudioManager | PlaySFX (string) → delivery |
Register | On Customer Waiting | AudioManager | PlaySFX (string) → bell |
Register | On Sale Completed | AudioManager | PlaySFX (string) → cash |
| Customer prefab → CustomerAI | On Left Unhappy | AudioManager | PlaySFX (string) → unhappy |
| Each shop Button | On Click | StoreEconomy | StoreEconomy.OrderBox → drag the ProductType in |
Row 5 is the heartbeat of the genre. A bell when someone reaches the till is what pulls the player away from stocking shelves. Without it they'll never know they're needed.
13. First playtest
Turn Auto Serve on for this.
- You can walk and look. Escape releases the cursor.
- A box spawns at the delivery point when you order one (test with a button, or place one by hand).
- You can pick it up — look at it, press E.
- You can stock a shelf — carry a matching box, look at the slot, press E. Items appear.
- The box empties and disappears after four presses.
- A customer walks in — if they spawn and stand still, your NavMesh isn't baked or the spawner is off it.
- They walk to a stocked shelf and take an item.
- They queue at the register.
- With Auto Serve on, they pay and leave, and cash goes up.
- An empty shelf makes them leave unhappy after 25 seconds.
- The day ends after 5 minutes and rent is deducted.
Then turn Auto Serve off and check step 9 again with you standing behind the counter pressing E.
14. Then add
- Art. Grey boxes get you here; shelves, products and a shop interior make it a game. This is your biggest remaining job.
- More products — each one is just a ProductType and a prefab.
- A back room with the delivery point and the ordering computer.
SaveSystem— tick Save Flags and Save Collectibles, and add aChoiceFlagManager. Cash and day would need a small addition; ask if you want it.- Price setting — letting the player choose retail prices, with customers refusing above the tolerance multiplier. Needs a small UI and a script.
- A day-end summary panel — wire
OnDayEndedto show revenue and expenses.
15. Design notes for your game specifically
Bake the NavMesh before anything else, and re-bake after moving walls. It's static geometry — if you rearrange the shop, the old NavMesh stays until you bake again. "Customers walk through the new shelf" always means a stale bake.
The genre runs on interruption. Stocking shelves is calm, repetitive, satisfying work. The bell at the register interrupts it. That tension — wanting to finish the box you're holding while someone waits — is the gameplay. Make the bell audible and the register visible from the shelves.
Money must be tight at the start. Every game you listed works because the first delivery is a real decision. Five boxes a day just to cover rent means the player has to think about what sells. If it feels safe in playtesting, cut Starting Cash to 150.
Popularity is your cheapest design lever. A product with Popularity 3 sells constantly and a Popularity 1 product gathers dust. That teaches the player to read demand without any tutorial — and adjusting one number rebalances your whole shop.
Scale With Day is your entire progression curve. At 0.8 seconds shaved per day, day 10 has customers arriving twice as fast as day 1. That single float is what makes the game get harder, and it's worth playing with more than almost anything else here.
Auto Serve is a genuine testing tool, not a shortcut. Use it to check whether your prices, rent and spawn rate produce a survivable but tense economy. Once the numbers work, turn it off and the game is about you keeping up with them.
16. Common problems
| What you see | What's wrong |
|---|---|
| Customers spawn and don't move | NavMesh not baked — this is almost always it |
| Customers walk through shelves | Stale bake — re-bake after moving anything |
| Customers can't reach a shelf | The shelf sits off the NavMesh, or is surrounded by other colliders |
| Customer spawns then vanishes | Spawner isn't on the NavMesh |
| Can't pick up boxes | Box not on the Carryable layer, or Carryable Layers unset, or no Carryable component |
| Box picked up but invisible | CarryPoint is inside the camera's near clip — move it further forward |
| Can't stock a shelf | Product doesn't match Assigned Product, or the shelf has no collider, or you're beyond Look Range |
| Items don't appear on the shelf | ProductType has no World Prefab, or Item Anchor unassigned |
| Items on the shelf fall off | They shouldn't — ShelfSlot disables their physics. If they do, the display copies aren't being created by the slot |
| Register does nothing | PlayerStandZone unassigned or you're outside it; or no customer is queuing |
| Queue forms inside a wall | Queue Direction points the wrong way — check the cyan gizmos |
| Cash never changes | Sales aren't completing; turn on Auto Serve to isolate |
| Game over immediately | Starting Cash lower than the first rent payment |
| Mouse won't move the view | Look action missing, or the cursor isn't locked — click in the game view |