Rapid prototyping isn’t writing disposable code. It’s deciding — fast — what deserves structure and what doesn’t. Unity, in practice.
“Hands up: who has built a prototype that worked, and then had to throw it away to build the real thing?” — wait for hands.
Every prototype is written twice.
The only question is who pays for the second one.— the thing nobody puts in the schedule
Fast, messy, fun. It proves the game. Written in a week.
The same game, rebuilt to be changeable. Takes three weeks, feels like zero progress, and ships late.
Make version one absorb change instead of resisting it. Four or five decisions, most of them under 15 minutes.
“Version one is not the problem. Version one is the point. The problem is that version one usually can’t take a single change without breaking.”
No new architecture to adopt. Nothing to install. Things you can do on Sunday in the project you already have.
Three sounds your code makes when an idea is hiding inside it — and what to name it.
Five cheap seams in Unity, the cost of each in minutes, and what each one buys you.
The four abstractions that kill prototypes, and a 30-second test to decide on the spot.
One gets you to Friday. The other gets you to launch.
Transition: “First, let’s be honest about what ‘fast’ means.”
Both teams “moved fast”. One kept moving.
“Notice where the lines cross — week three. Before that, structure genuinely slows you down. That’s why this is a judgement call, not a rule.”
Pick the change your designer asked for yesterday. How many files does it touch, and can they do it without you?
Not lines of code. Time from “what if…” to seeing it on screen. That number decides whether the game gets good.
“Prototyping speed isn’t typing speed. It’s the time between someone saying ‘what if’ and everyone seeing it on screen.”
Code is disposable.
Concepts are not.throw the code away — keep what you learned it was
You will rewrite the movement code three times.
You will not rewrite the idea that “things that can be hurt” are one kind of thing, that “what a weapon is” is data, and that the UI should never know where the score came from.
“This is the whole talk in one line: code is disposable, concepts are not.”
Your code makes three distinct sounds when an idea is hiding inside it.
“Three smells. You already know all of them — today they get names.”
public bool isStunned;
public bool isFrozen;
public bool isBurning;
public bool isInvulnerable;
public bool isDashing;
void Update() {
if (isStunned || isFrozen) return;
if (isBurning && !isInvulnerable) Damage(1f);
// and the 40 lines that handle
// every combination of the above
}
public enum Condition { Stun, Freeze, Burn, Invulnerable }
public class Conditions : MonoBehaviour {
readonly Dictionary<Condition, float> _until = new();
public bool Has(Condition c) =>
_until.TryGetValue(c, out var t) && t > Time.time;
public void Apply(Condition c, float seconds) =>
_until[c] = Time.time + seconds;
}
“When flags travel in a pack, they’re not flags. They’re one concept wearing five hats — a status condition, with a duration.”
public interface IDamageable {
void TakeDamage(float amount, GameObject source);
}
if (hit.collider.TryGetComponent(out IDamageable d))
d.TakeDamage(damage, gameObject);
public class Health : MonoBehaviour, IDamageable {
[SerializeField] float max = 100f;
public float Current { get; private set; }
public event Action<float> Changed; // UI, VFX
public event Action<GameObject> Died; // score, drops
void Awake() => Current = max;
public void TakeDamage(float amount, GameObject source) {
if (Current <= 0f) return;
Current = Mathf.Max(0f, Current - amount);
Changed?.Invoke(Current / max);
if (Current == 0f) Died?.Invoke(source);
}
}
“If you can copy a file and make it work by changing one word, that word is the concept — and the file is the duplicate.”
switch (type) {
case EnemyType.Walker: /* 20 lines */ break;
case EnemyType.Flyer: /* 25 lines */ break;
case EnemyType.Turret: /* 18 lines */ break;
}
public abstract class MoveBehaviour : ScriptableObject {
public abstract void Tick(Transform self, Transform target, float dt);
}
[CreateAssetMenu(menuName = "AI/Move/Chase")]
public class ChaseMove : MoveBehaviour {
[SerializeField] float speed = 3f;
public override void Tick(Transform self, Transform target, float dt) =>
self.position = Vector3.MoveTowards(self.position, target.position, speed * dt);
}
public class Enemy : MonoBehaviour {
[SerializeField] MoveBehaviour move; // drag the asset in
void Update() => move.Tick(transform, Player.Current, Time.deltaTime);
}
“A switch on a type enum is a list of things the designer can’t make without you.”
A concept has one job and one name. If the name needs “and”, you found two.
| What you were going to call it | What it actually is | Why it matters |
|---|---|---|
| PlayerManager | Input · Motor · Health · Inventory | Four things one file, four reasons to edit it, four ways to break it. |
| EnemyAIAndSpawner | Behaviour · Spawner | The “and” is doing the work of a folder. |
| GameManager | GameFlow · Score · Save | The file everyone edits, and nobody dares delete. |
| Health | Health | One job. This one is already a concept — keep it. |
“Before you write a class, say what it does in one sentence. If the sentence has an ‘and’, you’re about to write two classes in one file.”
Nobody wrote it. It accumulated.
Where intent comes from. Swap for AI, replay, or a tutorial that plays itself.
Turns intent into movement. The part you will rewrite three times.
Dash, shoot, interact. Each one an asset a designer can tune.
Already shared with every enemy and crate in the game.
“Four concepts, and you can find them in any player controller in this room right now.”
Five seams, cheapest first. Each one costs minutes — and each buys back a category of change.
“Now: where to cut. And the answer is never ‘everywhere’.”
The answer picks the seam for you. You don’t need taste, you need this table.
| What varies | The seam | Cost | In Unity |
|---|---|---|---|
| Numbers a designer tunes | data | 5 min | ScriptableObject asset with [CreateAssetMenu] |
| Kinds of the same thing | interface | 2 min | IDamageable, IInteractable + TryGetComponent |
| Who reacts to an event | event | 10 min | C# event, or a ScriptableObject channel across scenes |
| How one step is done | strategy | 15 min | abstract ScriptableObject, dragged into the inspector |
| Parts of an object | composition | 30 min | small MonoBehaviours on a prefab instead of one class |
“Don’t ask ‘what’s the right architecture’. Ask ‘what varies, and who changes it’. If the answer is a designer, it’s data. If it’s another system, it’s an event.”
[CreateAssetMenu(menuName = "Game/Weapon")]
public class WeaponDef : ScriptableObject {
public float damage = 10f;
public float fireRate = 6f; // per second
public int magazine = 12;
public GameObject projectile;
public AudioClip shot;
}
Pistol, shotgun and “the one that shoots cats” are three assets. No new code, no recompile, and your designer makes them.
Tuning during play mode. Changes to an asset survive exiting play mode — unlike values on a scene object.
“This is the highest return on five minutes in Unity. If you take one thing home, take this one.”
public interface IInteractable {
string Prompt { get; } // "Open", "Talk", "Pick up"
void Interact(GameObject who);
}
// the player never learns what a door is
void TryInteract() {
if (Physics.Raycast(eye.position, eye.forward, out var hit, 2.5f)
&& hit.collider.TryGetComponent(out IInteractable it))
it.Interact(gameObject);
}
Doors, chests, NPCs, the vending machine you add on Thursday — none of them touch the player script.
An interface with a single implementer is a rename with extra steps. Wait for the second real case.
“The interface isn’t for polymorphism theory. It’s so the player script stops growing every time we add a thing in the world.”
[CreateAssetMenu(menuName = "Game/Events/Int")]
public class IntEvent : ScriptableObject {
public event Action<int> Raised;
public void Raise(int value) => Raised?.Invoke(value);
}
// gameplay — knows nothing about UI
[SerializeField] IntEvent scoreChanged;
scoreChanged.Raise(score);
// HUD — in another scene, zero references
void OnEnable() => scoreChanged.Raised += Render;
void OnDisable() => scoreChanged.Raised -= Render;
Additive scenes, a HUD you can delete, an audio system that reacts to gameplay it has never heard of.
Within a system, call the method. Events across systems, calls within them — otherwise you lose the call stack and debugging gets slow.
“This is the one that makes a prototype feel like a product: the HUD, the audio and the save system all listen, and gameplay doesn’t know they exist.”
Week 4: you need one enemy that swims. It inherits Walker. Now you’re moving methods up and down a tree instead of making a game.
Health + Mover + Shooter + Loot. A swimming enemy is a different set of parts, made in the editor in a minute.
[RequireComponent(typeof(Health))]
public class Loot : MonoBehaviour {
[SerializeField] LootTable table;
void Awake() => GetComponent<Health>().Died += Drop;
void Drop(GameObject killer) {
foreach (var item in table.Roll())
Instantiate(item, transform.position, Quaternion.identity);
}
}
“Unity is already a composition engine. The prefab is the class — components are the parts.”
No package. No graph editor. No plugin to learn on a deadline.
public enum Phase { Boot, Menu, Playing, Paused, GameOver }
public class GameFlow : MonoBehaviour {
public static GameFlow I { get; private set; }
public Phase Phase { get; private set; }
public event Action<Phase> Changed;
void Awake() => I = this;
public void Go(Phase next) {
if (next == Phase) return;
Phase = next;
Time.timeScale = next == Phase.Paused ? 0f : 1f;
Changed?.Invoke(next);
}
}
Pause, game over, menu and the “press any key” screen stop being booleans scattered across five scripts.
Every screen and system asks one place what’s happening. When you outgrow it, you replace 20 lines — not a dependency.
“You do not need a state machine package for a prototype. You need five named phases and one event.”
What we set up before writing any gameplay — and why it pays for itself by Thursday.
“Practical part: this is our own checklist at AL-Arcade when a new prototype starts.”
Do these before the first mechanic, not after the first crisis.
/Player, /Enemies, /UI — each with its own scripts, prefabs, art. Not /Scripts, /Prefabs, /Materials.
The numbers everyone argues about, in one ScriptableObject, editable in play mode.
The 20 lines from the last slide. Everything else asks it.
God mode, skip level, spawn, time scale. The single biggest velocity win.
Project Settings → Editor. Entering play mode goes from 8 seconds to instant.
Loads any level by name, so nobody’s work depends on which scene is open.
“One hour, before any gameplay. Every one of these is about the loop between an idea and seeing it.”
public class DebugPanel : MonoBehaviour {
bool _open;
void Update() {
if (Input.GetKeyDown(KeyCode.F1)) _open = !_open;
}
void OnGUI() {
if (!_open) return;
GUILayout.BeginArea(new Rect(12, 12, 230, 320), GUI.skin.box);
if (GUILayout.Button("Kill all enemies")) Wave.KillAll();
if (GUILayout.Button("Give 1000 gold")) Wallet.Add(1000);
if (GUILayout.Button("Skip level")) GameFlow.I.Go(Phase.GameOver);
Time.timeScale = GUILayout.HorizontalSlider(Time.timeScale, 0f, 4f);
GUILayout.EndArea();
}
}
Testing the boss means reaching the boss. Twenty times a day. This button is worth more than any optimisation you’ll do this month.
It’s slow and it’s ugly — so nobody is ever tempted to ship it, and nobody spends an afternoon styling it.
“Every hour you spend replaying level one to test level three is an hour stolen from making the game good.”
Half of prototyping fast is knowing which structure not to build.
“I’ve spent 40 minutes telling you to add structure. Now the other half.”
When every call is an event, no stack trace tells you who did what. Debugging goes from seconds to an afternoon.
IPlayerService, IInventoryService, one class each. That’s not flexibility, it’s typing.
A GameManager holding fourteen references, initialised in an order only you understand — and only today.
DI container, full ECS, netcode, a save system — before anyone knows whether the game is fun. Build the fun first.
“Every one of these, I have done. The ECS one cost me two weeks on a game that turned out not to be fun.”
Duplication is far cheaper
than the wrong abstraction.— Sandi Metz
Copy-paste is reversible in 30 seconds.
A wrong abstraction is not: everyone builds on it, it grows parameters and flags, and by the time it’s clearly wrong it has twelve callers. That’s the rewrite you’re trying to avoid.
“If you remember one sentence from someone smarter than me: duplication is cheaper than the wrong abstraction.”
Two yeses or more: cut the seam now. Fewer: duplicate and move on.
Not “it might”. Two real cases on screen.
If yes, it’s data. This is almost always the strongest yes.
Gameplay → UI, audio, save, analytics. Boundaries want events.
Three edit sites is where mistakes start living.
“Say it out loud with me the next time you’re about to make an interface: does it vary today, does a designer touch it, does it cross a boundary, is it three edits?”
You cannot avoid every rewrite. You can make it a Tuesday instead of a sprint.
A feature lives in one folder, with its own prefabs and assets. Deleting it is selecting a folder — not an archaeology dig.
Features talk through events or shared data, never directly. Then any one of them can be replaced alone.
One scene where a mechanic is tested with nothing else in it. If it needs the whole game to run, it’s already coupled.
“The best prototypes I’ve worked on weren’t the ones we never rewrote. They were the ones where rewriting a system took a day.”
One file, four concepts, one extraction. Pair up with the person next to you.
“Pair up. Don’t open Unity — paper or a text editor is enough.”
public class PlayerController : MonoBehaviour {
public float speed = 5f, dashSpeed = 18f;
public float maxHealth = 100f, currentHealth;
public int ammo = 12, maxAmmo = 12;
public bool isStunned, isDashing, isDead;
public Slider healthBar; // the HUD, from in here
void Update() {
if (isDead || isStunned) return;
var h = Input.GetAxisRaw("Horizontal");
var v = Input.GetAxisRaw("Vertical");
transform.Translate(new Vector3(h, 0, v)
* speed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.Space)) Dash();
if (Input.GetMouseButtonDown(0) && ammo > 0) Shoot();
healthBar.value = currentHealth / maxHealth;
}
// …and 140 more lines
}
Write four names on paper. Use the “no ‘and’” test. Don’t write any code yet.
For each one: varies today? designer-facing? crosses a boundary? three edit sites?
Whichever scored highest. One extraction — and notice how little you had to move.
No new logic. Same game, different shape.
| Concept | Seam | What it unlocks the same day |
|---|---|---|
| PlayerInput | interface | An AI can drive the player. Replays. A tutorial that plays itself. Gamepad support becomes one class. |
| Motor | component | Rewrite movement without touching shooting. Knockback and dash stop fighting each other. |
| Health | component + events | Shared with enemies and crates. The HUD leaves the player script for good. |
| WeaponDef | data asset | Your designer balances every gun in play mode while you keep working. |
“Notice: nothing here is clever. It’s naming, and moving lines into files that have one job.”
If the name needs an “and”, it’s two things. Naming is the design.
Five minutes, and your designer stops waiting for you.
Keep your stack traces. Decouple only at boundaries.
Varies today · designer-facing · crosses a boundary · three edit sites.
Feature folders, no cross-references, a sandbox scene. Rewrite becomes a Tuesday.
Code is disposable. Concepts are not.
“One screenshot, five rules. If you do numbers-in-assets and the 30-second test, you’ve got 80% of the benefit.”
AL-Arcade is a game and software studio in Egypt. Unity games, multiplayer and motion-tracking installations, and production systems that run real businesses every day — which is exactly where you learn what “flexible” costs.
AL-ARCADE
GAMES · SYSTEMS · INTERACTIVE
Every slide, every code sample and the checklist — at this link. Take a photo now, then let’s talk.
“Thank you. One ask: pick one prototype you have right now, and do the numbers-in-assets seam tonight. It takes five minutes and you’ll feel it tomorrow.”
Prototype Fast, Not Twice