-
Notifications
You must be signed in to change notification settings - Fork 1.1k
FAQ
- Latest Unity also has ECS, should i learn Unity's ECS first or Entitas?
- How do I regenerate code when there are compile errors?
- How do I fix "Could Not Find .csproj" error message?
- How do I fix "Could not load file or assembly" error?
- Should I store references to entities inside components?
- I created specific assemblies and now components aren't being generated
- How to create custom CodeGenerator?
- Entity gets destroyed in one system and further systems can't react to it
- Events feature does not trigger
- How to create CustomEntityIndex?
- How
Groups
(Matcher
,Collector
) affect performance?
A: Answer from #695
Entitas and Unity ECS are both implementations of Entity Component System Architecture. They share a lot of the same concepts, the implementation is different though.
As you might have seen from the YouTube videos, we introduced Entitas back in 2015 already. My advise that I can give you on what to learn first strongly depends on your goals. If you want to build and release a game this year, I recommend using Entitas. It's production ready, has been around for some years and is battle tested on multiple platforms like PC, mobile, VR, etc. There's a healthy community which can help and answer questions. There are also a bunch of helpful features that are not available in Unity ECS, like Reactive Systems and Entitas Events. We developed a lot of conventions and guidelines to solve most of the problems already, which is a huge benefit in my opinion.
Unity stresses that their ECS is not production ready, but if you want to learn ECS I think it's worth checking out. Also, if you know that your game will require heavy multithreading, Unity ECS will be a great option because of their tight integration with the C# Job System. Keep in mind that apis might change in the future and that best practises will develop over time.
@mzaks wrote a blog post about features that you find in Entitas which he misses in Unity ECS
https://medium.com/@icex33/my-wish-list-for-unity3d-ecs-1f3985bbe308
A: Entitas code generation uses reflection, so it requires a solution with no compile errors. Renaming or editing components break links in generated code and cause compile errors, preventing you from regenerating your code. So you will have to first fix any compile errors before you will be able to regenerate your code.
You can rename component fields safely without regeneration as long as you apply rename-refactoring (i.e. you change the name in the rest of your code base at the same time).
You cannot rename components or add/delete fields in components without breaking the generated code. Here is a guide to dealing with this problem:
- Create a new folder and name it StreamingAssets (anything placed in this folder wont compile).
- Move all non-generated code into this new folder (e.g. Components, Systems, GameController, etc.).
- Delete the entire contents of the generated folder.
- Generate new code (this will create new contexts and features).
- Move your components back to their original location and perform refactoring.
- Generate code again (this will create new component files).
- Move the rest of your code back into your project.
- Fix errors in your systems if the changes you made cause any.
- Your done!
Alternatively, you can eliminate these steps and the issue itself entirely when refactoring by using the new Roslyn code generator available on the Asset Store.
See also: Code Generator
A: This error message means that either the project path is incorrect or the file doesn't exist.
First ensure that a project file already exist by selecting Assets>Open C# Project from the toolbar. Doing this will generate a new project file if one doesn't already exist. Then right-click on the Assets folder in the Unity project window and select Show in Explorer and ensure that the file found there that ends with .csproj
matches the file name set in the Entitas project path (e.g. "Assembly-CSharp.csproj").
If the .csproj
file did not appear, open Edit>Preferences>External Tools (or Unity>Preferences>External Tools for macOS) and select your Visual Studio in the External Scripts Editor field, then try again.
A: You'll receive this error when either the assembly file doesn't exist or the file path set in preferences is incorrect.
In new empty Unity projects you'll receive this error because the file has not yet been generated by Unity. To make Unity generate the file just open your C# project by going to the toolbar and selecting Assets>Open C# Project. Then verify that the path is correct by opening your Library/ScriptAssemblies/
folder located in the root of your Unity project folder and ensuring the Assemblies path in Entitas preferences matches it.
A: Storing direct references to other entities is not recommended (why?). Instead make use of the PrimaryEntityIndex along with a unique entity ID generator to create an IdComponent
. In your game controller you can subscribe to the OnEntityCreated
event on each of your contexts to invoke your id generator and assign an IdComponent to the newly created entity. Below is an example of such a system using an integer IdComponent which takes its value from a unique integer on each entity (its creationIndex).
Components.cs
[Game, Input, Ui] // contexts here
public class IdComponent :IComponent {
[PrimaryEntityIndexAttribute]
public int value; // must be public in order for the index to be generated
}
ContextsIdExtensions.cs
using System;
using Entitas;
public static class ContextsIdExtensions
{
public static void SubscribeId (this Contexts contexts)
{
foreach (var context in contexts.allContexts)
{
if (Array.FindIndex(context.contextInfo.componentTypes, v => v == typeof(IdComponent)) >= 0)
{
context.OnEntityCreated += AddId;
}
}
}
public static void AddId (IContext context, IEntity entity)
{
(entity as IIdEntity).ReplaceId(entity.creationIndex);
}
}
To auto-assign ids on entity creation do the following
GameController.cs
public class GameController : MonoBehaviour
{
private Systems _systems;
void Start()
{
Contexts contexts = Contexts.sharedInstance;
contexts.SubscribeId();
// ... systems init
}
}
Note: The IIdEntity interface will only be generated if the component has multiple contexts, such as [Game, Input, Ui]
in the example. If it is only in a single context, EG [Game]
it will not generate the Interface.
To find an entity using its ID, you have to have a PrimaryEntityIndex and call context.GetEntityWithId(id);
. If it is null the entity was not found.
A: Entitas doesn't know about your assemblies. Go to Tools>Entitas>Preferences and make sure you add your newly generated assembly files path to the assemblies field (separated by a comma)
- Create a class inside any subfolder of the
Editor
folder in Unity (Unity Manual - Special Folder Names) - Add a
using Entitas.CodeGeneration;
statement and implement anICodeGenerator
interface - Open
Entitas.properties
file (located at the root of the project) and add yourAssembly-CSharp-Editor.dll
full path to theEntitas.CodeGeneration.CodeGenerator.Plugins
key. Usually this assembly is located atLibrary/ScriptAssemblies/Assembly-CSharp-Editor.dll
. This is needed for the Code Generation system to be able to find your custom generator implementations (so it appears inside Entitas properties window) - Make sure to add a folder path of other Unity dlls (
Assembly-CSharp.dll
andAssembly-CSharp-firstpass.dll
) to theEntitas.CodeGeneration.CodeGenerator.SearchPaths
key (example: Match-One - Entitas.properties)
Now the code generator will find your custom ICodeGenerator and will display in the drop down. Steps 3 and 4 can be executed once in a project's lifetime. All key-values stay the same if you continue adding new generators to the Editor scripts folder
One way to prevent this is creating destroy flag component. See DestroyComponent, DestroySystem
A: Check if you did all steps for creating an event:
- Add
[Event(EventTarget.Any)]
or[Event(EventTarget.Self)]
attribute to a component class - If your component is a flag (has zero public members), in order to track both
true
andfalse
states also add "removed" version of event, eg[Event(EventTarget.Self, EventType.Removed)]
- Generate
- Add event systems to Systems per context, somewhere in the end after all modifying systems
- Implement event listener interface
- Get entity for
EventTarget.Self
or create new entity forEventTarget.Any
, add listener to this entity - If your listener is
MonoBehaviour
add it to your gameObject/prefab - Ensure you modify component values through generated methods (
Add/Replace/Remove
etc), editing values directly doesn't trigger reactive systems - Triggering event during event handling phase may not work due to eventSystem ordering. To fix this it's possible to adjust
[Event]
priority, reorganize entities to handle second event at the next frame, or make event handlers not depend on each other
After making these steps adding/replacing component should reactively trigger listeners
See Attributes:Event for more details
A:
"Get me all the things that are zappable and are on this grid coordinate"
using System.Collections.Generic;
using Entitas;
using Entitas.CodeGeneration.Attributes;
[CustomEntityIndex(typeof (GameContext))]
public class ZappableGridPositionEntityIndex : EntityIndex<GameEntity, Int2>
{
public ZappableGridPositionEntityIndex(GameContext context) : base (
"ZappableGridPositionEntityIndex",
context.GetGroup(
GameMatcher.AllOf(GameMatcher.GridPosition, GameMatcher.Position, GameMatcher.Zappable)
),
(e, c) =>
{
var gridPosition = c as GridPositionComponent ?? e.gridPosition;
return gridPosition.value;
}
)
{}
[EntityIndexGetMethod]
public HashSet<GameEntity> GetZappableEntitiesAtGridPosition(Int2 gridPosition)
{
return GetEntities(gridPosition);
}
}
But you can go more complex, and have the [EntityIndexGetMethod] take multiple arguments to match specific pairs / triplets etc of component values (FNGGames at the discord channel - 07/09/2020)
A:
You should always consider, how badly groups scale up in big games. Each group listens on every single component change on every single entity and then compares its Matcher
against the entity's components.
If you have 100 groups (each unique matcher creates a Group
) and replace 20.000 components per frame (which is not an extremely unusual use case for a real-time game), then that's already 2.000.000 calls on the Matches
-Method (+ potential HashSet
Inserts), then those groups, if an entity was added or removed, update all listeners, so all Collectors
and EntityIndices
, which again have a HashSet
Insert.
That can heavily decrease your performance.
Of course, the use of Groups
and EntityIndices
also creates Groups
, not only the use of Collectors
(ReactiveSystems
).
We've had to make sure for example, that we remove all Transform / Position Matchers
for our game, because for hundres of enemies walking around, each updating their position, their attack range's position, their collider's position etc. dragged down the performance extremely. Instead, all systems just assume, that Entities always update their positions. Unless they're flagged static in which case, it's necessary to change their position using a specific PhysicsService that manually calls the logic for updating all child entities etc
(issue links should be summarized and presented on the page)
Guides: Introduction - Installation - Upgrading - FAQ - Cookbook - Contributing
Need Help? Ask a question on Discord or create an issue.
- The Basics
- Concepts
- Architecture / Patterns