Entity Component System for Unity: Getting Started

In this Unity tutorial you’ll learn how to efficiently leverage the Entity Component System for more performant gameplay code. By Wilmer Lin.

4.8 (48) · 5 Reviews

Download materials
Save for later
Share

The cake is a lie.

For years, you’ve built your Unity applications around the best object-oriented practices: Classes, inheritance and encapsulation. But what if you’ve been doing it all wrong?

Unity’s new Data-Oriented Tech Stack, or DOTS, moves away from OOP toward data-oriented design. Central to this paradigm shift is the Entity Component System, or ECS.

ECS restructures your workflow around your game’s data and how it’s stored in memory. The result is more performant code that can handle massive scenes more efficiently.

Monobehaviour, we hardly knew ya.

In this tutorial, you’ll update part of a simple shoot ‘em up to use the Entity Component System. In doing so, you’ll learn the following:

  • How to create Entities
  • How to use hybrid ECS to ease into this new paradigm shift.
  • Components, and how they can store data efficiently, if used correctly.
  • Systems, the holders of logic and behaviors that act on your data, manipulating and transforming it for your game.
  • How to hook up all of the above to fully leverage ECS.

Tank and drone video demo

Note: This tutorial is for experienced readers. Before you begin, you’ll need working knowledge of C#, the Unity Editor and Unity 2019.3 or above for some features.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to get the project files. Unzip and open the IntroToECSStarter project.

In Window ► PackageManager, install and update the following packages.

Unity package manager window

Note: Selecting In Project from the drop down menu next to the + symbol will restrict the list of packages for easier updating.

The following packages are essential for any ECS-based project:

  • Entities implements the new ECS features.
  • Hybrid Renderer renders all entities created in ECS.

The demo also uses the following packages for visuals:

  • Cinemachine controls the follow camera.
  • Universal Render Pipeline, or URP, holds the graphical settings.
  • TextMeshPro displays the UI text elements.

Examine Assets/RW. Here, you’ll find some folders containing assets used to build the demo.

Unity asset folder structure

There’s a lot here, but in this tutorial you won’t touch the following directories:

  • Fonts.
  • Materials.
  • Models.
  • ParticleSystems.
  • PipelineSettings.
  • PostFX.
  • Shaders.
  • Sounds.
  • Textures.

There are folders for Scenes and Prefabs, but you’ll mostly work in the Scripts folder for this tutorial.

You’ll find several custom components for handling the player input, movement and shooting in Scripts/Player. Also, Scripts/Managers contains pre-built components to manage the game logic.

Stress Testing the Demo

Open SwarmDemoNonECS in Scenes.

Now, choose Maximize on Play in the Game view and set the Scale all the way to the left to get the full picture of the interface. Then enter Play mode to test the game.

Tank shooting at drones

Use the arrow or WASD keys to move the player’s tank. Point the mouse to aim the turret and left mouse button to fire bullets.

Notice that every few seconds, a new wave of enemies surrounds you. By default, the drones explode on contact, but don’t destroy the player.

This invincibility mode allows you to stress test your app. Hundreds of explosions, bullets and enemies clutter the Hierarchy.

To see the gameplay’s real-time impact, use the Stats in the Game window to track the main CPU time and the frame per second, or FPS count.

Unity stats window in Play mode

If you play long enough, you’ll notice the render time per frame increases while the FPS decreases. After a minute or so, the game slows down and becomes choppy as too many objects fill the screen.

Entity Component System to the rescue!

You can disable player invincibility for slightly more realistic test conditions. Locate EnemyDroneNonECS in RW/Prefabs/NonECS. Then open the prefab to edit and check Can Hit Player in the Enemy Non ECS component.

Enemy Non ECS script component

Save the prefab and play the game again. See your tank die when it collides with a drone.

Tank destroyed after enemies can now collide.

Although enemies can’t keep spawning ad infinitum, Unity still stutters and spikes under too many objects at once.

ECS: Performance by Default

In classic Unity development, GameObjects and Monobehaviours let you mix data with behavior. For example, floats or strings can live side-by-side with methods like Start and Update. Making a mishmash of data types within one object translates into a memory layout like this:

Object-oriented programming memory layout diagram

For example, your GameObject might reference several data types like a Transform, Renderer and Collider. Unity scatters the varied data across non-contiguous memory. With enough objects, it spills into slower RAM.

In contrast, ECS tries to group similar data into chunks. It attempts to allocate memory with fewer gaps, packing the data more tightly. Doing this keeps as much as possible in the very fast CPU memory cache tiers (L1, L2, L3).

Data-oriented design memory layout diagram

DOTS replaces object-oriented programming with data-oriented design. This architecture focuses on how to keep the data compact, which, unfortunately, means replacing the Monobehaviours you’re accustomed to using.

Instead, you’ll build your app from Entities, Components and Systems.

Entity, Component and System diagram

Entities are items that populate your program, although they are not objects in the traditional sense. An entity is a small integer ID pointing to other bits of data.

Components are the actual data containers. They are structs that hold values without any logic, and you’ll no doubt have a lot of them. ECS revolves around storing these small Components in a clever way.

Systems hold behaviors and logic. You’ll use them to manipulate and transform your data. Because Systems work on entire arrays of Entities at once, they can do so more efficiently.

Together these three parts form ECS.

Following this architectural pattern tends to cluster your data toward the very fast cache memory. The result is a significant speedup compared to the OOP equivalent. Unity calls this phenomenon performance by default.

Note: Avoid confusing an ECS Component from Unity.Entities with classic UnityEngine.Object Components. Despite similar names, they’re completely separate.

Removing Non-ECS Code

Load SwarmDemoECS from Scenes, which removes the code used to generate the enemies.

Then open EnemySpawner.cs in Scripts/Managers.

Normally, this script would instantiate enemy waves, but some of the logic is missing. Notice the Start and SpawnWave methods are blank. You’ll fill those in during the tutorial.

Head on back to the Unity Editor. Disable DemoManagers and PlayerTank in the Hierarchy. But don’t delete them! You’ll need them later.

Disabling objects in the Hierarchy panel

Confirm in Play mode that enemies no longer spawn. Don’t worry, you’ll add them back using ECS.

For now, you should have an empty scene except for a plane with a grid texture. This is a perfect blank slate to create some entities!

Empty scene with plane and grid texture