DEV Community

Cover image for 55+ Shipped Games Later — 10 Unity Lessons I Wish I Knew on Day One
Dilawar Hussain
Dilawar Hussain

Posted on

55+ Shipped Games Later — 10 Unity Lessons I Wish I Knew on Day One

Five years ago I thought being a good Unity developer meant knowing the API.

Then I started shipping. Mobile puzzlers, a 16-player social game, a $550K strategy game, VR training simulators, a fintech event game built in under two weeks — 55+ titles across iOS, Android, PC, WebGL and Meta Quest.

Shipping taught me things no tutorial did. Here are the 10 lessons that saved me the most time, bugs and sanity.


1. Prototype the fun before you architect anything

The most expensive mistake in game dev is building a beautiful architecture for a game that isn't fun.

Now I get a playable version in front of people as fast as possible — grey boxes, placeholder UI, hardcoded values. If the core loop isn't fun with cubes, it won't be fun with art.

When I set up a rapid-prototyping pipeline for my team, our time to first playable build dropped by about 30%. The trick wasn't a tool — it was permission to write throwaway code on purpose.

Rule: prototype code is allowed to be ugly. Production code is not. Never confuse the two.


2. Put your numbers in ScriptableObjects, not in code

If a designer has to ask you to change an enemy's speed, your architecture is wrong.

[CreateAssetMenu(menuName = "Game/Enemy Config")]
public class EnemyConfig : ScriptableObject
{
    public float moveSpeed = 3.5f;
    public int maxHealth = 100;
    public float attackCooldown = 1.2f;
}

public class Enemy : MonoBehaviour
{
    [SerializeField] private EnemyConfig config;

    private void Update()
    {
        transform.Translate(Vector3.forward * config.moveSpeed * Time.deltaTime);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now designers tune the game in the Inspector, you create variants (FastEnemy, TankEnemy) without new code, and balancing stops being a ticket in your queue.


3. Stop allocating in the hot path — pool everything that spawns

Garbage collection spikes are the #1 cause of "it stutters sometimes" on mobile. Bullets, particles, coins, floating damage numbers — if it spawns often, pool it. Unity has a built-in pool since 2021:

using UnityEngine;
using UnityEngine.Pool;

public class BulletSpawner : MonoBehaviour
{
    [SerializeField] private Bullet prefab;
    private ObjectPool<Bullet> pool;

    private void Awake()
    {
        pool = new ObjectPool<Bullet>(
            createFunc: () => Instantiate(prefab),
            actionOnGet: b => b.gameObject.SetActive(true),
            actionOnRelease: b => b.gameObject.SetActive(false),
            actionOnDestroy: b => Destroy(b.gameObject),
            defaultCapacity: 32,
            maxSize: 256);
    }

    public void Fire(Vector3 position)
    {
        Bullet bullet = pool.Get();
        bullet.transform.position = position;
        bullet.Init(() => pool.Release(bullet)); // bullet calls this when it's done
    }
}
Enter fullscreen mode Exit fullscreen mode

Same idea for strings in Update (cache them), GetComponent in loops (cache it in Awake) and LINQ in per-frame code (avoid it).


4. Profile on the worst device you support — not your dev machine

Your laptop lies to you. The game that runs at 120 FPS in the Editor can run at 25 FPS on a three-year-old budget Android phone — which is exactly what a big chunk of your players own.

My checklist before every mobile release:

  • Profile a development build on a real low-end device, never just the Editor
  • Watch the GC Alloc column in the Profiler — it should be near zero during gameplay
  • Check draw calls and overdraw, especially with transparent UI and particles
  • Test a 30-minute session — memory leaks don't show up in 2 minutes

5. Decide multiplayer authority on day one

Multiplayer isn't a feature you "add later". It changes how every system is written.

On a 16-player social game with proximity voice chat, the hardest bugs never came from the networking library — they came from unclear ownership. Who moves this object? Who decides this quest is complete? Who wins when two players grab the same item?

Before writing any netcode, write down for every piece of state:

State Who owns it? How is it synced?
Player position Owning client Continuous sync
Item pickup Master / server Request + confirm
Score Server Event on change

That one table has saved me more debugging hours than any library feature.


6. Put a backend in early — even a tiny one

Firebase or PlayFab from week one gives you three superpowers:

  1. Remote config — tune difficulty, prices and rewards without shipping a new build
  2. Analytics — find out where players actually quit, instead of guessing
  3. Leaderboards and cloud saves — features players expect, almost free to add

Adding a backend at the end means retrofitting every save, every reward and every number. Adding it at the start means you can fix a bad level with a config change at 2 a.m. — without waiting for store review.


7. For deadlines, cut scope — never quality

I once built an event game for a fintech company's live event in Singapore: brief to live in under two weeks, with leaderboards, video explainers and a quiz.

It shipped on time because we cut ruthlessly: one game mode instead of three, one art style, one platform (WebGL — nothing to install at the event). What we did not cut: input responsiveness, load time and the leaderboard working under load.

Players forgive missing features. They don't forgive a game that feels broken.


8. Keep AI API keys out of your game client

AI features are everywhere now — chat assistants, generated content, smart NPCs. The most common mistake I see: the OpenAI or Gemini key inside the build. Anyone can extract it in minutes.

Put a tiny server function between your game and the AI provider, and call that instead:

using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class AiClient : MonoBehaviour
{
    // Your own backend endpoint — it holds the real API key and enforces limits.
    [SerializeField] private string endpoint = "https://your-backend.example.com/ask";

    public IEnumerator Ask(string prompt, System.Action<string> onReply)
    {
        string json = JsonUtility.ToJson(new Request { prompt = prompt });

        using var req = new UnityWebRequest(endpoint, "POST");
        req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
        req.downloadHandler = new DownloadHandlerBuffer();
        req.SetRequestHeader("Content-Type", "application/json");

        yield return req.SendWebRequest();

        onReply(req.result == UnityWebRequest.Result.Success
            ? req.downloadHandler.text
            : "Sorry, something went wrong.");
    }

    [System.Serializable] private class Request { public string prompt; }
}
Enter fullscreen mode Exit fullscreen mode

The server keeps the key secret, rate-limits each player and filters content. Your client stays dumb — and safe.


9. Code reviews are a speed tool, not a bureaucracy tool

When I started running weekly code reviews while mentoring three junior developers, reported bugs across our sprints dropped by roughly 35%.

Not because reviews catch every bug — but because they spread knowledge. After a month, everyone knew where the save system lived, why the pooling rules existed and which "quick fix" patterns we'd banned.

Keep reviews small, frequent and kind. A 200-line review gets real feedback. A 2,000-line review gets "LGTM".


10. Unity isn't the answer to everything — and that's fine

Some of my favourite recent work isn't in Unity at all. For an AI sports app — selfie in, cinematic kit photos and celebration videos out — I used Flutter, because the product was mostly screens, flows and AI services, not realtime 3D.

The rule I use now:

  • Unity when the product lives in 3D, AR/VR or realtime graphics
  • Flutter when it's mostly UI, forms, feeds and API calls
  • Both when an app needs a 3D or AR moment inside a normal app

Knowing when not to use your favourite tool is a senior skill.


The short version

  1. Prototype the fun first
  2. Data in ScriptableObjects
  3. Pool everything that spawns
  4. Profile on your worst device
  5. Decide multiplayer authority on day one
  6. Backend early
  7. Cut scope, never quality
  8. AI keys stay on the server
  9. Small, frequent code reviews
  10. Pick the right tool, not the familiar one

I'm currently building open-source AI tools for the Unity Editor — follow me here if you want to see them first.

What's the one Unity lesson you learned the hard way? Drop it in the comments — I read every one. 👇

I'm Dilawar, a Senior Unity Engineer and Flutter & AI app developer. You can see the games, apps and XR projects behind these lessons at dilawarhussain.site.

Top comments (0)