ECS in Unity: Integration and Execution 1

Background

(originally published for Patreon backers November 2016)

General background for all these articles… I’m writing about major areas I’ve ignored in the past, using Unity as a convenient demo-environment for the ideas and tests:

  1. Multi-threading
  2. Querying / filtering
  3. Networking
  4. Unity integration:
    • As easy-to-use as the Unity Inspector (direct editing of components in 3D editor)
    • Easy use of Unity MonoBehaviours/Components on ECS Entities

I don’t want to be too language-centric. I ran a survey to see which languages people are using for ECS’s, and which they wanted more info on – wow, big spread! But talking to people over the past year, Unity kept coming up time and again as a particularly popular one where people want more info.

Screen Shot 2016-11-11 at 16.25.00

Overview: ECS Integration and Execution

Goal

We’re going to build a simple, effective ECS in the same way as you would inside any non-ECS game-engine. At the end of this article, we’ll have an ECS in Unity that interacts with legacy (non-ECS) code cleanly, and demonstrates simple isolation (runs on its own thread). The approach, the test-data, and the problems, should generalize well to other engines.

Steps

  • Make a simple scene in pure Unity
  • Add collision-detection and avoidance
  • Add visualizations so we can see what’s happening
  • Re-create the scene using our ECS
  • Add collision-detection in the ECS

It’s very long, so I’m splitting it into two articles (although Patreon backers will get both at once). The first article has most of the planning, demo-scene creation, and Unity testing. If you know Unity well already, skim-read that. The second article has all of the ECS-specific parts and covers most of the porting from Unity (or: random game-engine) into ECS.

Let’s get started…

Make a simple scene in pure Unity

Creating a new code-architecture inside an existing system is risky and time-consuming (wasting). To make this as smooth and manageable as possible, we’ll create a baseline project in legacy code, port it to ECS, and use that to check assumptions and planning around the ECS design. MVP FTW.

The demo scene

We’ll make thousands of squares move around in 2D on a plane (or, since Unity is a 3D engine: cubes moving on a flat surface, viewed from above).

Screen Shot 2016-11-11 at 17.17.42

Any demo scene will do, but I chose one so that:

  • It uses more than one type of Component on each Entity
  • Different types of Component will ideally be updated by different Processors
  • It’s easy to visualise whether the code is working at all
  • It’s easy to visualise bugs in the code (it’s working, but it’s wrong)
  • An excuse to do some CPU-intensive calculations that benefit from an ECS

In Unity…

Because this is our baseline, needed for lots of testing and ongoing development, we’re going to build most of the scene procedurally. This makes it easy for us to tweak parameters later on (how many cubes? how big? how much room can they move around in? where is the camera? … etc).

Class: CreateScene

This class will run even before the Start() methods, and guarantee we have a flat surface to work upon.

We use Unity’s CreatePrimitive command, that automatically creates not only a mesh (something that gets rendered), but also attaches a physical collider to it, so that any physics objects dropped on the mesh will sit on top of it.

Finally, we grab the scene camera (or create one if there isn’t one already) and move it to sit above the plane, looking downwards.

using UnityEngine;
using System.Collections;

public class CreateScene : MonoBehaviour
{
	public int _planeWidth = 50; // user can edit this in-editor
	public static int planeWidth; // when app starts, this copies the user-chosen value and exposes it to other classes

	public void Awake()
	{
		planeWidth = _planeWidth;
		GameObject plane = GameObject.CreatePrimitive( PrimitiveType.Cube );
		plane.name = "plane";
		plane.transform.localScale = new Vector3( planeWidth + 20f, 1f, planeWidth + 20f );
		plane.transform.position = new Vector3( 0f, -1f, 0f );

		Camera cam = Camera.main;
		if( cam == null )
		{
			GameObject _goCam = new GameObject( "Camera" );
			cam = _goCam.AddComponent<Camera>();
		}
		cam.transform.position = new Vector3( 0f, 50f, 0f );
		cam.transform.LookAt( Vector3.zero );
	}
}

Create an empty GameObject, attach that script, and run it. You should get a camera correctly auto-positioned looking down on the plane.

Screen Shot 2016-11-11 at 17.18.56

Class: PostSceneCreateCubes

This class runs once the game has started, and generates a random number of cubes that are dropped somewhere on the plane. It conveniently re-parents them into a GameObject so they don’t clutter the scene-hierarchy window. The positions are automatically set using the static variable that tells us how big the plane was to start with.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PostSceneCreateCubes : MonoBehaviour
{
	public int targetNumCubes = 1000;

	private List<GameObject> _internalCubes = new List<GameObject>();
	private GameObject _cubeHolder;

	void Start()
	{
		if( _cubeHolder == null )
			_cubeHolder = new GameObject( "Auto-created cubes" );

		for( int i = 0; i < targetNumCubes; i++ )
		{
			GameObject cubeN = GameObject.CreatePrimitive( PrimitiveType.Cube );
			cubeN.name = "Cube-" + i;
			cubeN.transform.position = new Vector3( Random.Range( -CreateScene.planeWidth / 2f, CreateScene.planeWidth / 2f ), 0.1f, Random.Range( -CreateScene.planeWidth / 2f, CreateScene.planeWidth / 2f ) );
	
			_internalCubes.Add( cubeN );
			cubeN.transform.SetParent( _cubeHolder.transform, true );
		}
	}	
}

Make a new GameObject for this, attach it, and run – you should get a random scattering of cubes. Great! Exciting! Easy (that’s the point ;)).

Screen Shot 2016-11-11 at 17.21.18

Class: UnityCubeMover

And the core: something that will move the cubes.

In a Unity game, traditionally this code would be placed in a “Cube” component attached to the cubes themselves. This isn’t a good way to write games/apps, and is a primary reason for adding an ECS to Unity. Even in Unity we often ignore it and place the code into a single shared class, like we do here. So we’re cheating a little: we know that eventually we want the code in a single class (that will be ported to become an ECS Processor).

First we need a way of knowing how fast we want each cube to move, and in which direction. I could try pre-rotating each cube to a random direction, and moving it “forwards” … but I chose to store an X,Y pair for the direction + speed instead.

using UnityEngine;
using System.Collections;

public class UnityCubeVelocity : MonoBehaviour
{
	public Vector3 velocity;
}

…if that class looks suspiciously like it’ll become a Component during the porting later on, then good.

Now we can make the main class which once per frame looks at all the cubes, and moves each one by random amounts.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class UnityCubeMover : MonoBehaviour
{
	public float maxXYSpeedPerSecond = 10f;

	protected List<GameObject> _internalCubes;

	public void Update()
	{
		if( _internalCubes == null )
			return;
		
			int i = -1;
			foreach( GameObject cube in _internalCubes )
			{
				i++;
				UnityCubeVelocity v = cube.GetComponent<UnityCubeVelocity>();
				if( v == null )
				{
					v = cube.AddComponent<UnityCubeVelocit>();
					v.velocity = new Vector3( Random.Range( -maxXYSpeedPerSecond, maxXYSpeedPerSecond ), 0f, Random.Range( -maxXYSpeedPerSecond, maxXYSpeedPerSecond ) );
				}
	
				Vector3 _translationThisFrame = v.velocity * Time.deltaTime;
				
				cube.transform.position += _translationThisFrame;		
			}
		}
	}	
}

This class needs to find the cubes we created, and since they’re created procedurally, we can’t use Unity’s normal mechanism for sharing this data (don’t use Unity’s Find; never use Unity’s Find unless you are absolutely desperate)

So, we’ll add a method to assign the cubes, and move the “AddComponent” call into it:

public class UnityCubeMover : MonoBehaviour
{
...
	public void SetInternalCubes(List<GameObject> cs)
	{
		_internalCubes = cs;

		foreach( GameObject cube in _internalCubes )
		{
			cube.AddComponent<UnityCubeVelocity>();
		}
	}
...
}

…and we’ll call that method from our PostSceneCreateCubes class:

public class PostSceneCreateCubes : MonoBehaviour
{
...
	public UnityCubeMover cubeMover;
...
	void Start()
	{

... original contents of this method go here ...

		if( cubeMover == null )
		{
			GameObject go_cubemover = new GameObject( "AUTO-CREATED CUBE MOVER" );
			cubeMover = go_cubemover.AddComponent<UnityCubeMover>();
		}
		cubeMover.SetInternalCubes( _internalCubes );
		_internalCubes = null;
	}
}

Add a new GameObject for the mover, and add UnityCubeMover. Run this, and … your cubes will jiggle like tiny ants.

Sadly, they’re not moving around smoothly – because we keep changing them. Every. Single. Frame. Forever. Without. Giving. Any. Time. To. Breathe.

Improving the scene

Smoother movement

It’s too quick for us to know if things are working. For a quick hack, let’s only change the direction/velocity once every N frames

I’m not happy with this hack: I’m sure there’s cleaner/clearer code ways of achieving this. I was looking for something engine-agnostic, it was quick to hack together, worked first time, hasn’t caused any problems yet. But it’s ugly/hard to read.

public class UnityCubeMover : MonoBehaviour
{
...
	protected static int _frameSkip = 60;
	protected int _framesSkippedSoFar = _frameSkip;
	
...
	public void Update()
	{
		if( _framesSkippedSoFar >= _frameSkip )
		{
			_framesSkippedSoFar = 0;

			foreach( GameObject cube in _internalCubes )
			{
				UnityCubeVelocity v = cube.GetComponent<UnityCubeVelocity>();
	
				v.velocity = new Vector3( Random.Range( -maxXYSpeedPerSecond, maxXYSpeedPerSecond ), 0f, Random.Range( -maxXYSpeedPerSecond, maxXYSpeedPerSecond ) );
			}
		}
		else
		{
			_framesSkippedSoFar++;
		
			... original code from the Update method goes here ...
		}
	}
}

Run again, and you should get smooth movement, with them all changing direction suddenly every 60 frames (every 2 seconds, if you’ve set Unity to a 30 FPS cap).

Colouring-in the cubes

White cubes on a white background are annoyingly hard to see. We’ll make a Unity Component that handles all the visual and animation on each individual cube, and attach it per-cube.

class: WanderingCube

using UnityEngine;
using System.Collections;

public class WanderingCube : MonoBehaviour
{
	public Vector3 maxAbsOfPositions;

	public static Color[] availableColors = new Color[] { Color.blue, Color.grey, Color.blue, Color.cyan, Color.yellow };
	public static Material[] availableMaterials;

	public void Awake()
	{
		if( availableMaterials == null )
		{
			availableMaterials = new Material[ availableColors.Length ];
			for( int i = 0; i < availableColors.Length; i++ )
			{
				availableMaterials[ i ] = new Material( Shader.Find( "Standard" ) );
				availableMaterials[ i ].color = availableColors[ i ];
			}
		}
		GetComponent<MeshRenderer>().sharedMaterial = availableMaterials[ Random.Range( 0, availableMaterials.Length - 1 ) ];
	}
}

…and attach it when we create our initial cubes:

public class PostSceneCreateCubes : MonoBehaviour
{
...
	public UnityCubeMover cubeMover;
...
	void Start()
	{
...
		for( int i = 0; i < targetNumCubes; i++ )
		{
			GameObject cubeN = GameObject.CreatePrimitive( PrimitiveType.Cube );
			cubeN.name = "Cube-" + i;
			cubeN.transform.position = new Vector3( Random.Range( -CreateScene.planeWidth / 2f, CreateScene.planeWidth / 2f ), 0.1f, Random.Range( -CreateScene.planeWidth / 2f, CreateScene.planeWidth / 2f ) );
			WanderingCube wcube = cubeN.AddComponent<WanderingCube>();
			wcube.maxAbsOfPositions = CreateScene.planeWidth / 2f * Vector3.one;
		}
...
	}
}

Run this, and you get multiple colours. Lovely.

Screen Shot 2016-11-11 at 17.22.29

Stopping them leaving the play-area

With these random procedural simulations we’ll often want to leave the code running for a while, looking out for problems. At the moment, the cubes eventually wander off the playing area. The simplest old-school solution is to teleport them back onto the other side whenever they fly off.

I’m putting this into Unity’s LateUpdate() so that it happens AFTER all of our other game-logic, and we don’t have to worry about it interfering with collision detection (much).

using UnityEngine;
using System.Collections;

public class WanderingCube : MonoBehaviour
{
...
	public void LateUpdate()
	{
		Vector3 pos = transform.position;
		
		if( pos.x > CreateScene.planeWidth / 2f )
			pos.x -= 2f * CreateScene.planeWidth / 2f;
		else if( pos.x < -1f * CreateScene.planeWidth / 2f )
			pos.x += 2f * CreateScene.planeWidth / 2f;
	
		if( pos.z > CreateScene.planeWidth / 2f )
			pos.z -= 2f * CreateScene.planeWidth / 2f;
		else if( pos.z < -1f * CreateScene.planeWidth / 2f )
			pos.z += 2f * CreateScene.planeWidth / 2f;

		transform.position = pos;
	}
}

Collision Detection 1: using Unity’s in-game CD for debugging

We’re going to use two parallel forms of collision detection. The first one is for visualisation and is purely observational: all collisions are allowed, interpenetration happens all the time, no effect on gameply. This will use Unity’s built in “Physics Trigger” concept.

The second form is for gameplay, and we’ll put more effort into it. It will be a full collision detection-and-prevention system that actually blocks things from moving, makes them bounce off each other, etc.

Visual debugging: cubes that detect collisions

We could set a single colour to every cube – or randomize the colours – but we’re going to go one better. Each cube will have a colour that indicates what’s happening to that cube.

Visual debugging is infinitely faster and more efficient than console-debugging or using the debugger (if you like this idea, go read @redblobgames’ articles on game programming; Amit likes to use visual interactive diagrams in his articles. Very time-consuming to make, but wonderful to play with).

To do this, we:

  1. Make all cubes blue to start with
  2. Create a “collided” material, and colour it red
  3. Add the necessary physics things in Unity to detect collisions
  4. When a cube collides, we change it material to the collided one
using UnityEngine;
using System.Collections;

public class WanderingCube : MonoBehaviour
{
...
	public static Color[] availableColors = new Color[] { Color.blue };
	public static Material[] availableMaterials;
	public static Material errorMaterial;
...
	public void Awake()
	{

... original contents of this method goes here ...

		BoxCollider col = gameObject.GetComponent<BoxCollider>(); // needed to receive OnTrigger callbacks
		if( col == null )
			col = gameObject.AddComponent<BoxCollider>();
		col.isTrigger = true;

		Rigidbody rb = gameObject.AddComponent<Rigidbody>(); // needed to receive OnTrigger callbacks
		rb.useGravity = false;
	}
...
/** This is a piece of GUI/UX: we use this to make cubes intelligently warn us whenever they
    inter-penetrate; our code should prevent this from happening .... "should" ... muahahahahaha!
    
    (if you've ever had the pain of race conditions in multi-threaded code, you may see where I'm
    going with this...)
    */
	public void OnTriggerEnter(Collider other)
	{
			GetComponent<MeshRenderer>().sharedMaterial = errorMaterial;
	}

}

This will work, but … the cubes go red and never go back to blue.

And there’s another problem – depending upon your version of Unity, the cubes MIGHT detect the plane itself as “colliding” – NB: due to Unity’s generally weird physics, it’s not guaranteed whether this will or won’t happen on your install.

So we do two changes:

  1. Use a co-routine to automatically animate a change: switch back to the original material after a few seconds
  2. Place a Unity “tag” on each cube and tell the physics to ignore collisions unless they are “tagged” as cubes

Pre-tag objects

First, because Unity’s editor is old-fashioned about this, manually pre-create the tag in your editor:

Menu: Edit – Project Settings – Tags and Layers – … and select “Tags” at the top, then hit the plus button to create a new one

Call your tag WanderingCube, and then add code to the WanderingCube class so that new cubes automatically tag themselves:

public class WanderingCube : MonoBehaviour
{
...
	public void Awake()
	{
		tag = "WanderingCube";
		...
	}
...
}

Flash the collision indicator – but only for cube/cube collisions

using UnityEngine;
using System.Collections;

public class WanderingCube : MonoBehaviour
{
...
	private Material _originalMaterial;
	private Coroutine _coro_ResetMaterial;
...
	public void OnTriggerEnter(Collider other)
	{
		if( other.gameObject.tag == tag )
		{
			if( _coro_ResetMaterial != null )
			{
				StopCoroutine( _coro_ResetMaterial );
				GetComponent<MeshRenderer>().sharedMaterial = _originalMaterial;
			}

			_originalMaterial = GetComponent<MeshRenderer>().sharedMaterial;

			GetComponent<MeshRenderer>().sharedMaterial = errorMaterial;

			_coro_ResetMaterial = StartCoroutine( "ResetMaterialToOriginal" );
		}
	}

    /** Using a co-routine allows us to display it for more than a single frame, ie human-friendly
    but fire-and-forget in the original method that triggers it
    */
	public IEnumerator ResetMaterialToOriginal()
	{
		yield return new WaitForSeconds( 0.4f );
		GetComponent<MeshRenderer>().sharedMaterial = _originalMaterial;
	}
}

Run that.

Now you should have cubes merrily running around, flashing red when they collide, but otherwise happily passing through each other. This is ideal – it sets the stage for us to add our own collision-detection in the next stage.

Screen Shot 2016-11-11 at 17.23.38

Collision Detection 2: using Unity’s in-game CD for Gameplay

We’ll use Unity at first, to test the code, then later switch to our own implementation (which can execute entirely inside the ECS). However, there’s another reason not to rely upon Unity’s CD…

Unity3D has no collision detection system. Instead, it re-uses the 3rd-party physics engine built-in to Unity, and uses an approximation of CD from that.

If you move a transform.position in Unity, there is no direct way to find out if it will collide, where it will collide, or what it collides with.

Most of the time, no-one cares about the difference between “collision detection” and “approximate collision detection by asking the physics engine”. You use the physics methods outside the physics loop (which you should never do – it’s inaccurate) and Unity doesn’t complain. Occasionally it’ll give you noticeably wrong results.

In particular:

  • RayCast
  • BoxCast
  • RayCastAll
  • …etc

…all run instantly, using approximated data from the most-recent physics tick – which is usually (always?) out of synch with rendering. I’ve got some interesting physics-heavy projects that demo this and some of the odd side-effects. IMHO the engine should throw an exception when any of these are used outside the physics loop – you need to be aware that the data is wrong.ยง

Making Collision Detection easy: the rise of the AABB

Writing CD is well-documented in computer games for more than 30 years. However, doing it correctly is a bit of a pain, and lots of code (that I can’t be bothered to write). We’re going to use a cheap hack to the scene/game design that allows us to write a much simpler (but 100% correct) CD later on.

Google it for more info, but the absolute minimum is that you have to “sweep” your objects through space, and compare the overlapping swept shapes. Rather than asking “do the squares overlap?” you have to ask “do the swept squares overlap?”.

No collision detected MISSING collision!
2dsweep-0 2dsweep-1

…using swept shapes instead, you detect the collisions because your new shapes overlap:

2dsweep-2

We can massively simplify this: if we only use cubes (oooh! What a coincidence!) and we only allow AA (axis-aligned) movement, we can trivially calculate the swept-cube as a rectangular, stretched cube. Or, in 2D, we move from comparing squares to comparing rectangles – and all programming languages have built-in methods for comparing if pairs of rectangles overlap (i.e. collide).

2dsweep-3

This requires a tweak:

public class UnityCubeMover : MonoBehaviour
{
...
	public void Update()
	{
...
			foreach( GameObject cube in _internalCubes )
			{
				UnityCubeVelocity v = cube.GetComponent<UnityCubeVelocity>();

				if( Random.Range(0,2) < 1 )
				{
					v.velocity = new Vector3( Random.Range( -maxXYSpeedPerSecond, maxXYSpeedPerSecond ), 0f, 0f );
				}
				else
				{
					v.velocity = new Vector3( 0f, 0f, Random.Range( -maxXYSpeedPerSecond, maxXYSpeedPerSecond ) );
				}
			}
...
	}
...
}

…now the cubes should only move up-down or left-right.

Preventing collisions

We need to do three things: detect collisions, prevent them, and … show that we’ve prevented them.

To detect collisions, we’ll create a “will moving this cube cause a collision?” method, and if the answer is “yes”, we’ll simply block it.

public class UnityCubeMover : MonoBehaviour
{
...
	public bool blockCollisions = true;
...
	public void Update()
	{
...
				Vector3 _translationThisFrame = v.velocity * Time.deltaTime;
				
				GameObject collidee;
				bool _willCollide = wouldMovingCubeCollide( cube, _translationThisFrame, out collidee );
				
				if( _willCollide && blockCollisions )
				{
					//Debug.Log( "Cube " + cube + " (bounds.extends = " + cube.GetComponent<BoxCollider>().bounds.extents + ") WOULD HAVE hit (but I prevented it) " + collidee + " alng translation = " + _translationThisFrame );
					Debug.DrawRay( cube.transform.position + Vector3.up, _translationThisFrame );
				}
				else
					cube.transform.position += _translationThisFrame;
...
	}
}

…to fulfil that, we use the simplest possible collide-check: we ask Unity physics to do all the hard work (although we know this will be slightly inaccurate):

public class UnityCubeMover : MonoBehaviour
{
...
	/**
	Uses Unity physics to do arbitrary collision.	
	In testing, it ends up being wrong about 1% of the time, due to the frame rate diff of physic vs rendering.
	*/
	protected bool wouldMovingCubeCollide( GameObject movingCube, Vector3 translation, out GameObject objectCollidedWith )
	{
		UnityCubeVelocity v = movingCube.GetComponent<UnityCubeVelocity>();
	
		RaycastHit hit;
		if( blockCollisions && Physics.BoxCast( movingCube.transform.position, /* Unity's docs for this method are bad */ movingCube.GetComponent<BoxCollider>().bounds.extents,  translation, out hit, Quaternion.identity, /** check 10% ahead because Unity floats are rather inaccurate; feel free to use 1.0 instead */ 1.1f * translation.magnitude ) )
		{
			objectCollidedWith = hit.collider.gameObject;
			return true;
		}
				
		objectCollidedWith = null;
		return false;
	}
}

You can test it by clicking the “blockCollisions” method on and off while it’s running – but that’s not really clear enough for me.

If you run this code, the cubes will move around, and gradually come to a halt as they avoid collisions. Then, when the directions re-randomize, they’ll (mostly) all start moving again. Since we’re preventing potential collisions by stopping dead, even cubes that look like they might not collide get stopped – instead of going “as far as I could and stopping at the last moment”. Use the Rays that I’m drawing in the Scene view to confirm this in cases where it’s unclear why individual (pairs of) cubes have stopped.

Screen Shot 2016-11-05 at 20.43.14

Displaying prevented-collisions

Let’s make this easier to see. We’ll allow cubes to go an extra colour: yellow, for when they are frozen to avoid a collision.

public class UnityCubeMover : MonoBehaviour
{
...
	public void Update()
	{
...
		if( _framesSkippedSoFar >= _frameSkip )
		{
...
		}
		else
		{
			_framesSkippedSoFar++;
...

...original method contents here...

			foreach( GameObject cube in _internalCubes )
			{
				if( _willCollide && blockCollisions )
				{
					cube.GetComponent<WanderingCube>().OnCollisionWasPrevented();
				}
			}
		}
	}
...
}

…and upgrade the WanderingCube to support that:

using UnityEngine;
using System.Collections;

public class WanderingCube : MonoBehaviour
{
...
	public static Material errorMaterial, almostCollidedMaterial;
...
	public void Awake()
	{
...
		if( availableMaterials == null )
		{
			errorMaterial = new Material( Shader.Find( "Standard" ) );
			errorMaterial.color = Color.red;

			almostCollidedMaterial = new Material( Shader.Find( "Standard" ) );
			almostCollidedMaterial.color = new Color( 0.5f, 0.5f, 0 );
			...
		}
	}
...
/** This is a piece of GUI/UX: we use this to make cubes intelligently tell us when
they've been FORCED to stop moving by the collision-detection system
*/
	public void OnCollisionWasPrevented()
	{
		if( _coro_ResetMaterial != null )
		{
			StopCoroutine( _coro_ResetMaterial );
			GetComponent<MeshRenderer>().sharedMaterial = _originalMaterial;
		}

		_originalMaterial = GetComponent<MeshRenderer>().sharedMaterial;

		GetComponent<MeshRenderer>().sharedMaterial = almostCollidedMaterial;

		_coro_ResetMaterial = StartCoroutine( "ResetMaterialToOriginal" );
	}
}

Now you should find that cubes are blue when moving, flash red briefly when they collided, and stop and turn yellow when they MIGHT collide:

Screen Shot 2016-11-11 at 17.40.28

Wait – why are we seeing red? We should never see red!

Correct. There are three things that allow collisions to happen despite our precautions.

At the end of each frame we’re teleporting cubes who’ve gone off the edge of the playing field back on to the other side. Since we ignore this in the CD code, we expect to see a few random red flashes around the edges of the playing area. We could fix that, but … it’s useful. It’s a known error, and it proves that our “VISUALLY detect collision, even if the CD code has failed” code is actually running and working correctly. If we ever get a version of the demo where nothig is ever going red, we know we accidentally broke something.

Secondly, there’s the problem of Unity physics approximating the CD. Each case where you get a flash of red is where there was a near-miss that wasn’t going to happen during the last physics step (which is where Unity’s CD approximation gets its data from), but did happen during the next physics step (which happens out of sync with our game-logic).

Finally … there’s a bug. I said at the start:

“I’m putting this into Unity’s LateUpdate() so that it happens AFTER all of our other game-logic, and we don’t have to worry about it interfering with collision detection (much).”

…that LateUpdate that’s doing the teleport is also overriding the on-screen position of cubes, but NOT changing the ECS’s version of the data. Over time, this gets more and more corrupt. We’ll delete that code from WanderingCube.LateUpdate(), and re-create it inside the (new, ECS version of) CubeMover.

When we port this identical code to an ECS, and stop using Unity physics, you expect to see all the red flashes in the middle of playing area go away…

Converting this demo into an ECS demo

With all the setup out of the way, onto the ECS stuff. There’s some obvious parts to this, and some non-obvious ones. We’re NOT going to throw everything away – we’ll be keeping some of the legacy code (things that are specific to the game-engine, and work well living outside the ECS). In particular: the animation of cube collisions (blue, red for collided, yellow for blocked) is useful to keep outside the ECS – it lets us debug our ECS without worrying that the debugging code is broken.

Next…

Patreon backers can access part 2 and later articles immediately – link here: requires password.

In part 1, we created a test scene for doing collision detection and rendering in Unity, in a way that we can then port to an ECS. In part 2, we’ll do the actual conversion, and create a simple ECS that exists (and runs) independently inside Unity, but is managed by Unity classes.

3 thoughts on “ECS in Unity: Integration and Execution 1

  1. Ilya Belyy

    foreach( EntityID eid in ECSStore.allEntitiesByID )
    {
    CDrivingForce force = ECSStore.editable( eid );
    if( force != null ) …
    }

    Why don’t you just iterate over allMappedComponentsByType[CDrivingForce] ?

    Also, I was under impression that you use structs for components after some hints either here or on twitter.

  2. adam Post author

    @Ilya – to be clear, the API I’ve used here for accessing components/entities isn’t one I’d recommend except as a starting-point. I wanted the minimum possible so I could focus on what we’re doing with it, rather than worry about the implementation (For now).

    In general yes, I advocate structs for components. But taking advantage of that in C# is particularly difficult (they keep postponing adding true by-address semantics to the language, and the only way to efficiently use them is with unsafe code or some nasty bridging – because Unity blocks unsafe code)

Leave a Reply

Your email address will not be published. Required fields are marked *