Unity
Unity is a general-purpose real-time engine and the most widely deployed one in the industry — the majority of mobile games and a large share of indie and mid-size PC and console titles ship on it. It combines a scene editor, an asset pipeline, physics, rendering, audio, and a C# scripting layer, and exports the same project to roughly twenty platforms.
Its defining design choice is composition over inheritance: a GameObject is an almost empty container, and everything it does comes from Components attached to it. A player is a transform plus a renderer plus a collider plus a rigidbody plus your movement script. This is easy to learn and scales reasonably far — and the point at which it stops scaling is exactly where Unity's newer ECS stack (DOTS) begins.
TL;DR
- GameObject + Components: containers with behavior attached, not deep class hierarchies.
- Scripts derive from
MonoBehaviourand hook a fixed lifecycle —Awake,Start,Update,FixedUpdate,LateUpdate. Updateis per-frame,FixedUpdateis per-physics-step. Put movement of rigidbodies inFixedUpdateand multiply frame-rate-dependent values byTime.deltaTime.- Prefabs are reusable GameObject templates; ScriptableObjects are shared data assets that avoid duplicating config on every instance.
- The three render pipelines — Built-in, URP, and HDRP — are not interchangeable; choose before you build content.
- The classic performance traps:
GetComponentinUpdate,Instantiate/Destroychurn, and allocations that trigger GC spikes. - Profile with the Profiler and Frame Debugger, not by intuition.
Quick Example
A cached, frame-rate-independent player controller — the shape almost every Unity script takes:
Three things in that snippet carry most of the lesson: the component is cached in Awake, input is read in Update but applied in FixedUpdate, and every movement value is scaled by delta time.
Core Concepts
GameObjects and components
A GameObject has a name, a Transform, and a list of components. It has no behavior of its own.
Components communicate through references you cache, not by reaching across the scene each frame. GetComponent<T>() searches the object's component list; Find and FindObjectOfType search the entire scene and are dramatically worse.
The MonoBehaviour lifecycle
Order is guaranteed, and knowing it prevents a whole class of null-reference bugs:
The rule that follows: initialize yourself in Awake, reference others in Start. A Start that runs before another object's Awake cannot happen; an Awake that reaches for another object's initialized state can.
Update vs. FixedUpdate
Time.deltaTime is the elapsed time for the current frame. Any per-frame value that represents a rate — speed, rotation, interpolation — must be multiplied by it, or your game runs faster on faster hardware.
Prefabs and ScriptableObjects
A prefab is a serialized GameObject template. Instantiate it at runtime, edit the asset to update every instance, and use prefab variants for specializations.
A ScriptableObject is a data asset that lives outside the scene — it exists once in memory regardless of how many objects reference it:
Every rifle in the game points at one WeaponData asset. Designers tune it in the inspector without touching code, and you avoid duplicating the same twelve fields onto a thousand instances. ScriptableObjects are also the idiomatic way to build shared event channels and runtime sets that decouple systems.
Render pipelines
⚠️ Warning: Switching pipelines mid-project breaks every material and most custom shaders. Pick one before you build content, and pick URP unless you have a specific reason not to.
Performance
Unity performance work splits cleanly into three budgets, and the Profiler tells you which one you've blown.
Garbage collection
Unity's default collector causes visible frame hitches when it runs. Anything that allocates in Update — a new on a class, string concatenation, a LINQ query, boxing a struct — feeds it.
NonAlloc variants exist for exactly this reason: Physics.RaycastNonAlloc, GetComponents(list), and CompareTag instead of gameObject.tag ==.
Draw calls and batching
Every material change is a draw call. Reduce them with static batching for non-moving geometry, GPU instancing for many copies of the same mesh, and texture atlases so more objects share a material. The Frame Debugger shows exactly what was drawn and why batching broke.
Object pooling
Instantiate and Destroy are expensive and generate garbage. For anything spawned repeatedly — bullets, enemies, particles, damage numbers — pool instead:
Unity ships UnityEngine.Pool.ObjectPool<T> if you'd rather not hand-roll it.
Best Practices
Cache every component reference
GetComponent in Update is the single most common Unity performance mistake. Fetch in Awake, store in a field, use forever. The same applies to Camera.main, which is a scene search in disguise.
Use [SerializeField] on private fields instead of making them public
You get inspector editing without exposing the field to every other script. Public fields are an API surface; serialized private fields are configuration.
Prefer events over polling
A script checking if (player.health <= 0) every frame is doing work 60 times a second to detect something that happens once. Raise an event on the state change and let listeners react. ScriptableObject-based event channels do this without hard references between systems.
Keep physics layers meaningful
Configure the layer collision matrix so objects that can never interact aren't tested against each other. Raycasts should always pass a LayerMask — an unfiltered raycast tests everything in the scene and often hits the caster itself.
Separate simulation from presentation
Move rigidbodies in FixedUpdate and interpolate visuals in Update (or set the Rigidbody's Interpolate mode). Mixing the two produces movement that stutters at some frame rates and not others — a bug that's miserable to diagnose after the fact.
Profile on the target device
The editor is not representative. A game that runs at 200 FPS on your desktop can be thermally throttled to 24 FPS on a mid-range phone. Attach the Profiler to a real build on real hardware before drawing conclusions.
Use the Addressables system for content loading
Loading everything from Resources/ puts it all in memory at startup and bloats build size. Addressables give you async loading, memory management, and remote content delivery — worth adopting before the project is large.
Common Mistakes
Calling GetComponent every frame
Forgetting Time.deltaTime
Moving a Rigidbody through its Transform
Using Find at runtime
Instantiating and destroying constantly
Assuming OnTriggerEnter fires without a Rigidbody
A trigger event requires at least one of the two colliders to have a Rigidbody attached — a static collider hitting another static collider generates nothing. Mark it kinematic if you don't want physics to move it.
FAQ
Unity or Unreal?
Unity if you're targeting mobile, working in 2D, building a small or mid-size team's project, or you prefer C#. Unreal if you want the strongest out-of-the-box rendering, are building a large 3D game, or your team is comfortable in C++. Both ship successful games at every scale; the ecosystem, hiring pool, and licensing terms usually decide it rather than technical capability.
Is C# fast enough for games?
For the great majority of games, yes — the engine's heavy lifting (rendering, physics, animation) is native C++ and your scripts are orchestration. The pressure point is garbage collection rather than raw execution speed, which is why avoiding per-frame allocations matters so much. When you genuinely need more, the Burst compiler and the Job System compile constrained C# to highly optimized native code.
What is DOTS, and do I need it?
DOTS (Data-Oriented Technology Stack) is Unity's ECS, Job System, and Burst compiler — a data-oriented alternative to GameObjects designed for tens of thousands of entities. You need it for RTS-scale unit counts, large simulations, or heavy procedural work. For a typical game it adds significant complexity for performance you don't need; the Job System and Burst are useful on their own without adopting full ECS.
How do I structure a large Unity project?
Keep scenes thin and put behavior in prefabs, use ScriptableObjects for configuration and cross-system events, and keep a clear separation between gameplay systems and MonoBehaviours that merely adapt them to the engine. Assembly definitions enforce module boundaries and dramatically cut compile times. The most valuable habit is resisting the singleton GameManager that gradually accumulates every system's state.
Is Unity free?
Unity Personal is free below a revenue threshold; Pro and Enterprise tiers are paid seat licenses above it. Terms have changed more than once in recent years, so check the current license before building a business model around a specific tier — this is a real consideration when comparing against Godot, which is MIT-licensed with no revenue conditions.
How do I handle save data?
PlayerPrefs is fine for settings and trivial state, and inappropriate for game saves — it's unencrypted, size-limited, and platform-dependent. Serialize to JSON or a binary format and write to Application.persistentDataPath. Version your save format from the first release; migrating saves you didn't version is a recurring source of shipped-game pain.
Related Topics
- Unreal Engine — The main alternative for high-end 3D
- Godot — Open-source engine with a similar node/scene model
- Game Loops & ECS — The architecture behind
Updateand DOTS - Game Physics — What
FixedUpdateandRigidbodyare actually doing - Shader Programming — Shader Graph, URP, and custom materials
- Multiplayer Netcode — Netcode for GameObjects and authority models
- Game AI — NavMesh, state machines, and behavior trees
- C# — The scripting language
- Profiling — Finding the actual bottleneck
- Mobile Development — Unity's largest deployment target