Create a Unity ECS Project

Full workflows and code to set up an ECS project with the latest Unity Editor and compatible package versions

What you'll develop on this page

Basic game settings will be configured in a GameObject in a Sub Scene and can be viewed in the "Entity Inspector" at runtime.

Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Setting-up-a-project-for-ECS

Project setup

  • Install Unity Hub 3.1.2 if you do not have it already

  • Install Unity 2020 LTS if you do not have it already

    • Go to "Installs" on the left side of Unity Hub pop-up window

    • Click "Add" and choose 2020 LTS

    • Once 2020 LTS is downloaded, click on the three vertical dots menu (kebab menu) in the top right corner to add platform modules depending on your development and target platforms (OSX/Linux/Windows)

    • Include iOS Build Support if you plan on completing the AR Foundation section of this gitbook

  • Go to "Projects" in Unity Hub and click "New" in the top left corner of the window and select 2020 LTS and create a new 3D project, "Asteroids 3D XR Multiplayer"

Note For those on a Fedora system (does not apply to Mac or Windows)

From Giiba on our discord:

  • Upon installing the burst compiler you might get a cascading error that began with blah blah... DllNotFoundException: libdl.so ...blah blah

  • /usr/lib64/libdl.so.2 and creating a symlink ln -s libdl.so.2 libdl.so solved the error

  • Editor needs a restart after adding the symlink

Packages setup

  • Once you're in Unity, navigate to Package Manager from "Window"

  • Within PackageManager find the little "gear" icon and select Advanced Project Settings

  • Check the box next to Enable Preview Packages and also Show Dependencies at the bottom of the window

    • This will allow you to view the preview packages that are included included in the package manifest

  • Outside of the Unity Editor, navigate to your project folder (wherever you saved it locally) and open manifest.json (inside the Packages folder) with Visual Studio code or your choice of editor

    • These are all the packages that are currently included in your project

  • We are now going to manually add a new package, the Hybrid Rendering package

  • Add the following line to the manifest somewhere between the curly braces and save:

    "com.unity.rendering.hybrid": "0.50.0-preview.44",
  • When you click back into the Editor, Unity will notice the updated manifest and "pull" down the hybrid renderer package we added (you'll see the "Importing" window pop-up with a loading bar)

  • If interested, read up to the "Hybrid Renderer" section in this blog post to learn what makes the Hybrid Renderer "hybrid"

    • Read the whole blog post if you want to be an ECS master, it is a great post!

  • Hybrid Renderer automatically pulls in ECS dependencies (everything you need for ECS)

  • You can see the dependencies of the package if you navigate back to the PackageManager in the Editor and select the Hybrid Renderer

  • A key dependency is Entities, which brings in even more additional dependencies

    • Entities v0.50.1-preview.2

      • Burst v1.6.4

      • Properties v1.7.0-preview

      • Properties UI v1.7.0-preview

      • Serialization v1.7.0-preview.1

      • Collections v1.2.3

      • Mathematics v1.2.5

      • Asset Bundle v1.0.0

      • Unity Web Request v2.3.1-preview

      • Performance testing API v2.3.1-preview

      • Mono Cecil v1.10.1

      • Jobs v0.50.0-preview.9

      • Scriptable Build Pipeline v1.19.2

      • Platform v0.50.0-preview.4

      • Roslyn Compiler for Unity v0.2.1-preview

      • Unity Profiling Core API v1.0.0

  • Adding certain packages directly from the Package Manager window is no longer possible (explanation in this post)

  • For the duration of this gitbook, we will almost exclusively be adding packages through the manifest

  • Now we will add support for Universal Render Pipeline

    • This error is what happens if you do not add the Universal Render Pipeline (URP)

  • We could have also chosen to add High Definition Render Pipeline (HDRP), but we want to support mobile devices

    • More discussion on difference between URP vs. HDRP can be found here

  • Add the following line to the manifest somewhere between the curly braces and save

"com.unity.render-pipelines.universal": "10.9.0",

We now have an ECS project set up

  • created a new project

  • added hybrid renderer to the manifest

  • added universal render pipeline support

Sub Scene setup

First, some background:

Unity has no plans to sunset GameObject/MonoBehaviour functionality given how incredibly powerful and mature their workflows are. That's why Unity built tools and workflows for developers to support GameObject workflows while also utilizing DOTS (a sorta 2-in-1, you can use both!).

One such workflow is the use of "Sub Scenes." Unity gives you the ability to split your scene into several Sub Scenes, which you use to partition various GameObjects and Entities. When a Sub Scene window is open (a.k.a. 'editable'), it operates in "GameObject world." When a Sub Scene window is closed, it operates in "Entity-performant world" (so to speak).

How is this helpful? Here's an example: Visual artists can build GameObject assets in Sub Scenes, as long as they have the Sub Scene window open in Editor. When done editing, the visual artist closes the Sub Scene window, which triggers the conversion of these GameObjects into Entities. The Entities can be streamed in and out as needed. The conversion-to-entities allows for enormous environments (as demo'd in the Megacity walkthrough below). If all those entities had been game objects, it would absolutely destroy the performance of a normal authoring scene.

This is how it works: This conversion of GameObjects into Entities within a Sub Scene takes place either when (1) you hit Play in Editor while the Sub Scene is open at the time, or when (2) you close the Sub Scene from its Inspector.

The conversion process itself shouldn't take long at all, even for massive scale environments like the Megacity demo.

External resources, if interested:

Unity's Megacity walkthrough: https://www.youtube.com/watch?v=j4rWfPyf-hk Watch this if you want a general understanding of the benefits of Sub Scenes.

Unity's post about Sub Scene conversion workflows: https://forum.unity.com/threads/new-subscene-converttoentity-workflows.638785/ Read this if you want to be up to speed with the latest and greatest updates.

Procedural generation of Sub Scenes: https://forum.unity.com/threads/generate-sub-scenes-programmatically.868984/#post-6598072 Read this if you are hardcore.

Now let's implement:

  • Right click on the Hierarchy window and choose "New Sub Scene" > "Empty Scene"

  • Name this Scene "ConvertedSubScene"

    • This will automatically create a new folder with the parent scene name

    • This will also automatically create a new folder called "SceneDependencyCache" which is used by Unity to help load/unload Sub Scenes

  • Select "ConvertedSubScene" in Hierarchy

  • Check out the settings in the Inspector

    • "Auto Load Scene" is selected true by default, which means this Sub Scene will automatically load its Entities when the scene is loaded

  • Double click on "ConvertedSubScene" in Assets/Scenes/SampleScene and hit the play button

    • Notice there are no cameras in the Sub Scene so nothing renders

  • Return to "SampleScene" in Assets/Scenes and hit the play button

    • Notice that the skybox renders because a camera is present

You can see how Unity is able to handle both GameObjects and Entities at runtime without adding a single Entity, Component, or System. The GameObject camera functions as expected even with a Sub Scene loaded into the scene.

Unity has release DOTS-specific Editor windows to help developers manage their DOTS projects. The windows available can be found in "Window" > "DOTS".

  • Let's combine all these views into a single view that has tabs for DOTS Hierarchy, Systems, Components, and Archetypes

    • We will use these windows to see:

      • Entities (hexagon icon)

      • Components (puzzle piece icon)

      • Systems (hexagon with arrows icon)

      • As well as "Archetypes" (types of entities) (hexagon with interior edges icon)

If you are confused by Worlds and Systems read the section on "System organization" at the bottom of the link here.

  • Let's checkout the amount of entities we currently have in our project by navigating to DOTS Hierarchy by clicking on the DOTS Hierarchy tab

    • You should see 3 entities (while the Sub Scene is "open")

  • Now let's close the Sub Scene by hitting the "close" button in the inspector when the ConvertedSubScene is selected in the Hierarchy

  • You Should now see 5 entities

  • Click through the 5 entities and notice how their components and values are shown in the Editor Inspector window

  • Think of the current 5 entities as autogenerated Unity.Entities "boiler plate" for our current scene / SubScene setup

We now are able to navigate Entities, Systems, Archetypes and see their associated data in the Inspector.

We now have a Sub Scene loading into our scene

  • We created a Sub Scene "ConvertedSubScene" in the Hierarchy

  • Created a "DOTS Window" containing the new DOTS Editor Views

  • Saw how the number of entities changes when a Sub Scene is open vs. closed because of the Sub Scene conversion work flow

Initializing game settings

First, some background:

Conversion Workflow

To use Unity’s DOTS technology, you need to create entities, components and systems.

The generation process that consumes GameObjects (authoring data) and generates entities and components (runtime data) is called conversion.

  • This process is the preferred way of authoring ECS data

  • It is a fundamental part of DOTS, and not something temporary

  • Conversion is only about data, there is no conversion process for code

The overall workflow looks like this:

  1. The Unity Editor is a user interface to work with authoring data

  2. The conversion from authoring data to runtime data happens in the Unity Editor

  3. The runtime (e.g. the game) should only ever have to deal with runtime data

Fundamental principles

Authoring data and runtime data are optimized for wildly different goals.

  • Authoring data is optimized for flexibility

    • Human understandability and editability

    • Version control (mergeability, no duplication)

    • Teamwork organization

  • Runtime data is optimized for performance

    • Cache efficiency

    • Loading time and streaming

    • Distribution size

A key observation is that nothing requires a 1:1 mapping between GameObjects and entities.

  • A single GameObject can turn into a set of entities, e.g. procedural generation

  • Multiple GameObjects can be aggregated into a single entity, e.g. LOD baking

  • Some GameObjects might have no purpose at runtime, e.g. level editing markers

The same can be said about components. A conversion system can read from any amount of Unity components and add any amount of ECS components to any amount of entities.

Key concepts

All those concepts get explained in further detail in the rest of this document, but it's useful to introduce some vocabulary beforehand.

  • Authoring scene A regular Unity scene, containing GameObjects, destined to be converted to runtime data.

  • Subscene A simple GameObject component that references an authoring scene and will either load the authoring scene (when the Subscene is in edit mode), or stream in the converted entity scene (when the Subscene is closed).

  • Entity scene The result of converting an authoring scene. Because entity scenes are the output of an asset import, they are stored in the Library folder. Entity scenes can be made of multiple sections, and each of those can be independently loaded.

  • LiveConversion When an authoring scene is loaded as GameObjects for editing, every change will trigger an update to the entity scene, making it look as if the entity scene was directly edited, we call this process LiveConversion.

From Unity's ECS Conversion Workflow documentation

🔑 Major Key Alert 🔑

It is worth the effort to read through the Conversion Workflow documentation and wrap your head around Authoring/Conversion as it is a key part of ECS development

Unity's Talk on conversion workflows: https://www.youtube.com/watch?v=TdlhTrq1oYk Watch this if you want a more in-depth explanation (and/or prefer videos).

Now let's implement:

  • Create a new folder called "Scripts and Prefabs" in the "Assets" folder within the Project window

  • Navigate to the "Scripts and Prefabs" folder, right click, choose "Create" > "ECS" > "Runtime Component Type", and name it "GameSettingsComponent"

  • Copy the below code snippet into GameSettingsComponent.cs:

using Unity.Entities;

public struct GameSettingsComponent : IComponentData
{
    public float asteroidVelocity;
    public float playerForce;
    public float bulletVelocity;
    public int numAsteroids;
    public int levelWidth;
    public int levelHeight;
    public int levelDepth;
}

Make sure to first clear the file before pasting in this code snippet.

  • Components cannot store data pre-runtime. So, to set game settings in the Editor ("authoring"), we will need to use the conversion workflow

    • public float asteroidVelocity = 10f; (cannot set values in IComponentData)

  • Navigate to "ConvertedSubScene" and create a new Empty GameObject named "GameSettings"

    • This GameObject will hold a script that will allow us to "author" game settings in the Editor and have those values converted at runtime into the GameSettingsComponent

  • In "Scripts and Prefabs", right click, choose "Create" > "ECS" > "Authoring Component Type", and create "SetGameSettingsSystem"

    • "Authoring" will allow us to add data in the Editor before runtime

  • SetGameSettingsSystem.cs

using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

public class SetGameSettingsSystem : UnityEngine.MonoBehaviour, IConvertGameObjectToEntity
{
    public float asteroidVelocity = 10f;
    public float playerForce = 50f;
    public float bulletVelocity = 500f;

    public int numAsteroids = 200;
    public int levelWidth = 2048;
    public int levelHeight = 2048;
    public int levelDepth = 2048;
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var settings = default(GameSettingsComponent);

        settings.asteroidVelocity = asteroidVelocity;
        settings.playerForce = playerForce;
        settings.bulletVelocity = bulletVelocity;

        settings.numAsteroids = numAsteroids;
        settings.levelWidth = levelWidth;
        settings.levelHeight = levelHeight;
        settings.levelDepth = levelDepth;
        dstManager.AddComponentData(entity, settings);
    }
}
  • If you ever want to set data before runtime in the Editor ("authoring") and have it exist at runtime as Component data for ECS to use, you will use a conversion workflow similar to above:

      1. Create the IComponentData that you will need

      2. Create an IConvertGameObjectToEntity (also an Interface, link to Unity docs) with public fields that match IComponentData

      3. Within the IConvertGameObjectToEntity use Convert() to take the MonoBehaviour data and set it to a Component during the conversion process

    • On the next page we show how to use [GenerateAuthoringComponent] to add IComponentData directly on GameObjects

  • Click on the "GameSettings" object in the Hierarchy, then in the Inspector click "Add Component" and select SetGameSettingsSystem to add the MonoBehaviour to the GameObject

  • Make sure it's added and then navigate back to "SampleScene"

  • Select "ConvertedSubScene" from the Hierarchy (make sure it's selected) and then click "Reimport" in Inspector

  • Navigate to the DOTS Hierarchy window and notice a new Entity with a GameSettingsComponent

  • The new Entity with a GameSettingComponent can also be seen via a new EntityArchetype

Nice! The Sub Scene has followed the Convert() process and we have added the GameSettingsComponent to the converted GameSettings GameObject

Remember, the GameSettings GameObject in our ConvertedSubScene automatically gets converted because it is a GameObject in a Sub Scene.

Our SetGameSettingsSystem set on that GameObject has "Convert()" triggered when going through the conversion workflow, which:

  1. takes the new Entity (which used to be the GameSettings GameObject) and

  2. adds a GameSettingsComponent to it, then:

  3. sets the GameSettingsComponent data to the data set in the Editor

...which results in an Entity with a GameSettingsComponent. We will use this Entity in the next section.

A different way to get GameObject data into an ECS Component

In the latest ECS release GameObjectConversionSystem can also look for classic Unity components. So you can add data to a GameObject through a normal MonoBehavior component, and then "search" for that MonoBehavior component in an EntityQuery.

Here's the "hello world" of conversion systems, that does a 1:1 conversion of all authoring components of a certain type to their ECS equivalent.

// Authoring component
class FooAuthoring : MonoBehaviour
{
    public float Value;
}

// Runtime component
struct Foo : IComponentData
{
    public float SquaredValue;
}

// Conversion system, running in the conversion world
class FooConversion : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
        // Iterate over all authoring components of type FooAuthoring
        Entities.ForEach((FooAuthoring input) =>
        {
            // Get the destination world entity associated with the authoring GameObject
            var entity = GetPrimaryEntity(input);

            // Do the conversion and add the ECS component
            DstEntityManager.AddComponentData(entity, new Foo
            {
                SquaredValue = input.Value * input.Value
            });
        });
    }
}

In a GameObjectConversionSystem, ForEach will not create jobs. It runs on the main thread, without Burst, and this allows accessing classic Unity without restraint. This is also why it doesn't require a call to .Run() or .Schedule().

Also note that the entity query looks for classic Unity components, in this case FooAuthoring that derives from MonoBehaviour. Since those are reference types, they do not require ref or in.

From Conversion systems 101 in Unity Docs

We now have "authored" data on a GameObject that's been converted to Component data on an Entity

  • We created GameSettingsComponent.cs as our IComponentData the ("C" in "ECS")

  • We created SetGameSettingsSystem.cs which uses Unity's Sub Scene Conversion Workflow

  • We created GameSettings GameObject in ConvertedSubScene and added SetGameSettings as a Component

  • We navigated to our DOTS Editor windows to see that the new Entity and its GameSettingsComponent have been added to the Entity list

Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Setting-up-a-Project-for-ECS

git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Setting-up-a-Project-for-ECS'

Last updated