[FreedomAI] Week 1 - ECS

  As an innovation training project, it is very important to innovate. We first carried out engineering innovation in order to write this algorithm framework, using the ECS framework.

What is ECS?

   This framework may have existed more than ten years ago, but it probably became very popular after Overwatch proposed it at GDC2016, and Unity2018 will also adopt the ECS programming specification.

   First of all, let's talk about what this is in words. It stipulates that your game has a world first. There are several entities and several systems in this world. Each entity consists of several components, and each system will take out the entity with the fixed combination of components in the system and update it when updating each frame. And this also constitutes the three concepts of ECS: Entity, Component, System. In particular, we also stipulate that there can only be data and operations on data in Component, and only functions in System.

  Writing this, you will probably find that he is different from object-oriented thinking. For example, we have players, monsters. According to the object-oriented thinking, first we have to write a player class, which has player data and player behavior such as: attack, injury, rendering, etc. There is also a monster class, which has data and behavior. But if it is ECS, there will be two entities, player and monster, with their own attack components (with attack data) and injury components (with injury data). . . . There will be an attack system, an injury system, and a rendering system. According to whether there is an attack component, an injured component, or a rendering component, the entity will be automatically selected for operation, but it does not care which entity it is. For the system, as long as there are components, it is the same. .

Advantages of ECS:

  Although this is an object-oriented framework, it has to be said that this idea is actually more like functional programming. The function is not restricted to the behavior of a class, but the execution process is regarded as the result of the continuous mapping of the system (function) to the components (data) in the entity.

   The benefits are just some of my personal understanding: each system has a single function, it is very good for debugging, and it is born with interface isolation. Inheritance is replaced by composition. In ECS, inheritance is rarely used, and more is the composition of different components and different functions, which greatly reduces the amount of code. And for large projects, it is actually a very crashing thing to inherit all the time. Then, good division of labor. . (For students like us who are not very familiar with team development)

Code:

using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;
using System.Reflection;
using System;


namespace UnityECS
{

	public class BitBunch
	{
		public bool[] mBitArray = new bool[200];
		public int mMaxCount = 200;
		public int mCount = 0;

		public bool GetBit(int mx)
		{
			return mBitArray[mx];
		}

		public void SetCount(int count)
		{
			if (count > mMaxCount)
			{
				mBitArray = new bool[count * 2];
				mCount = count;
				for (int i = 0; i < mCount; i++)
					mBitArray [i] = false;
			}
			else
			{
				mCount = count;
				for (int i = 0; i < mCount; i++)
					mBitArray [i] = false;
			}
		}
			
		public void AddBit(bool mb)
		{
			if (mCount < mMaxCount)
			{
				mBitArray[mCount] = mb;
				mCount++;
			}
			else
			{
				mMaxCount *= 2;
				bool[] tBitArray = new bool[mMaxCount];
				for (int i = 0; i < mCount; i++)
					tBitArray [i] = mBitArray [i];
				mBitArray = tBitArray;
				mBitArray[mCount] = mb;
				mCount++;
			}
		}

		public void SetBit(bool res,int mx)
		{
			mBitArray [mx] = res;
		}

		public static bool Equal(BitBunch mbfir,BitBunch mbsec)
		{
			if (mbfir.mCount != mbsec.mCount)
			{
				return false;
			}
			for (int i = 0; i < mbfir.mCount; i++)
			{
				if(mbfir.GetBit(i)!=mbsec.GetBit(i))
				{
					return false;
				}
			}
			return true;
		}

		public static BitBunch And(BitBunch mbfir,BitBunch mbsec)
		{
			BitBunch tBitBunch = new BitBunch ();
			for (int i = 0; i < mbfir.mCount; i++)
				tBitBunch.AddBit (mbfir.GetBit(i)&mbsec.GetBit(i));
			return tBitBunch;
		}

	};

	public class UEntity
	{
		public ECSWorld mWorld;

		public uint mWorldID;

		public BitBunch mAllBitBunch = new BitBunch ();
		public UComponent[] mUComponent = new UComponent[1024];
		int mMaxComponentCount = 1024;
		public int mComponentCount = 0;

		public T GetComponent<T>() where T:UComponent
		{
			Type requestType = typeof(T);
			for (int i = 0; i < mComponentCount; i++)
			{
				Type tType = mUComponent [i].GetType ();
				if (tType == requestType)
				{
					return (T)mUComponent [i];
				}
			}
			return null;
		}

		public void AddComponent<T>(T mT)where T:UComponent
		{
			if (mComponentCount < mMaxComponentCount)
			{
				mUComponent [mComponentCount] = mT;
				mUComponent [mComponentCount].mUEntity = this;
				if (mWorld != null)
				{
					uint tid = mWorld.GetComponentID (mT.GetType ());
					mAllBitBunch.SetBit (true, (int) time);
				}
				mComponentCount++;
			}
			else
			{
				mMaxComponentCount *= 2;
				UComponent[] tComponent = new UComponent[mMaxComponentCount];
				for (int i = 0; i < mComponentCount; i++)
					tComponent [i] = mUComponent [i];
				mUComponent = tComponent;
				mUComponent [mComponentCount] = mT;
				mUComponent [mComponentCount].mUEntity = this;
				if (mWorld != null)
				{
					uint tid = mWorld.GetComponentID (mT.GetType ());
					mAllBitBunch.SetBit (true, (int) time);
				}
				mComponentCount++;
			}
		}

		public bool DestroyComponent<T>()where T:UComponent
		{
			uint tid = mWorld.GetComponentID (typeof(T));
			mAllBitBunch.SetBit (false, (int) time);

			for (int i = 0; i < mComponentCount; i++)
			{
				if (mUComponent [i].GetType () == typeof(T))
				{
					for (int j = i + 1; j < mComponentCount; j++)
					{
						mUComponent [j - 1] = mUComponent [j];
					}
					mComponentCount--;
					return true;
				}
			}
			return false;
		}

		public void Release()
		{
			for (int i = 0; i < mComponentCount; i++)
			{
				mUComponent [i].Release ();
			}
			mWorld.deleteEntity (this);
		}

		public void Clear()
		{
			mComponentCount = 0;
			mAllBitBunch.SetCount ((int) mWorld.mComponentCount);
		}

		public virtual void Init()
		{
			
		}

	};

	public class UComponent
	{
		public UEntity mUEntity;

		public virtual void Release()
		{
			
		}

		public virtual void Init()
		{
			
		}

		public virtual UComponent Clone()
		{
			return null;
		}

	};

	public class USystem
	{
		public ECSWorld mWorld;
		public BitBunch mRequestBitBunch = new BitBunch();
		public List<UEntity> mListEntity = new List<UEntity> ();
		public uint power;

		public void AddRequestComponent(Type mt)
		{
			uint time = mWorld.GetComponentID (mt);
			mRequestBitBunch.SetBit (true,(int)tid);
		}

		public virtual void Update (UEntity uEntity)
		{
			
		}

		public virtual void Init()
		{
			
		}

		public static bool operator < (USystem uSystem1,USystem uSystem2)
		{
			if (uSystem1.power < uSystem2.power)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		public static bool operator > (USystem uSystem1,USystem uSystem2)
		{
			if (uSystem1.power > uSystem2.power)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

	};

	public class ECSWorld
	{
	    USystem[] mUSystem=new USystem[1024];
		UEntity[] mUEntity=new UEntity[1024];
		public uint mSystemCount=0;
		public uint mEntityCount=0;
		public uint mMaxSystemCount = 1024;
		public uint mMaxEntityCount = 1024;
		Dictionary<Type,uint> mDictionary = new Dictionary<Type, uint>();
		public uint mComponentCount = 0;

		public void registerSystem(USystem uSystem)
		{
			if (mSystemCount < mMaxSystemCount)
			{
				mUSystem [mSystemCount] = System;
				mUSystem [mSystemCount].mWorld = this;
				mSystemCount++;
			}
			else
			{
				mMaxSystemCount *= 2;
				USystem[] uSystems = new USystem[mMaxSystemCount];
				for (int i = 0; i < mSystemCount; i++)
					uSystems [i] = mUSystem [i];
				mUSystem = Systems;
				mUSystem [mSystemCount] = System;
				mUSystem [mSystemCount].mWorld = this;
				mSystemCount++;
			}
		}

		public void registerEntity(UEntity uEntity)
		{
			if (mEntityCount < mMaxEntityCount)
			{
				mUEntity[mEntityCount] = uEntity;
				mUEntity [mEntityCount].mWorld = this;
				mUEntity [mEntityCount].mWorldID = mEntityCount;
				mEntityCount ++;
			}
			else
			{
				mMaxEntityCount *= 2;
				UEntity[] uEntitys = new UEntity[mMaxEntityCount];
				for (int i = 0; i < mEntityCount; i++)
					uEntitys [i] = mUEntity [i];
				mUEntity = uEntitys;
				mUEntity[mEntityCount] = uEntity;
				mUEntity [mEntityCount].mWorld = this;
				mUEntity [mEntityCount].mWorldID = mEntityCount;
				mEntityCount ++;
			}
		}


		public void deleteEntity(UEntity uEntity)
		{
			for (int i = (int)uEntity.mWorldID; i < mEntityCount-1; i++)
			{
				mUEntity [i] = mUEntity [i + 1];
				mUEntity [i].mWorldID = (uint)i;
			}
			mEntityCount--;
		}


		public void registerComponent(Type t)
		{
			mDictionary.Add (t,mComponentCount);
			mComponentCount++;
			for (int i = 0; i < mSystemCount; i++)
				mUSystem [i].mRequestBitBunch.SetCount ((int)mComponentCount);
			for (int i = 0; i < mEntityCount; i++)
				mUEntity [i] .mAllBitBunch.SetCount ((int) mComponentCount);
		}

		public uint GetComponentID(Type t)
		{
			return mDictionary [t];
		}

		List<UEntity> FindAllEntity(BitBunch mBitBunch)
		{
			List<UEntity> mList = new List<UEntity> ();
			for (int i = 0; i < mEntityCount; i++)
			{
				if (BitBunch.Equal(BitBunch.And (mBitBunch, mUEntity [i].mAllBitBunch),mBitBunch))
				{
					mList.Add (mUEntity[i]);
				}
			}
			return mList;
		}

		public void Update()
		{
			for (int i = 0; i < mSystemCount; i++)
			{
				if (mUSystem [i].mListEntity.Count != 0)
				{
					for (int j = 0; j < mUSystem[i].mListEntity.Count; j++)
					{
						mUSystem [i] .Update (mUSystem [i] .mListEntity [j]);
					}
					continue;
				}
				List<UEntity> tList = FindAllEntity (mUSystem[i].mRequestBitBunch);
				for (int j = 0; j < tList.Count; j++)
				{
					mUSystem [i].Update (tList[j]);
				}
			}
		}

		public virtual void registerAllEntity()
		{
			
		}

		public virtual void registerAllSystem()
		{
			
		}

		public virtual void registerAllComponent()
		{
			
		}

		public void Init()
		{
			registerAllSystem ();
			registerAllEntity ();
			registerAllComponent ();

			for (int i = 0; i < mSystemCount; i++)
			{
				mUSystem [i].Init ();
			}

			ECSSort.QuickSortRelax (mUSystem,0,(int)mSystemCount-1);

			for (int i = 0; i < mEntityCount; i++)
			{
				mUEntity [i].Init ();
			}

		}

		public class ECSSort
		{
			
			public static void QuickSortRelax<T>(IList<T> data)where T:USystem
			{
				 QuickSortRelax(data, 0, data.Count - 1);
			}

			public static void QuickSortRelax<T>(IList<T> data, int low, int high)where T:USystem
			{
				 if (low >= high)
					return;
				 T temp = data[(low + high) / 2];
				 int i = low - 1, j = high + 1;
				 while (true)
				 {
					while (data[++i] < temp) ;
					while (data[--j] > temp) ;
					if (i >= j)
						break;
					Swap(data, i, j);
				 }
				QuickSortRelax(data, j + 1, high);
				QuickSortRelax(data, low, i - 1);
			}

			public static void Swap<T>(IList<T> data,int a,int b)
			{
				T temp = data [a];
				data [a] = data [b];
				data [b] = temp;
			}

		}
	};
};

The code is not difficult to write, so I will explain two points. First, why a sorting mechanism is added to the system. Second, what is BitBunch under UEntity and USystem?

one:

Because we hope that some systems have an execution order, just like Unity's logic script can set priorities, adding a sorting mechanism to the system is also for the same consideration, because some logic systems, if there is no restriction on the execution order, may An error occurred.

two:

Remember when we said that each world would pick out entities with a specific combination of components that the system cares about? This is very important, this feature completes the dynamic pairing of data and functions.

So how is it done? We'll do bit encoding. For example, there are 4 components, then the component number is 0000-1111.

Obviously as long as the components of the entity contain the components required by the system. That is, 1111 can be paired with 0011. That is to do such a bit operation A&B=? B is enough.

Others, for example, we use some reflection properties, because the category of the component will eventually be mapped to a number, and the type stored in the dictionary is the Type obtained by typeof(T).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325802477&siteId=291194637